用 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 回调函数,呵呵,你爱怎么样就怎么样吧……
怎么样?一点都不难吧。罗里罗嗦地说了一大堆,可能有人会开始不耐烦了……呵,别着急,下面我就给出源代码。补充一句:本方法只是提供了一种新的思路,如果你在深入研究中发现了我的错误,或者有更好的解决方法,请给我来信啊,我的邮箱: lcother@163.net。
(注意,本技术只能在 NT/2000/XP 平台下使用)
;********************************************************* ;程序名称:用 SEH 技术实现 API Hook ;适用系统:Win NT/2000/XP ;作者:罗聪 ;日期:2002-11-22 ;出处:http://www.LuoCong.com(老罗的缤纷天地) ;注意事项:如欲转载,请保持本程序的完整,并注明: ;转载自“老罗的缤纷天地”(http://www.LuoCong.com) ;********************************************************* .386 .model flat, stdcall option casemap: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 equ 1 IDC_CHECKBUTTON_HOOK equ 3000 IDC_BUTTON_ABOUT equ 3001 IDC_BUTTON_EXIT equ 3002 .data szDlgName db "lc_dialog", 0 szMsgAbout db "-= SEH for API Hook =-", 13, 10, 13, 10,/ "作者:罗聪(lcother@163.net)", 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 1024 dup(?) .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 mov eax, 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 mov eax, wParam mov edx, eax shr edx, 16 movzx eax, ax .if edx == BN_CLICKED .if eax == IDC_BUTTON_EXIT || eax == IDCANCEL invoke EndDialog, hWnd, NULL .elseif eax == IDC_BUTTON_ABOUT || eax == IDOK mov [mbp.cbSize], sizeof mbp mov eax, 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 .elseif eax == IDC_CHECKBUTTON_HOOK ; 把内存保护设置成 可读/可写/可执行: invoke VirtualProtect, [dwAddress], 1, PAGE_EXECUTE_READWRITE, addr dwOldProtect invoke IsDlgButtonChecked, hWnd, IDC_CHECKBUTTON_HOOK mov edx, [dwAddress] test eax, eax .if zero? ; uninstall hook mov cl, [bOldByte] ; bOldByte = API 原入口地址 mov byte ptr [edx], cl ; 恢复 API 的原入口地址 invoke lstrcpy, addr szMsgAbout, addr szText ; 恢复原对话框的输出文字: .else ; re-install hook mov cl, byte ptr [edx] ; byte ptr [edx] = API 原入口地址 mov byte ptr [edx], 0CCh ; 断点异常(INT 3 指令) mov [bOldByte], cl ; 储存 API 的原入口地址 invoke lstrcat, addr szMsgAbout, addr szMyText ; 改变原对话框的输出文字: .endif .endif .endif .else mov eax, FALSE ret .endif mov eax, TRUE ret WndProc endp ;**************************************** ; 函数功能:处理异常错误 ;**************************************** Error_Handler proc uses ecx lpExceptRecord:DWORD, lpFrame:DWORD, lpContext:DWORD, lpDispatch:DWORD ; 输出 "API hooked": invoke MessageBox, [mbp.hwndOwner], addr szMsgHooked, addr szCaption,/ MB_OK or MB_ICONINFORMATION ; 储存并改变 SetHook 函数的返回值:(经过修正) ; (想不明白?呵呵,用调试器跟踪一下吧,我也说不清楚,只能意会不能言传……) mov eax, [lpContext] mov eax, [eax][CONTEXT.regEsp] mov ecx, [eax] mov [eax], offset SetHook mov [dwRetAddr], ecx ; 把 API 原入口地址写回去,以便继续运行原 API: ; (跟踪一下吧,我实在是不知道怎么才能说得清楚……) mov eax, [dwAddress] mov cl, [bOldByte] mov byte ptr [eax], cl ; 继续下一个 Execution: mov eax, ExceptionContinueExecution ret Error_Handler endp ;**************************************** ; 函数功能:设置 API Hook ;**************************************** SetHook proc uses ecx mov eax, [dwAddress] mov cl, [eax] mov byte ptr [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 |
没啥特别的,仔细一想就明白了。