FoxSoftWare 發(fā)表于 2007-4-14 17:19:00
原帖:http://www.oioj.net/blog/new3/foxxp/archives/2007/362277.shtml

--------------------------------------------------------------------------------
標(biāo) 題: 匯編ring3下實(shí)現(xiàn)HOOK API【原創(chuàng)】
作 者: 非安全
時 間: 2006-07-12,18:39
鏈 接: http://bbs.pediy.com/showthread.php?threadid=28895

匯編ring3下實(shí)現(xiàn)HOOK API(二次修改版)

【文章標(biāo)題】匯編ring3下實(shí)現(xiàn)HOOK API
【文章作者】nohacks(非安全,hacker0058)
【作者主頁】hacker0058.ys168.com
【文章出處】看雪論壇(bbs.pediy.com)

===========================[ 匯編ring3下實(shí)現(xiàn)HOOK API ]=======================

???????????????????????????????????????????????? Author: nohacks
???????????????????????????????????????????????? Emil: kker.cn@163.com
???????????????????????????????????????????????? Version: 1.1
???????????????????????????????????????????????? Date: 7.18.2006
????????????????????????????????????????????????

=====[ 1. 內(nèi)容 ]=============================================

1. 內(nèi)容
2. 介紹
? 2.1 什么叫Hook API?
? 2.2 API Hook的應(yīng)用介紹
? 2.3 API Hook的原則
3. 掛鉤方法
? 3.1 改寫IAT導(dǎo)入表法
? 3.2 改寫內(nèi)存地址JMP法
4. 匯編實(shí)現(xiàn)
? 4.1. 代碼
? 4.2. 分析
5. 結(jié)束語


=====[ 2. 介紹 ]================================================

?? 這篇文章是有關(guān)在OS Windows下掛鉤API函數(shù)的方法。所有例子都在基于NT技術(shù)的Windows版本NT4.0

及以上有效(Windows NT 4.0, Windows 2000, Windows XP)。可能在其它Windows系統(tǒng)也會有效。

?? 你應(yīng)該比較熟悉Windows下的進(jìn)程、匯編器、和一些API函數(shù),才能明白這篇文章里的內(nèi)容。


=====[2.1 什么叫Hook API?]=================================
??
?? 所謂Hook就是鉤子的意思,而API是指Windows開放給程序員的編程接口,使得在用戶級別下可

以對操作系統(tǒng)進(jìn)行控制,也就是一般的應(yīng)用程序都需要調(diào)用API來完成某些功能,Hook API的意思

就是在這些應(yīng)用程序調(diào)用真正的系統(tǒng)API前可以先被截獲,從而進(jìn)行一些處理再調(diào)用真正的API來完

成功能。

?====[2.2 API Hook的應(yīng)用介紹]=================================
???
?? API Hook技術(shù)應(yīng)用廣泛,常用于屏幕取詞,網(wǎng)絡(luò)防火墻,病毒木馬,加殼軟件,串口紅外通訊,游戲外

掛,internet通信等領(lǐng)域API HOOK的中文意思就是鉤住API,對API進(jìn)行預(yù)處理,先執(zhí)行我們的函數(shù),例

如我們用API Hook技術(shù)掛接ExitWindowsEx API函數(shù),使關(guān)機(jī)失效,掛接ZwOpenProcess函數(shù)(如:老王的

EncryptPE),隱藏進(jìn)程等等......

====[2.3 API Hook的原則]=====================================
??
?? HOOK API有一個原則,這個原則就是:被HOOK的API的原有功能不能受到任何影響。就象醫(yī)生救人,

如果把病人身體里的病毒殺死了,病人也死了,那么這個“救人”就沒有任何意義了。如果你HOOK API

之后,你的目的達(dá)到了,但API的原有功能失效了,這樣不是HOOK,而是REPLACE,操作系統(tǒng)的正常功能

就會受到影響,甚至?xí)罎ⅰ?/p>

====[ 3. 掛鉤方法 ]==============================================

總的來說,常用的掛鉤API方法有以下兩種:

3.1 改寫IAT導(dǎo)入表法

