WIN32练习项目(函数调用监视器)
一、簡介
這是滴水三期WIN32最后一個項目,進程通信用的是共享內存的方式。
通過內存注入方式向目標進程victim注入DLL,監視程序可以通過IAT HOOK監視目標進程調用MessageBoxA,CreateFileA,通過inline HOOK監視victim自己實現的Add函數。另外,監視程序還可以遠程調用這三個函數。
二、運行結果
注入
監控MessageBoxA
項目和可執行文件可以到我的github下載
https://github.com/Kwansy98/WIN32hanshudiaoyongjianshiqi
三、目標程序Victim.exe
victim是要注入的程序,界面如下圖:
這個程序可以創建對話框,調用CreateFileA獲取文件句柄并打印文件大小,可以做加法運算。沒什么特別的,你可以自己寫一個。
四、控制程序HackVictim.exe
這個程序的界面如下圖:
為了方便調試,我創建該項目時用的是控制臺項目的模板,在main函數里畫界面。這樣可以方便的printf。
該程序的功能是將DLL用內存注入的方式注入到指定進程,注入成功后可以監視目標程序的函數調用,前兩個函數是IAT表里有的,第三個函數是目標程序自己實現的加法函數。
點擊“開始監控”后,在目標程序victim調用函數后,會有控制臺輸出。也可以點擊“遠程調用”來遠程調用指定函數。
IAT HOOK和Inline HOOK經測試可以正常卸載。
講一下控制程序的注意點(坑點和難點)
內存注入這塊,我是從文件系統中讀取dll,然后拉伸,然后修復IAT,然后修復重定位,然后用VirtualAllocEx 和WriteProcessMemory完成注入的。需要注意一點就是IAT表里面只有kernel32.dll和user32.dll的函數是正確的(比如LoadLibrary,MessageBox),因為這兩個dll不管在哪個進程里位置都固定,其他dll里的函數的地址全是錯的。
注入之后要計算入口點地址,創建遠程線程時傳的地址是遠程進程中的入口函數,這個值得計算方法是: 入口點到DLL基址的偏移 + 遠程DLL基址。
五、DLL
這部分是核心,是本項目最難的環節。DLL被控制程序注入到目標程序后,很多函數都要自己手動獲取函數地址(通過LoadLibrary+GetProcAddress)。
typedef int (*PFNSPRINTF)( char *, const char *,...); typedef void* (*PFNMALLOC)(size_t); typedef void (*PFNFREE)(void*); typedef void* (*PFNMEMSET)(void*,int,size_t); typedef void* (*PFNMEMCPY)(void*,void*,size_t); typedef size_t (*PFNSTRLEN)(const char *); ...... PFNSPRINTF _sprintf; PFNMALLOC _malloc; PFNFREE _free; PFNMEMSET _memset; PFNMEMCPY _memcpy; PFNSTRLEN _strlen; ...... // 手動加載要用到的函數 HMODULE hModule = LoadLibraryA("MSVCRT.dll"); _sprintf = (PFNSPRINTF)GetProcAddress(hModule, "sprintf"); _malloc = (PFNMALLOC)GetProcAddress(hModule, "malloc"); _free = (PFNFREE)GetProcAddress(hModule, "free"); _memset = (PFNMEMSET)GetProcAddress(hModule, "memset"); _memcpy = (PFNMEMCPY)GetProcAddress(hModule, "memcpy"); _strlen = (PFNSTRLEN)GetProcAddress(hModule, "strlen");為了方便調試,我給DLL申請了個控制臺,自己封裝了一個輸出函數,當然,肯定沒有printf好用了。
// 申請控制臺 AllocConsole(); g_hStdout = CreateFileA("CONOUT$",GENERIC_READ|GENERIC_WRITE,FILE_SHARE_WRITE,0,OPEN_EXISTING,0,0); //SetStdHandle(STD_OUTPUT_HANDLE,hStdout); Log("申請控制臺成功\n"); ...... void Log(LPCSTR text) {WriteFile(g_hStdout,text,_strlen(text),0,0); }有一個坑點需要注意,我們寫自己修改的MessageBoxA,CreateFileA函數時,記得要寫上調用約定WINAPI,不寫的話程序會崩潰。
// 被監控的MessageBox int WINAPI MyMessageBoxA(HWND hWnd,LPCTSTR lpText,LPCTSTR lpCaption,UINT uType) {typedef int (WINAPI *PFNMESSAGEBOX)(HWND,LPCTSTR,LPCTSTR,UINT); PFNMESSAGEBOX pFnMessageBox = (PFNMESSAGEBOX)GetProcAddress(LoadLibraryA("user32.dll"), "MessageBoxA");char szOutput[1000] = {0};_sprintf(szOutput,"MessageBoxA(%X, %s, %s, %X)\n", hWnd, lpText, lpCaption, uType);Log(szOutput);return pFnMessageBox(hWnd,lpText,lpCaption,uType); }inline hook部分,我們寫自定義Add函數時,要注意堆棧平衡,切記不要在裸函數里定義局部變量,除非你自己提升了堆棧,否則會把上層函數的數據覆蓋掉的。我這里用最簡單的做法,就是使用全局變量。
// 被監控的Add void __declspec(naked)MyAdd() { // 獲取關心的寄存器狀態__asm{mov g_context.Esp,espmov g_context.Eax,eaxmov g_context.Ecx,ecxmov g_context.Edx,edxmov g_context.Ebx,ebx} // 保存8個常用寄存器和標志寄存器__asm{pushadpushfd}// 我的代碼,注意堆棧平衡 __asm{ mov eax,g_context.Espmov ecx,[eax+0x4]mov g_num1,ecxmov eax,g_context.Espmov ecx,[eax+0x8]mov g_num2,ecx}_sprintf(g_szLogBuffer, "Add(%d, %d)\n", g_num1,g_num2);Log(g_szLogBuffer);// 恢復寄存器,執行被替換的代碼,然后返回// 004011D0 55 push ebp// 004011D1 8B EC mov ebp,esp// 004011D3 83 EC 40 sub esp,40h__asm{popfdpopadpush ebpmov ebp,espsub esp,40hjmp g_ret} }六、總結
這個項目我寫了4、5天,基本上都是用的前面的知識,確實有點難度,但總體上還是順利做出來了,卡得最久的還是進程通信那塊。
總結
以上是生活随笔為你收集整理的WIN32练习项目(函数调用监视器)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 生成有控制台的WIN32程序
- 下一篇: 修改PE可选头关闭ASLR