用戶層下攔截系統api的原理與實現

http://www.leftworld.net/wenzhang/show/2251.html

寫這篇文章是爲了複習一些知識,最近在做畢業設計,之中大量地使用了這種技術,主要是用在攔截 winsock 函數,對於其他系統 api,其效果也是一樣的。

 前段時間寫了一個 HookSend 的小程序,其實就是用的這裏所說的方法.只不過這裏講得更詳細一點,熟悉的就不用看了,另外歡迎大蝦指出錯誤,不勝感激@_@

攔截 api 的技術有很多種,大體分爲用戶層和內核層的攔截.這裏只說說用戶層的攔截.而用戶層也分爲許多種:修改PE文件導入表,直接修改要攔截的 api 的內存(從開始到最後,使程序跳轉到指定的地址執行)。不過大部分原理都是修改程序流程,使之跳轉到你要執行的地方,然後再返回到原地址.原來 api 的功能必須還能實現.否則攔截就失去作用了.修改文件導入表的方法的缺點是如果用戶程序動態加載(使用 LoadLibrary 和 GetProcAddress 函數),攔截將變得複雜一些.所以這裏介紹一下第二種方法,直接修改 api,當然不是全局的。(後面會說到)

需要了解的一些知識:

1、windows 內存的結構屬性和進程地址空間

2、函數堆棧的一些知識

一、win2000 和 xp 的內存結構和進程地址空間

windows 採用 4GB 平坦虛擬地址空間的做法。即每個進程單獨擁有 4GB 的地址空間。每個進程只能訪問自己的這 4GB 的虛擬空間,而對於其他進程的地址空間則是不可見的。這樣保證了進程的安全性和穩定性。但是,這 4GB 的空間是一個虛擬空間,在使用之前,我們必須先保留一段虛擬地址,然後再爲這段虛擬地址提交物理存儲器。可是我們的內存大部分都還沒有 1GB,那麼這 4GB 的地址空間是如何實現的呢?事實上 windows 採用的內存映射這種方法,即把物理磁盤當作內存來使用,比如我們打開一個可執行文件的時候,操作系統會爲我們開闢這個 4GB 的地址空間:0x00000000--0xffffffff。其中 0x00000000--0x7fffffff 是屬於用戶層的空間,0x80000000--0xffffffff 則屬於共享內核方式分區,主要是操作系統的線程調度,內存管理,文件系統支持,網絡支持和所有設備驅動程序。對於用戶層的進程,這些地址空間是不可訪問的。任何訪問都將導致一個錯誤。開闢這4GB的虛擬地址空間之後,系統會把磁盤上的執行文件映射到進程的地址空間中去(一般是在地址 0x00400000,可以通過修改編譯選項來修改這個地址)而一個進程運行所需要的動態庫文件則一般從 0x10000000 開始加載。但是如果所有的動態庫都加載到這個位置肯定會引起衝突。因此必須對一些可能引起衝突的 dll 編譯時重新修改基地址。但是對於所有的操作系統所提供的動態庫 windows 已經定義好了映射在指定的位置。這個位置會隨着版本的不同而會有所改變,不過對於同一臺機器上的映射地址來說都是一樣的。即在 a 進程裏映射的kernel32.dll 的地址和在進程 b 裏的 kernel32.dll 的地址是一樣的。對於文件映射是一種特殊的方式,使得程序不需要進行磁盤 I/O 就能對磁盤文件進行操作,而且支持多種保護屬性。對於一個被映射的文件,主要是使用CreateFileMapping 函數,利用他我們可以設定一些讀寫屬性:PAGE_READONLY、PAGE_READWRITE、PAGE_WRITECOPY。第一參數指定只能對該映射文件進行讀操作。任何寫操作將導致內存訪問錯誤。第二個參數則指明可以對映射文件進行讀寫。這時候,任何對文件的讀寫都是直接操作文件的。而對於第三個參數 PAGE_WRITECOPY 顧名思義就是寫入時拷貝,任何向這段內存寫入的操作(因爲文件是映射到進程地址空間的,對這段空間的讀寫就相當於對文件進行的直接讀寫)都將被系統捕獲,並重新在你的虛擬地址空間重新保留並分配一段內存,你所寫入的一切東西都將在這裏,而且你原先的指向映射文件的內存地址也會實際指向這段重新分配的內存,於是在進程結束後,映射文件內容並沒有改變,只是在運行期間在那段私有拷貝的內存裏面存在着你修改的內容。windows 進程運行所需要映射的一些系統 dll 就是以這種方式映射的,比如常用的 ntdll.dll、kernel32.dll、gdi32.dll。幾乎所有的進程都會加載這三個動態庫。如果你在一個進程裏修改這個映射文件的內容,並不會影響到其他的進程使用他們。你所修改的只是在本進程的地址空間之內的。事實上原始文件並沒有被改變。