?? 修改可執(zhí)行文件的IAT表(即輸入表)因?yàn)樵谠摫碇杏涗浟怂姓{(diào)用API的函數(shù)地址,則只需將這些

地址改為自己函數(shù)的地址即可,但是這樣有一個局限,因?yàn)橛械某绦驎託ぃ@樣會隱藏真實(shí)的IAT表

,從而使該方法失效。

3.2 改寫內(nèi)存地址JMP法

??? 直接跳轉(zhuǎn),改變API函數(shù)的入口或出口的幾個字節(jié),使程序跳轉(zhuǎn)到自己的函數(shù),該方法不受程序加殼

的限制。這種技術(shù),說起來也不復(fù)雜,就是改變程序流程的技術(shù)。在CPU的指令里,有幾條指令可以改變

程序的流程:JMP,CALL,INT,RET,RETF,IRET等指令。理論上只要改變API入口和出口的任何機(jī)器碼

,都可以HOOK,下面我就說說常用的改寫API入口點(diǎn)的方法:
???
??? 因?yàn)楣ぷ髟赗ing3模式下,我們不能直接修改物理內(nèi)存,只能一個一個打開修改,但具體的方法又分成

好幾種,我給大家介紹幾種操作思路:

? <1>首先改寫API首字節(jié),要實(shí)現(xiàn)原API的功能需要調(diào)用API時先還原被修改的字節(jié),然后再調(diào)用原API,調(diào)

用完后再改回來,這樣實(shí)現(xiàn)有點(diǎn)麻煩,但最簡單,從理論上說有漏HOOK的可能,因?yàn)槲覀兿冗€原了API,如果

在這之前程序調(diào)用了API,就有可能逃過HOOK的可能!

? (2)把被覆蓋的匯編代碼保存起來,在替代函數(shù)里模擬被被覆蓋的功能,然后調(diào)用原函數(shù)(原地址+被覆

蓋長度).但這樣會產(chǎn)生一個問題,不同的匯編指令長度是不一樣的(比如說我們寫入的JMP指令占用5個字

節(jié),而我們寫入的這5個字節(jié)占用的位置不一定正好是一個或多個完整的指令,有可能需要保存7個字節(jié),

才不能打亂程序原有的功能,需要編寫一個龐大的判斷體系來判斷指令長度,網(wǎng)上已經(jīng)有這樣的匯編程序

(Z0MBiE寫的LDE32),非常的復(fù)雜!

? (3)把被HOOK的函數(shù)備份一下,調(diào)用時在替代函數(shù)里調(diào)用備份函數(shù).為了避免麻煩,可以直接備份整個

DLL缺點(diǎn)就是太犧牲內(nèi)存,一般不推薦使用這種方法!

?
=====[ 4. 匯編實(shí)現(xiàn) ]==============================================

本文就是建立在第2種方法之上的!本著先易后難的原則,今天我們先來說說它的第1種操作思路.
?
? 我們拿API函數(shù)ExitWindowsEx來說明,下面是我在OD里攔下的ExitWindowsEx原入口部分

???? 77D59E2D??????????? $? 8BFF????????? mov edi,edi?
???? 77D59E2F??????????? .? 55??????????? push ebp
???? 77D59E30??????????? .? 8BEC????????? mov ebp,esp
???? 77D59E32??????????? .? 83EC 18?????? sub esp,18
????? ......

? 如果我們把ExitWindowsEx的入口點(diǎn)改為下面的,會出現(xiàn)什么情況?

??? 77D59E2D?????????????? B8 00400000?? mov eax,4000
??? 77D59E32?????????????? FFE0????????? jmp eax
??? ......


? 我們可想而知,程序執(zhí)行到77D59E32處就會改變流程跳到00400000的地方


? 如果我們的00400000處是這樣的子程:

=======================
MyAPI proc? bs:DWORD? ,dwReserved:DWORD? ;和ExitWindowsEx一樣帶2個參數(shù)????????????????

;做你想做的事

......

;這里放API入口點(diǎn)改回原機(jī)器碼的代碼

;如果你是備份的整個DLL,就直接調(diào)用備份API,不用改來改去了,不會有漏勾API的可能!

invoke ExitWindowsEx,bs,dwReserved
?????????????????????????
;這里放HOOK API的代碼
?
.endif

mov eax,TRUE

ret
=======================

?? 這里的MyAPI是和ExitWindowsEx參數(shù)一樣的的子程,因?yàn)槌绦蚴窃贏PI的入口部分跳轉(zhuǎn)的,根據(jù)

