Hijacking a thread in another process

My API hooking currently relies on creating a block of memory in the target process to hold the string that you need to pass to LoadLibraryW() and then calling CreateRemoteThread() and passing the address of LoadLibraryW() as the thread start address and the address of our block of memory as the thread parameter. It works well and even allows you to know if the operation worked, by waiting for the remote thread to terminate and then examining its exit code. However, there are times when you might not want (or be able to) create a thread in the target process and for those situations something else is called for…

My latest bit of exploratory code is based on “InjectThread.c” which I found on www.catch22.net. The general idea being that rather than starting a new thread in the target process you hijack an existing thread, change the next instruction that it’s going to execute to one of yours and then have your code return back to where the code was prior to your interference. The sample does this by copying code and data onto the target’s stack, this will likely fail if DEP is enabled but I expect that if the code were placed in a piece of remotely allocated memory with the correct permissions then everything would be good…

My first stumbling block was that the example code didn’t work; well, that’s not strictly true, it worked in a release build but not in a debug build. I spent a while fiddling with the settings that made a release build different to a debug build and couldn’t work out what was causing the problem so I looked at the compiler output…

I’m not especially clever at assembly language programming. I understand the general concepts and I can read and understand simple code but I don’t write a great deal; there simply isn’t the need in my line of work and although I’ve bought several books in the past I’ve never found the time to study them. This API hooking project has had me reading more assembly than I have in a while; many of the pieces of sample code that I found originally used inline assembly for hook functions, etc. This was useful as my I needed to view (and understand) the disassembly of the release and debug builds of the injected code to work out what was going on…

The disassembly clearly showed the problem, which was that the debug build was inserting some stack checking code in the epilogue of the injected function and, well, it didn’t like the fact that we were messing with the stack… Since I still use VC6 for most of my work and since this stack checking is part of VC6’s /GZ compiler option and the other bits are useful so I didn’t want to turn it off. I hoped that __declspec(naked) might help, but it didn’t seem to… For now, I’ve decided that the easiest fix was to simply use the code that the compiler generated for the release build. My injected code is likely to be simple and remain stable so I simply compiled it as a release build and then created an array of bytes with the code bytes from the disassembly window.

// Note that we don't actually use this function as only the release build works
// due to the fact that the debug build is built with /GZ which adds stack 
// checks to the epilogue of the function and these go bang when injected...
     
void WINAPI InjProc(INJDATA *pInjData)
{
   pInjData->pfnLoadLibrary(pInjData->szText);
}
  
// This is the code that InjProc generates in a release build. We inject this
// directly rather than copying from the function pointer to get the generated
// code...
  
static const BYTE s_code[] = 
{
   0x55,                // push        ebp
   0x8B, 0xEC,          // mov         ebp,esp
   0x8B, 0x45, 0x08,    // mov         eax,dword ptr [pInjData]
   0x83, 0xC0, 0x04,    // add         eax,4
   0x50,                // push        eax
   0x8B, 0x4D, 0x08,    // mov         ecx,dword ptr [pInjData]
   0xFF, 0x11,          // call        dword ptr [ecx]
   0x5D,                // pop         ebp
   0xC2, 0x04, 0x00     // ret         4
};

Then, rather copying the code from the address of the function (as the original sample does) I simply pass the hard-coded array of bytes in…

   INJDATA injData;
  
   injData.pfnLoadLibrary = LoadLibraryA;
   strcpy(injData.szText, "TestDll.dll");
  
   CThreadHijacker hijacker(
      hProcess,
      hThread,
      (CThreadHijacker::PINJCODE)(void*)s_code, 
      sizeof(s_code), 
      &injData, 
      sizeof(injData));

It’s a bit of a hack, but it works and means that my thread hijacking code can be used in a debug build…