這樣,在後面的修改系統 api 的時候,實際就是修改這些動態庫地址內的內容。前面說到這不是修改全局 api 就是這個原因,因爲他們都是以寫入時拷貝的方式來映射的。不過這已經足夠了,windows 提供了 2 個強大的內存操作函數ReadProcessMemory 和 WriteProcessMemory。利用這兩個函數我們就可以隨便對任意進程的任意用戶地址空間進行讀寫了。但是,現在有一個問題,我們該寫什麼,說了半天,怎麼實現跳轉呢?現在來看一個簡單的例子:

MessageBox(NULL, "World", "Hello", 0);

我們在執行這條語句的時候,調用了系統 api MessageBox,實際上在程序中我沒有定義 UNICODE 宏,系統調用的是MessageBox 的 ANSI 版本 MessageBoxA,這個函數是由 user32.dll 導出的。下面是執行這條語句的彙編代碼:

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)]

前面四條指令分別爲參數壓棧,因爲 MessageBoxA 是 __stdcall 調用約定,所以參數是從右往左壓棧的。最後再CALL 0x0042428c

看看 0042428c 這段內存的值:

0042428C   0B 05 D5 77 00 00 00

可以看到這個值 0x77d5050b,正是 user32.dll 導出函數 MessageBoxA 的入口地址。

這是 0x77D5050B 處的內容,

77D5050B 8B FF                mov         edi, edi

77D5050D 55                   push        ebp

77D5050E 8B EC                mov         ebp, esp

理論上只要改變 api 入口和出口的任何機器碼,都可以攔截該 api。這裏我選擇最簡單的修改方法,直接修改 api 入口的前十個字節來實現跳轉。爲什麼是十字節呢?其實修改多少字節都沒有關係,只要實現了函數的跳轉之後,你能把他們恢復並讓他繼續運行纔是最重要的。在 CPU 的指令裏,有幾條指令可以改變程序的流程:JMP,CALL,INT,RET,RETF,IRET等指令。這裏我選擇 CALL 指令,因爲他是以函數調用的方式來實現跳轉的,這樣可以帶一些你需要的參數。到這裏,我該說說函數的堆棧了。

總結:windows 進程所需要的動態庫文件都是以寫入時拷貝的方式映射到進程地址空間中的。這樣,我們只能攔截指定的進程。修改目標進程地址空間中的指定 api 的入口和出口地址之間的任意數據,使之跳轉到我們的攔截代碼中去,然後再恢復這些字節,使之能順利工作。

 

二、函數堆棧的一些知識

正如前面所看到MessageBoxA函數執行之前的彙編代碼,首先將四個參數壓棧,然後 CALL MessageBoxA,這時候我們的線程堆棧看起來應該是這樣的:

|        |     <---ESP

|返回地址|

|參數1   |

|參數2   |

|參數3   |

|參數4   |

|......  |

我們再看 MessageBoxA 的彙編代碼,

77D5050B 8B FF                mov         edi, edi

77D5050D 55                   push        ebp

77D5050E 8B EC                mov         ebp, esp

注意到堆棧的操作有 push ebp,這是保存當前的基址指針,以便一會兒恢復堆棧後返回調用線程時使用,然後再有mov ebp, esp就是把當前 esp 的值賦給 ebp,這時候我們就可以使用 ebp + 偏移 來表示堆棧中的數據,比如參數 1 就可以表示成 [ebp + 8],返回地址就可以表示成 [ebp + 4]... 如果我們在攔截的時候要對這些參數和返回地址做任何處理,就可以使用這種方法。如果這個時候函數有局部變量的話,就通過減小 ESP 的值的方式來爲之分配空間。接下來就是保存一些寄存器:EDI、ESI、EBX。要注意的是,函數堆棧是反方向生長的。這時候堆棧的樣子:

|...     |

|EDI     |   <---ESP

|ESI     |

|EBX     |

|局部變量|

|EBP     |

|返回地址|

|參數1   |

