Как правильно сохранить и восстановить контекст потока на 64-битном процессе (Windows)?

У меня есть этот код ниже для освобождения библиотеки из 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);
        }
    }
}

2 ответа

Решение

Windows 64-bit использует соглашение о вызовах fastcall. В этом соглашении вызывающая функция ответственна за резервирование 4 * 64 бит (32 байта) в стеке для вызываемой функции для сохранения регистров. Это означает, что ваши звонки должны выглядеть так:

sub rsp, 32
call rax
add rsp, 32

В вашем коде ваши вызовы перезаписывают стек FreeLibrary или Sleep, который не принадлежит их фрейму стека, что впоследствии приводит к сбою.

Вы не выполняете какую-либо обработку ошибок, чтобы убедиться, что память фактически была выделена и записана в другой процесс перед ее выполнением, или чтобы убедиться, что ReadProcessMemory() успешно, или чтобы убедиться, что приостановка / возобновление потока и обмен контекстами были успешными.

Скорее всего, удаленный поток, скорее всего, завершил выполнение вашего внедренного кода и попытается запустить свой исходный код (или даже случайный код, который, случается, следует за вашим выделенным блоком в памяти), прежде чем у вашего инжектора появится возможность вернуться к исходной контекстной информации. Это может быть причиной сбоя.

Вместо того, чтобы перехватывать существующий поток в другом процессе и менять его контекст за его спиной, вы можете рассмотреть возможность использования CreateRemoteThread() вместо этого, чтобы запустить ваш внедренный код в своем собственном выделенном потоке. Нет необходимости менять контекст.

Другие вопросы по тегам