【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\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 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()
- {
- 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) == )
- {
- //获取调用程序中的该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 = ;
- //则改变页面的保护属性为 【写时复制】
- 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 ;
- }
示意怎么去拦截
头文件 和 库
- #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(, )<<endl<<endl;
- PROC pfnCurrent = (PROC)GetProcAddress(GetModuleHandle(_T("DllForLanjie.dll")), "_Add@8");
- if(NULL == pfnCurrent) return -;
- TCHAR chImageName[MAX_PATH + ] = {};
- ZeroMemory(chImageName, sizeof(chImageName));
- DWORD dwRet = GetProcessImageFileName(GetCurrentProcess(), chImageName, _countof(chImageName));
- if( == dwRet)
- {
- cerr<<"some error"<<endl;
- return -;
- }
- cout<<endl<<"开始 将当前exe的导入段中的模块DllForLanjie.dll中的函数Add的地址替换为show函数"<<endl;
- BOOL bRet = ReplaceIATEntryInOneMod("DllForLanjie.dll", pfnCurrent, show, GetModuleHandle(NULL));
- if(FALSE == bRet)
- {
- cout<<"替换失败 退出!"<<endl;
- return -;
- }
- cout<<"替换成功 继续!"<<endl;
- cout<<endl<<endl<<"下面调用三次Add函数 计算100 和 200 的和"<<endl;
- for (int i = ; i < ; ++ i)
- {
- Add(, );
- }
- cout<<endl<<endl;
- return ;
- }
执行结果
执行过程中,VS2005一直报运行时异常
每次点击【忽略】才能继续,为何?
该实例已经成功的拦截了DllForLanjie.dll中的API Add函数,替换为了exe中的show函数。
【windows核心编程】一个API拦截的例子的更多相关文章
- 【Windows核心编程】一个使用内存映射文件进行进程间通信的例子
进程间通信的方式有很多种,其底层原理使用的都是内存映射文件. 本文实现了Windows核心编程第五版475页上的demo,即使用内存映射文件来在进程间通信. 进程1 按钮[Create mappin ...
- Windows核心编程第二章,字符串的表示以及宽窄字符的转换
目录 Windows核心编程,字符串的表示以及宽窄字符的转换 1.字符集 1.1.双字节字符集DBCS 1.2 Unicode字符集 1.3 UTF-8编码 1.4 UTF - 32编码. 1.5 U ...
- C++Windows核心编程读书笔记
转自:http://www.makaidong.com/%E5%8D%9A%E5%AE%A2%E5%9B%AD%E6%96%87/71405.shtml "C++Windows核心编程读书笔 ...
- windows核心编程 DLL技术 【转】
注:本文章转载于网络,源地址为:http://blog.csdn.net/ithzhang/article/details/7051558 本篇文章将介绍DLL显式链接的过程和模块基地址重定位及模块绑 ...
- 【转】《windows核心编程》读书笔记
这篇笔记是我在读<Windows核心编程>第5版时做的记录和总结(部分章节是第4版的书),没有摘抄原句,包含了很多我个人的思考和对实现的推断,因此不少条款和Windows实际机制可能有出入 ...
- 《Windows核心编程系列》二十谈谈DLL高级技术
本篇文章将介绍DLL显式链接的过程和模块基地址重定位及模块绑定的技术. 第一种将DLL映射到进程地址空间的方式是直接在源代码中引用DLL中所包含的函数或是变量,DLL在程序运行后由加载程序隐式的载入, ...
- 《windows核心编程系列》十九谈谈使用远程线程来注入DLL。
windows内的各个进程有各自的地址空间.它们相互独立互不干扰保证了系统的安全性.但是windows也为调试器或是其他工具设计了一些函数,这些函数可以让一个进程对另一个进程进行操作.虽然他们是为调试 ...
- 《windows核心编程系列》十七谈谈dll
DLL全称dynamic linking library.即动态链接库.广泛应用与windows及其他系统中.因此对dll的深刻了解,对计算机软件开发专业人员来说非常重要. windows中所有API ...
- windows核心编程 - 线程同步机制
线程同步机制 常用的线程同步机制有很多种,主要分为用户模式和内核对象两类:其中 用户模式包括:原子操作.关键代码段 内核对象包括:时间内核对象(Event).等待定时器内核对象(WaitableTim ...
随机推荐
- 套题T1
间隙妖怪(gap.cpp/c/pas) 题目描述: 八云紫是幻想乡的间隙妖怪.她喜欢和八云橙玩一个叫做翻转的游戏.具体规则如下,八云紫对一个长度为N字符串做M次翻转操作,每次操作给定一个X,八云紫将X ...
- lintcode 中等题: reverse linked list II 翻转链表II
题目 翻转链表 II 翻转链表中第m个节点到第n个节点的部分 样例 给出链表1->2->3->4->5->null, m = 2 和n = 4,返回1->4-> ...
- Codeforces D546:Soldier and Number Game
题目链接 输入t对数 a, b 求(b,a]内的每个数拆成素因子的个数和 这里每个数都可以写成素数的乘积,可以写成几个素数的和就有几个素因子,这里求的是(b,a]内的素因子和 思路: 素数的素因子个数 ...
- cv 论文(CNN相关)
最近发现很多以前看的论文都忘了,所以想写点东西来整理下之前的paper,paper主要是cv(computer vision)方向的. 第一篇:Gradient-based learning appl ...
- *[topcoder]TheMatrix
http://community.topcoder.com/stat?c=problem_statement&pm=13035 矩阵棋盘的题,比较典型.可以定两条column夹住,然后上下扫, ...
- Android Studio删除工程里面无用的代码和资源
如果你是一个经常开发android应用程序或者做android维护项目的人,我想说你对我谈论的这个话题,一定会感兴趣的. 因为只有做到了这两点,你的项目生成的apk包才会更小,而不是随着你的开发和维护 ...
- C++:虚函数的详解
5.4.2 虚函数详解 1.虚函数的定义 虚函数就是在基类中被关键字virtual说明,并在派生类重新定义的函数.虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问 ...
- 转Struts 权限控制
权限最核心的是业务逻辑,具体用什么技术来实现就简单得多. 通常:用户与角色建立多对多关系,角色与业务模块构成多对多关系,权限管理在后者关系中. 对权限的拦截,如果系统请求量大,可以用Struts2拦截 ...
- Android Include标签
编程的世界有的时候很微妙,有的时候就好像是在解决一个哲学问题,Android开发的时候,所有的布局,颜色,等(其实这些都可以称之为资源,Android中的资源是指非代码部分,如图片.音频.视频.字符等 ...
- JDBC学习总结(三)
1.ResultSet光标控制 在创建Statement或PreparedStatement时使用的是Connection的无参数createStatement()方法或preparedSta ...