|參數2   |

|參數3   |

|參數4   |

|...     |

在函數返回的時候,由函數自身來進行堆棧的清理,這時候清理的順序和開始入棧的順序恰恰相反,類似的彙編代碼可能是這樣的:

pop   edi

pop   esi

pop   ebx

add   esp, 4

pop   ebp

ret   0010

先恢復那些寄存器的值,然後通過增加 ESP 的值的方式來釋放局部變量。這裏可以用 mov esp, ebp 來實現清空所有局部變量和其他一些空閒分配空間。接着函數會恢復 EBP 的值,利用指令 POP EBP 來恢復該寄存器的值。接着函數運行 ret 0010 這個指令。該指令的意思是,函數把控制權交給當前棧頂的地址的指令,同時清理堆棧的 16 字節的參數。如果函數有返回值的話,那在 EAX 寄存器中保存着當前函數的返回值。如果是 __cdecl 調用方式,則執行 ret 指令,對於堆棧參數的處理交給調用線程去做。如 wsprintf 函數。

這個時候堆棧又恢復了原來的樣子。線程得以繼續往下執行...

在攔截 api 的過程之中一個重要的任務就是保證堆棧的正確性。你要理清每一步堆棧中發生了什麼。

三、形成思路

呵呵,不知道你現在腦海是不是有什麼想法。怎麼去實現攔截一個api?

這裏給出一個思路,事實上攔截的方法真的很多,理清了一個,其他的也就容易了。而且上面所說的2個關鍵知識,也可以以另外的形式來利用。

我以攔截 CreateFile 這個 api 爲例子來簡單說下這個思路吧:

首先,既然我們要攔截這個 api 就應該知道這個函數在內存中的位置吧,至少需要知道從哪兒入口。CreateFile 這個函數是由 kernel32.dll 這個動態庫導出的。我們可以使用下面的方法來獲取他映射到內存中的地址:

  HMODULE hkernel32 = LoadLibrary("Kernel32.dll");

  PVOID dwCreateFile = GetProcAddress(hkernei32, "CreateFileA");

這就可以得到 createfile 的地址了,注意這裏是獲取的 createfile 的 ansic 版本。對於 UNICODE 版本的則獲取 CreateFileW。這時 dwCreateFile 的值就是他的地址了。對於其他進程中的 createfile 函數也是這個地址,前面說過 windows 指定了他提供的所有的 dll 文件的加載地址。

接下來,我們該想辦法實現跳轉了。最簡單的方法就是修改這個 api 入口處的代碼了。但是我們該修改多少呢?修改的內容爲什麼呢?前面說過我們可以使用 CALL 的方式來實現跳轉,這種方法的好處是可以爲你的攔截函數提供一個或者多個參數。這裏只要一個參數就足夠了。帶參數的函數調用的彙編代碼是什麼樣子呢,前面也已經說了,類似與調用 MessageBoxA 時的代碼:

PUSH   參數地址

CALL   函數入口地址(這裏爲一個偏移地址)

執行這 2 條指令就能跳轉到你要攔截的函數了,但是我們該修改成什麼呢。首先,我們需要知道這 2 條指令的長度和具體的機器代碼的值。其中 PUSH 對應 0x68,而 CALL 指令對應的機器碼爲 0xE8,而後面的則分別對應攔截函數的參數地址和函數的地址。注意第一個是一個直接的地址,而第二個則是一個相對地址。當然你也可以使用 0xFF0x15 這個 CALL 指令來進行直接地址的跳轉。

下面就是計算這 2 個地址的值了,

對於參數和函數體的地址,要分情況而定,對於對本進程中 api 的攔截,則直接取地址就可以了。對於參數,可以先定義一個參數變量,然後取變量地址就 ok 了。

如果是想攔截其他進程中的 api,則必須使用其他一些方法,最典型的方法是利用 VirtualAllocEx 函數來在其他進程中申請和提交內存空間。然後用 WriteProcessMemory 來分別把函數體和參數分別寫入申請和分配的內存空間中去。然後再生成要修改的數據,最後用 WriteProcessMemory 來修改 api 入口,把入口的前 10 字節修改爲剛剛生成的跳轉數據。比如在遠程進程中你寫入的參數和函數體的內存地址分別爲 0x00010000 和 0x00011000,則生成的跳轉數據爲 68 00 00 01 00 E8 00 10 01 00(PUSH 00010000   CALL 00011000),這樣程序運行 createfile 函數的時候將會先運行 PUSH 00010000   CALL 00011000,這樣就達到了跳轉的目的。此刻我們應該時刻注意堆棧的狀態,對於 CreateFile 有

