Three Ways to Inject Your Code into Another Process 一文中提到成功注入進程後,子類化視窗過程的一些技巧。作者分配了一塊堆記憶體,然後將線程參數和線程函數前後緊挨著拷貝到這片堆記憶體中,最後CreateRemoteThread運行線程函數。作者的這種方式要獲得線程參數只能通過遠程線程自定位的方式。我這麼懶惰可能直接把需要的參數當做CreateRemoteThread的參數一起傳遞給遠程線程了,當然,雖然沒驗證過,但是應該可行。
作者的文章被轉載這麼多次,他提到的技巧不嘗試一下有點暴殄天物的感覺,於是就有了這篇文章。
本文依然一步步走,一下子寫出正確的代碼感覺少了點挫敗感,同時也比較符合人類進步的規律;再說這同時記錄了自己犯下的錯誤。
注意,工程設定中需要去掉增量串連和 /Gz選項
1)先上直觀的代碼,當然是運行出錯的。
列表1:
#include <stdafx.h>#include <windows.h>#include <stdio.h>#include <assert.h>#include <string.h>typedef HANDLE (*open)(LPCTSTR,DWORD,DWORD,LPSECURITY_ATTRIBUTES,DWORD,DWORD,HANDLE);typedef BOOL (*read)(HANDLE,LPVOID,DWORD,LPDWORD,LPOVERLAPPED);typedef BOOL (*write)(HANDLE,LPVOID,DWORD,LPDWORD,LPOVERLAPPED);typedef DWORD (*func)(void*);#define ERRERS 1#define PATHLEN 64#define PROLOGUE 0x23#define FUNCOFFSET 0x44#define STRUCTLEN sizeof(INJCODE)#define OPENPROCESSPROITY PROCESS_CREATE_THREAD|PROCESS_QUERY_INFORMATION|PROCESS_VM_OPERATION|PROCESS_VM_WRITE|PROCESS_VM_READstruct INJCODE{char filePath[PATHLEN];func func1; //writeFunc};void MinorFunc(){__asm{int 0x03;}}void AfterMinorFunc(){}DWORD FuncV1(void* args){INJCODE* injCode;injCode = (INJCODE*)MinorFunc;injCode--; (injCode->func1)(); return 0;}DWORD AfterMajorFunc(void* args){return 0;}INJCODE* injCode;int main(){printf("FuncV1:%p\n",FuncV1);DWORD pid,dwThreadId;DWORD writtenNum,readNum;INJCODE* injCode;FILE* fp = fopen("c:\\pid.txt","r+");assert(fp);fscanf(fp,"%d",&pid);fclose(fp);HANDLE remoteProgHd = OpenProcess(OPENPROCESSPROITY,FALSE,pid);DWORD funcLen = ((DWORD)AfterMajorFunc-(DWORD)FuncV1) + ((DWORD)AfterMinorFunc-(DWORD)MinorFunc);injCode = (INJCODE*)VirtualAllocEx(remoteProgHd,NULL,sizeof(INJCODE)+funcLen,MEM_COMMIT,PAGE_EXECUTE_READWRITE); //本意是想給injCode->filePath賦值的結果: //讀injCode變數,引起異常的訪問方式strcpy(injCode->filePath,"abc"); //同樣會引起異常 <pre name="code" class="cpp"> injCode->func1=FuncV1; return 0;}
代碼初看沒問題吧,調試運行,程式運行到
strcpy(injCode->filePath,"abc");
必然出錯,看windbg輸出,C0000005,同時dt injCode分配的記憶體也有錯誤的提示:
排查了很久,才找到根結所在:
injCode = (INJCODE*)VirtualAllocEx(remoteProgHd,NULL,sizeof(INJCODE)+funcLen,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
VirtualAllocEx成功返回,injCode指向的記憶體區(0x00000ac0)在目標進程中,同樣的地址在本進程中可能未分配或者沒有相應存取權限,如果訪問會導致出錯。在這段代碼中,程式企圖讀本進程0ac0處的記憶體值,從windbg輸出來看這片記憶體應該還未分配,因此出現異常。
記得injCode指向的是遠程線程的記憶體區。因此對任何由VirtualAllocEx(RemoteHandle);分配的記憶體進行的讀寫操作只能藉助Read/WriteProcessMemory來訪問遠程結構體變數中的各個成員
姑且稱用VirtualAllocEx建立遠程進程變數吧,便於理解~
2)上更正main函數版的,用正確的方法給injCode結構各變數賦值:
列表2)
#include <stdafx.h>#include <windows.h>#include <stdio.h>#include <assert.h>#include <string.h>typedef HANDLE (*open)(LPCTSTR,DWORD,DWORD,LPSECURITY_ATTRIBUTES,DWORD,DWORD,HANDLE);typedef BOOL (*read)(HANDLE,LPVOID,DWORD,LPDWORD,LPOVERLAPPED);typedef BOOL (*write)(HANDLE,LPVOID,DWORD,LPDWORD,LPOVERLAPPED);typedef DWORD (*func)(void*);#define ERRERS 1#define PATHLEN 64#define PROLOGUE 0x23#define FUNCOFFSET 0x44#define STRUCTLEN sizeof(INJCODE)#define OPENPROCESSPROITY PROCESS_CREATE_THREAD|PROCESS_QUERY_INFORMATION|PROCESS_VM_OPERATION|PROCESS_VM_WRITE|PROCESS_VM_READstruct INJCODE{char filePath[PATHLEN];//+0x00func func1;//+0x40func func2;//+0x44};void MinorFunc(){__asm{int 0x03;}}void AfterMinorFunc(){}DWORD MajorFunc(void* args){INJCODE* injCode;injCode = (INJCODE*)MajorFunc;injCode--;(injCode->func2)(NULL);return 0;}#if 0DWORD FuncV2(void* args){INJCODE* injCode;__asm{call CurEip;CurEip:pop eax;//指向writeFunc的起始sub eax,PROLOGUE;//指向堆分配的INJCODE起始sub eax,FUNCOFFSET;mov injCode,eax;}return 0;}#endifDWORD AfterMajorFunc(void* args){return 0;}INJCODE* injCode;int main(){DWORD pid,dwThreadId;DWORD writtenNum,readNum;INJCODE* injCode;FILE* fp = fopen("c:\\pid.txt","r+");assert(fp);fscanf(fp,"%d",&pid);fclose(fp);HANDLE remoteProgHd = OpenProcess(OPENPROCESSPROITY,FALSE,pid);//主函數和被調用函數的長度,首先存放MajorFunc,後面緊跟著MinorFuncDWORD funcLen = ((DWORD)AfterMajorFunc-(DWORD)MajorFunc) + ((DWORD)AfterMinorFunc-(DWORD)MinorFunc);injCode = (INJCODE*)VirtualAllocEx(remoteProgHd,NULL,sizeof(INJCODE)+funcLen,MEM_COMMIT,PAGE_EXECUTE_READWRITE);//INJCODE!+0x40 func1所在,存放主函數指標 DWORD Func1Addr = ((DWORD)((char*)injCode)+0x40);////INJCODE!+0x44 func2所在,存放被調函數指標 DWORD Func2Addr = ((DWORD)((char*)injCode)+0x44);DWORD MajorFuncBase = ((DWORD)((char*)injCode)+sizeof(INJCODE));DWORD MinorFuncBase = ((DWORD)((char*)injCode)+sizeof(INJCODE)+((DWORD)AfterMajorFunc-(DWORD)MajorFunc));WriteProcessMemory(remoteProgHd,((char*)injCode)+0x00,"c:\\1.txt",strlen("c:\\1.txt"),&writtenNum);//injCode!func1=MajorFuncWriteProcessMemory(remoteProgHd,(char*)Func1Addr,&MajorFuncBase,sizeof(DWORD),&writtenNum);//injCode!func2=MinorFuncWriteProcessMemory(remoteProgHd,(char*)Func2Addr,&MinorFuncBase,sizeof(DWORD),&writtenNum);//複製函數到遠程進程WriteProcessMemory(remoteProgHd,(char*)MajorFuncBase,MajorFunc,((DWORD)AfterMajorFunc-(DWORD)MajorFunc),&writtenNum);WriteProcessMemory(remoteProgHd,(char*)MinorFuncBase,MinorFunc,((DWORD)AfterMinorFunc-(DWORD)MinorFunc),&writtenNum);HANDLE remoteThread = CreateRemoteThread(remoteProgHd,NULL,0,(LPTHREAD_START_ROUTINE)((char*)MajorFuncBase),NULL,0,&dwThreadId);WaitForSingleObject(remoteThread, INFINITE);return 0;}
編譯運行,injCode變數中各個成員的值已經成功更新到目標進程了:
可以看到b50000 +40 +44處分別是兩個函數的地址值,再看下這兩個地址上的函數:
可是運行CreateRemoteThread後,被注入的進程運行到call ecx後還是出錯,why。
回到代碼MajorFunc中:
<pre name="code" class="cpp">INJCODE* injCode;injCode = (INJCODE*)MajorFunc;injCode--;
對應的Opcode為:
列表3)
mov dword ptr [ebp-4],401030hmov eax,dword ptr [ebp-4]
MajorFunc被翻譯為401030,絕對位址,更重要的,401030是在注入進程的地址空間中,目標進程的地址空間中未必有,因此會導致訪問出錯。代碼運行在其他進程空間中,而且地址不確定,怎麼才能獲得injCode的地址。這就用到線程自定位了,遠程線程獲得當前指令的記憶體位址,減去位移量,最終定位到injCode的起始地址。因為線程啟動並執行函數位於injCode分配的記憶體空間中,且緊挨著injCode變數,修改修改位移找個起始地址還不簡單。
列表4) 終結版:
// redirect.cpp : Defines the entry point for the console application.//#include <stdafx.h>#include <windows.h>#include <stdio.h>#include <assert.h>#include <string.h>typedef HANDLE (*open)(LPCTSTR,DWORD,DWORD,LPSECURITY_ATTRIBUTES,DWORD,DWORD,HANDLE);typedef BOOL (*read)(HANDLE,LPVOID,DWORD,LPDWORD,LPOVERLAPPED);typedef BOOL (*write)(HANDLE,LPVOID,DWORD,LPDWORD,LPOVERLAPPED);typedef DWORD (*func)(void*);#define ERRERS 1#define PATHLEN 64#define PROLOGUE 0x0e#define FUNCOFFSET 0x48#define STRUCTLEN sizeof(INJCODE)#define OPENPROCESSPROITY PROCESS_CREATE_THREAD|PROCESS_QUERY_INFORMATION|PROCESS_VM_OPERATION|PROCESS_VM_WRITE|PROCESS_VM_READstruct INJCODE{char filePath[PATHLEN];//+0x00func func1;//+0x40func func2;//+0x44};void MinorFunc(){__asm{int 0x03;}}void AfterMinorFunc(){}DWORD MajorFunc(void* args){INJCODE* injCode;__asm{call CurEip;CurEip:pop eax;//指向writeFunc的起始sub eax,PROLOGUE;//指向堆分配的INJCODE起始sub eax,FUNCOFFSET;mov injCode,eax;}(injCode->func2)(NULL);return 0;}DWORD AfterMajorFunc(void* args){return 0;}INJCODE* injCode;int main(){DWORD pid,dwThreadId;DWORD writtenNum,readNum;INJCODE* injCode;FILE* fp = fopen("c:\\pid.txt","r+");assert(fp);fscanf(fp,"%d",&pid);fclose(fp);HANDLE remoteProgHd = OpenProcess(OPENPROCESSPROITY,FALSE,pid);//主函數和被調用函數的長度,首先存放MajorFunc,後面緊跟著MinorFuncDWORD funcLen = ((DWORD)AfterMajorFunc-(DWORD)MajorFunc) + ((DWORD)AfterMinorFunc-(DWORD)MinorFunc);injCode = (INJCODE*)VirtualAllocEx(remoteProgHd,NULL,sizeof(INJCODE)+funcLen,MEM_COMMIT,PAGE_EXECUTE_READWRITE);//INJCODE!+0x40 func1所在,存放主函數指標 DWORD Func1Addr = ((DWORD)((char*)injCode)+0x40);////INJCODE!+0x44 func2所在,存放被調函數指標 DWORD Func2Addr = ((DWORD)((char*)injCode)+0x44);DWORD MajorFuncBase = ((DWORD)((char*)injCode)+sizeof(INJCODE));DWORD MinorFuncBase = ((DWORD)((char*)injCode)+sizeof(INJCODE)+((DWORD)AfterMajorFunc-(DWORD)MajorFunc));WriteProcessMemory(remoteProgHd,((char*)injCode)+0x00,"c:\\1.txt",strlen("c:\\1.txt"),&writtenNum);//injCode!func1=MajorFuncWriteProcessMemory(remoteProgHd,(char*)Func1Addr,&MajorFuncBase,sizeof(DWORD),&writtenNum);//injCode!func2=MinorFuncWriteProcessMemory(remoteProgHd,(char*)Func2Addr,&MinorFuncBase,sizeof(DWORD),&writtenNum);//複製函數到遠程進程WriteProcessMemory(remoteProgHd,(char*)MajorFuncBase,MajorFunc,((DWORD)AfterMajorFunc-(DWORD)MajorFunc),&writtenNum);WriteProcessMemory(remoteProgHd,(char*)MinorFuncBase,MinorFunc,((DWORD)AfterMinorFunc-(DWORD)MinorFunc),&writtenNum);HANDLE remoteThread = CreateRemoteThread(remoteProgHd,NULL,0,(LPTHREAD_START_ROUTINE)((char*)MajorFuncBase),NULL,0,&dwThreadId);WaitForSingleObject(remoteThread, INFINITE);return 0;}注入後運行MinorFunc結果:
跳轉到0xb50078後:
順利的運行到MinorFunc中。
至此,全文完。