A long time ago, when we still used SAPGUI 6xx, there was a little bug special feature that allowed us to log into an SAP system even though someone else was already logged in with the same user ID. It was the Ctrl+click.
If you are not familiar with that bug special feature, this is what I’m talking about:
Since the advent of SAPGUI 720, this is no longer possible. If you’re not using my sapgui-multilogon tool, of course.
This little tool brings back the feature we consultants often need. You can now stop using SAPGUI 710 and update already.
You can download the ready-to-use (Windows only, sorry) binaries in the Releases section.
(And yes, it’s open source!)
How does it work?
The app is composed of two files: an executable program and a DLL.
The important part goes like this:
1) The .exe program finds (or starts) the SAPGUI process:
// Check if SAP Logon is already running
int iPID = GetPID("saplogon.exe");
if (! iPID)
iPID = GetPID("saplgpad.exe");
if (iPID) {
// Process found, open it to inject the library
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, iPID);
if (! hProcess) {
ShowLastErrorMessage();
return -1;
}
}
else {
// Create the SAP Logon process //
STARTUPINFO stStartupInfo = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION stProcInfo;
if(! CreateProcess(szExePath, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, szDirPath, &stStartupInfo, &stProcInfo)) {
ShowLastErrorMessage();
return -1;
}
hProcess = stProcInfo.hProcess;
hMainThread = stProcInfo.hThread;
}
2) The .exe then injects the DLL into the SAPGUI process:
// Copy the DLL path to the remote process //
int iSize = strlen (szDllPath) + 1;
void * pRemoteLibraryPath = VirtualAllocEx (hProcess, NULL, iSize, MEM_COMMIT, PAGE_READWRITE);
if (! WriteProcessMemory (hProcess, pRemoteLibraryPath, szDllPath, iSize, NULL)) {
ShowLastErrorMessage();
return -1;
}
// Create a remote LoadLibraryA suspended thread //
HANDLE hInjThread = CreateRemoteThread (hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE) GetProcAddress (GetModuleHandle ("kernel32"),
"LoadLibraryA"), pRemoteLibraryPath, 0, NULL);
if (! hInjThread) return FALSE;
ResumeThread(hInjThread);
ResumeThread(hMainThread);
3) The DLL installs hooks to alter the working of some SAPGUI internal methods (this is done using my Hook library:
// SAPfewui.dll (User Interface Manager) hooks
ASSERT_HOOK_INSTALL(CUiObject_IsChecked, "SAPfewui.dll", "?IsChecked@CUiObject@@QBEHXZ");
ASSERT_HOOK_INSTALL(CUiObject_SetChecked, "SAPfewui.dll", "?SetChecked@CUiObject@@UAEXH@Z");
4) When you click on a radiobutton, these methods are called, then our hook stub takes care of the magic:
/**
* UI objects (screen controls) IsCheck method
* Called to determine if a screen control is checked (eg checkboxes)
* Here, if Ctrl key is pressed, we invert the checked state of the UIObject.
* This makes any radiobutton act as a checkbox when clicked (with Ctrl pressed ofc).
*/
HOOK_STUB_FUNCTION(CUiObject_IsChecked, int, __thiscall, (void)) {
void * pThis;
int iChecked;
GET_THIS(pThis);
iChecked = HOOK_HOP(CUiObject_IsChecked)();
// If Ctrl is pressed, invert the checked state
// (check if not checked and vice versa)
if (IS_KEY_PRESSED(VK_CONTROL)) {
SET_THIS(pThis);
HOOK_HOP(CUiObject_SetChecked)(!iChecked);
return FALSE;
}
else
return iChecked;
}
/**
* UI objects (screen controls) SetChecked method
* Called to change the checked state of a screen control (eg checkboxes)
* Here, if Ctrl key is pressed, we just ignore the calls.
* This makes the radiobuttons act as checkboxes, because no radiobuttons are de-checked
* when another radiobutton in the same group is checked.
*/
HOOK_STUB_FUNCTION(CUiObject_SetChecked, void, __thiscall, (int iChecked)) {
void * pThis;
GET_THIS(pThis);
// If Ctrl is pressed, ignore this call
if (IS_KEY_PRESSED(VK_CONTROL))
return;
SET_THIS(pThis);
HOOK_HOP(CUiObject_SetChecked)(iChecked);
}