曾经,调试时跟进HeapAlloc,结果发现直接进入到ntdll的RtlAllocateHeap中,感到很有趣,就使用Dependency Walker查看kernel32.dll的导出函数,结果发现HeapAlloc的地址直接显示的就是NTDLL.RtlAllocateHeap。于是反汇编查看kernel32.dll文件,发现本以为是汇编代码的HeapAlloc的函数体就是字符串NTDLL.RtlAllocateHeap。

想想以前也曾经自己实现过GetProcAddress,就是直接从导出表获取地址返回而已。照这样来看这样实现肯定是不完善的。这到底是如何设计的?查阅了一下《Windows PE权威指南》,在导出表一章没有找到相关说明。又看了一下微软的PE COFF格式文档,也没有找到相关信息。于是决定自己来研究一下。

先仔细的看了一下导出表相关各结构体的定义,没有觉得有哪个字段标明某个函数是真实的代码还是重定向字符串。就自己来分析PE文件,在分析文件时注意到PE文件中所有重定向字符串和IMAGE_EXPORT_DIRECTORY结构的位置布局,感觉这些字符串应该是位于数据目录IMAGE_DIRECTORY_ENTRY_EXPORT包括的地址范围内,也就是说如果导出函数地址位于此范围,就是重定向函数,因为数据目录IMAGE_DIRECTORY_ENTRY_EXPORT不应该包含任何可执行代码的。

于是照此思路编写代码测试了一下,结果与猜想一致。但是这毕竟只是推测,还没有找到官方证实。想到对导出函数重定向的支持代码Ldr里肯定会有,就从LdrGetProcedureAddress跟到LdrpSnapThunk去分析,最终在LdrpSnapThunk里找到了相关的代码,主要逻辑就是先从导出表中找到对应函数的地址,然后判断函数地址是否在数据目录IMAGE_DIRECTORY_ENTRY_EXPORT所指的地址范围内,如果不在则是真实地址,在此范围则需要进一步重定向。

例如,用如下代码遍历kernel32.dll中的重定向导出函数:

#include <tchar.h>
#include <stdio.h>
#include <stddef.h>
#include <Windows.h> VOID ListRedirects(HMODULE hModule)
{
if(NULL != hModule)
{
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)hModule;
PIMAGE_OPTIONAL_HEADER pOptionalHeader = (PIMAGE_OPTIONAL_HEADER)((PBYTE)hModule + pDosHeader->e_lfanew + offsetof(IMAGE_NT_HEADERS, OptionalHeader));
PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((PBYTE)hModule + pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
LPCSTR lpstrLibraryName = (LPCSTR)hModule + pExportDirectory->Name;
PDWORD aryAddressOfFunctions = (PDWORD)((PBYTE)hModule + pExportDirectory->AddressOfFunctions);
PDWORD aryAddressOfNames = (PDWORD)((PBYTE)hModule + pExportDirectory->AddressOfNames);
LPWORD aryAddressOfNameOrdinals = (LPWORD)((PBYTE)hModule + pExportDirectory->AddressOfNameOrdinals);
DWORD dwIndex = ;
while(dwIndex < pExportDirectory->NumberOfNames)
{
PCSTR pstrFunctionName = (PCSTR)hModule + aryAddressOfNames[dwIndex];
PVOID pFunctionAddress = (PBYTE)hModule + aryAddressOfFunctions[aryAddressOfNameOrdinals[dwIndex]];
if((PBYTE)pFunctionAddress > (PBYTE)pExportDirectory && (PBYTE)pFunctionAddress < (PBYTE)pExportDirectory + pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size) //LdrpSnapThunk中也是如此判断
{
printf("%s.%s -> %s\r\n", lpstrLibraryName, pstrFunctionName, (PCSTR)pFunctionAddress);
}
++dwIndex;
}
}
} int _tmain(int argc, _TCHAR* argv[])
{
HMODULE hModule = LoadLibraryW(L"kernel32.dll");
ListRedirects(hModule);
return ;
}

在VC中,使用#pragma预处理指令可以制作包含重定向函数的DLL:

#pragma comment(linker,"/export:HeapAlloc=NTDLL.RtlAllocateHeap,@2000")
#pragma comment(linker,"/export:HeapFree=NTDLL.RtlFreeHeap,@2001")
#pragma comment(linker,"/export:HeapReAlloc=NTDLL.RtlReAllocateHeap,@2002")
#pragma comment(linker,"/export:HeapSize=NTDLL.RtlSizeHeap,@2003")

@后面跟的是导出序号。

这样把一个导出函数重定向到另一个DLL中的某个函数,与通过导入表引入另一个DLL中的某个函数是不同的。重定向函数只要不被其他模块引入是不会被解析的,哪怕是重定向到一个根本不存在的DLL中或者指向某个根本不存在的函数,也不会影响当前模块的正常加载。直到这个函数真正被使用,Ldr才会真正去定位它的真实地址,因为重定向的目标函数不会出现在当前模块的导入表中。但是通过导入表引入的某个模块或者函数不存在的话,在加载时就会报错。

看了一下Windows NT 4.0的kernel32.dll,HeapAlloc的重定向已经有了,应该是从NT最初版本(手头没有)堆分配函数就已经被转移到ntdll.dll中了。估计这种重定向技术在NT开发时作为向下兼容的一种手段在PE格式设计就被设计出来了。

Ok. That's all.

