【问题标题】:How to properly save and restore thread context on 64 bit process (Windows)?如何在 64 位进程(Windows)上正确保存和恢复线程上下文?
【发布时间】:2013-12-21 18:03:01
【问题描述】:

我在下面有这段代码,用于从一些 64 位进程中释放库。它完成了它的工作,但问题是在恢复保存的上下文后,目标进程就会崩溃。不知道这里有什么问题。它应该将所有寄存器和标志设置为以前的状态,对吧?我做错了什么?

#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);
        }
    }
}

【问题讨论】:

    标签: c assembly 64-bit code-injection threadcontext


    【解决方案1】:

    Windows 64 位使用 fastcall 调用约定。在此约定中,函数的调用者负责在堆栈上保留 4 * 64 位(32 字节),以供被调用函数保存寄存器。 这意味着您的调用应如下所示:

    sub rsp, 32
    call rax
    add rsp, 32
    

    在您的代码中,您对 FreeLibrary 或 Sleep 的调用会覆盖不属于其堆栈帧的堆栈,从而导致稍后崩溃。

    【讨论】:

      【解决方案2】:

      您没有进行任何错误处理以确保内存在执行之前实际分配并写入其他进程,或者确保ReadProcessMemory() 成功,或者确保线程挂起/恢复和上下文交换成功。

      很有可能,远程线程可能已完成运行您注入的代码,并在您的注入器有机会换回原始代码之前尝试运行其原始代码(甚至是恰好跟随您在内存中分配的块的随机代码)上下文信息。这可能是导致崩溃的原因。

      与其劫持另一个进程中的现有线程并在其背后换出其上下文,不如考虑使用CreateRemoteThread(),而不是在其自己的专用线程中运行注入的代码。无需上下文交换。

      【讨论】:

      • 正如我所说 - 注入代码很好 - 它可以工作。查看FreeLibrary_InjectionCode_x64 构造函数,它用FreeLibrary_InjectionCodeRAW_x64 填充内存。代码和int status 4096 就足够了。
      • 关于阅读整个FreeLibrary_InjectionCode_x64,只是更方便(对我来说)。
      • 如果您仔细查看 asm 代码 - 远程线程实际上正在循环中等待恢复到其原始状态。如果我想使用CreateRemoteThread() 方法来注入我的代码,那是毫无疑问的。需要明确的是,一切正常(内存已分配,ReadProcessMemory() 不会失败,远程线程更改int status 并等待 beign 交换回其原始状态)除了一件事:恢复线程上下文(此处崩溃)。
      • 为什么不使用CreateRemoteThread()呢?这会更安全,也更容易管理。无需编写汇编代码(FreeLibrary() 可以是执行的线程 proc,hMod 作为其输入参数)。不需要上下文交换,不需要远程睡眠,不需要循环。不再需要状态变量,如果需要,可以通过GetExitCodeThread() 检索FreeLibrary() 的结果。调用线程可以使用WaitForSingleObject()等待远程线程终止。
      • 我无法使用CreateRemoteThread()。我需要静默执行代码,因为大多数防病毒程序都将我的应用程序检测为威胁。另一个原因是 CreateRemoteThread() 方法在 windows 8 中的某些进程上不起作用。
      猜你喜欢
      • 1970-01-01
      • 2010-12-10
      • 2012-01-12
      • 2011-05-18
      • 1970-01-01
      • 2014-09-08
      • 2018-11-07
      • 2011-10-03
      • 1970-01-01
      相关资源
      最近更新 更多