閱讀本文之前,我先假設讀者已經知道了 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
沒啥特別的,仔細一想就明白了。