DLL的导出函数重定向机制

曾经,调试时跟进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 = 0;
        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 0;
}

在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.

时间: 2024-08-04 18:23:33

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

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

定义: TESTDLLEXPORT_API int fnTestDllExport(void); TESTDLLEXPORT_API int fnTestCall(void); TESTDLLEXPORT_API int fnAddInt(int i,int j); TESTDLLEXPORT_API BOOL fnContact(char* a); 建立一个.def文件 LIBRARY TestDllExportEXPORTS fnContact @1fnAddInt @2fnTestDllE

关于DLL模块导出函数

当然以前我知道有一个.def文件的,里面写的都是需要导出的函数,以为与__declspec(dllexport)作用是一样的.但是今天看公司项目源码的时候才知道,它们两个导出方法是有一定的区别的,编译导出来的符号名称是略有不同的,如果供其他语言调用dephin,等,就麻烦些. references: http://blog.csdn.net/zhuxiaoyang2000/article/details/6387247 http://baike.baidu.com/view/2779203.ht

自己编写DLL并导出函数

sub.c #include<windows.h> #include"sub.h" int WINAPI DllMain(_In_ HANDLE _HDllHandle, _In_ DWORD _Reason, _In_opt_ LPVOID _Reserved) { return TRUE; } EXPORT int sub(int a,int b) { return a-b; } EXPORT int add(int a,int b) { return a+b; } s

MFC DLL 导出函数的定义方式

一直在鼓捣DLL,每天的工作都是调试一个一个的DLL,往DLL里面添加自己的代码,但是对于DLL一直不太了解啊!今天一查资料,才发现自己对于DLL编写的一些基本知识也不了解.要学习,这篇文章先总结DLL的导出函数的方法. 1. 首先说一下如何建立一个普通的DLL工程!(以VS2008为例) New Project  -->  Win32 标签 --> 填写工程名称 -->  点 OK,进入创建 Widzard  -->  Next 进入第二步 -->  Application

Dll 导出函数那些破事

经常使用VC6的Dependency查看DLL导出函数的名字,会发现有DLL导出函数的名字有时大不相同,导致不同的原因大多是和编译DLL时候指定DLL导出函数的界定符有关系. VC++支持两种语言:即C/C++,这也是造成DLL导出函数差异的根源 我们用VS2008新建个DLL工程,工程名为“TestDLL” 把默认的源文件后缀 .CPP改为.C(C文件) 输入测试代码如下: 01 int _stdcall MyFunction(int iVariant) 02 { 03 return 0; 0

C#中调用C++的dll的参数为指针类型的导出函数(包括二级指针的情况)

严格来说这篇文章算不上C++范围的,不过还是挂了点边,还是在自己的blog中记录一下吧. C++中使用指针是家常便饭了,也非常的好用,这也是我之所以喜欢C++的原因之一.但是在C#中就强调托管的概念了,指针就不用想了.本来如果就在C#的世界里面写代码,也还算舒服,但是万事万物总有联系,这不,现在公司的另外一个用C#作的项目就碰到问题了,要调用之前用C++写的一个DLL中的一些函数,很多函数的参数都是指针类型的,这下可麻烦咯,公司里做C#的都是刚起步,C++又只有我最熟悉,这项技术研究工作又光荣的

dll导出函数(摘)

目录 1.DLL中导出函数的方式有两种 2.查看dll导出函数; 3.__declspec(dllexport)和.def文件的区别. 1.DLL中导出函数的方式有两种: 方式一:在每一个函数声明前添加标识符: __declspec(dllexport) 比如:__declspec(dllexport) int add(int a, int b){return a+b;} 在这种方式下,如果调用该dll的是一个c++程序(同一个编译器的版本)是没有问题的.但是,如果调用该dll是一个其它语言的程

VC++编写DLL导出函数及其调用方法

DLL (Dynamic Link Library)动态链接库,是一个包含可由多个程序同时使用的代码和数据的库,DLL不是可执行文件,其优点主要有:1. 有助于节省内存:2. 有助于资源共享:3. 不需编译的软件系统升级:4. 支持多语言程序.当然,有的时候我们也可以将一些核心的或者不愿意公开提供的函数编写为DLL,从而起到隐藏和保护的作用. 下面结合实例详细说明在Visual Studio 2008 SP1 IDE中如何创建.编写和导出VC++ MFC DLL,以及如何调用生成的DLL.(完整

在C++中调用DLL中的函数 (3)

1.dll的优点 代码复用是提高软件开发效率的重要途径.一般而言,只要某部分代码具有通用性,就可将它构造成相对独立的功能模块并在之后的项目中重复使用.比较常见的例子是各种应用程序框架,ATL.MFC等,它们都以源代码的形式发布.由于这种复用是“源码级别”的,源代码完全暴露给了程序员,因而称之为“白盒复用”.“白盒复用”的缺点比较多,总结起来有4点. 暴露了源代码:多份拷贝,造成存储浪费: 容易与程序员的“普通”代码发生命名冲突: 更新功能模块比较困难,不利于问题的模块化实现: 实际上,以上4点概