0

I've got this code below for releasing library from some 64 bit process. It does its job, but the problem is that after restoring saved context, the target process just crashes. Dunno what is the issue here. It should set all registers and flags for what they were before, right?. What am I doin' wrong?

#ifdef _WIN64

const static unsigned char FreeLibrary_InjectionCodeRAW_x64[] =
{
    0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //mov rax, value
    0x48, 0xB9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //mov rcx, value
    0xFF, 0xD0, //call rax (FreeLibrary)
    0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //mov rax, value
    0xC7, 0x00, 0x01, 0x00, 0x00, 0x00, //mov [rax],1
    0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //mov rax, value
    0xB9, 0x64, 0x00, 0x00, 0x00, //mov ecx, 0x64
    0xFF, 0xD0, //call Sleep 
    0xEB, 0xED, //jmp
    0x00, 0x00, 0x00, 0x00 //status
};

#pragma pack(push, 1)
struct FreeLibrary_InjectionCode_x64
{
    FreeLibrary_InjectionCode_x64()
    {
        memcpy(this, FreeLibrary_InjectionCodeRAW_x64, sizeof(FreeLibrary_InjectionCodeRAW_x64));
    }

    char code_1[2];
    FARPROC lpFreeLibrary;
    char code_2[2];
    HMODULE hLib;
    char code_3[4];
    LPVOID lpStatusAddress;
    char code_4[8];
    FARPROC lpSleep;
    char code_5[9];
    int status;
};
#pragma pack(pop)

#endif

void FreeLib(const char what[], const char where[])
{
    HANDLE hToken;
    OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken);
    SetPrivilege(hToken, SE_DEBUG_NAME, TRUE);
    CloseHandle(hToken);
    OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, FALSE, &hToken);
    SetPrivilege(hToken, SE_DEBUG_NAME, TRUE);
    CloseHandle(hToken);

    HMODULE hMod;
    DWORD dwProcessId = GetProcessIdByName(where);
    if ((hMod = GetModuleHandleInProcess(what, dwProcessId)) != NULL)
    {
        HANDLE hProcess = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE | SYNCHRONIZE, FALSE, dwProcessId);
        if (hProcess != NULL)
        {
            HMODULE hKernel = LoadLibrary("kernel32.dll");
            FARPROC FLaddr = GetProcAddress(hKernel, "FreeLibrary");
            FARPROC Saddr = GetProcAddress(hKernel, "Sleep");

            HANDLE hThread = OpenThread(THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_QUERY_INFORMATION | THREAD_SET_INFORMATION | THREAD_SUSPEND_RESUME,
                FALSE, GetValidThreadIdInProcess(dwProcessId));

            if (hThread != NULL && FLaddr != NULL && Saddr != NULL)
            {
                LPVOID addr = VirtualAllocEx(hProcess, NULL, 4096, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
                LPVOID lpStatusAddress = (PUCHAR)addr + (sizeof(FreeLibrary_InjectionCode_x64)-sizeof(int));
                FreeLibrary_InjectionCode_x64 code = FreeLibrary_InjectionCode_x64();
                code.hLib = hMod;
                code.lpFreeLibrary = FLaddr;
                code.lpSleep = Saddr;
                code.lpStatusAddress = lpStatusAddress;
                WriteProcessMemory(hProcess, addr, &code, sizeof(FreeLibrary_InjectionCode_x64), NULL);

                CONTEXT ctx, oldCtx;
                ctx.ContextFlags = CONTEXT_ALL;

                SuspendThread(hThread);
                GetThreadContext(hThread, &ctx);

                memcpy(&oldCtx, &ctx, sizeof(CONTEXT));
                ctx.Rip = (DWORD64)addr; 

                SetThreadContext(hThread, &ctx);
                ResumeThread(hThread);

                while (!code.status)
                {
                    Sleep(15);
                    ReadProcessMemory(hProcess, addr, &code, sizeof(FreeLibrary_InjectionCode_x64), NULL);
                }

                SuspendThread(hThread);
                SetThreadContext(hThread, &oldCtx);
                ResumeThread(hThread);

                VirtualFreeEx(hProcess, addr, 4096, MEM_DECOMMIT);

                CloseHandle(hThread);
            }

            CloseHandle(hProcess);
        }
    }
}

2 Answers2

1

Windows 64-bit uses the fastcall calling convention. In this convention the caller of a function is responsible for reserving 4 * 64 bit (32 byte) on the stack for the called function to save registers. This means your calls should look like this:

sub rsp, 32
call rax
add rsp, 32

In your code your calls to FreeLibrary or Sleep overwrite stack that doesn't belong to their stack frame, causing a crash later on.

Tannin
  • 488
  • 5
  • 11
0

You are not doing any error handling to make sure the memory was actually allocated and written to the other process before executing it, or to make sure that ReadProcessMemory() succeeded, or to make sure that the thread suspend/resume and context swapping was successful.

Chances are, the remote thread is likely done running your injected code and tries to run its original code (or even random code that happens to follow your allocated block in memory) before your injector has a chance to swap back in the original context information. That might account for the crashing.

Instead of hijacking an existing thread in the other process and swapping out its context behind its back, you might want to consider using CreateRemoteThread() instead to run your injected code in its own dedicated thread. No context swapping needed.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • As I said - the injection code is just fine - it works. Look at the `FreeLibrary_InjectionCode_x64` constructor, it fills memory with `FreeLibrary_InjectionCodeRAW_x64`. 4096 is enough for code and `int status`. – user3068002 Dec 05 '13 at 01:05
  • About reading entire `FreeLibrary_InjectionCode_x64`, it's just more convenient (for me). – user3068002 Dec 05 '13 at 01:11
  • If you look closely at the asm code - the remote thread is actually waiting in loop for being restored to its original state. There would be no question if I wanted to use `CreateRemoteThread()` method to inject my code. To be clear, everything works (memory is allocated, `ReadProcessMemory()` does not fail, remote thread changes `int status` and waits for beign swaped back to its original state) except one thing which is: restoring thread context (crash here). – user3068002 Dec 05 '13 at 13:16
  • Why not use `CreateRemoteThread()`, though? It would be a lot safer, and easier to manage. No assembly code to write (`FreeLibrary()` can be the thread proc executed, with `hMod` as its input parameter). No context swapping needed, no remote sleeping needed, no looping needed. The status variable would not be needed anymore, the result of `FreeLibrary()` could be retrieved via `GetExitCodeThread()` if needed. The calling thread can use `WaitForSingleObject()` to wait for the remote thread to terminate. – Remy Lebeau Dec 05 '13 at 16:12
  • I just can't use `CreateRemoteThread()`. I need silent code execution, cause most antivirus programs detects my application as a threat. The other reason is that `CreateRemoteThread()` method does not work on some processes in windows 8. – user3068002 Dec 05 '13 at 17:07
  • And you think hijacking an existing thread to run arbitrary code will not trigger antivirus/antimalware tools? Good luck with that. – Remy Lebeau Dec 05 '13 at 19:14
  • Thanks. I just need to know `How to properly save and restore thread context on 64 bit process`. – user3068002 Dec 05 '13 at 20:03
  • Assuming the target process is also 64-bit (since your injector process is 64-bit), then I would strongly suggest you add adequate error handling to your existing code at all of the API function calls, just to rule out any unexpected failures. After that, try adding calls to `Get/SetXStateFeaturesMask()` (which are currently missing in your code) if you are running on Win7 SP1 or later, as MSDN says they *MUST* be called when using `Get/SetThreadContext()`. – Remy Lebeau Dec 05 '13 at 20:40