阅读本文之前,我先假设读者已经知道了 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
没啥特别的,仔细一想就明白了。