stdcall約定(參數(shù)數(shù)據(jù)從右向左依次壓棧,恢復(fù)堆棧的工作交由被調(diào)用者),此時堆棧還沒有恢復(fù),我們

在子程里取出的參數(shù)數(shù)據(jù)依然有效,我們可以在這里執(zhí)行自己的代碼,你可以決定是否繼續(xù)按原參數(shù)或改

變參數(shù)后再調(diào)用原API,也可以什么都不做,當(dāng)然在調(diào)用之前,我們要先還原我們修改過的API(可以事先用

API函數(shù)ReadProcessMemory讀出原API的前幾個字節(jié)備份之),調(diào)用完后再改回來繼續(xù)HOOK API,不過這種

方法有漏API的可能(原因前面已經(jīng)說了),你如果覺得這個方法不妥,因?yàn)橐话阆到y(tǒng)DLL都不大,你可以備

份整個DLL.

下面我就列出ring3下HOOK API的幾個步驟:

1.得到要掛勾API的入口點(diǎn)

2.修改API的入口點(diǎn)所在頁的頁面保護(hù)為可讀寫模式

3.用ReadProcessMemory讀出API的入口點(diǎn)開始的幾字節(jié)備份

4.用WriteProcessMemory修改API的入口點(diǎn)象這樣的形式:
?
? mov eax,4000
?
? jmp eax

?其中的4000要用和原API參數(shù)一樣的子程序地址代替


? 在這個子程序里我們決定用什么參數(shù)再調(diào)用原API,不過調(diào)用之前要用備份的前8字節(jié)改回來

調(diào)用之后在掛勾,如此反復(fù).

?

=====[ 4.1. 代碼 ]==============================================

? 前面所講的是本進(jìn)程掛勾,我們要掛勾所有進(jìn)程,可以用全局勾子,需要單獨(dú)的一個DLL,我們可

以在DLL的DLL_PROCESS_ATTACH事件里來HOOK API

=================================hookdll.dll==========================
.486
.model flat,stdcall?? ;參數(shù)的傳遞約定是stdcall(從右到左,恢復(fù)堆棧的工作交由被調(diào)用者)
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib


HOOKAPI struct
a? byte ?
PMyapi DWORD ???
d BYTE ??
e BYTE ?
HOOKAPI ends


;子程序聲明
WriteApi proto :DWORD ,:DWORD,:DWORD,:DWORD
MyAPI proto? :DWORD? ,:DWORD
GetApi proto? :DWORD,:DWORD


;已初始化數(shù)據(jù)
.data
hInstance dd 0
WProcess dd 0
hacker HOOKAPI <>
CommandLine LPSTR ?

Papi1 DWORD ?
Myapi1 DWORD ?
ApiBak1 db 10 dup(?)
DllName1? db "user32.dll",0?????
ApiName1? db "ExitWindowsEx",0
mdb db "下面的程序想關(guān)閉計(jì)算機(jī),要保持阻止嗎?",0


;未初始化數(shù)據(jù)

.data?
hHook dd ?
hWnd dd ?

;程序代碼段

.code

DllEntry proc hInst:HINSTANCE, reason:DWORD, reserved1:DWORD
??
?
?.if reason==DLL_PROCESS_ATTACH???? ;當(dāng)DLL加載時產(chǎn)生此事件
??????? push hInst
??????? pop hInstance

invoke GetCommandLine??
mov CommandLine,eax???????????????????????????????????????? ;取程序命令行

;初始化

mov hacker.a,0B8h???? ;mov eax,
;mov hacker.d PMyapi? ;0x000000
mov hacker.d,0FFh???? ;jmp
mov hacker.e, 0E0h??? ;eax
?

invoke?? GetCurrentProcess?????????????????????????????????? ;取進(jìn)程偽句柄

