用 SEH 技術(shù)實現(xiàn) API Hook

下載本節(jié)例子程序和源代碼 (5.21 KB)
本地下載

閱 讀本文之前,我先假設(shè)讀者已經(jīng)知道了 SEH 和 API Hook 的基本概念,因為我不打算在此進(jìn)行掃盲工作。什么?你不懂什么叫 SEH 和 API Hook ?那……先去找點資料看看吧,到處都有哦,推薦讀物:Jeffrey Richter 大牛的《Windows核心編程》。(沒話可說,研究系統(tǒng)底層編程的葵花寶典,必備!)

另外值得補(bǔ)充的是,API Hook 跟一般的 Hook 是一點關(guān)系都沒有的,雖然它們都是“Hook”,但是在技術(shù)上卻有著天壤之別。啊……不明白?先去看看葵花寶典吧……

呵呵,廢話不多說了,讓我們開始吧。

經(jīng) 常研究 Crack 的朋友一定會知道 INT 3 這個指令。(你不知道?我倒……) 這個指令在軟件調(diào)試中非常有用,因為我們可以利用它來設(shè)置特定的斷點(BreakPoint),當(dāng)程序遇到 INT 3 指令的時候,將會產(chǎn)生一個斷點異常,這個異常在 Windows.inc 里面定義為 EXCEPTION_BREAKPOINT ,對應(yīng)值是 080000003h 。Hoho,說了那么多,你想到什么了嗎?

是的,聰明的你應(yīng)該已經(jīng)想到了!既然是異常,就肯定可以通過 SEH 來進(jìn)行處理。于是我們可以這樣做:在調(diào)用 API 之前,先設(shè)置一個斷點,然后當(dāng) API 正式運行的時候,就會因為碰到 INT 3 指令而進(jìn)入我們的異常處理模塊,接著我們就可以在處理模塊里面為所欲為了——是改變什么東西還是讓它順利通過,我沒話說,看你喜歡吧……

簡單地說,過程就是類似這樣的:

程 序遇到 INT 3 指令后,產(chǎn)生一個中斷異常,這時 Windows 就拿著一份處理異常的活挨個問 SEH 鏈表上的回調(diào)函數(shù):“你干不干?”,“不干”,“你呢?”,“我也不干”……當(dāng) Windows 終于問到我們定義好的斷點異常處理函數(shù)后,他說:“讓我來干好了!”,于是 Windows 就不會再問余下的人了,他把全權(quán)托給了我們的處理函數(shù),至于我們的函數(shù)在之后做了什么手腳……呵呵,只有天知道!

明白了嗎?其實在這里我們是利用了軟件調(diào)試上的一個小技巧,實現(xiàn)了“偽 API Hook”。嚴(yán)格來說,這種方法不能算是真正的 API Hook ,但是由于我們可以在 SEH 回調(diào)函數(shù)中為所欲為,而系統(tǒng)不會發(fā)覺,所以也可以勉強(qiáng)算個數(shù)吧。

弄清楚原理后,剩下的就不難了。我們首先要保存目標(biāo) API 的入口地址,接著要設(shè)置一個 INT 3 指令,然后就在 SEH 的回調(diào)函數(shù)中進(jìn)行地址修正等工作,最后萬事倶備,只欠東風(fēng)了。程序一運行,就進(jìn)入了我們的 SEH 回調(diào)函數(shù),呵呵,你愛怎么樣就怎么樣吧……

怎么樣?一點都不難吧。羅里羅嗦地說了一大堆,可能有人會開始不耐煩了……呵,別著急,下面我就給出源代碼。補(bǔ)充一句:本方法只是提供了一種新的思路,如果你在深入研究中發(fā)現(xiàn)了我的錯誤,或者有更好的解決方法,請給我來信啊,我的郵箱:lcother@163.net

(注意,本技術(shù)只能在 NT/2000/XP 平臺下使用)

;*********************************************************
;程序名稱:用 SEH 技術(shù)實現(xiàn) API Hook
;適用系統(tǒng):Win NT/2000/XP
;作者:羅聰
;日期:2002-11-22
;出處:http://www.LuoCong.com(老羅的繽紛天地)
;注意事項:如欲轉(zhuǎn)載,請保持本程序的完整,并注明:
;轉(zhuǎn)載自“老羅的繽紛天地”(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,\
????????????????????????????"作者:羅聰(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() 的參數(shù),",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:
????; 設(shè)置 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

????; 恢復(fù)原來的 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
????????????????; 把內(nèi)存保護(hù)設(shè)置成 可讀/可寫/可執(zhí)行:
????????????????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??????????????????????????; 恢復(fù) API 的原入口地址
????????????????????invoke lstrcpy,addr szMsgAbout,addr szText????; 恢復(fù)原對話框的輸出文字:
????????????????.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

;****************************************
; 函數(shù)功能:處理異常錯誤
;****************************************
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 函數(shù)的返回值:(經(jīng)過修正)
????; (想不明白?呵呵,用調(diào)試器跟蹤一下吧,我也說不清楚,只能意會不能言傳……)
????moveax,[lpContext]
????moveax,[eax][CONTEXT.regEsp]
????movecx,[eax]
????mov[eax],offset SetHook
????mov[dwRetAddr],ecx

????; 把 API 原入口地址寫回去,以便繼續(xù)運行原 API:
????; (跟蹤一下吧,我實在是不知道怎么才能說得清楚……)
????moveax,[dwAddress]
????movcl,[bOldByte]
????movbyteptr[eax],cl

????; 繼續(xù)下一個 Execution:
????moveax, ExceptionContinueExecution
????ret
Error_Handler endp

;****************************************
; 函數(shù)功能:設(shè)置 API Hook
;****************************************
SetHook procusesecx
????moveax,[dwAddress]
????movcl,[eax]
????movbyteptr[eax],0CCh????; 斷點異常(INT 3 指令)
????mov[bOldByte],cl
????jmp[dwRetAddr]???????????? ; 跳回經(jīng)過 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??????"關(guān)于(&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

沒啥特別的,仔細(xì)一想就明白了。

老羅
2002-11-22