用 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
.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() 的参数,",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