阅读:1947回复:4
API-HOOK and ANTI-API-HOOK For Ring3
<<API-HOOK and ANTI-API-HOOK For Ring3>>
转载请保留版权.谢谢 Anskya@Gmail.com 今天突然看到"堕落天才"仁兄的两篇文章 感谢他的的文章和共享精神.谢谢...突然手痒..有感而发 API-HOOK和ANTI-API-HOOK已经不算什么新鲜的技术了 一般大概用的技术都差不多 [1]简要介绍API-HOOK 1.IAT补丁 介绍: 一般调用函数都是call [MessageBoxA]这样的格式 很明显[MessageBoxA]下的地址就是函数的真正的地址 代码:-------------------------------------------------------------------------------- Delphi: push 0 push 0 push 0 push 0 call -$000467cd(这里是MessageBox在导入表的偏移) -------------------------------------------------------------------------------- -$000467cd下的代码就是: jmp dword ptr [$004514b0] 004514b0下的地址是---77D504EA(刚好就是MessageBoxA的地址) IAT补丁的意思就是修改jmp dword ptr [$004514b0]这句为自己的钩子地址 然后钩子返回的时候返回77D504EA地址 (这里用Delphi的Debug是因为顺手.还有一点就是..VC看不到栈值) 优点:简单... 缺点:如果是动态调用的函数导入表中是不会出现这种函数的 所以就出现了下面的技术 2.内存补丁: 介绍: 由于IAT的缺点于是牛们就想到了动态修改DLL函数的内存 还是以MessageBoxA为例子: 代码:-------------------------------------------------------------------------------- 77D504EA > 8BFF MOV EDI,EDI 77D504EC 55 PUSH EBP 77D504ED 8BEC MOV EBP,ESP 77D504EF 833D BC04D777 0>CMP DWORD PTR DS:[77D704BC],0 77D504F6 74 24 JE SHORT USER32.77D5051C 77D504F8 64:A1 18000000 MOV EAX,DWORD PTR FS:[18] 77D504FE 6A 00 PUSH 0 77D50500 FF70 24 PUSH DWORD PTR DS:[EAX+24] 77D50503 68 240BD777 PUSH USER32.77D70B24 77D50508 FF15 C812D177 CALL DWORD PTR DS:[<&KERNEL32.Interlocke>; kernel32.InterlockedCompareExchange 77D5050E 85C0 TEST EAX,EAX 77D50510 75 0A JNZ SHORT USER32.77D5051C 77D50512 C705 200BD777 0>MOV DWORD PTR DS:[77D70B20],1 77D5051C 6A 00 PUSH 0 77D5051E FF75 14 PUSH DWORD PTR SS:[EBP+14] 77D50521 FF75 10 PUSH DWORD PTR SS:[EBP+10] 77D50524 FF75 0C PUSH DWORD PTR SS:[EBP+C] 77D50527 FF75 08 PUSH DWORD PTR SS:[EBP+8] 77D5052A E8 2D000000 CALL USER32.MessageBoxExA 77D5052F 5D POP EBP 77D50530 C2 1000 RETN 10 -------------------------------------------------------------------------------- 一般挂钩法就是修改前5个字节 mov edi,edi push ebp mov ebp,esp 刚好是5个字节.jmp到HookProc的地址 然后再调回到MessageBoxA+5的地方... 优点:比较实用 缺点:Ring3很好用...如果非要弄个缺点就是 有的时候函数代码头部未必是 mov edi,edi push ebp mov ebp,esp 许多API函数的头部都是这样的但是一些cdecl调用格式 或者非stdcall格式的函数无法挂钩.但是配合脱钩一起用会发现 效果不错...可以当bpx用..再配合一点汇编知识就可以获取寄存器数据等 3.深入上面的 有许许多多的什么陷井技术,栈填写返回地址等...RelocationTable挂钩技术 其实就是内存补丁技术...不过用了不同的方法填写返回地址而已 4.SEH or VEH挂钩 填写Int3,Int1等指令让程序产生异常然后调转到钩子执行过程. 个人很习惯这种方法VEH玩过一下...但是由于兼容性不强.你可以在HookSpy的代码 找到VEH的代码和相关应用.配合着调试API用起来也很过瘾 至少你不用DebugActiveProcess函数去挂接进程 直接利用CreateRemoteThread函数注入DLL.注入方法很多看个人喜好 具体查看罗聪前辈的<<用 SEH 技术实现 API Hook>> VEH技术实现最近打算也写一篇...正在孕酿(关于着方面的知识太少了) 有个地方需要注意~SEH和VEH有点不太好调试~许多异常都会碑调试器捕获到 所以最好用OD或者专用的调试器...编程工具自带的调试器会捕获所有的异常 5.调试寄存器 不多说了看EliCZ叔叔的文章和代码吧 说了一大对无聊的东西现在来说说反调试的问题 [2]常用的ANTI-APIHOOK技术 1.IAT-API-HOOK 由于修改的导入表地址. 最简单的方法就是你需要的函数全部使用GetProcAddress函数来获取 代码:-------------------------------------------------------------------------------- typedef int(*TMessageBoxA)(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType); void __fastcall TForm1::Button1Click(TObject *Sender) { TMessageBoxA MsgBox; MsgBox = (TMessageBoxA)GetProcAddress(LoadLibrary("user32.dll"), "MessageBoxA"); MsgBox(0, 0, 0, 0); } -------------------------------------------------------------------------------- 2.反内存补丁(着重介绍这里) 1.相信许多人都用过Madshi的madCollection 通过跟踪发现他是 一般挂钩都是修改前5个字节 代码:-------------------------------------------------------------------------------- 77D504EA >- FF25 1E00055F JMP DWORD PTR DS:[5F05001E]--被补丁了. 77D504F0 3D BC04D777 CMP EAX,user32.77D704BC 77D504F5 007424 64 ADD BYTE PTR SS:[ESP+64],DH 77D504F9 A1 18000000 MOV EAX,DWORD PTR DS:[18] 77D504FE 6A 00 PUSH 0 77D50500 FF70 24 PUSH DWORD PTR DS:[EAX+24] 77D50503 68 240BD777 PUSH user32.77D70B24 77D50508 FF15 C812D177 CALL DWORD PTR DS:[<&KERNEL32.Interlocke>; kernel32.InterlockedCompareExchange 77D5050E 85C0 TEST EAX,EAX 77D50510 75 0A JNZ SHORT user32.77D5051C 77D50512 C705 200BD777 0>MOV DWORD PTR DS:[77D70B20],1 77D5051C 6A 00 PUSH 0 77D5051E FF75 14 PUSH DWORD PTR SS:[EBP+14] 77D50521 FF75 10 PUSH DWORD PTR SS:[EBP+10] 77D50524 FF75 0C PUSH DWORD PTR SS:[EBP+C] 77D50527 FF75 08 PUSH DWORD PTR SS:[EBP+8] 77D5052A E8 2D000000 CALL user32.MessageBoxExA 77D5052F 5D POP EBP 77D50530 C2 1000 RETN 10 -------------------------------------------------------------------------------- 知道就可以脱钩了.那我们如何判断他是否被挂钩呢? 代码:-------------------------------------------------------------------------------- bool IsHook(char *lpChar) { if(*lpChar == 0xFF) return true; } function IsHook(lpFunc: Pointer): Boolean; begin Result := False; if (Char(lpFunc^)=#$FF) then Result := True; end; -------------------------------------------------------------------------------- 判断第一个字节是否是0xFF,你也可以根据一些特别的挂钩修改 例如有的是直接jmp 调转到直接的地址...E9.或者call-F8(机器码) 2.栈保存地址.. 和上面一样不过这里有一点点区别挂钩方式被修改成 代码:-------------------------------------------------------------------------------- 77D504EA > 68 04BF4000 PUSH 40BF04------这里被写成钩子过程地址 77D504EF C3 RETN-------------返回 77D504F0 3D BC04D777 CMP EAX,user32.77D704BC 77D504F5 007424 64 ADD BYTE PTR SS:[ESP+64],DH 77D504F9 A1 18000000 MOV EAX,DWORD PTR DS:[18] 77D504FE 6A 00 PUSH 0 77D50500 FF70 24 PUSH DWORD PTR DS:[EAX+24] 77D50503 68 240BD777 PUSH user32.77D70B24 77D50508 FF15 C812D177 CALL DWORD PTR DS:[<&KERNEL32.Interlocke>; kernel32.InterlockedCompareExchange 77D5050E 85C0 TEST EAX,EAX 77D50510 75 0A JNZ SHORT user32.77D5051C 77D50512 C705 200BD777 0>MOV DWORD PTR DS:[77D70B20],1 77D5051C 6A 00 PUSH 0 77D5051E FF75 14 PUSH DWORD PTR SS:[EBP+14] 77D50521 FF75 10 PUSH DWORD PTR SS:[EBP+10] 77D50524 FF75 0C PUSH DWORD PTR SS:[EBP+C] 77D50527 FF75 08 PUSH DWORD PTR SS:[EBP+8] 77D5052A E8 2D000000 CALL user32.MessageBoxExA 77D5052F 5D POP EBP 77D50530 C2 1000 RETN 10 -------------------------------------------------------------------------------- 执行完毕后再调转回来 对付这种挂钩方式...方法一般2种 分析一下他是如何挂钩的吧...首先他要先获取你要挂钩的函数地址 填写前六个字节 1.你提前挂钩...然后让他挂你的钩子. 这样你直接调用你自己的返回地址就好了最好可以多复制一点. 另外说一点.许多API函数都是调用xxxxW或者xxxxEx什么的 所以一般A系函数都很短你甚至可以直接复制到自身进程里面去执行 具体代码和资料看下面的描述 2.脱钩. 这里代码很多.几乎所有的API-Hook library都有这个函数 自己看一下吧.我就不多说了 3.利用反汇编引擎搜索钩子返回地址!Cool.直接调用返回函数!! 由于挂钩的时候会把修改的代码转移到别的地方 挂钩需要使用6个字节的空间.所以挂钩的时候就需要计算 需要多长的指令...(所以一般CodeHook Library里面都自带一个长度反汇编引擎) 看个人喜好...看你用的是什么库了(29A等许多病毒代码里面都有许多) 常用的: Opcode Length Disassembler Coded By Ms-Rem(ASM,C,Delphi版本) Length Disassembler Engine By Zombie 其它的还有.刚在WASM上发现一个新的,没有用过反正就那两种原理 也没有多少测试...好了下面说说如何寻找返回地址 钩子都是需要返回地址的.不然这个函数就会被屏蔽.无效代码 可是返回地址如何获取呢???既然是挂钩那就肯定有一个返回地址.. 不然他自己怎么调用API函数? 这里引入一个疑问... 返回地址一般都是最后~由于是跨段代码调转,所以搜索ret后最后 一个跨段调转即可... 1.判断是否被挂钩, 一般API函数开头都是 代码:-------------------------------------------------------------------------------- mov edi, edi push ebp mov ebp, esp or push ebp mov ebp, esp -------------------------------------------------------------------------------- 第一种长度2字节,第二种长度1字节.一般说来程序第一个字节都不太可能是 E9,E8,FF之类的(为什么很少看到enter这个指令?据说有BUG?) 一旦超过5个字节就认为他被挂钩了! 2.确定他复制走了多少地址 (由于需要六个字节,但是前面三条指令只有5个字节.他就从第4行开始动手, 然后下来就是.开始计算了.) 祭出LDE或者OLD. 可以确定第一行指令是,push 0x00000000,jmp 0x00000000 这样的东东...如果有哪位仁兄使用了比较另类的挂钩指令的话.那就需要特殊处理 了 SizeOfCode(void *Code, unsigned char **pOpcode);返回指令长度 SizeOfProc(void *Proc);获取过程长度.-她会从指针开头反汇编.直到遇到ret (友情提示.代码有个BUG) 代码:-------------------------------------------------------------------------------- unsigned long __fastcall SizeOfProc(void *Proc) { ULONG Length; PUCHAR pOpcode; ULONG Result = 0; do { Length = SizeOfCode(Proc, &pOpcode); Result += Length; if ((Length == 1) && (*pOpcode == 0xC3)) break; Proc = (PVOID)((ULONG)Proc + Length); } while (Length); return Result; } -------------------------------------------------------------------------------- 当指令长度为1~机器码为C3才认为结束...许多stdcall都是自己恢复堆栈平衡的 ...这里友情提示一下吧...自己编程的时候要小心.至于如何修改?嘿嘿.. 很容易我就不多说了.省得别人说我鸡婆... 使用SizeOfCode函数获取指令长度直到大于6或者等于6 如果第一行就是5个字节.取代码指针. 3.既然已经获取到了地址 稍微跟踪一下就会发现函数返回的地址是ret往上第一条指令!也就是最后一条指令 由于是跨段调转所以会使用call[]---机器码FF的指令 代码:-------------------------------------------------------------------------------- function GetProcAddressEx(Proc: Pointer): Pointer; var lpCallRet: Pointer; iCodeLen: Integer; begin Result := nil; lpCallRet := nil; iCodeLen := SizeOfCode(Proc); // 判断第一行代码是否为push 0xXXXXXXXX if (iCodeLen = 5) and (Byte(Proc^) = $68) then begin // 获取0xXXXXXXXX Proc := Pointer(PDWORD(longword(Proc) + 1)^); while True do begin iCodeLen := SizeOfCode(Proc); // 判断是否为call dword ptf[0xXXXXXXXX] if (iCodeLen = 6) and (Byte(Proc^) = $FF) and (Byte(Pointer (longword(Proc) + 1)^) = $15) then begin // 获取0xXXXXXXXX lpCallRet := Proc; Break; end; // 函数结尾 if (Byte(Proc^) = $C3) then Break; Proc := pointer(longword(Proc) + iCodeLen); end; end; if lpCallRet <> nil then begin Result := Pointer(PDWORD(PDWORD(longword(lpCallRet) + 2)^)^); end; end; -------------------------------------------------------------------------------- 不好意思...由于暂时没有编程工具.文章里面出现的代码是以前写的 这篇文章完全是一篇回忆录...以前写的代码和资料全部丢失了.. 这里使用的是Ms-Rem的Opcode Length Disassembler Engine 这里描述的是如何bypass 堆跳转挂钩模式...jmp和call调转模式 都一样的原理和代码可以实现.大家可以自己模拟作一下.关键是思路 大家有什么好的方法希望提供... OLD引擎您可以从Ms-Rem的process_hunter Src中获取到 www.wasm.ru 再次感谢许许多多的知名的不知名的大侠们的文章和代码.感谢 thank:EliCZ,Madshi,Ms-Rem,Aphex...29A Group 转载请保留版权.谢谢 Anskya@Gmail.com转 |
|
最新喜欢:Dragon...
|
沙发#
发布于:2007-02-19 22:40
Anti ApiHook For Ring3
写点代码~ bool RemoveFWHook(char* szDllPath, char* szFuncName) { HMODULE lpBase = LoadLibrary(szDllPath); LPVOID lpFunc = GetProcAddress(lpBase, szFuncName); if(!lpFunc) return false; DWORD dwRVA = (DWORD)lpFunc-(DWORD)lpBase; HANDLE hFile = CreateFile(szDllPath,GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL, NULL); if(INVALID_HANDLE_VALUE == hFile) return false; DWORD dwSize = GetFileSize(hFile, NULL); HANDLE hMapFile = CreateFileMapping(hFile, NULL, PAGE_READONLY|SEC_IMAGE, 0, dwSize, NULL); LPVOID lpBaseMap = MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, dwSize); LPVOID lpRealFunc = (LPVOID)((DWORD)lpBaseMap+dwRVA); DWORD dwOldProtect; BOOL bRes=true; if(VirtualProtect(lpFunc, 10, PAGE_EXECUTE_READWRITE, &dwOldProtect)) { memcpy(lpFunc, lpRealFunc, 10); }else{ bRes=false; } UnmapViewOfFile(lpBaseMap); CloseHandle(hMapFile); CloseHandle(hFile); return bRes; } |
|
|
板凳#
发布于:2007-02-19 22:41
本代码恢复api入口10字节而已~不是很好,大家可以自己写一个恢复多一些的~
|
|
|
地板#
发布于:2007-02-21 02:07
最好能贴点轰动全球的,例如R3下用VB/VBS/JS/Pascal恢复SSDT/Inline之类的,哈哈~
|
|
地下室#
发布于:2007-04-30 19:06
研究下
|
|
|