DLL的导出函数重定向机制的更多相关文章

  1. DLL中导出函数的两种方式(dllexport与.def文件)

    DLL中导出函数的声明有两种方式: 一种方式是:在函数声明中加上__declspec(dllexport): 另外一种方式是:采用模块定义(.def)文件声明,(.def)文件为链接器提供了有关被链接 ...

  2. DLL声明导出函数的两种方式

    DLL中导出函数的声明有两种方式:一种为在函数声明中加上__declspec(dllexport):另外一种方式是采用模块定义(.def) 文件声明,.def文件为链接器提供了有关被链接程序的导出.属 ...

  3. (转)DLL中导出函数的两种方式(dllexport与.def文件)

    DLL中导出函数的两种方式(dllexport与.def文件)http://www.cnblogs.com/enterBeijingThreetimes/archive/2010/08/04/1792 ...

  4. 【转】DLL中导出函数的两种方式(dllexport与.def文件)

    DLL中导出函数的两种方式(dllexport与.def文件) DLL中导出函数的声明有两种方式: 一种方式是:在函数声明中加上__declspec(dllexport):另外一种方式是:采用模块定义 ...

  5. dll动态链接库导出函数方法 -- 静态导出(__declspec前缀导出)

    简介 在之前已经笔者已经写过利用.def文件进行dll函数动态导出的文章,那么今天就给大家介绍一下,如何利用**__declspec**函数前缀进行简单的静态函数导出. 要点 大家阅读过动态导出的文章 ...

  6. DLL动态链接库导出函数方法 -- 动态导出(.def文件导出)

    简介 动态链接库最大的优势在于可以提供给其他应用程序共享的资源,最小化应用程序代码的复杂度,其中一个十分重要的功能就是dll可以导出封装函数的功能.导出函数有两种主要方式,分别是静态导入和动态导入,本 ...

  7. MinGW g++.exe 编译 DLL 时,导出函数名带@的问题

    今天尝试用CodeBlocks写了一个简单的Dll,发现生成的 dll 文件导出的函数名后面都有一个 @xxx 从生成的 libDll2.def 中看到: EXPORTS DllMain@ @ Max ...

  8. 查看静态库(.lib)和动态库(.dll)的导出函数的信息 error LNK2001: 无法解析的外部符号 _Delete

    转自VC错误:http://www.vcerror.com/?p=1381 在window下查看动态库的导出函数可以用vs自带的Dependenc工具: 查看静态库的信息要用命令行来实现: 首先运行V ...

  9. C++ DLL中导出函数的声明的方法

    定义: TESTDLLEXPORT_API int fnTestDllExport(void); TESTDLLEXPORT_API int fnTestCall(void); TESTDLLEXPO ...

随机推荐

  1. Sqoop1.99.7将MySQL数据导入到HDFS中

    准备 本示例将实现从MySQL数据库中将数据导入到HDFS中 参考文档: http://sqoop.apache.org/docs/1.99.7/user/Sqoop5MinutesDemo.html ...

  2. Windows环境部署并调试pyspark(一)

    准备: windows环境说明:Python2.7 + pipspark版本:spark-1.6.1-bin-hadoop2.6 step1: 下载并解压tar包到自定义的路径.(下载链接 https ...

  3. win10 uwp 列表模板选择器

    本文主要讲ListView等列表可以根据内容不同,使用不同模板的列表模板选择器,DataTemplateSelector. 如果在 UWP 需要定义某些列的显示和其他列不同,或者某些行的显示和其他行不 ...

  4. win10 uwp json

    本文讲的是关于在uwp使用json的简单使用,json应用很多,因为我只是写简单使用,说的东西可能不对或者不符合每个人的预期.如果觉得我有讲的不对的,就多多包含,或者直接关掉这篇文章,但是请勿生气或者 ...

  5. ubuntu 13.04 64位安装32位兼容包

    未安装32位兼容包,编译出现未找到指定目录下gcc命令,则安装兼容包:sudo apt-get install ia32-libs 若提示:不能安装,与相关软件冲突,则按如下安装: 在网上找到更新源, ...

  6. 创建一个ROS工作空间(ROS Workspace)

    详细参照 http://wiki.ros.org/ROS/Tutorials/InstallingandConfiguringROSEnvironment1.mkdir -p ~/catkin_ws/ ...

  7. uva1629,Cake Slicing,记忆化搜索

    同上个题一样,代码相似度极高,或者说可以直接用一个模板吧 dp[i,j,p,q]表示一块长为j-i+1,宽为q-p+1,左上角在位置(i,j)上的蛋糕,dp[]表示当前状态下的最优值,然后对该块蛋糕枚 ...

  8. ELK系列~Nxlog日志收集加转发(解决log4日志换行导致json转换失败问题)

    本文章将会继承上一篇文章,主要讲通过工具来进行日志的收集与发送,<ELK系列~NLog.Targets.Fluentd到达如何通过tcp发到fluentd> Nxlog是一个日志收集工具, ...

  9. Pyhton编程(四)之基本数据类型-字符串详解

    一:字符串是什么? 字符串是Python最常用的一种数据类型,虽然看似简单,但能够以不同的方式来使用它们. 字符串就是一系列的字符,在Python中,用引号括起来的都是字符串,其中的引号可以是单引号, ...

  10. Linux命令用法

    1.cut http://www.cnblogs.com/dong008259/archive/2011/12/09/2282679.html 2.sed http://www.cnblogs.com ...