一个模块的导入段包含一组DLL。为了让模块能够运行,这些DLL是必须的。导入段还包含一个符号表。它列出了该模块从各DLL中导入的符号。当模块调用这些导入符号的时候,系统实际上会调用转换函数,获得导入函数在导入表的地址,然后再跳到相应的位置。如果我们能将导入段中相应导入函数的地址替换成自定义的函数的地址,即可实现对该函数的拦截。在自定义的函数中,我们既可以调用拦截的函数,也可以执行其他工作。

要实现修改导入段来拦截API必须对PE文件格式有很好的了解。网上关于它的资料铺天盖地,自己搜吧。此处不打算介绍。

为了修改要拦截的函数在导入段的地址,首先要获得PE文件导入段的地址。这可以调用ImageDirectoryEntryToData.函数。

  1. PVOID WINAPI ImageDirectoryEntryToData(
  2. __in   PVOID Base,
  3. __in   BOOLEAN MappedAsImage,
  4. __in   USHORT DirectoryEntry,
  5. __out  PULONG Size );

Base为要获得导入段所在模块的基地址。它一般是GetModuleHandle的返回值。

MappedAsImage,它为true时,系统将该模块以映像文件的形式映射,否则以数据文件的形式映射。

DirectoryEntry,要获得的段的索引。此函数不仅仅能够获得导入段的地址,根据此索引的不同,该函数返回对应段的地址。此处我们使用 IMAGE_DIRECTORY_ENTRY_IMPORT表示我们要获得导入段的地址。

Size返回表项的大小。注意其类型。

看代码:

  1. ULONG size;
  2. HMODULE hModule=GetModuleHandle(NULL);
  3. PIMAGE_IMPORT_DESCRIPTOR pImport=
  4. ImageDirectoryEntryToData(hModule,true,IMAGE_DIRECTORY_ENTRY_IMPORT,&size);
  5. while(pImport->FirstThunk)
  6. {
  7. int i=0;
  8. char *ModuleName=(char*)((BYTE*)hModule+pImport->Name);
  9. PIMAGE_THUNK_DATA pThunk=(PIMAGE_THUNK_DATA)
  10. ((BYTE*)hModule+pImport->FirstThunk);
  11. while(pThunk->u1.Function)
  12. {
  13. char*Func=(char*)((BYTE*)hModule+pThunk->u1.AddressOfData+2);
  14. If(Func=="MessageBoxA")
  15. {
  16. MessageBox("找到函数");
  17. }
  18. }
  19. }

IMAGE_IMPORT_DESCRIPTOR 结构是导入段的基本结构,导入段是由此类型组成的数组构成,每个DLL对应数组中的一项。数组的最后一项为NULL。每一项中有两个关键成员,指出IMAGEA_THUNK_DATA类型的数组的偏移量,这两个数组分别列出了从此DLL导入的函数名称,以及它它们在本进程地址空间的RVA。

以下是其定义:

  1. typedef struct _IMAGE_IMPORT_DESCRIPTOR {
  2. union {
  3. DWORD Characteristics;
  4. DWORD OriginalFirstThunk;// 指向一个 IMAGE_THUNK_DATA 结构数组的RVA
  5. }
  6. DWORD TimeDateStamp;// 文件生成的时间
  7. DWORD ForwarderChain;// 这个数据一般为0,可以不关心
  8. DWORD Name;   // RVA,指向DLL名字的指针,ASCII字符串
  9. DWORD FirstThunk; //指向一个 IMAGE_THUNK_DATA 结构数组的RVA,这个数据与IAT所指向的地址一致
  10. }IMAGE_IMPORT_DESCRIPTOR,*PIMAGE_IMPORT_DESCRIPTOR

OriginalFirstThunk 和FirstThunk是此结构中的关键成员。它们指出这两个IMAGE_THUNK_DATA类型的数组。

Name 是指向DLL名称的指针。

pImport指向模块的导入段,可以使用它来访问IMAGE_IMPORT_DESCRIPTOR结构的每一个成员。

注意:以上所有偏移量都是相对于模块基地址的,因此在实际使用中一定要加上模块的地址。

