用 SEH 技术实现 API Hook
下载本节例子程序和源代码 (5.21 KB) |
阅 读本文之前,我先假设读者已经知道了 SEH 和 API Hook 的基本概念,因为我不打算在此进行扫盲工作。什么?你不懂什么叫 SEH 和 API Hook ?那……先去找点资料看看吧,到处都有哦,推荐读物:Jeffrey Richter 大牛的《Windows核心编程》。(没话可说,研究系统底层编程的葵花宝典,必备!)
另外值得补充的是,API Hook 跟一般的 Hook 是一点关系都没有的,虽然它们都是“Hook”,但是在技术上却有着天壤之别。啊……不明白?先去看看葵花宝典吧……
呵呵,废话不多说了,让我们开始吧。
经 常研究 Crack 的朋友一定会知道 INT 3 这个指令。(你不知道?我倒……) 这个指令在软件调试中非常有用,因为我们可以利用它来设置特定的断点(BreakPoint),当程序遇到 INT 3 指令的时候,将会产生一个断点异常,这个异常在 Windows.inc 里面定义为 EXCEPTION_BREAKPOINT ,对应值是 080000003h 。Hoho,说了那么多,你想到什么了吗?
是的,聪明的你应该已经想到了!既然是异常,就肯定可以通过 SEH 来进行处理。于是我们可以这样做:在调用 API 之前,先设置一个断点,然后当 API 正式运行的时候,就会因为碰到 INT 3 指令而进入我们的异常处理模块,接着我们就可以在处理模块里面为所欲为了——是改变什么东西还是让它顺利通过,我没话说,看你喜欢吧……
简单地说,过程就是类似这样的:
程 序遇到 INT 3 指令后,产生一个中断异常,这时 Windows 就拿着一份处理异常的活挨个问 SEH 链表上的回调函数:“你干不干?”,“不干”,“你呢?”,“我也不干”……当 Windows 终于问到我们定义好的断点异常处理函数后,他说:“让我来干好了!”,于是 Windows 就不会再问余下的人了,他把全权托给了我们的处理函数,至于我们的函数在之后做了什么手脚……呵呵,只有天知道!
明白了吗?其实在这里我们是利用了软件调试上的一个小技巧,实现了“伪 API Hook”。严格来说,这种方法不能算是真正的 API Hook ,但是由于我们可以在 SEH 回调函数中为所欲为,而系统不会发觉,所以也可以勉强算个数吧。
弄清楚原理后,剩下的就不难了。我们首先要保存目标 API 的入口地址,接着要设置一个 INT 3 指令,然后就在 SEH 的回调函数中进行地址修正等工作,最后万事倶备,只欠东风了。程序一运行,就进入了我们的 SEH 回调函数,呵呵,你爱怎么样就怎么样吧……
怎么样?一点都不难吧。罗里罗嗦地说了一大堆,可能有人会开始不耐烦了……呵,别着急,下面我就给出源代码。补充一句:本方法只是提供了一种新的思路,如果你在深入研究中发现了我的错误,或者有更好的解决方法,请给我来信啊,我的邮箱: [email protected]。
(注意,本技术只能在 NT/2000/XP 平台下使用)
;********************************************************* ;程序名称:用 SEH 技术实现 API Hook ;适用系统:Win NT/2000/XP ;作者:罗聪 ;日期:2002-11-22 ;出处:http://www.LuoCong.com(老罗的缤纷天地) ;注意事项:如欲转载,请保持本程序的完整,并注明: ;转载自“老罗的缤纷天地”(http://www.LuoCong.com) ;********************************************************* .386 .modelflat,stdcall optioncasemap:none include \masm32\include\windows.inc include \masm32\include\kernel32.inc include \masm32\include\user32.inc includelib \masm32\lib\kernel32.lib includelib \masm32\lib\user32.lib WndProc proto:DWORD,:DWORD,:DWORD,:DWORD Error_Handler proto:DWORD,:DWORD,:DWORD,:DWORD SetHook proto .const IDI_LC equ1 IDC_CHECKBUTTON_HOOK equ3000 IDC_BUTTON_ABOUT equ3001 IDC_BUTTON_EXIT equ3002 .data szDlgName db "lc_dialog",0 szMsgAbout db "-= SEH for API Hook =-",13,10,13,10,\ "作者:罗聪([email protected])",13,10,13,10,\ "老罗的缤纷天地",13,10,\ "http://www.LuoCong.com",13,10,0 szMyText db 13,10,13,10,"(哈哈,看到有什么不同了吗?)",0 szMsgHooked db "MessageBoxIndirectA() has been hooked!",\ 13,10,13,10,\ "即将改变原来的 MessageBoxIndirectA() 的参数,",13,10,\ "请注意后面的对话框跟没有 Hook 之前有什么不同……",0 szCaption db "SEH for API Hook by LC",0 szLibUser db "user32",0 szProcMsgBoxInd db "MessageBoxIndirectA",0 dwAddress dd 0 dwOldProtect dd 0 bOldByte db 0 dwRetAddr dd 0 .data? hInstance HINSTANCE ? mbp MSGBOXPARAMS <> szText db 1024dup(?) .code main: ; 设置 SEH 链: assume fs:nothing push offset Error_Handler push fs:[0] mov fs:[0],esp invoke GetModuleHandle, NULL mov hInstance,eax invoke DialogBoxParam, hInstance,offset szDlgName,0, WndProc,0 ; 恢复原来的 SEH 链: pop fs:[0] pop eax invoke ExitProcess,0 WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM .if uMsg == WM_CLOSE invoke EndDialog, hWnd,0 .elseif uMsg == WM_INITDIALOG moveax, hWnd mov[mbp.hwndOwner],eax invoke LoadIcon, hInstance, IDI_LC invoke SendMessage, hWnd, WM_SETICON, ICON_SMALL,eax ; 储存 API 的原入口地址: invoke GetModuleHandle,addr szLibUser invoke GetProcAddress,eax,addr szProcMsgBoxInd mov[dwAddress],eax ; 保存原对话框的输出文字: invoke lstrcpy,addr szText,addr szMsgAbout .elseif uMsg == WM_COMMAND moveax, wParam movedx,eax shredx,16 movzxeax,ax .ifedx== BN_CLICKED .ifeax== IDC_BUTTON_EXIT ||eax== IDCANCEL invoke EndDialog, hWnd, NULL .elseifeax== IDC_BUTTON_ABOUT ||eax== IDOK mov[mbp.cbSize],sizeof mbp moveax, hInstance mov[mbp.hInstance],eax mov[mbp.lpszText],offset szMsgAbout mov[mbp.lpszCaption],offset szCaption mov[mbp.dwStyle], MB_OK or MB_APPLMODAL or MB_USERICON mov[mbp.lpszIcon], IDI_LC invoke MessageBoxIndirect,addr mbp .elseifeax== IDC_CHECKBUTTON_HOOK ; 把内存保护设置成 可读/可写/可执行: invoke VirtualProtect,[dwAddress],1, PAGE_EXECUTE_READWRITE,addr dwOldProtect invoke IsDlgButtonChecked, hWnd, IDC_CHECKBUTTON_HOOK movedx,[dwAddress] testeax,eax .if zero? ; uninstall hook movcl,[bOldByte] ; bOldByte = API 原入口地址 movbyteptr[edx],cl ; 恢复 API 的原入口地址 invoke lstrcpy,addr szMsgAbout,addr szText ; 恢复原对话框的输出文字: .else ; re-install hook movcl,byteptr[edx] ; byte ptr [edx] = API 原入口地址 movbyteptr[edx],0CCh ; 断点异常(INT 3 指令) mov[bOldByte],cl ; 储存 API 的原入口地址 invoke lstrcat,addr szMsgAbout,addr szMyText ; 改变原对话框的输出文字: .endif .endif .endif .else moveax, FALSE ret .endif moveax, TRUE ret WndProc endp ;**************************************** ; 函数功能:处理异常错误 ;**************************************** Error_Handler procusesecx lpExceptRecord:DWORD, lpFrame:DWORD, lpContext:DWORD, lpDispatch:DWORD ; 输出 "API hooked": invoke MessageBox,[mbp.hwndOwner],addr szMsgHooked,addr szCaption,\ MB_OK or MB_ICONINFORMATION ; 储存并改变 SetHook 函数的返回值:(经过修正) ; (想不明白?呵呵,用调试器跟踪一下吧,我也说不清楚,只能意会不能言传……) moveax,[lpContext] moveax,[eax][CONTEXT.regEsp] movecx,[eax] mov[eax],offset SetHook mov[dwRetAddr],ecx ; 把 API 原入口地址写回去,以便继续运行原 API: ; (跟踪一下吧,我实在是不知道怎么才能说得清楚……) moveax,[dwAddress] movcl,[bOldByte] movbyteptr[eax],cl ; 继续下一个 Execution: moveax, ExceptionContinueExecution ret Error_Handler endp ;**************************************** ; 函数功能:设置 API Hook ;**************************************** SetHook procusesecx moveax,[dwAddress] movcl,[eax] movbyteptr[eax],0CCh ; 断点异常(INT 3 指令) mov[bOldByte],cl jmp[dwRetAddr] ; 跳回经过 Hook 之后的 API 的返回地址(很重要!) SetHook endp end main ;******************** over ******************** ;by LC |
它的资源文件:
#include "resource.h" #define IDI_LC 1 #define IDC_CHECKBOX_HOOK 3000 #define IDC_BUTTON_ABOUT 3001 #define IDC_BUTTON_EXIT 3002 #define IDC_STATIC -1 IDI_LC ICON "lc.ico" LC_DIALOG DIALOGEX 10, 10, 200, 50 STYLE DS_SETFONT | DS_CENTER | WS_MINIMIZEBOX | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "SEH for API Hook by LC, 2002-11-22" FONT 8, "MS Sans Serif" BEGIN AUTOCHECKBOX "&Hook MessageBoxIndirectA", IDC_CHECKBOX_HOOK, 5, 5, 190, 12 PUSHBUTTON "关于(&A)", IDC_BUTTON_ABOUT, 5, 30, 90, 14, BS_FLAT | BS_CENTER PUSHBUTTON "退出(&X)", IDC_BUTTON_EXIT, 105, 30, 90, 14, BS_FLAT | BS_CENTER END |
没啥特别的,仔细一想就明白了。
老罗
2002-11-22