• 【windows核心编程】一个API拦截的例子


     

     

    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 descriptor
    DWORD   OriginalFirstThunk;         //指向IMAGE_THUNK_DATA结构数组的指针,RVA to original unbound IAT (PIMAGE_THUNK_DATA)
    };
    DWORD   TimeDateStamp;                  // 0 if not bound,
    // -1 if bound, and real date	ime 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 forwarders
    DWORD   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;             // PDWORD
    DWORD 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")), "_Add@8"); 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函数。

     

     

     

  • 相关阅读:
    最大子序列和问题之算法优化
    数据揭秘:低学历成功逆袭概率有多少?感谢父母送我读书!
    据说这份高考卷,只有程序员能得满分!
    牛客OI赛制测试赛2
    斯特林公式
    N!的近似值_斯特林公式
    矩阵快速幂
    回文树
    回文树入门
    环和链的判断
  • 原文地址:https://www.cnblogs.com/cuish/p/3834946.html
Copyright © 2020-2023  润新知