文章作者:kiki
攔截api的技術(shù)有很多種,大體分為用戶層和內(nèi)核層的攔截.這里只說說用戶層的攔截.而用戶層也分為許多種:修改PE文件導(dǎo)入表,直接修改要攔截的api的內(nèi)存(從開始到最后,使程序跳轉(zhuǎn)到指定的地址執(zhí)行).不過大部分原理都是修改程序流程,使之跳轉(zhuǎn)到你要執(zhí)行的地方,然后再返回到原地址.原來api的功能必須還能實(shí)現(xiàn).否則攔截就失去作用了.修改文件導(dǎo)入表的方法的缺點(diǎn)是如果用戶程序動態(tài)加載(使用LoadLibrary和GetProcAddress函數(shù)),攔截將變得復(fù)雜一些.所以這里介紹一下第二種方法,直接修改api,當(dāng)然不是全局的.(后面會說到)
需要了解的一些知識:
1.windows內(nèi)存的結(jié)構(gòu)屬性和進(jìn)程地址空間
2.函數(shù)堆棧的一些知識
一:win2000和xp的內(nèi)存結(jié)構(gòu)和進(jìn)程地址空間
windows采用4GB平坦虛擬地址空間的做法。即每個進(jìn)程單獨(dú)擁有4GB的地址空間。每個進(jìn)程只能訪問自己的這4GB的虛擬空間,而對于其他進(jìn)程的地址空間則是不可見的。這樣保證了進(jìn)程的安全性和穩(wěn)定性。但是,這4GB的空間是一個虛擬空間,在使用之前,我們必須先保留一段虛擬地址,然后再為這段虛擬地址提交物理存儲器。可是我們的內(nèi)存大部分都還沒有1GB,那么這4GB的地址空間是如何實(shí)現(xiàn)的呢?事實(shí)上windows采用的內(nèi)存映射這種方法,即把物理磁盤當(dāng)作內(nèi)存來使用,比如我們打開一個可執(zhí)行文件的時候,操作系統(tǒng)會為我們開辟這個4GB的地址空間:0x00000000--0xffffffff。其中0x00000000--0x7fffffff是屬于用戶層的空間.0x80000000--0xffffffff則屬于共享內(nèi)核方式分區(qū),主要是操作系統(tǒng)的線程調(diào)度,內(nèi)存管理,文件系統(tǒng)支持,網(wǎng)絡(luò)支持和所有設(shè)備驅(qū)動程序。對于用戶層的進(jìn)程,這些地址空間是不可訪問的。任何訪問都將導(dǎo)致一個錯誤。開辟這4GB的虛擬地址空間之后,系統(tǒng)會把磁盤上的執(zhí)行文件映射到進(jìn)程的地址空間中去(一般是在地址0x00400000,可以通過修改編譯選項(xiàng)來修改這個地址)而一個進(jìn)程運(yùn)行所需要的動態(tài)庫文件則一般從0x10000000開始加載。但是如果所有的動態(tài)庫都加載到這個位置肯定會引起沖突。因此必須對一些可能引起沖突的dll編譯時重新修改基地址。但是對于所有的操作系統(tǒng)所提供的動態(tài)庫windows已經(jīng)定義好了映射在指定的位置。這個位置會隨著版本的不同而會有所改變,不過對于同一臺機(jī)器上的映射地址來說都是一樣的。即在a進(jìn)程里映射的kernel32.dll的地址和在進(jìn)程b里的kernel32.dll的地址是一樣的。對于文件映射是一種特殊的方式,使得程序不需要進(jìn)行磁盤i/o就能對磁盤文件進(jìn)行操作,而且支持多種保護(hù)屬性。對于一個被映射的文件,主要是使用CreateFileMapping函數(shù),利用他我們可以設(shè)定一些讀寫屬性:PAGE_READONLY,PAGE_READWRITE,PAGE_WRITECOPY.第一參數(shù)指定只能對該映射文件進(jìn)行讀操作。任何寫操作將導(dǎo)致內(nèi)存訪問錯誤。第二個參數(shù)則指明可以對映射文件進(jìn)行讀寫。這時候,任何對文件的讀寫都是直接操作文件的。而對于第三個參數(shù)PAGE_WRITECOPY顧名思義就是寫入時拷貝,任何向這段內(nèi)存寫入的操作(因?yàn)槲募怯成涞竭M(jìn)程地址空間的,對這段空間的讀寫就相當(dāng)于對文件進(jìn)行的直接讀寫)都將被系統(tǒng)捕獲,并重新在你的虛擬地址空間重新保留并分配一段內(nèi)存,你所寫入的一切東西都將在這里,而且你原先的指向映射文件的內(nèi)存地址也會實(shí)際指向這段重新分配的內(nèi)存,于是在進(jìn)程結(jié)束后,映射文件內(nèi)容并沒有改變,只是在運(yùn)行期間在那段私有拷貝的內(nèi)存里面存在著你修改的內(nèi)容。windows進(jìn)程運(yùn)行所需要映射的一些系統(tǒng)dll就是以這種方式映射的,比如常用的ntdll.dll,kernel32.dll,gdi32.dll.幾乎所有的進(jìn)程都會加載這三個動態(tài)庫。如果你在一個進(jìn)程里修改這個映射文件的內(nèi)容,并不會影響到其他的進(jìn)程使用他們。你所修改的只是在本進(jìn)程的地址空間之內(nèi)的。事實(shí)上原始文件并沒有被改變。
這樣,在后面的修改系統(tǒng)api的時候,實(shí)際就是修改這些動態(tài)庫地址內(nèi)的內(nèi)容。前面說到這不是修改全局api就是這個原因,因?yàn)樗麄兌际且詫懭霑r拷貝的方式來映射的。不過這已經(jīng)足夠了,windows提供了2個強(qiáng)大的內(nèi)存操作函數(shù)ReadProcessMemory和WriteProcessMemory.利用這兩個函數(shù)我們就可以隨便對任意進(jìn)程的任意用戶地址空間進(jìn)行讀寫了。但是,現(xiàn)在有一個問題,我們該寫什么,說了半天,怎么實(shí)現(xiàn)跳轉(zhuǎn)呢?現(xiàn)在來看一個簡單的例子:
MessageBox(NULL, "World", "Hello", 0);
我們在執(zhí)行這條語句的時候,調(diào)用了系統(tǒng)api MessageBox,實(shí)際上在程序中我沒有定義UNICODE宏,系統(tǒng)調(diào)用的是MessageBox的ANSI版本MessageBoxA,這個函數(shù)是由user32.dll導(dǎo)出的。下面是執(zhí)行這條語句的匯編代碼:
0040102A push 0
0040102C push offset string "Hello" (0041f024)
00401031 push offset string "World" (0041f01c)
00401036 push 0
00401038 call dword ptr [__imp__MessageBoxA@16 (0042428c)]
前面四條指令分別為參數(shù)壓棧,因?yàn)镸essageBoxA是__stdcall調(diào)用約定,所以參數(shù)是從右往左壓棧的。最后再CALL 0x0042428c
看看0042428c這段內(nèi)存的值:
0042428C 0B 05 D5 77 00 00 00
可以看到這個值0x77d5050b,正是user32.dll導(dǎo)出函數(shù)MessageBoxA的入口地址。
這是0x77D5050B處的內(nèi)容,
77D5050B 8B FF mov edi,edi
77D5050D 55 push ebp
77D5050E 8B EC mov ebp,esp
理論上只要改變api入口和出口的任何機(jī)器碼,都可以攔截該api。這里我選擇最簡單的修改方法,直接修改api入口的前十個字節(jié)來實(shí)現(xiàn)跳轉(zhuǎn)。為什么是十字節(jié)呢?其實(shí)修改多少字節(jié)都沒有關(guān)系,只要實(shí)現(xiàn)了函數(shù)的跳轉(zhuǎn)之后,你能把他們恢復(fù)并讓他繼續(xù)運(yùn)行才是最重要的。在CPU的指令里,有幾條指令可以改變程序的流程:JMP,CALL,INT,RET,RETF,IRET等指令。這里我選擇CALL指令,因?yàn)樗且院瘮?shù)調(diào)用的方式來實(shí)現(xiàn)跳轉(zhuǎn)的,這樣可以帶一些你需要的參數(shù)。到這里,我該說說函數(shù)的堆棧了。
總結(jié):windows進(jìn)程所需要的動態(tài)庫文件都是以寫入時拷貝的方式映射到進(jìn)程地址空間中的。這樣,我們只能攔截指定的進(jìn)程。修改目標(biāo)進(jìn)程地址空間中的指定api的入口和出口地址之間的任意數(shù)據(jù),使之跳轉(zhuǎn)到我們的攔截代碼中去,然后再恢復(fù)這些字節(jié),使之能順利工作。
二:函數(shù)堆棧的一些知識
正如前面所看到MessageBoxA函數(shù)執(zhí)行之前的匯編代碼,首先將四個參數(shù)壓棧,然后CALL MessageBoxA,這時候我們的線程堆棧看起來應(yīng)該是這樣的:
| | <---ESP
|返回地址|
|參數(shù)1|
|參數(shù)2|
|參數(shù)3|
|參數(shù)4|
|.. |
我們再看MessageBoxA的匯編代碼,
77D5050B 8B FF mov edi,edi
77D5050D 55 push ebp
77D5050E 8B EC mov ebp,esp
注意到堆棧的操作有PUSH ebp,這是保存當(dāng)前的基址指針,以便一會兒恢復(fù)堆棧后返回調(diào)用線程時使用,然后再有mov ebp,esp就是把當(dāng)前esp的值賦給ebp,這時候我們就可以使用 ebp+偏移 來表示堆棧中的數(shù)據(jù),比如參數(shù)1就可以表示成[ebp+8],返回地址就可以表示成[ebp+4]..如果我們在攔截的時候要對這些參數(shù)和返回地址做任何處理,就可以使用這種方法。如果這個時候函數(shù)有局部變量的話,就通過減小ESP的值的方式來為之分配空間。接下來就是保存一些寄存器:EDI,ESI,EBX.要注意的是,函數(shù)堆棧是反方向生長的。這時候堆棧的樣子:
|....|
|EDI| <---ESP
|ESI|
|EBX|
|局部變量|
|EBP |
|返回地址|
|參數(shù)1|
|參數(shù)2|
|參數(shù)3|
|參數(shù)4|
|.. |
在函數(shù)返回的時候,由函數(shù)自身來進(jìn)行堆棧的清理,這時候清理的順序和開始入棧的順序恰恰相反,類似的匯編代碼可能是這樣的:
pop edi
pop esi
pop ebx
add esp, 4
pop ebp
ret 0010
先恢復(fù)那些寄存器的值,然后通過增加ESP的值的方式來釋放局部變量。這里可以用mov esp, ebp來實(shí)現(xiàn)清空所有局部變量和其他一些空閑分配空間。接著函數(shù)會恢復(fù)EBP的值,利用指令POP EBP來恢復(fù)該寄存器的值。接著函數(shù)運(yùn)行ret 0010這個指令。該指令的意思是,函數(shù)把控制權(quán)交給當(dāng)前棧頂?shù)牡刂返闹噶睿瑫r清理堆棧的16字節(jié)的參數(shù)。如果函數(shù)有返回值的話,那在EAX寄存器中保存著當(dāng)前函數(shù)的返回值。如果是__cdecl調(diào)用方式,則執(zhí)行ret指令,對于堆棧參數(shù)的處理交給調(diào)用線程去做。如wsprintf函數(shù)。
這個時候堆棧又恢復(fù)了原來的樣子。線程得以繼續(xù)往下執(zhí)行...
在攔截api的過程之中一個重要的任務(wù)就是保證堆棧的正確性。你要理清每一步堆棧中發(fā)生了什么。
三:形成思路
呵呵,不知道你現(xiàn)在腦海是不是有什么想法。怎么去實(shí)現(xiàn)攔截一個api?
這里給出一個思路,事實(shí)上攔截的方法真的很多,理清了一個,其他的也就容易了。而且上面所說的2個關(guān)鍵知識,也可以以另外的形式來利用。
我以攔截CreateFile這個api為例子來簡單說下這個思路吧:
首先,既然我們要攔截這個api就應(yīng)該知道這個函數(shù)在內(nèi)存中的位置吧,至少需要知道從哪兒入口。CreateFile這個函數(shù)是由kernel32.dll這個動態(tài)庫導(dǎo)出的。我們可以使用下面的方法來獲取他映射到內(nèi)存中的地址:
HMODULE hkernel32 = LoadLibrary("Kernel32.dll");
PVOID dwCreateFile = GetProcAddress(hkernei32, "CreateFileA");
這就可以得到createfile的地址了,注意這里是獲取的createfile的ansic版本。對于UNICODE版本的則獲取CreateFileW。這時dwCreateFile的值就是他的地址了。對于其他進(jìn)程中的createfile函數(shù)也是這個地址,前面說過windows指定了他提供的所有的dll文件的加載地址。
接下來,我們該想辦法實(shí)現(xiàn)跳轉(zhuǎn)了。最簡單的方法就是修改這個api入口處的代碼了。但是我們該修改多少呢?修改的內(nèi)容為什么呢?前面說過我們可以使用CALL的方式來實(shí)現(xiàn)跳轉(zhuǎn),這種方法的好處是可以為你的攔截函數(shù)提供一個或者多個參數(shù)。這里只要一個參數(shù)就足夠了。帶參數(shù)的函數(shù)調(diào)用的匯編代碼是什么樣子呢,前面也已經(jīng)說了,類似與調(diào)用MessageBoxA時的代碼:
PUSH 參數(shù)地址
CALL 函數(shù)入口地址(這里為一個偏移地址)
執(zhí)行這2條指令就能跳轉(zhuǎn)到你要攔截的函數(shù)了,但是我們該修改成什么呢。首先,我們需要知道這2條指令的長度和具體的機(jī)器代碼的值。其中PUSH對應(yīng)0x68,而CALL指令對應(yīng)的機(jī)器碼為0xE8,而后面的則分別對應(yīng)攔截函數(shù)的參數(shù)地址和函數(shù)的地址。注意第一個是一個直接的地址,而第二個則是一個相對地址。當(dāng)然你也可以使用0xFF0x15這個CALL指令來進(jìn)行直接地址的跳轉(zhuǎn)。
下面就是計算這2個地址的值了,
對于參數(shù)和函數(shù)體的地址,要分情況而定,對于對本進(jìn)程中api的攔截,則直接取地址就可以了。對于參數(shù),可以先定義一個參數(shù)變量,然后取變量地址就ok了。
如果是想攔截其他進(jìn)程中的api,則必須使用其他一些方法,最典型的方法是利用VirtualAllocEx函數(shù)來在其他進(jìn)程中申請和提交內(nèi)存空間。然后用WriteProcessMemory來分別把函數(shù)體和參數(shù)分別寫入申請和分配的內(nèi)存空間中去。然后再生成要修改的數(shù)據(jù),最后用WriteProcessMemory來修改api入口,把入口的前10字節(jié)修改為剛剛生成的跳轉(zhuǎn)數(shù)據(jù)。比如在遠(yuǎn)程進(jìn)程中你寫入的參數(shù)和函數(shù)體的內(nèi)存地址分別為0x00010000和0x00011000,則生成的跳轉(zhuǎn)數(shù)據(jù)為 68 00 00 01 00 E8 00 10 01 00(PUSH 00010000 CALL 00011000),這樣程序運(yùn)行createfile函數(shù)的時候?qū)冗\(yùn)行PUSH 00010000 CALL 00011000,這樣就達(dá)到了跳轉(zhuǎn)的目的。此刻我們應(yīng)該時刻注意堆棧的狀態(tài),對于CreateFile有
HANDLE CreateFile(
LPCTSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile
);
可以看到其有7個參數(shù),于是在調(diào)用之前,堆棧應(yīng)該已經(jīng)被壓入了這7個參數(shù),堆棧的樣子:
|....| <---ESP
|createfile執(zhí)行后的下一條指令地址|
|參數(shù)1|
|參數(shù)2|
|參數(shù)3|
|參數(shù)4|
|參數(shù)5|
|參數(shù)6|
|參數(shù)7|
|..|
這是執(zhí)行到我們的跳轉(zhuǎn)語句:PUSH 00010000,于是堆棧又變了:
|....| <---ESP
|00010000|
|createfile執(zhí)行后的下一條指令地址|
|參數(shù)1|
|參數(shù)2|
|參數(shù)3|
|參數(shù)4|
|參數(shù)5|
|參數(shù)6|
|參數(shù)7|
|..|
接著執(zhí)行CALL 00011000,堆棧變?yōu)椋?br>|...| <---ESP
|api入口之后的第11個字節(jié)的指令的地址|
|00010000|
|createfile執(zhí)行后的下一條指令地址|
|參數(shù)1|
|參數(shù)2|
|參數(shù)3|
|參數(shù)4|
|參數(shù)5|
|參數(shù)6|
|參數(shù)7|
|..|
接下來就到了我們的攔截函數(shù)中拉,當(dāng)然,函數(shù)肯定也會做一些類似動作,把EBP壓棧,為局部變量分配空間等。這時候堆棧的樣子又變了:
|EDI| <---ESP
|ESI|
|EBX|
|局部變量|
|EBP| <---EBP
|api入口之后的第11個字節(jié)的指令的地址|
|00010000|
|createfile執(zhí)行后的下一條指令地址|
|參數(shù)1|
|參數(shù)2|
|參數(shù)3|
|參數(shù)4|
|參數(shù)5|
|參數(shù)6|
|參數(shù)7|
|..|
這時候,你想做什么就盡情地做吧,獲取參數(shù)信息,延緩執(zhí)行CreateFile函數(shù)等等。拿獲取打開文件句柄的名字來說吧,文件名是第一個參數(shù),前面說過我們可以用[EBP+8]來獲取參數(shù),但是對照上面的堆棧形狀,中間又加了另外一些數(shù)據(jù),所以我們用[EBP+16]來獲取第一個參數(shù)的地址。比如:
char* PFileName = NULL;
__asm{
MOV EAX,[EBP+16]
MOV [szFileName], EAX
}
比如我們用一個messagebox來彈出一個信息,說明該程序即將打開一個某謀路徑的文件句柄。但是有一個要注意的是,如果你想攔截遠(yuǎn)程進(jìn)程的話,對于那個攔截函數(shù)中所使用到的任何函數(shù)或者以任何形式的相對地址的調(diào)用都要停止。因?yàn)槊總€進(jìn)程中的地址分配都是獨(dú)立的,比如上面的CALL MessageBoxA改成直接地址的調(diào)用。對于使用messagebox,我們應(yīng)該定義一個函數(shù)指針,然后把這個指針的值賦值為user32.dll中導(dǎo)出該函數(shù)的直接地址。然后利用這個指針來進(jìn)行函數(shù)調(diào)用。對于messagebox函數(shù)的調(diào)用可以這樣,在源程序中定義一個參數(shù)結(jié)構(gòu)體,參數(shù)中包含一個導(dǎo)出函數(shù)的地址,把這個地址設(shè)為MessageBoxA的直接地址,獲取地址的方法就不說了。然后把這個參數(shù)傳給攔截函數(shù),就可以使用拉。這也是利用一個參數(shù)的原因。類似代碼如下:
typedef struct _RemoteParam {
DWORD dwMessageBox;
} RemoteParam, * PRemoteParam;
typedef int (__stdcall * PFN_MESSAGEBOX)(HWND, LPCTSTR, LPCTSTR, DWORD);//定義一個函數(shù)指針
//攔截函數(shù)
void HookCreateFile(LPVOID lParam)
{
RemoteParam* pRP = (RemoteParam*)lParam;//獲取參數(shù)地址
char* PFileName = NULL;//定義一個指針
__asm{
MOV EAX,[EBP+16]
MOV [szFileName], EAX //把CreateFile第一個參數(shù)的值,文件的路徑的地址傳 //給szFileName
}
//定 義一個函數(shù)指針
PFN_MESSAGEBOX pfnMessageBox = (PFN_MESSAGEBOX)pRP->dwMessageBox;
pfnMessageBox(NULL, PFileName, PFileName, MB_ICONINFORMATION |MB_OK);
//輸出要打開的文件的路徑
//.....
}
對于你要使用的其他函數(shù),都是使用同樣的方式,利用這個參數(shù)來傳遞我們要傳遞的函數(shù)的絕對地址,然后定義這個函數(shù)指針,就可以使用了。
好了,接下來我們該讓被攔截的api正常工作了,這個不難,把他原來的數(shù)據(jù)恢復(fù)一下就可以了。那入口的10個字節(jié)。我們在改寫他們的時候應(yīng)該保存一下,然后也把他放在參數(shù)中傳遞給攔截函數(shù),呵呵,參數(shù)的作用可多了。接著我們就可以用WriteProcessMemory函數(shù)來恢復(fù)這個api的入口了,代碼如下:
PFN_GETCURRENTPROCESS pfnGetCurrentProcess = (PFN_GETCURRENTPROCESS)pRP->dwGetCurrentProcess;
PFN_WRITEPROCESSMEMORY pfnWriteProcessMemory = (PFN_WRITEPROCESSMEMORY)pRP->dwWriteProcessMemory;
if(!pfnWriteProcessMemory(pfnGetCurrentProcess(),
(LPVOID)pfnConnect,
(LPCVOID)pRP->szOldCode,
10,
NULL))
pfnMessageBox(NULL, pRP->szModuleName1, pRP->szModuleName2, MB_ICONINFORMATION | MB_OK);
其中這些函數(shù)指針的定義和上面的類似。
而參數(shù)中的szoldcode則是在源程序中在修改api之前保存好,然后傳給攔截函數(shù),在源程序中是用ReadProcessMemory函數(shù)來獲取他的前10個字節(jié)的:
ReadProcessMemory(GetCurrentProcess(),
(LPCVOID)RParam.dwCreateFile,
oldcode,
10,
&dwPid)
strcat((char*)RParam.szOldCode, (char*)oldcode);
接下來如果你還繼續(xù)保持對該api的攔截,則又該用WriteProcessMemory 來修改入口了,跟前面的恢復(fù)入口是一樣的,只不過把szOldCode換成了szNewCode了而已。這樣你又能對CreateFile繼續(xù)攔截了。
好了,接下來該進(jìn)行堆棧的清理了,也許你還要做點(diǎn)其他事情,盡管做去。但是清理堆棧是必須要做的,在函數(shù)結(jié)束的時候,因?yàn)樵谖覀兎湃蝍pi恢復(fù)執(zhí)行之后,他又return 到我們的函數(shù)中來了,這個時候的堆棧是什么樣子呢?
|EDI| <---ESP
|ESI|
|EBX|
|局部變量|
|EBP| <---EBP
|api入口之后的第11個字節(jié)的指令的地址|
|00010000|
|createfile執(zhí)行后的下一條指令地址|
|參數(shù)1|
|參數(shù)2|
|參數(shù)3|
|參數(shù)4|
|參數(shù)5|
|參數(shù)6|
|參數(shù)7|
|..|
我們的目標(biāo)是把返回值記錄下來放到EAX寄存器中去,把返回地址記錄下來,同時把堆棧恢復(fù)成原來的樣子。
首先我們恢復(fù)那些寄存器的值,接著釋放局部變量,可以用mov esp, ebp.因?yàn)槲覀儾磺宄唧w的局部變量分配了多少空間。所以使用這個方法。
__asm
{POP EDI
POP ESI
POP EBX //恢復(fù)那些寄存器
MOV EDX, [NextIpAddr]//把返回地址放到EDX中,因?yàn)榇龝? //EBX被恢復(fù)后,線程中的所有局部變量就不能正常使用了。
MOV EAX, [RetValue]//返回值放到EAX中,當(dāng)然也可以修改這個返回值
MOV ESP, EBP//清理局部變量
POP EBP//恢復(fù)EBP的值
ADD ESP, 28H //清理參數(shù)和返回地址,注意一共(7+1+1+1)*4
PUSH EDX //把返回地址壓棧,這樣棧中就只有這一個返回地址了,返回之后棧 //就空了
RET
}
這樣,一切就完成了,堆棧恢復(fù)了應(yīng)該有的狀態(tài),而你想攔截的也攔截到了。
四:后記
攔截的方式多種多樣,不過大體的思路卻都相同。要時刻注意你要攔截的函數(shù)的堆棧狀態(tài)以及在攔截函數(shù)中的對數(shù)據(jù)的引用和函數(shù)的調(diào)用(地址問題)。
//////////////////////////////////////////////////////////////////////
附錄:一個攔截CreateFile函數(shù)的簡單實(shí)現(xiàn)
//////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <windows.h>
#include <Psapi.h>
#pragma comment(lib, "psapi.lib")
typedef struct _RemoteParam {
DWORD dwCreateFile;
DWORD dwMessageBox;
DWORD dwGetCurrentProcess;
DWORD dwWriteProcessMemory;
unsigned char szOldCode[10];
DWORD FunAddr;
} RemoteParam, * PRemoteParam;
typedef HANDLE (__stdcall * PFN_CREATEFILE)(LPCTSTR,DWORD,DWORD,LPSECURITY_ATTRIBUTES,DWORD,DWORD,HANDLE);
typedef int (__stdcall * PFN_MESSAGEBOX)(HWND, LPCTSTR, LPCTSTR, DWORD);
typedef BOOL (__stdcall * PFN_WRITEPROCESSMEMORY)(HANDLE,LPVOID,LPCVOID,SIZE_T,SIZE_T*);
typedef HANDLE (__stdcall * PFN_GETCURRENTPROCESS)(void);
#define PROCESSNUM 128
#define MYMESSAGEBOX "MessageBoxW"
#define MYCREATEFILE "CreateFileW"
void HookCreateFile(LPVOID lParam)
{
RemoteParam* pRP = (RemoteParam*)lParam;
DWORD NextIpAddr = 0;
DWORD dwParamaAddr = 0;
HANDLE RetFpHdl = INVALID_HANDLE_VALUE;
LPCTSTR lpFileName;
DWORD dwDesiredAccess;
DWORD dwShareMode;
LPSECURITY_ATTRIBUTES lpSecurityAttributes;
DWORD dwCreationDisposition;
DWORD dwFlagsAndAttributes;
HANDLE hTemplateFile;
PFN_CREATEFILE pfnCreatefile = (PFN_CREATEFILE)pRP->dwCreateFile;
__asm
{
MOV EAX,[EBP+8]
MOV [dwParamaAddr], EAX
MOV EAX,[EBP+12]
MOV [NextIpAddr], EAX
MOV EAX,[EBP+16]
MOV [lpFileName], EAX
MOV EAX,[EBP+20]
MOV [dwDesiredAccess],EAX
MOV EAX,[EBP+24]
MOV [dwShareMode],EAX
MOV EAX,[EBP+28]
MOV [lpSecurityAttributes],EAX
MOV EAX,[EBP+32]
MOV [dwCreationDisposition],EAX
MOV EAX,[EBP+36]
MOV [dwFlagsAndAttributes],EAX
MOV EAX,[EBP+40]
MOV [hTemplateFile],EAX
}
PFN_MESSAGEBOX pfnMessageBox = (PFN_MESSAGEBOX)pRP->dwMessageBox;
int allowFlag = pfnMessageBox(NULL, lpFileName, NULL, MB_ICONINFORMATION | MB_YESNO);
if(allowFlag == IDYES)
{
unsigned char szNewCode[10];
int PramaAddr = (int)dwParamaAddr;
szNewCode[4] = PramaAddr>>24;
szNewCode[3] = (PramaAddr<<8)>>24;
szNewCode[2] = (PramaAddr<<16)>>24;
szNewCode[1] = (PramaAddr<<24)>>24;
szNewCode[0] = 0x68;
int funaddr = (int)pRP->FunAddr - (int)pfnCreatefile - 10 ;
szNewCode[9] = funaddr>>24;
szNewCode[8] = (funaddr<<8)>>24;
szNewCode[7] = (funaddr<<16)>>24;
szNewCode[6] = (funaddr<<24)>>24;
szNewCode[5] = 0xE8;
PFN_GETCURRENTPROCESS pfnGetCurrentProcess = (PFN_GETCURRENTPROCESS)pRP->dwGetCurrentProcess;
PFN_WRITEPROCESSMEMORY pfnWriteProcessMemory = (PFN_WRITEPROCESSMEMORY)pRP->dwWriteProcessMemory;
pfnWriteProcessMemory(pfnGetCurrentProcess(),
(LPVOID)pfnCreatefile,
(LPCVOID)pRP->szOldCode,
10,
NULL);
RetFpHdl = pfnCreatefile(lpFileName,
dwDesiredAccess,
dwShareMode,
lpSecurityAttributes,
dwCreationDisposition,
dwFlagsAndAttributes,
hTemplateFile);
pfnWriteProcessMemory(pfnGetCurrentProcess(),
(LPVOID)pfnCreatefile,
(LPCVOID)szNewCode,
10,
NULL);
}
__asm
{POP EDI
POP ESI
POP EBX
MOV EDX, [NextIpAddr]
MOV EAX, [RetFpHdl]
MOV ESP, EBP
POP EBP
ADD ESP, 28H
PUSH EDX
RET
}
}
BOOL AdjustProcessPrivileges(LPCSTR szPrivilegesName)
{
HANDLE hToken;
TOKEN_PRIVILEGES tkp;
if(!OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,&hToken))
{
return FALSE;
}
if(!LookupPrivilegeValue(NULL,szPrivilegesName,
&tkp.Privileges[0].Luid))
{
CloseHandle(hToken);
return FALSE;
}
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if(!AdjustTokenPrivileges(hToken,FALSE,&tkp,sizeof(tkp),NULL,NULL))
{
CloseHandle(hToken);
return FALSE;
}
CloseHandle(hToken);
return TRUE;
}
void printProcessNameByPid( DWORD ProcessId )
{
HANDLE pHd;
HMODULE pHmod;
char ProcessName[MAX_PATH] = "unknown";
DWORD cbNeeded;
pHd = OpenProcess( PROCESS_QUERY_INFORMATION |PROCESS_VM_READ, FALSE, ProcessId );
if(pHd == NULL)
return;
if(!EnumProcessModules( pHd, &pHmod, sizeof(pHmod), &cbNeeded))
return;
if(!GetModuleFileNameEx( pHd, pHmod, ProcessName, MAX_PATH))
return;
printf( "%d\t%s\n", ProcessId, ProcessName);
CloseHandle( pHd );
return;
}
int main(void)
{
if(!AdjustProcessPrivileges(SE_DEBUG_NAME))
{
printf("AdjustProcessPrivileges Error!\n");
return -1;
}
DWORD Pids[PROCESSNUM];
DWORD dwProcessNum = 0;
if(!EnumProcesses(Pids, sizeof(Pids), &dwProcessNum))
{
printf("EnumProcess Error!\n");
return -1;
}
for( DWORD num = 0; num < (dwProcessNum / sizeof(DWORD)); num++)
printProcessNameByPid(Pids[num]);
printf("\nAll %d processes running. \n", dwProcessNum / sizeof(DWORD));
DWORD dwPid = 0;
printf("\n請輸入要攔截的進(jìn)程id:");
scanf("%d", &dwPid);
HANDLE hTargetProcess = OpenProcess(PROCESS_VM_OPERATION|PROCESS_VM_WRITE|PROCESS_VM_READ, FALSE, dwPid);
if(hTargetProcess == NULL)
{
printf("OpenProcess Error!\n");
return -1;
}
DWORD dwFunAddr = (DWORD)VirtualAllocEx(hTargetProcess, NULL, 8192,
MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if((LPVOID)dwFunAddr == NULL)
{
printf("申請線程內(nèi)存失敗!\n");
CloseHandle(hTargetProcess);
return -1;
}
DWORD dwPramaAddr = (DWORD)VirtualAllocEx(hTargetProcess, NULL, sizeof(RemoteParam),
MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if((LPVOID)dwPramaAddr == NULL)
{
printf("申請參數(shù)內(nèi)存失敗!\n");
CloseHandle(hTargetProcess);
return -1;
}
printf("\n線程內(nèi)存地址:%.8x\n"
"參數(shù)內(nèi)存地址:%.8x\n",
dwFunAddr, dwPramaAddr);
RemoteParam RParam;
ZeroMemory(&RParam, sizeof(RParam));
HMODULE hKernel32 = LoadLibrary("kernel32.dll");
HMODULE hUser32 = LoadLibrary("user32.dll");
RParam.dwCreateFile = (DWORD)GetProcAddress(hKernel32, MYCREATEFILE);
RParam.dwGetCurrentProcess = (DWORD)GetProcAddress(hKernel32, "GetCurrentProcess");
RParam.dwWriteProcessMemory = (DWORD)GetProcAddress(hKernel32, "WriteProcessMemory");
RParam.dwMessageBox = (DWORD)GetProcAddress(hUser32, MYMESSAGEBOX);
unsigned char oldcode[10];
unsigned char newcode[10];
int praadd = (int)dwPramaAddr;
int threadadd = (int)dwFunAddr;
newcode[4] = praadd>>24;
newcode[3] = (praadd<<8)>>24;
newcode[2] = (praadd<<16)>>24;
newcode[1] = (praadd<<24)>>24;
newcode[0] = 0x68;
int offsetaddr = threadadd - (int)RParam.dwCreateFile - 10 ;
newcode[9] = offsetaddr>>24;
newcode[8] = (offsetaddr<<8)>>24;
newcode[7] = (offsetaddr<<16)>>24;
newcode[6] = (offsetaddr<<24)>>24;
newcode[5] = 0xE8;
printf("NewCode:");
for(int j = 0; j < 10; j++)
printf("0x%.2x ",newcode[j]);
printf("\n\n");
if(!ReadProcessMemory(GetCurrentProcess(),
(LPCVOID)RParam.dwCreateFile,
oldcode,
10,
&dwPid))
{
printf("read error");
CloseHandle(hTargetProcess);
FreeLibrary(hKernel32);
return -1;
}
strcat((char*)RParam.szOldCode, (char*)oldcode);
RParam.FunAddr = dwFunAddr;
printf(
"RParam.dwCreateFile:%.8x\n"
"RParam.dwMessageBox:%.8x\n"
"RParam.dwGetCurrentProcess:%.8x\n"
"RParam.dwWriteProcessMemory:%.8x\n"
"RParam.FunAddr:%.8x\n",
RParam.dwCreateFile,
RParam.dwMessageBox,
RParam.dwGetCurrentProcess,
RParam.dwWriteProcessMemory,
RParam.FunAddr);
printf("RParam.szOldCode:");
for( int i = 0; i< 10; i++)
printf("0x%.2x ", RParam.szOldCode[i]);
printf("\n");
if(!WriteProcessMemory(hTargetProcess, (LPVOID)dwFunAddr, (LPVOID)&HookCreateFile, 8192, &dwPid))
{
printf("WriteRemoteProcessesMemory Error!\n");
CloseHandle(hTargetProcess);
FreeLibrary(hKernel32);
return -1;
}
if(!WriteProcessMemory(hTargetProcess, (LPVOID)dwPramaAddr, (LPVOID)&RParam, sizeof(RemoteParam), &dwPid))
{
printf("WriteRemoteProcessesMemory Error!\n");
CloseHandle(hTargetProcess);
FreeLibrary(hKernel32);
return -1;
}
if(!WriteProcessMemory(hTargetProcess, (LPVOID)RParam.dwCreateFile, (LPVOID)newcode, 10, &dwPid))
{
printf("WriteRemoteProcessesMemory Error!\n");
CloseHandle(hTargetProcess);
FreeLibrary(hKernel32);
return -1;
}
printf("\nThat's all, good luck :)\n");
CloseHandle(hTargetProcess);
FreeLibrary(hKernel32);
return 0;
}