HANDLE CreateFile(
            LPCTSTR   lpFileName,
            DWORD     dwDesiredAccess,
            DWORD     dwShareMode, 
            LPSECURITY_ATTRIBUTES   lpSecurityAttributes,
            DWORD    dwCreationDisposition,
            DWORD    dwFlagsAndAttributes,
            HANDLE   hTemplateFile
            );

可以看到其有 7 個參數,於是在調用之前,堆棧應該已經被壓入了這 7 個參數,堆棧的樣子:

|... |     <---ESP

|createfile執行後的下一條指令地址|

|參數1|

|參數2|

|參數3|

|參數4|

|參數5|

|參數6|

|參數7|

|...  |

這是執行到我們的跳轉語句:PUSH 00010000,於是堆棧又變了:

|.... |     <---ESP

|00010000|

|createfile執行後的下一條指令地址|

|參數1|

|參數2|

|參數3|

|參數4|

|參數5|

|參數6|

|參數7|

|.... |

接着執行 CALL 00011000,堆棧變爲:

|...|   <---ESP

|api入口之後的第11個字節的指令的地址|

|00010000|

|createfile執行後的下一條指令地址|

|參數1|

|參數2|

|參數3|

|參數4|

|參數5|

|參數6|

|參數7|

|...|

接下來就到了我們的攔截函數中拉,當然,函數肯定也會做一些類似動作,把EBP壓棧,爲局部變量分配空間等。這時候堆棧的樣子又變了:

|EDI|   <---ESP

|ESI|

|EBX|

|局部變量|

|EBP|     <---EBP

|api入口之後的第11個字節的指令的地址|

|00010000|

|createfile執行後的下一條指令地址|

|參數1|

|參數2|

|參數3|

|參數4|

|參數5|

|參數6|

|參數7|

|...|

這時候,你想做什麼就盡情地做吧,獲取參數信息,延緩執行 CreateFile 函數等等。拿獲取打開文件句柄的名字來說吧,文件名是第一個參數,前面說過我們可以用 [EBP + 8] 來獲取參數,但是對照上面的堆棧形狀,中間又加了另外一些數據,所以我們用 [EBP + 16] 來獲取第一個參數的地址。比如:

char* PFileName = NULL;

__asm{

        MOV EAX, [EBP + 16]

        MOV [szFileName], EAX

}

比如我們用一個 messagebox 來彈出一個信息,說明該程序即將打開一個某謀路徑的文件句柄。但是有一個要注意的是,如果你想攔截遠程進程的話,對於那個攔截函數中所使用到的任何函數或者以任何形式的相對地址的調用都要停止。因爲每個進程中的地址分配都是獨立的,比如上面的 CALL MessageBoxA 改成直接地址的調用。對於使用 messagebox,我們應該定義一個函數指針,然後把這個指針的值賦值爲 user32.dll 中導出該函數的直接地址。然後利用這個指針來進行函數調用。對於 messagebox 函數的調用可以這樣,在源程序中定義一個參數結構體,參數中包含一個導出函數的地址,把這個地址設爲 MessageBoxA 的直接地址,獲取地址的方法就不說了。然後把這個參數傳給攔截函數,就可以使用拉。這也是利用一個參數的原因。類似代碼如下:

typedef struct _RemoteParam {
        DWORD dwMessageBox;
    } RemoteParam, * PRemoteParam;

 

typedef int (__stdcall * PFN_MESSAGEBOX)(HWND, LPCTSTR, LPCTSTR, DWORD);    // 定義一個函數指針

// 攔截函數
    void HookCreateFile(LPVOID lParam)
    {
        RemoteParam* pRP = (RemoteParam *)lParam;    // 獲取參數地址
        char* PFileName = NULL;    // 定義一個指針
        __asm {
            MOV EAX,[EBP + 16]
            MOV [szFileName], EAX     // 把 CreateFile 第一個參數的值,文件的路徑的地址傳給 szFileName
        }

        // 定義一個函數指針
        PFN_MESSAGEBOX pfnMessageBox = (PFN_MESSAGEBOX)pRP->dwMessageBox;
        pfnMessageBox(NULL, PFileName, PFileName, MB_ICONINformATION | MB_OK);

        // 輸出要打開的文件的路徑
        // .....
    }

