【windows核心編程】一個API攔截的例子

來源:互聯網
上載者:User

標籤:des   style   blog   http   color   使用   

 

 

API攔截

修改PE檔案匯入段中的匯入函數地址 為 新的函數地址

這涉及PE檔案格式中的匯入表和IAT,PE檔案中每個隱式連結的DLL對應一個IMAGE_IMPORT_DESCRIPTOR描述符結構,而每個IMAGE_IMPORT_DESCRIPTOR結構中的FirstThunk指向一個IMAGE_THUNK_DATA結構數組的首地址。

在這個IAMGE_THUNK_DATA數組中,每一項對應一個該DLL模組的匯入函數(對使用該DLL模組的PE檔案來說是 匯入)。

 

 結構大致如下

 

 

 攔截某DLL模組中的API時,先在PE檔案的匯入段中尋找該DLL,然後再尋找該API的地址,將其地址替換為新的地址。

即先遍曆IMAGE_IMPORT_DESCRIPTOR數組,找到其Name和給定DLL名字相同的模組,然後再遍曆其FirstThunk指向的IAMGE_THUNK_DATA數組,找到待攔截的API,然後將新地址替換其地址。

 

 

本例中,LanjieAPI.exe中使用了DllForLanjie.dll模組,該模組匯出了一個計算兩個數的和的函數Add(int a, int b),本文執行個體將該Add函數攔截,替換為show函數來顯示資訊。

 

上代碼

/************************************************************************//* PE檔案中,每個隱式連結的DLL都對應一個IMAGE_IMPORT_DESCRIPTOR結構(winnt.h)typedef struct _IMAGE_IMPORT_DESCRIPTOR {union {DWORD   Characteristics;            // 0 for terminating null import descriptorDWORD   OriginalFirstThunk;         //指向IMAGE_THUNK_DATA結構數組的指標,RVA to original unbound IAT (PIMAGE_THUNK_DATA)};DWORD   TimeDateStamp;                  // 0 if not bound,// -1 if bound, and real date\time stamp//     in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)// O.W. date/time stamp of DLL bound to (Old BIND)DWORD   ForwarderChain;                 // -1 if no forwardersDWORD   Name;                           //該模組的名字DWORD   FirstThunk;                     //指向IMAGE_THUNK_DATA結構數組的指標,RVA to IAT (if bound this IAT has actual addresses)} IMAGE_IMPORT_DESCRIPTOR;PE檔案的某個匯入模組中的 每個函數 對應一個IMAGE_THUNK_DATA結構typedef struct _IMAGE_THUNK_DATA32 {union {DWORD ForwarderString;      // PBYTE DWORD Function;             // PDWORDDWORD Ordinal;DWORD AddressOfData;        //指向IMAGE_IMPORT_BY_NAME結構的指標, PIMAGE_IMPORT_BY_NAME} u1;} IMAGE_THUNK_DATA32;typedef struct _IMAGE_IMPORT_BY_NAME {WORD    Hint;BYTE    Name[1];} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;其中OriginalFirstThunk和FirstThunk均各自一個IMAGE_THUNK_DATA結構類型數組所不同的是:OriginalFirstThunk指向的IAMGE_THUNK_DATA結構數組是單獨的一項,並且不可更改,成為INT,有時也成為提示名表。FirstThunk所指向的是IMAGE_THUNK_DATA結構類型數組是又PE裝載器重寫的,PE裝載器首先搜尋OriginalFirstThunk,如果找到則載入程式迭代搜尋數組中的每個指標,找到每個IMAGE_IMPORT_BY_NAME結構所指向的輸入函數的地址,然後載入器用函數真正入口地址來替代由FirstThunk數組中的每個函數入口,因此他成為【輸入地址表IAT】。/************************************************************************/

 

 

 

DLL匯出函數

//DllForLanjie.dll匯出函數extern "C" __declspec(dllexport) int __stdcall Add(int a, int b){    return a + b;}

 

 

 

攔截代碼

