64 Bit Injection Cave






4.80/5 (8 votes)
Code injection cave for 64 bit processes
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[] =
{
// Placeholder for the return address
0x68, 0xAA, 0xAA, 0xAA, 0xAA, // push 0AAAAAAAAh
// Save the flags
0x9c, // pushfq
// Save the registers
0x50, // push rax
0x51, // push rcx
0x52, // push rdx
0x53, // push rbx
0x55, // push rbp
0x56, // push rsi
0x57, // push rdi
0x41, 0x50, // push r8
0x41, 0x51, // push r9
0x41, 0x52, // push r10
0x41, 0x53, // push r11
0x41, 0x54, // push r12
0x41, 0x55, // push r13
0x41, 0x56, // push r14
0x41, 0x57, // push r15
// Placeholder for the string address and LoadLibrary
0x48, 0xB9, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, // mov rcx, 0BBBBBBBBBBBBBBBBh
0x48, 0xB8, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, // mov rax, 0CCCCCCCCCCCCCCCCh
// Call LoadLibrary with the string parameter
0xFF, 0xD0, // call rax
// Restore the registers
0x41, 0x5F, // pop r15
0x41, 0x5E, // pop r14
0x41, 0x5D, // pop r13
0x41, 0x5C, // pop r12
0x41, 0x5B, // pop r11
0x41, 0x5A, // pop r10
0x41, 0x59, // pop r9
0x41, 0x58, // pop r8
0x5F, // pop rdi
0x5E, // pop rsi
0x5D, // pop rbp
0x5B, // pop rbx
0x5A, // pop rdx
0x59, // pop rcx
0x58, // pop rax
// Restore the flags
0x9D, // popfq
0xC3 // ret
};
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;
/*
* Insert the addresses into the local copy of the codeToInject before copying it to
* the remote process
*/
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;
}