對於你要使用的其他函數,都是使用同樣的方式,利用這個參數來傳遞我們要傳遞的函數的絕對地址,然後定義這個函數指針,就可以使用了。

好了,接下來我們該讓被攔截的 api 正常工作了,這個不難,把他原來的數據恢復一下就可以了。那入口的 10 個字節。我們在改寫他們的時候應該保存一下,然後也把他放在參數中傳遞給攔截函數,呵呵,參數的作用可多了。接着我們就可以用 WriteProcessMemory 函數來恢復這個 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);

其中這些函數指針的定義和上面的類似。

而參數中的szoldcode則是在源程序中在修改api之前保存好,然後傳給攔截函數,在源程序中是用ReadProcessMemory函數來獲取他的前10個字節的:

ReadProcessMemory(GetCurrentProcess(),

                                (LPCVOID)RParam.dwCreateFile,

                                oldcode,

                                10,

                                &dwPid)

strcat((char*)RParam.szOldCode, (char*)oldcode);

接下來如果你還繼續保持對該 api 的攔截,則又該用 WriteProcessMemory 來修改入口了,跟前面的恢復入口是一樣的,只不過把 szOldCode 換成了 szNewCode 了而已。這樣你又能對 CreateFile 繼續攔截了。

好了,接下來該進行堆棧的清理了,也許你還要做點其他事情,儘管做去。但是清理堆棧是必須要做的,在函數結束的時候,因爲在我們放任 api 恢復執行之後,他又 return 到我們的函數中來了,這個時候的堆棧是什麼樣子呢?

|EDI|   <---ESP

|ESI|

|EBX|

|局部變量|  

|EBP|     <---EBP

|api入口之後的第11個字節的指令的地址|    

|00010000|

|createfile執行後的下一條指令地址|

|參數1|

|參數2|

|參數3|

|參數4|

|參數5|

|參數6|

|參數7|

|..|

我們的目標是把返回值記錄下來放到 EAX 寄存器中去,把返回地址記錄下來,同時把堆棧恢復成原來的樣子。

首先我們恢復那些寄存器的值,接着釋放局部變量,可以用 mov esp, ebp。因爲我們不清楚具體的局部變量分配了多少空間。所以使用這個方法。

__asm
    {
        POP EDI
        POP ESI
        POP EBX                    // 恢復那些寄存器
        MOV EDX, [NextIpAddr]      // 把返回地址放到 EDX 中,因爲待會兒 EBX 被恢復
                                   // 後,線程中的所有局部變量就不能正常使用了。
        MOV EAX, [Retvalue]        // 返回值放到 EAX 中,當然也可以修改這個返回值
        MOV ESP, EBP               // 清理局部變量
        POP EBP                    // 恢復 EBP 的值
        ADD ESP, 28H               // 清理參數和返回地址,注意一共 (7 + 1 + 1 + 1) * 4
        PUSH EDX                   // 把返回地址壓棧,這樣棧中就只有這一個返回地址了,返回之後棧就空了
        RET
    }

這樣,一切就完成了,堆棧恢復了應該有的狀態,而你想攔截的也攔截到了。

四、後記

  攔截的方式多種多樣,不過大體的思路卻都相同。要時刻注意你要攔截的函數的堆棧狀態以及在攔截函數中的對數據的引用和函數的調用(地址問題)。

  

//////////////////////////////////////////////////////////////////////

附錄:一個攔截CreateFile函數的簡單實現

//////////////////////////////////////////////////////////////////////

#include <stdio.h>
#include <windows.h>
#include <Psapi.h>
#pragma comment(lib, "psapi.lib") 
typedef struct _RemoteParam {
            DWORD             dwCreateFile;
            DWORD             vdwMessageBox;
            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( "%dt%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請輸入要攔截的進程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");
        CloseHandle(hTargetProcess);
        return -1;
    }

    DWORD dwPramaAddr = (DWORD)VirtualAllocEx(hTargetProcess, NULL, sizeof(RemoteParam),
                                    MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if((LPVOID)dwPramaAddr == NULL)
    {
        printf("申請參數內存失敗!\n");
        CloseHandle(hTargetProcess);
        return -1;
    }

    printf("\n線程內存地址:%.8x\n 參數內存地址:%.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;

    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.dwCreate文件:%.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); 

    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;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章