Introduction
The software I am developing uses the excellent Injection cave code by Darawk,
taken from: http://www.gamerztools.net/foros/showthread.php?1080-Dll-Injection-Using-Code-Caves-by-Darawk.
This is great for 32 bit, but I needed it to run on 64 bit as well. Since the solution uses inline assembly which is not supported in Visual Studio for 64 bit,
I had to find another way to do it. After searching far and wide for a 64 bit injection cave, I ended up writing it myself.
The Compiled Code for 64 bit
In Darawk's code mentioned above, the code to be injected was written in inline assembly. The name of the function was then used as the pointer from which to copy the compiled code at runtime.
The problem is that Visual Studio for 64 bit has no support for inline assembly. Therefore Darawk's code cannot be used as is. The solution I chose was to produce 64 bit machine code and include it in a hard-coded array in my code.
To achieve this, I took Darawk’s assembly code and compiled it with ml64.
As expected, it does not compile as is, so I ported the code to MASM64.
There are several differences that had to be incorporated here:
- MASM64 uses
fastcall
, so the function's argument has to be passed in a register and not on the stack. - The length of the addresses - 32 vs. 64 bit - must be taken into account.
- MASM64 has no instruction that pushes all registers on the stack (like
pushad
in 32bit) so this had to be done by pushing all the registers explicitly.
Once the 64 bit assembly compiled successfully with ml64, I put the resulting machine code into an array, and injected the array itself into the target process.
Using the Code
Following is the injection function with the machine code array it injects.
Note that Darawk's 32 bit code used VirtualProtect
to protect the injected code while writing to it, since it is in the code segment. In our case, the injected code
is on the heap. You should consider running the injection function under lock to prevent clashes in case it can be run from more than one thread at a time.
unsigned char codeToInject[] =
{
0x68, 0xAA, 0xAA, 0xAA, 0xAA, 0x9c, 0x50, 0x51, 0x52, 0x53, 0x55, 0x56, 0x57, 0x41, 0x50, 0x41, 0x51, 0x41, 0x52, 0x41, 0x53, 0x41, 0x54, 0x41, 0x55, 0x41, 0x56, 0x41, 0x57, 0x48, 0xB9, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0x48, 0xB8, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xFF, 0xD0, 0x41, 0x5F, 0x41, 0x5E, 0x41, 0x5D, 0x41, 0x5C, 0x41, 0x5B, 0x41, 0x5A, 0x41, 0x59, 0x41, 0x58, 0x5F, 0x5E, 0x5D, 0x5B, 0x5A, 0x59, 0x58, 0x9D, 0xC3 };
int WINAPI inject_lib_cave( HANDLE hProcess, HANDLE hThread, const char* lib_name )
{
void* dllString;
void* stub;
DWORD64 stubLen, loadLibAddr;
DWORD oldIP;
CONTEXT ctx;
BOOL result = FALSE;
DWORD suspend_result = -1;
stubLen = sizeof( codeToInject );
loadLibAddr = (DWORD64)GetProcAddress( GetModuleHandleA("Kernel32"),
"LoadLibraryA" );
dllString = VirtualAllocEx(hProcess, NULL, (strlen(lib_name) + 1),
MEM_COMMIT, PAGE_READWRITE);
stub = VirtualAllocEx(hProcess, NULL, stubLen, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if( dllString == NULL || stub == NULL )
{
if(dllString != NULL) free( dllString );
if(stub != NULL) free( stub );
MessageBoxA( NULL, "Virtual Alloc failed.", "My Msg", MB_OK );
goto clean_exit;
}
result = WriteProcessMemory(hProcess, dllString, lib_name, strlen(lib_name), NULL);
if ( !result )
{
MessageBoxA( NULL, "Could not write process memory for dllString.",
"My Msg", MB_OK );
goto clean_exit;
}
suspend_result = SuspendThread( hThread );
if ( suspend_result == -1 )
{
MessageBoxA( NULL, "Could not suspend thread.",
"My Msg", MB_OK );
goto clean_exit;
}
ctx.ContextFlags = CONTEXT_CONTROL;
GetThreadContext(hThread, &ctx);
oldIP = (DWORD)ctx.Rip;
ctx.Rip = (DWORD)stub;
ctx.ContextFlags = CONTEXT_CONTROL;
memcpy( codeToInject + 1, &oldIP, sizeof( oldIP ) );
memcpy( codeToInject + 31, &dllString, sizeof( dllString ) );
memcpy( codeToInject + 41, &loadLibAddr, sizeof( loadLibAddr ) );
result = WriteProcessMemory(hProcess, stub, codeToInject, stubLen, NULL);
if ( !result )
{
MessageBoxA( NULL, "Could not write process memory.",
"My Msg", MB_OK );
goto clean_exit;
}
result = SetThreadContext(hThread, &ctx);
if ( !result )
{
MessageBoxA( NULL, "Could not set thread context.",
"My Msg", MB_OK );
goto clean_exit;
}
clean_exit:
if ( suspend_result > -1 )
{
suspend_result = ResumeThread( hThread );
if ( suspend_result == -1 )
{
MessageBoxA( NULL, "Could not resume thread.",
"My Msg", MB_OK );
}
}
return result;
}