?mov WProcess ,eax
???
invoke GetApi,addr DllName1,addr ApiName1??????????????????? ;取API地址
?
?mov Papi1,eax?????????????????????????????????????????????? ;保存API地址

invoke ReadProcessMemory,WProcess,Papi1,addr ApiBak1,8,NULL? ;備份原API的前8字節(jié)

?mov hacker.PMyapi,offset MyAPI?? ;0x0000,這里設(shè)置替代API的函數(shù)地址

invoke WriteApi,WProcess,Papi1, addr hacker ,size HOOKAPI??? ;HOOK API

.endif

.if? reason==DLL_PROCESS_DETACH

invoke WriteApi,WProcess,Papi1, addr ApiBak1 ,8?????????????? ;還原API

.endif

?mov? eax,TRUE
??? ret
DllEntry Endp

GetMsgProc proc nCode:DWORD,wParam:DWORD,lParam:DWORD
??? invoke CallNextHookEx,hHook,nCode,wParam,lParam
???? mov eax,TRUE
????
????? ret
GetMsgProc endp

InstallHook proc
??
??? invoke SetWindowsHookEx,WH_GETMESSAGE,addr GetMsgProc,hInstance,NULL
??? mov hHook,eax
??? ret
InstallHook endp

UninstallHook proc
??? invoke UnhookWindowsHookEx,hHook
?? invoke WriteApi,WProcess,Papi1, addr ApiBak1 ,8
? ret
UninstallHook endp

GetApi proc DllNameAddress:DWORD,ApiNameAddress:DWORD

invoke? GetModuleHandle,DllNameAddress???? ;取DLL模塊句柄
??
? .if eax==NULL
?
? invoke LoadLibrary ,DllNameAddress??? ;加載DLL
?
?? .endif
?
?invoke GetProcAddress,eax,ApiNameAddress? ;取API地址
??

mov eax,eax
?
ret

GetApi endp


;============================下面是核心部分=========================


WriteApi proc Process:DWORD ,Papi:DWORD,Ptype:DWORD,Psize:DWORD

LOCAL mbi:MEMORY_BASIC_INFORMATION
LOCAL msize:DWORD


;返回頁面虛擬信息
invoke VirtualQueryEx,Process, Papi,addr mbi,SIZEOF MEMORY_BASIC_INFORMATION

;修改為可讀寫模式

invoke VirtualProtectEx,Process, mbi.BaseAddress,8h,PAGE_EXECUTE_READWRITE,addr

mbi.Protect

;開始寫內(nèi)存

invoke? WriteProcessMemory,Process, Papi, Ptype,Psize ,NULL

PUSH eax

;改回只讀模式

invoke VirtualProtectEx,Process,mbi.BaseAddress,8h,PAGE_EXECUTE_READ,addr mbi.Protect

pop eax

ret

WriteApi endp

?

;替代的API,參數(shù)要和原來一樣

MyAPI proc? bs:DWORD? ,dwReserved:DWORD?????????????????????

invoke MessageBox, NULL,? CommandLine, addr mdb, MB_YESNO????? ;彈出信息框選擇是否阻止

.if eax==7?????????????????????????????????????????????????? ;如果選擇否

?invoke WriteApi,WProcess,Papi1, addr ApiBak1 ,8????????????? ;先還原API
?
?invoke ExitWindowsEx,bs,dwReserved?????????????????????????? ;再調(diào)用API
?
?invoke WriteApi,WProcess,Papi1, addr hacker ,sizeof HOOKAPI? ;調(diào)用完后再改回來
?
.endif

mov eax,TRUE
ret

MyAPI endp

End DllEntry


===============================hookdll.def=============================

LIBRARY hookdll
EXPORTS InstallHook
EXPORTS UninstallHook


=====[ 4.2. 分析 ]==============================================

HOOKAPI struct
a? byte ?
PMyapi DWORD ???
d BYTE ??
e BYTE ?
HOOKAPI ends


?? 為了便于理解和使用,我定義了一個結(jié)構(gòu):這個結(jié)構(gòu)有4個成員,第一個成員a,是個字節(jié)型,我用來放

0B8h(mov eax),PMyapi一個整數(shù)型,用來放我們的替代API函數(shù)的地址(0X000),第3個和第4個成員我分別

用來放JMP和EAX(jmp eax)那么連起來就是 mov,0X0000 ; jmp eax?


?.if reason==DLL_PROCESS_ATTACH????
??????? push hInst
??????? pop hInstance

invoke GetCommandLine??
mov CommandLine,eax????????????????????????????????????????

;初始化

mov hacker.a,0B8h???? ;mov eax,
;mov hacker.d PMyapi? ;0x0000
mov hacker.d,0FFh???? ;jmp
mov hacker.e, 0E0h??? ;eax


invoke?? GetCurrentProcess??????????????????????????????????

?mov WProcess ,eax


? 當(dāng)DLL加載時,我們先保存模塊句柄,讀取程序命令行,然后初始化HOOKAPI結(jié)構(gòu),寫入我們要寫到內(nèi)存的

指令(PMyapi以后寫入)并調(diào)用GetCurrentProcess取出進(jìn)程偽句柄方便以后寫內(nèi)存.

invoke GetApi,addr DllName1,addr ApiName1???????????????????
?
?mov Papi1,eax??????????????????????????????????????????????

invoke ReadProcessMemory,WProcess,Papi1,addr ApiBak1,8,NULL?
?
mov hacker.PMyapi,offset MyAPI?? ;0x0000??

invoke WriteApi,WProcess,Papi1, addr hacker ,size HOOKAPI??? ;HOOK API


? 接下來用子程GetApi取出要掛勾API的入口點(diǎn),并用ReadProcessMemory讀出入口點(diǎn)8字節(jié)備份之,寫入
PMyapi調(diào)用子程WriteApi改寫API的入口點(diǎn),這個子程我不準(zhǔn)備詳細(xì)說了,它非常的簡單,無非就是幾個

API的調(diào)用.它的核心就是通過WriteProcessMemory改寫內(nèi)存.

.if? reason==DLL_PROCESS_DETACH

invoke WriteApi,WProcess,Papi1, addr ApiBak1 ,8??????????????

.endif

?mov? eax,TRUE
??? ret

?? 如果這個DLL被卸載了,那么那個在DLL里的替代函數(shù)(MyAPI)將是無效的,如果這個時候程序再調(diào)用這

個API,將出現(xiàn)非法操作,因此在DLL卸載前,我們必須還原API.

?? 總結(jié)一下,現(xiàn)在只要程序加載這個DLL,這個程序的ExitWindowsEx就會被我們勾住,接下來要怎樣才能

讓所有的程序都加載這個DLL呢?這就需要安裝全局勾子:

? InstallHook proc
??
????? invoke SetWindowsHookEx,WH_GETMESSAGE,addr GetMsgProc,hInstance,NULL
???
????? invoke WriteApi,WProcess,Papi1, addr hacker ,sizeof HOOKAPI

????? mov hHook,eax
???? ret
? InstallHook endp

?? 通過SetWindowsHookEx安裝勾子,最后一個參數(shù)可以決定該鉤子是局部的還是系統(tǒng)范圍的。如果該值

為NULL,那么該鉤子將被解釋成系統(tǒng)范圍內(nèi)的,那它就可以監(jiān)控所有的進(jìn)程及它們的線程。

如果該函數(shù)調(diào)用成功的話,將在eax中返回鉤子的句柄,否則返回NULL。我們必須保存該句柄,因?yàn)楹?/p>

面我們還要它來卸載鉤子,可以看出,我們創(chuàng)建的Hook類型是WH_CALLWNDPROC類型,該類型的Hook在進(jìn)程

與系統(tǒng)一通信時就會被加載到進(jìn)程空間,從而調(diào)用dll的初始化函數(shù)完成真正的Hook,值得一提的是:因

為要調(diào)用SetWindowsHookEx來安裝鉤子,我們GUI程序的這個DLL不會被

UnhookWidowHookEx卸載,也就只有一次DLL_PROCESS_ATTACH事件,因此這里再要

HOOK API一次!

我們回頭來看看鉤子回調(diào)函數(shù):

? GetMsgProc proc nCode:DWORD,wParam:DWORD,lParam:DWORD
????? invoke CallNextHookEx,hHook,nCode,wParam,lParam
?????? mov eax,TRUE
????
?????? ret
? GetMsgProc endp

?? 可以看到這里只是調(diào)用CallNextHookEx將消息交給Hook鏈中下一個環(huán)節(jié)處理,因?yàn)檫@里API函數(shù)
SetWindowsHookEx的唯一作用就是讓進(jìn)程加載我們的dll。

? UninstallHook proc
???? invoke UnhookWindowsHookEx,hHook
???? invoke WriteApi,WProcess,Papi1, addr ApiBak1 ,8
?? ret
? UninstallHook endp

要卸載一個鉤子時調(diào)用UnhookWidowHookEx函數(shù),該函數(shù)僅有一個參數(shù),就是欲卸載的鉤子的句柄。鉤

子卸載后我們也要還原我們GUI程序的API.

? LIBRARY hookdll
? EXPORTS InstallHook
? EXPORTS UninstallHook

?? 我們公開DLL里的InstallHook和UninstallHook函數(shù),方便程序調(diào)用,這樣我們只要在另外的程序中調(diào)

用InstallHook便可安裝全局勾子,勾住所有程序中的API:ExitWindowsEx,執(zhí)行我們自定的子程!

如果不需要了,可以調(diào)用UninstallHook卸載全局勾子.

?? 請注意:對于遠(yuǎn)程鉤子,鉤子函數(shù)必須放到DLL中,它們將從DLL中映射到其它的進(jìn)程空間中去。當(dāng)

WINDOWS映射DLL到其它的進(jìn)程空間中去時,不會把數(shù)據(jù)段也進(jìn)行映射。簡言之,所有的進(jìn)程僅共享DLL

的代碼,至于數(shù)據(jù)段,每一個進(jìn)程都將有其單獨(dú)的拷貝。這是一個很容易被忽視的問題。您可能想當(dāng)然

的以為,在DLL中保存的值可以在所有映射該DLL的進(jìn)程之間共享。在通常情況下,由于每一個映射該

DLL的進(jìn)程都有自己的數(shù)據(jù)段,所以在大多數(shù)的情況下您的程序運(yùn)行得都不錯。但是鉤子函數(shù)卻不是如

此。對于鉤子函數(shù)來說,要求DLL的數(shù)據(jù)段對所有的進(jìn)程也必須相同。這樣您就必須把數(shù)據(jù)段設(shè)成共享

的:

?一般來說, 目標(biāo)文件有三個段, 分別是 text/data/bss 段.

.text 段放置代碼, 是只讀且可運(yùn)行段

.data 段放置靜態(tài)數(shù)據(jù), 這些數(shù)據(jù)會被放置入 exe 文件. 這個段是可讀寫, 但是不能運(yùn)行的.

.bss 段放置動態(tài)數(shù)據(jù), 這些數(shù)據(jù)不被放入 exe 文件, 在exe文件被加載入內(nèi)存后才分配的空間.

你可以通過在鏈接開關(guān)中指定段的屬性來實(shí)現(xiàn):

/SECTION:name,[E][R][W][S][D][K][L][P][X]

其中S表示共享,已初期化的段名是.data,未初始化的段名是.bss。假如您想要寫一個包含鉤子函數(shù)的

DLL,而且想使它的未初始化的數(shù)據(jù)段在所有進(jìn)程間共享,您必須這么做:
?
link /section:.bss[S]? /DLL? /SUBSYSTEM:WINDOWS ..........

否則,您的全局勾子將不能正常工作!

=====[ 5. 結(jié)束語 ]================================================

??? 我歡迎任何人提出更多的這里沒有提到的掛鉤方法,我肯定那會有很多。同樣歡迎補(bǔ)充我介紹得不

是很詳細(xì)的方法。也可以把我懶得寫的其它方法完成,把源代碼發(fā)給我。這篇文檔的目的是演示掛鉤技

術(shù)的細(xì)節(jié),我希望我做到了。
???
============================[ End ]========================

??? 水平有限,歡迎大家指出錯漏之處。QQ:23453161 Email:kker.cn@163.com

??? 例子源程序(MASM+RadASM和Windows XP2系統(tǒng)下編譯通過):