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拦截的例子的更多相关文章

  1. 【Windows核心编程】一个使用内存映射文件进行进程间通信的例子

    进程间通信的方式有很多种,其底层原理使用的都是内存映射文件. 本文实现了Windows核心编程第五版475页上的demo,即使用内存映射文件来在进程间通信. 进程1 按钮[Create  mappin ...

  2. Windows核心编程第二章,字符串的表示以及宽窄字符的转换

    目录 Windows核心编程,字符串的表示以及宽窄字符的转换 1.字符集 1.1.双字节字符集DBCS 1.2 Unicode字符集 1.3 UTF-8编码 1.4 UTF - 32编码. 1.5 U ...

  3. C++Windows核心编程读书笔记

    转自:http://www.makaidong.com/%E5%8D%9A%E5%AE%A2%E5%9B%AD%E6%96%87/71405.shtml "C++Windows核心编程读书笔 ...

  4. windows核心编程 DLL技术 【转】

    注:本文章转载于网络,源地址为:http://blog.csdn.net/ithzhang/article/details/7051558 本篇文章将介绍DLL显式链接的过程和模块基地址重定位及模块绑 ...

  5. 【转】《windows核心编程》读书笔记

    这篇笔记是我在读<Windows核心编程>第5版时做的记录和总结(部分章节是第4版的书),没有摘抄原句,包含了很多我个人的思考和对实现的推断,因此不少条款和Windows实际机制可能有出入 ...

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

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

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

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

  8. 《windows核心编程系列》十七谈谈dll

    DLL全称dynamic linking library.即动态链接库.广泛应用与windows及其他系统中.因此对dll的深刻了解,对计算机软件开发专业人员来说非常重要. windows中所有API ...

  9. windows核心编程 - 线程同步机制

    线程同步机制 常用的线程同步机制有很多种,主要分为用户模式和内核对象两类:其中 用户模式包括:原子操作.关键代码段 内核对象包括:时间内核对象(Event).等待定时器内核对象(WaitableTim ...

随机推荐

  1. PHP中的多态

    多态的概念一般是强类型语言来谈的,因为强类型语言它必须要声明参数类型,比如一个手电筒对象的打开方法其参数申明了只能是蓝光,就不能传其他光.但可以用父类渲染的方式使其多态,比如声明一个光的父类,让其它颜 ...

  2. 一个简单的将GUI程序的log信息输出到关联的Console窗口中(AllocConsole SetConsoleTitle WriteConsole 最后用ShowWindow(GetConsoleWindow)进行显示)

    // .h 文件 #pragma once class CConsoleDump { public: explicit CConsoleDump(LPCTSTR lpszWindowTitle = N ...

  3. Map.putAll方法——追加另一个Map对象到当前Map集合(转)

    该方法用来追加另一个Map对象到当前Map集合对象,它会把另一个Map集合对象中的所有内容添加到当前Map集合对象. 语法  putAll(Map<? extends K,? extends V ...

  4. LA 6187 - Never Wait for Weights 并查集的带权路径压缩

    只有一个地方需要注意: 设节点a的根为u,b的跟为v,则:a = u + d[a];  b = v + d[b]; 已知:b-a=w.所以v - u = d[a] - d[b] + w; 在合并两个集 ...

  5. PHP程序员最常犯的11个MySQL错误

    对于大多数web应用来说,数据库都是一个十分基础性的部分.如果你在使用PHP,那么你很可能也在使用MySQL—LAMP系列中举足轻重的一份子. 对于很多新手们来说,使用PHP可以在短短几个小时之内轻松 ...

  6. 字符设备 register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev()

    1. 字符设备结构体 内核中所有已分配的字符设备编号都记录在一个名为 chrdevs 散列表里.该散列表中的每一个元素是一个 char_device_struct 结构,它的定义如下: static ...

  7. Android权限安全(10)应用与设备绑定

    有些时候开发者想要把应用与设备绑定 如: 计费应用下载,防导出运行. app内部注册付费等等. 常用的绑定方案:

  8. C#使用sharppcap实现网络抓包-----2

    虽然网上已经有了SharpSniffer 这一个SharpSniffer还是原创的无他,唯为学习工程文件下载:SharpSniffer.rar 1.创建套接字2.绑定到本机3.设置IOControl4 ...

  9. Android线性布局(Linear Layout)

    Android线性布局(Linear Layout) LinearLayout是一个view组(view group),其包含的所有子view都以一个方向排列,垂直或是水平方向.我们能够用androi ...

  10. Akka的Actor模型及使用实例

    本文的绝大部分内容转载自rerun.me这一blog,老外写的东西就是好啊. ACTORS介绍 Anyone who has done multithreading in the past won't ...