Как правильно сохранить и восстановить контекст потока на 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()
вместо этого, чтобы запустить ваш внедренный код в своем собственном выделенном потоке. Нет необходимости менять контекст.