Click here to Skip to main content
Click here to Skip to main content

64 Bit Injection Cave

, 19 Jun 2011
Rate this:
Please Sign up or sign in to vote.
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:

  1. MASM64 uses fastcall, so the function's argument has to be passed in a register and not on the stack.
  2. The length of the addresses - 32 vs. 64 bit - must be taken into account.
  3. 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;
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Rimon Orni
Software Developer Concealium IT Security Ltd.
Israel Israel
I am an experienced Software Developer working mainly in C++ and C. Worked mostly in Security - previously at Check Point and today at Concealium on Data Security in the Cloud.
These days developing mostly for Windows, but always happy to go back to good old Unix.

Comments and Discussions

 
QuestionRet address PinmemberBrian Sullender19-May-13 5:18 
QuestionGreat article but it didn't work for me PinmemberOmerikoo7-Mar-12 8:09 
AnswerRe: Great article but it didn't work for me PinmemberRimon Orni23-May-12 21:06 
GeneralReason for my vote of 5 Great tip. Please stay in touch. PinmemberMichael Haephrati1-Feb-12 9:34 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140709.1 | Last Updated 19 Jun 2011
Article Copyright 2011 by Rimon Orni
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid