?? windows hook 易核心編程(3) api hook 續(xù) 攔截api.txt
字號(hào):
上一期,我們講了用HOOK技術(shù)實(shí)現(xiàn)遠(yuǎn)程線程插入,相信大家還記憶猶新.
這一期我們來(lái)談?wù)?API HOOK
API Hook技術(shù)應(yīng)用廣泛,常用于屏幕取詞,網(wǎng)絡(luò)防火墻,病毒木馬,加殼軟件,串口紅外通訊,游戲外
掛,internet通信等領(lǐng)域API HOOK的中文意思就是鉤住API,對(duì)API進(jìn)行預(yù)處理,先執(zhí)行我們的函數(shù),例
如我們用API Hook技術(shù)掛接ExitWindowsEx API函數(shù),使關(guān)機(jī)失效,掛接ZwOpenProcess函
數(shù),隱藏進(jìn)程等等......
總的來(lái)說(shuō),常用的掛鉤API方法有以下兩種: <一>改寫IAT導(dǎo)入表法
我們知道,Windows下的可執(zhí)行文檔的文件格式是一種叫PE(“portable executable”,可移植
的可執(zhí)行文件)的文件格式,這種文件格式
是由微軟設(shè)計(jì)的,接下來(lái)這張圖描述了PE文件的結(jié)構(gòu):
+-------------------------------+ - offset 0
| MS DOS標(biāo)志("MZ") 和 DOS塊 |
+-------------------------------+
| PE 標(biāo)志 ("PE") |
+-------------------------------+
| .text | - 模塊代碼
| 程序代碼 |
| |
+-------------------------------+
| .data | - 已初始化的(全局靜態(tài))數(shù)據(jù)
| 已初始化的數(shù)據(jù) |
| |
+-------------------------------+
| .idata | - 導(dǎo)入函數(shù)的信息和數(shù)據(jù)
| 導(dǎo)入表 |
| |
+-------------------------------+
| .edata | - 導(dǎo)出函數(shù)的信息和數(shù)據(jù)
| 導(dǎo)出表 |
| |
+-------------------------------+
| 調(diào)試符號(hào) |
+-------------------------------+
這里對(duì)我們比較重要的是.idata部分的導(dǎo)入地址表(IAT)。這個(gè)部分包含了導(dǎo)入的相關(guān)信息和導(dǎo)
入函數(shù)的地址。有一點(diǎn)很重要的是我們必須知道PE文件是如何創(chuàng)建的。當(dāng)在編程語(yǔ)言里間接調(diào)用任
意API(這意味著我們是用函數(shù)的名字來(lái)調(diào)用它,而不是用它的地址),編譯器并不直接把調(diào)用連接到
模塊,而是用jmp指令連接調(diào)用到IAT,IAT在系統(tǒng)把進(jìn)程調(diào)入內(nèi)存時(shí)時(shí)會(huì)由進(jìn)程載入器填滿。這就是
我們可以在兩個(gè)不同版本的Windows里使用相同的二進(jìn)制代碼的原因,雖然模塊可能會(huì)加載到不同的
地址。進(jìn)程載入器會(huì)在程序代碼里調(diào)用所使用的IAT里填入直接跳轉(zhuǎn)的jmp指令。所以我們能在IAT里
找到我們想要掛鉤的指定函數(shù),我們就能很容易改變那里的jmp指令并重定向代碼到我們的地址。完
成之后每次調(diào)用都會(huì)執(zhí)行我們的代碼了。
我們通過(guò)使用imagehlp.dll里的ImageDirectoryEntryToData來(lái)很容易地找到IAT。
.DLL命令 ImageDirectoryEntryToData, 整數(shù)型, "imagehlp", , , 返回IMAGE_IMPORT_DESCRIPTOR數(shù)組的首地址
.參數(shù) Base, 整數(shù)型, , 模塊句柄
.參數(shù) MappedAsImage, 邏輯型, , 真
.參數(shù) DirectoryEntry, 整數(shù)型, , 恒量:IMAGE_DIRECTORY_ENTRY_IMPORT,1
.參數(shù) Size, 整數(shù)型, 傳址, IMAGE_IMPORT_DESCRIPTOR數(shù)組的大小
.數(shù)據(jù)類型 IMAGE_IMPORT_DESCRIPTOR, , 輸入描述結(jié)構(gòu)
.成員 OriginalFirstThunk, 整數(shù)型, , , 它是一個(gè)RVA(32位),指向一個(gè)以0結(jié)尾的、由IMAGE_THUNK_DATA(換長(zhǎng)數(shù)據(jù))的RVA構(gòu)成的數(shù) 組,其每個(gè)IMAGE_THUNK_DATA(換長(zhǎng)數(shù)據(jù))元素都描述一個(gè)函數(shù)。此數(shù)組永不改變。
.成員 TimeDateStamp, 整數(shù)型, , , 它是一個(gè)具有好幾個(gè)目的的32位的時(shí)間日期戳.
.成員 ForwarderChain, 整數(shù)型, , , 它是輸入函數(shù)列表中第一個(gè)中轉(zhuǎn)的、32位的索引。
.成員 Name, 整數(shù)型, , , 它是一個(gè)DLL文件的名稱(0結(jié)尾的ASCII碼字符串)的、32位的RVA。
.成員 FirstThunk, 整數(shù)型, , , 它也是一個(gè)RVA(32位),指向一個(gè)0結(jié)尾的、由IMAGE_THUNK_DATA(換長(zhǎng)數(shù)據(jù))的RVA構(gòu)成的數(shù)組,其每 個(gè)IMAGE_THUNK_DATA(換長(zhǎng)數(shù)據(jù))元素都描述一個(gè)函數(shù)。此數(shù)組是輸入地址表的一部分,并且可以改變。
如果我們找到了就必須用VirtualProtectEx函數(shù)來(lái)改變內(nèi)存頁(yè)面的保護(hù)屬性,然后就可以通過(guò)
WriteProcessMemory在內(nèi)存中的這些部分寫入代碼了。在改寫了地址之后我們要把保護(hù)屬性改回來(lái)
。在調(diào)用VirtualProtectEx之前我們還要先知道有關(guān)頁(yè)面的信息,這通過(guò)VirtualQueryEx來(lái)實(shí)現(xiàn)。
這種方法的好處是比較穩(wěn)定,但有漏API的可能,因?yàn)椴⒉皇撬械腁PI調(diào)用都是通過(guò)IAT的,可能易
程序就是這種情況,我當(dāng)初也是想用這種方法,但是在易里面調(diào)試時(shí)總不能在IAT里得到正確的程序調(diào)
用的API,常常是些無(wú)關(guān)的API(可能是易的核心支持庫(kù)在做怪),不得不放棄.
本來(lái)計(jì)劃中是沒(méi)有這一章的,但自從我的Windows Hook 易核心編程(3) API Hook發(fā)表以來(lái),得到
了廣大易友的支持,這種情況很令我感動(dòng),于是再接再力,寫出了這章,希望沒(méi)有辜負(fù)廣大易友對(duì)我的
支持與信任,好,廢話不多說(shuō)了,我們言歸正轉(zhuǎn).
在開(kāi)始之前,讓我們先來(lái)回顧一下:
什么叫Hook API?
所謂Hook就是鉤子的意思,而API是指Windows開(kāi)放給程序員的編程接口,使得在用戶級(jí)別下可
以對(duì)操作系統(tǒng)進(jìn)行控制,也就是一般的應(yīng)用程序都需要調(diào)用API來(lái)完成某些功能,Hook API的意思
就是在這些應(yīng)用程序調(diào)用真正的系統(tǒng)API前可以先被截獲,從而進(jìn)行一些處理再調(diào)用真正的API來(lái)完
成功能。在講Hook API之前先來(lái)看一下如何Hook消息,例如Hook全局鍵盤消息,從而可以知道用戶
按了哪些鍵.這種Hook消息的功能可以由以下函數(shù)來(lái)完成,該函數(shù)將一新的Hook加入到原來(lái)的Hook
鏈中,當(dāng)某一消息到達(dá)后會(huì)依次經(jīng)過(guò)它的Hook鏈再交給應(yīng)用程序,這個(gè)在我的Windows Hook 易核心
編程(1)和(2)里有具體的說(shuō)明和應(yīng)用,大家可以看一看.
.DLL命令 api_SetWindowsHookExA, 整數(shù)型, , "SetWindowsHookExA"
.參數(shù) idHook, 整數(shù)型, , Hook類型,例如WH_KEYBOARD,WH_MOUSE
.參數(shù) lpfn, 子程序指針, , 鉤子回調(diào)函數(shù),Hook處理過(guò)程函數(shù)的地址,
.參數(shù) nMod, 整數(shù)型, , 包含Hook處理過(guò)程函數(shù)的dll句柄(若在本進(jìn)程可以為NULL)
.參數(shù) dwThreadID, 整數(shù)型, , 要Hook的線程ID,若為0,表示全局Hook所有
.DLL命令api_UnhookWindowsHookEx, 邏輯型, , "UnhookWindowsHookEx"
.參數(shù) hhook, 整數(shù)型,要關(guān)閉鉤子的句柄.
這里需要提一下的就是如果是Hook全局的而不是某個(gè)特定的進(jìn)程則需要將Hook過(guò)程編寫為一個(gè)
DLL,以便讓任何程序都可以加載它來(lái)獲取Hook過(guò)程函數(shù)。而對(duì)于Hook API微軟并沒(méi)有提供直接的
接口函數(shù),也許它并不想讓我們這樣做,不過(guò)有2種方法可以完成該功能。第一種,修改可執(zhí)行文
件的IAT表(即輸入表),因?yàn)樵谠摫碇杏涗浟怂姓{(diào)用API的函數(shù)地址,則只需將這些地址改為自
己函數(shù)的地址即可,但是這樣有一個(gè)局限,因?yàn)橛械某绦驎?huì)加殼,這樣會(huì)隱藏真實(shí)的IAT表,從而
使該方法失效。第二種方法是直接跳轉(zhuǎn),改變API函數(shù)的頭幾個(gè)字節(jié),使程序跳轉(zhuǎn)到自己的函數(shù),
然后恢復(fù)API開(kāi)頭的幾個(gè)字節(jié),在調(diào)用API完成功能后再改回來(lái)又能繼續(xù)Hook了,但是這種方法也有
一個(gè)問(wèn)題就是同步的問(wèn)題,當(dāng)然這是可以克服的,并且該方法不受程序加殼的限制。
上一章我們就是用的第二種方法,通過(guò)直接改寫API函數(shù)ExitWindowsEx入口點(diǎn)為{195}(即匯編的
返回命令retn),達(dá)到關(guān)機(jī)失效的目地,其實(shí)這不算是真正的API HOOK,沒(méi)有實(shí)現(xiàn)自定API函數(shù)的功能.
記得我說(shuō)過(guò),如果要實(shí)現(xiàn)自定API函數(shù),可以寫入一個(gè)跳轉(zhuǎn)指令,JMP OX00000(其中OX00000為自定API
函數(shù)地址),最后還給大家留了一道題,就是參照論壇上的教程寫出自定API函數(shù)的功能,不知道大家
完成的什么樣了,呵呵.
其實(shí),要真正實(shí)現(xiàn)API HOOK,用JMP OX00000是不能達(dá)到我們的目地的,因?yàn)橐D(zhuǎn)移參數(shù),我們先要
mov eax OX00000 ,然后再jmp eax,在易里面用用字節(jié)集連起來(lái)表示就是:
{ 184 } + 到字節(jié)集 (到整數(shù) (&new_api)) + { 255, 224 }
其中new_api就是新的自定API函數(shù)地址,結(jié)合上期的內(nèi)容,我們現(xiàn)在就來(lái)解析核心代碼:
===================================
.子程序 修改API首地址, 邏輯型
.參數(shù) Process, 整數(shù)型, , 目標(biāo)進(jìn)程句柄
.參數(shù) Papi, 整數(shù)型, , 要修改的API函數(shù)地址
.參數(shù) type, 字節(jié)集, , 要改寫的內(nèi)容,字節(jié)集
.局部變量 mbi, 虛擬信息
.局部變量 結(jié)果, 邏輯型
.局部變量 MyAPI, 整數(shù)型
.局部變量 Ptype, 字節(jié)集
.如果真 (Papi = 0)
返回 (假)
.如果真結(jié)束
.如果真 (返回虛擬信息 (Process, Papi, mbi, 28) = 0)
返回 (假)
.如果真結(jié)束
.如果真 (修改虛擬保護(hù) (Process, mbi.BaseAddress, 取字節(jié)集長(zhǎng)度 (type) + 1, #PAGE_EXECUTE_READWRITE, mbi.Protect) = 假)
返回 (假)
.如果真結(jié)束
結(jié)果 = 寫內(nèi)存字節(jié) (Process, Papi, type, 取字節(jié)集長(zhǎng)度 (type), 0)
修改虛擬保護(hù) (Process, mbi.BaseAddress, 取字節(jié)集長(zhǎng)度 (type) + 1, #PAGE_EXECUTE_READ, mbi.Protect) ' 改回只讀模式
返回 (結(jié)果)
==================================
這個(gè)子程序也就是上期用到的,上一期第3個(gè)參數(shù)是寫的{195},這里換成 { 184 } + 到字節(jié)集 (到
整數(shù) (&new_api)) + { 255, 224 }就可以了,新的API函數(shù)要和原API函數(shù)的參數(shù)一樣,還拿API函
數(shù)ExitWindowsEx來(lái)說(shuō),看下面的新的API函數(shù):
======================================
.子程序 new_api, 整數(shù)型, , 新的API函數(shù),要與原來(lái)的API函數(shù)的參數(shù)一樣
.參數(shù) 標(biāo)志, 整數(shù)型
.參數(shù) 保留值, 整數(shù)型
' 給自己留條后門先
.如果 (保留值 = 3389)
HOOKAPI (假)
api_ExitWindowsEx (標(biāo)志, 保留值)
.否則
信息框 (“您的操作已經(jīng)被易攔截了!” + #換行符 + “ExitWindowsEx” + #換行符 + “參數(shù)<1>:” + 到文本 (標(biāo)志) + #換行符 + “參數(shù)<2>:” + 到文本 (保留值), 0, )
.如果結(jié)束
返回 (1)
=======================================
看下面的代碼:
.子程序 HOOKAPI, 邏輯型
.參數(shù) 是否HOOK, 邏輯型
.局部變量 TYPE, 字節(jié)集
.如果 (是否HOOK)
TYPE = { 184 } + 到字節(jié)集 (到整數(shù) (&new_ExitWindowsEx)) + { 255, 224 }
返回 (修改API首地址(取當(dāng)前進(jìn)程偽句柄 (), API, TYPE))
.否則
返回 (修改API首地址(取當(dāng)前進(jìn)程偽句柄 (), API, API_BAK))
當(dāng)然,改寫前,還是要備份和取API函數(shù)地址的:
API = 取DLL函數(shù)地址 (取程序或DLL句柄 (“user32.dll”), “ExitWindowsEx”)
API_BAK = 指針到字節(jié)集 (API, 8)
============================================
這樣,只要一句HOOKAPI(真)就可以輕松改寫本進(jìn)程的API函數(shù),一句HOOKAPI(假)就可以還原API函
數(shù),當(dāng)然前提是在本進(jìn)程,因?yàn)榈刂凡荒芸邕M(jìn)程使用,要想掛勾其他進(jìn)程,我們可以使用我前面提到
的Windows全局消息勾子,為了取得較好的效果,推薦使用WH_GETMSSAGE類的勾子.
要使用全局消息勾子,先要在一個(gè)獨(dú)立的DLL里聲明鉤子回調(diào)函數(shù),看下面的代碼:
============================
.子程序 GetMsgProc, 整數(shù)型, 公開(kāi), 鉤子回調(diào)函數(shù)
.參數(shù) code, 整數(shù)型
.參數(shù) wParam, 整數(shù)型
.參數(shù) lParam, 整數(shù)型
返回 (api_CallNextHookEx (hhook, code, wParam, lParam)) ' 鉤子循環(huán)
====================================
主程序通過(guò)調(diào)用SetWindowsHookEx就可以安裝鉤子 ,繼續(xù)看代碼;
.子程序 安裝全局鉤子, 整數(shù)型, , 安裝全局消息鉤子
.參數(shù) DLLPath, 文本型,DLL名
.局部變量 hMod, 整數(shù)型
.局部變量 lpProc, 子程序指針
hMod = api_LoadLibraryA (DLLPath)
lpProc = api_GetProcAddress (hMod, “GetMsgProc”)
hook = api_SetWindowsHookExA (#WH_GETMESSAGE, lpProc, hMod, 0)
返回 (hook)
======================================
主程序通過(guò)api_UnhookWindowsHookEx(hook)就可以關(guān)閉全局鉤子.
結(jié)合下面的子程序(上期的HOOK_API)便可以還原API.
=============================
.子程序 還原API
.局部變量 i, 整數(shù)型
.局部變量 結(jié)果, 邏輯型
.局部變量 進(jìn)程信息, 進(jìn)程信息輸出, , "0"
.局部變量 進(jìn)程句柄, 整數(shù)型
.局部變量 修改內(nèi)容, 整數(shù)型
刷新進(jìn)程信息 (進(jìn)程信息) '這個(gè)在上期中講過(guò),我就不重復(fù)了.
.計(jì)次循環(huán)首 (取數(shù)組成員數(shù) (進(jìn)程信息), i)
進(jìn)程句柄 = 打開(kāi)進(jìn)程 (2035711, 0, 進(jìn)程信息 [i].進(jìn)程標(biāo)識(shí))
.如果真 (API_BAK ≠ { 0 })
結(jié)果 = 修改API首地址 (進(jìn)程句柄, API, API_BAK)
.如果真結(jié)束
關(guān)閉內(nèi)核對(duì)象 (進(jìn)程句柄)
.計(jì)次循環(huán)尾 ()
===========================
可以看出,我們創(chuàng)建的Hook類型是WH_CALLWNDPROC類型,該類型的Hook在進(jìn)程與系統(tǒng)一通信時(shí)
就會(huì)被加載到進(jìn)程空間,從而調(diào)用dll的初始化函數(shù)完成真正的Hook,而在SetWindowsHookEx函數(shù)
中指定的HookProc函數(shù)將不作任何處理,只是調(diào)用CallNextHookEx將消息交給Hook鏈中下一個(gè)環(huán)節(jié)
處理,因?yàn)檫@里SetWindowsHookEx的唯一作用就是讓進(jìn)程加載我們的dll。
整個(gè)框架就是這樣了,具體的就不在細(xì)說(shuō)了.附件里有完整的易源碼,大家可以下載下來(lái)研究.
以上就是一個(gè)最簡(jiǎn)單的Hook API的例子,該種技術(shù)可以完成許多功能。例如網(wǎng)游外掛制作過(guò)程中
截取發(fā)送的與收到的封包(API函數(shù)send)即可使用該方法,或者也可以在Hook到API后加入木馬功能
,反向連接指定的主機(jī)或者監(jiān)聽(tīng)某一端口,還有許多加殼也是用該原理來(lái)隱藏IAT表,填入自己的
函數(shù)地址,等等.
希望本文能夠?qū)σ恍┡笥延兴鶐椭?dāng)然,一定有許多不足之處,希望看了文章的高手
可以指出:以下程序在Windows XP +sp2和易4.02中測(cè)試通過(guò),有什么Bug,請(qǐng)通知我.)
?? 快捷鍵說(shuō)明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號(hào)
Ctrl + =
減小字號(hào)
Ctrl + -