BOOL ReplaceIATEntryInOneMod(         PCSTR pszCalleeModName,  //被調模組,要攔截的API所在的模組         PROC pfnCurrent,          //被攔截的API,其所在模組中的地址         PROC pfnNew,              //用來替換的新函數的地址         HMODULE hmodCaller        //調用新函數的模組           ){    ULONG ulSize = 0UL;    PIMAGE_IMPORT_DESCRIPTOR pImportDesc = NULL;  //匯入描述符    __try    {        pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)(ImageDirectoryEntryToData(hmodCaller, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &ulSize));    }    //__except(InvalidReadExceptionFilter(GetExceptionInformation))    __except(1)      {        cerr<<"Exception !"<<endl;    }    if(NULL == pImportDesc) return FALSE;    //結束條件為 當前該IMAGE_IMPORT_DESCRIPTOR結構(Name欄位)指標為NULL    for (; pImportDesc->Name; pImportDesc++)    {        //尋找匯入段中每個模組的地址,轉為PBYTE計算是為了指標+1時是加了一個位元組        PSTR pszModName = (PSTR)((PBYTE)hmodCaller + pImportDesc->Name);        //如果找到同名模組        if(lstrcmpiA(pszModName, pszCalleeModName) == 0)        {            //擷取調用程式中的該pImageDesc模組的IMAGE_THUNK_DATA數組的首地址(在PE檔案中)            PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA)((PBYTE)hmodCaller + pImportDesc->FirstThunk);                        //在模組中尋找要攔截的【函數】            for (; pThunk->u1.Function; pThunk++)            {                PROC* ppfn = (PROC*)(&pThunk->u1.Function);                //如果找到了需要的【函數】                BOOL bFound = (*ppfn == pfnCurrent);                if (bFound)                {                    //如果往被攔截函數的地址寫新函數的地址 【失敗】                    if (! WriteProcessMemory(GetCurrentProcess(), ppfn, &pfnNew, sizeof(pfnNew), NULL)                         && (ERROR_NOACCESS == GetLastError())   )                    {                        DWORD dwOldProtect = 0;                        //則改變頁面的保護屬性為 【寫時複製】                        if(VirtualProtect(ppfn, sizeof(pfnNew), PAGE_WRITECOPY, &dwOldProtect))                        {                            BOOL b1 = WriteProcessMemory(GetCurrentProcess(), ppfn, &pfnNew, sizeof(pfnNew), NULL);                            BOOL b2 = VirtualProtect(ppfn, sizeof(pfnNew), dwOldProtect, &dwOldProtect);                            return b1 && b2;                        } //if                        else                        {                            return FALSE;                        }                    } //if                    //如果執行到這裡了,則肯定是成功的                    return TRUE;                } //if            } //for        } //if    } //for    return FALSE;}

 

 

 

用來替換的新函數, 在當前exe中定義

int __stdcall show(){    cout<<endl<<"如果看到此行資訊 說明 你的函數 被 當前函數 攔截了  呵呵"<<endl;    return 0;}

 

 

 

示意怎麼去攔截

標頭檔 和 庫

 

#include "stdafx.h"#include <iostream>#include <Windows.h>#include <Dbghelp.h>#include <Psapi.h>using namespace std;#pragma comment(lib, "psapi.lib")#pragma comment(lib, "Dbghelp.lib")#pragma comment(lib, "DllForLanjie.lib")extern "C" __declspec(dllimport) int __stdcall Add(int a, int b);

 

 

 

 

int _tmain(int argc, _TCHAR* argv[]){
   //本例中擷取ExitProess地址為NULL,用depends查看該exe的使用kernel32.dll的匯入函數中沒有ExitProcess,
//可能是因為這個原因所以地址為NULL
//PROC pfnCurrent = (PROC)GetProcAddress(GetModuleHandle(_T("kernel32.dll")), "ExitProcess"); //PROC pfnCurrent = (PROC)GetProcAddress(GetModuleHandle(_T("kernel32.dll")), "Sleep"); cout<<"攔截之前 調用Add函數 計算Add(10, 20)"<<endl; cout<<"Add(10, 20) = "<<Add(10, 20)<<endl<<endl; PROC pfnCurrent = (PROC)GetProcAddress(GetModuleHandle(_T("DllForLanjie.dll")), "[email protected]"); if(NULL == pfnCurrent) return -1; TCHAR chImageName[MAX_PATH + 1] = {0}; ZeroMemory(chImageName, sizeof(chImageName)); DWORD dwRet = GetProcessImageFileName(GetCurrentProcess(), chImageName, _countof(chImageName)); if(0 == dwRet) { cerr<<"some error"<<endl; return -2; } cout<<endl<<"開始 將當前exe的匯入段中的模組DllForLanjie.dll中的函數Add的地址替換為show函數"<<endl; BOOL bRet = ReplaceIATEntryInOneMod("DllForLanjie.dll", pfnCurrent, show, GetModuleHandle(NULL)); if(FALSE == bRet) { cout<<"替換失敗 退出!"<<endl; return -3; } cout<<"替換成功 繼續!"<<endl; cout<<endl<<endl<<"下面調用三次Add函數 計算100 和 200 的和"<<endl; for (int i = 0; i < 3; ++ i) { Add(100, 200); } cout<<endl<<endl; return 0;}

 

 

 

執行結果

執行過程中,VS2005一直報運行時異常

 

每次點擊【忽略】才能繼續,為何?

該執行個體已經成功的攔截了DllForLanjie.dll中的API Add函數,替換為了exe中的show函數。

 

 

 

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.