OriginalFirstThunk指向NAT,NAT列出了从该DLL导入的所有导入函数的名称。

FirstThunk 指向IAT,IAT列出了与NAT相对应的导入函数的地址。

看此结构定义:

  1. typedef struct _IMAGE_THUNK_DATA32 {
  2. union {
  3. PBYTE  ForwarderString;
  4. PDWORD Function;
  5. DWORD Ordinal;
  6. PIMAGE_IMPORT_BY_NAME  AddressOfData;
  7. } u1;
  8. } IMAGE_THUNK_DATA32;

上述代码中,外层循环while(pImport->FirstThunk)用于遍历所有DLL,内层循环用于遍历查找每个DLL中的导入函数名称。我们以查找MessageBoxA函数为例来做介绍。此处我们通过对NAT进行扫描,将其与我们给出的字符串类型的函数名MessageBoxA进行比较。注意要使用大小写不敏感的函数:

  1. int WINAPI lstrcmpi(
  2. __in  LPCTSTR lpString1,
  3. __in  LPCTSTR lpString2
  4. );

这是一种方法,另一种方法是比较函数地址。我们可以对IAT进行扫描,将得到的地址与我们给出的函数地址进行比较。这两种方法是经常使用的方法。

下面给出使用地址比较的方法。

  1. HMODULE hModule=GetModuleHandle(NULL);
  2. ULONG size;
  3. PIMAGE_IMPORT_DESCRIPTOR pImport=(PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData
  4. (hModule,true,IMAGE_DIRECTORY_ENTRY_IMPORT,&size);
  5. UpdateData();
  6. //获得MessageBoxA的地址。
  7. PROC pfnOld=GetProcAddress(GetModuleHandle("User32.dll"),"MessageBoxA");
  8. while(pImport->FirstThunk)
  9. {
  10. char *ModuleName=(char*)((BYTE*)hModule+pImport->Name);
  11. PIMAGE_THUNK_DATA pThunk=(PIMAGE_THUNK_DATA)((BYTE*)hModule+pImport->FirstThunk);
  12. while(pThunk->u1.Function)
  13. {
  14. PROC *ppfn=(PROC*)&(pThunk->u1.Function);
  15. if(*ppfn==pfnOld)pfnOld为要查询的函数地址。
  16. {
  17. MessageBox("找到给定的函数");
  18. return ;
  19. }
  20. pThunk++;
  21. }
  22. pImport++;
  23. }

当然以上仅仅是找到相应的函数。要用我们期望的函数代替它,还需要我们自己定义函数。

此处有一点要特别注意,当我们对某函数进行拦截时,要替换的函数必须与被替换的函数的原型完全相同。

比如此时我们要对MessageBoxA进行拦截,我们定义自己的函数的原型与MessageBoxA原型是完全一致的:

  1. int WINAPI MyMessageBoxA(HWND hWnd,LPSTR  str,LPSTR caption ,UINT type)
  2. {
  3. //定义函数的行为。
  4. }

我们可以在函数中调用被拦截的函数,也可以不对拦截的函数进行调用而进行其他操作。

注意:在拦截后,原来的函数地址已经被我们自定义的函数的地址覆盖,如果之后需要调用被拦截的函数,就必须在覆盖之前保存被拦截函数的地址。如果我们没有保存而直接调用,调用的只是替换后的函数。

以下代码将对MessageBox的调用进行拦截,并用我们的自定义函数MyMessageBox替代。

g_addr用于存储拦截之前MessageBox的地址

  1. Typedef int (WINAPI *PFNMESSAGEBOX(HWND,LPSTR,LPSTR,UINT);//定义函数指针。
  2. PFNMESSAGEBOX g_addr=(PFNMESSAGEBOX)MessageBoxA;//存储MessageBoxA的地址。
  3. //自定义的函数。
  4. int WINAPI MyMessage(HWND hWnd,LPSTR a,LPSTR b,UINT type)
  5. {
  6. return ((PFNMESSAGEBOX)g_addr)(hWnd,a,"替换后的函数!?",MB_YESNO);
  7. }
  8. //对 MessageBox进行拦截,用MyMessageBox进行替换。
  9. Void ReplaceOneFunc(PCSTR ModuleName,PFNMESSAGEBOX pfnOld,PFNMESSAGEBOX pfnNew)
  10. {
  11. pfnOld=(PFNMESSAGEBOX)MessageBoxA;
  12. pfnNew=(PFNMESSAGEBOX)MyMessageBox;
  13. HMODULE hModule=GetModuleHandle(NULL);
  14. ULONG size;
  15. PIMAGE_IMPORT_DESCRIPTOR pImport=(PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData
  16. (hModule,true,IMAGE_DIRECTORY_ENTRY_IMPORT,&size);
  17. UpdateData();
  18. while(pImport->FirstThunk)
  19. {
  20. char *ModuleName=(char*)((BYTE*)hModule+pImport->Name);
  21. PIMAGE_THUNK_DATA pThunk=(PIMAGE_THUNK_DATA)((BYTE*)hModule+pImport->FirstThunk);
  22. while(pThunk->u1.Function)
  23. {
  24. PFNMESSAGEBOX *ppfn=(PFNMESSAGEBOX*)&(pThunk->u1.Function);
  25. if(*ppfn==pfnOld)
  26. {
  27. MessageBox("找到函数!恭喜你!");
  28. SIZE_T num;
  29. WriteProcessMemory(GetCurrentProcess(),ppfn,&pfnNew,4,&num);
  30. MessageBox(NULL,"现在调用的是替换后的API!","",MB_OK);
  31. ((PFNMESSAGEBOX)g_addr)(NULL,"哈哈,现在终于轮到你了","",MB_OK);
  32. return ;
  33. }
  34. pThunk++;
  35. }
  36. pImport++;
  37. }
  38. MessageBox("没有找到该函数!");
  39. }

WriteProcessMemory(GetCurrentProcess(),ppfn,&pfnNew,4,&num);

此函数用于修改内存。实现用MyMessagebox的地址替换MessageBox的地址。关于此函数的功能请参考MSDN。

((PFNMESSAGEBOX)g_addr)(NULL,"哈哈,现在终于轮到你了","",MB_OK);

此句用于调用在拦截之前MessageBox的地址。此处的调用是调用的MessageBox函数,而不是MyMessageBox函数。

至此关于修改导入段拦截API介绍完毕。

《windows核心编程系列》二十二谈谈修改导入段拦截API。的更多相关文章

  1. 《windows核心编程系列》十九谈谈使用远程线程来注入DLL。

    windows内的各个进程有各自的地址空间.它们相互独立互不干扰保证了系统的安全性.但是windows也为调试器或是其他工具设计了一些函数,这些函数可以让一个进程对另一个进程进行操作.虽然他们是为调试 ...

  2. 《windows核心编程系列》十八谈谈windows钩子

    windows应用程序是基于消息驱动的.各种应用程序对各种消息作出响应从而实现各种功能. windows钩子是windows消息处理机制的一个监视点,通过安装钩子能够达到监视指定窗体某种类型的消息的功 ...

  3. 《Windows核心编程系列》十四谈谈默认堆和自定义堆

    堆 前面我们说过堆非常适合分配大量的小型数据.使用堆可以让程序员专心解决手头的问题,而不必理会分配粒度和页面边界之类的事情.因此堆是管理链表和数的最佳方式.但是堆进行内存分配和释放时的速度比其他方式都 ...

  4. 《windows核心编程系列》十六谈谈内存映射文件

    内存映射文件允许开发人员预订一块地址空间并为该区域调拨物理存储器,与虚拟内存不同的是,内存映射文件的物理存储器来自磁盘中的文件,而非系统的页交换文件.将文件映射到内存中后,我们就可以在内存中操作他们了 ...

  5. 《Windows核心编程系列》十二谈谈Windows内存体系结构

    Windows内存体系结构 理解Windows内存体系结构是每一个励志成为优秀的Windows程序员所必须的. 进程虚拟地址空间 每个进程都有自己的虚拟地址空间.对于32位操作系统来说,它的地址空间是 ...

  6. 《windows核心编程系列》十五谈谈windows线程栈

    谈谈windows线程栈. 当系统创建线程时会为线程预订一块地址空间区域,注意仅仅是预订.默认情况下预定的这块区域的大小是1MB,虽然预订这么多,但是系统并不会给全部区域调拨物理存储器.默认情况下,仅 ...

  7. 《Windows核心编程系列》二十谈谈DLL高级技术

    本篇文章将介绍DLL显式链接的过程和模块基地址重定位及模块绑定的技术. 第一种将DLL映射到进程地址空间的方式是直接在源代码中引用DLL中所包含的函数或是变量,DLL在程序运行后由加载程序隐式的载入, ...

  8. 《windows核心编程系列》二谈谈ANSI和Unicode字符集 .

    http://blog.csdn.net/ithzhang/article/details/7916732转载请注明出处!! 第二章:字符和字符串处理 使用vc编程时项目-->属性-->常 ...

  9. 《windows核心编程系列》二十一谈谈基址重定位和模块绑定

    每个DLL和可执行文件都有一个首选基地址.它表示该模块被映射到进程地址空间时最佳的内存地址.在构建可执行文件时,默认情况下链接器会将它的首选基地址设为0x400000.对于DLL来说,链接器会将它的首 ...

随机推荐

  1. Spring MVC 异步处理请求,提高程序性能

    原文:http://blog.csdn.net/he90227/article/details/52262163 什么是异步模式 如何在Spring MVC中使用异步提高性能? 一个普通 Servle ...

  2. linux的shell的until循环举例说明

    执行脚本: sh login.sh user,其中user为第一个参数 如下所示,如果用户‘user’登录,'who | grep "$1"'为true,until循环结束,程序继 ...

  3. 常见Python运行时错误

    1)忘记在 if , elif , else , for , while , class ,def 声明末尾添加 :(导致 “SyntaxError :invalid syntax”) 该错误将发生在 ...

  4. VC++ 2010编译错误 fatal error C1189 error This file requires _WIN32_WINNT to be #defined at least

    打开你的C++工程,找到里面的stdafx.h文件,然后把下面的红色内容替换成绿色的 参考:http://blog.csdn.net/dongliqiang2006/article/details/5 ...

  5. 【转载】How browsers work--Behind the scenes of modern web browsers (前端必读)

    浏览器可以被认为是使用最广泛的软件,本文将介绍浏览器的工 作原理,我们将看到,从你在地址栏输入google.com到你看到google主页过程中都发生了什么. 将讨论的浏览器 今天,有五种主流浏览器- ...

  6. LINQ体验(1)——Visual Studio 2008新特性

    一.写本系列的目的 我平时利用课余零碎时间来学习ASP.NET3.5.LINQ.Silverlight.ASP.NET 3.5 Extensions等新东西,通过笔记形式来记录自己所学的历 程.也给大 ...

  7. Android lowmemorykiller

    drivers/staging/android/lowmemorykiller.c lowmemorykiller 在系统空闲内存不足时, 根据一定机制选择某个进程, 然后杀死它. 1. regist ...

  8. Python爬虫开发【第1篇】【Scrapy框架】

    Scrapy 框架介绍 Scrapy是用纯Python实现一个为了爬取网站数据.提取结构性数据而编写的应用框架. Srapy框架,用户只需要定制开发几个模块就可以轻松的实现一个爬虫,用来抓取网页内容以 ...

  9. [故障处理]西部数据wd elements xp 无法识别

    百度后,看到如下帖子,供需要的朋友参考,黑体字为本人修改添加: http://blog.sina.com.cn/s/blog_539747670102w62w.html 经咨询WD厂商(厂商电话800 ...

  10. YTU 2573: 连续奇数和

    2573: 连续奇数和 时间限制: 1 Sec  内存限制: 128 MB 提交: 63  解决: 37 题目描述 小明看到一本书上写着:任何数字的立方都可以表示为连续奇数的和. 比如: 2^3 = ...