zoukankan      html  css  js  c++  java
  • 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
    ", 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.

  • 相关阅读:
    JAVA——汉诺塔
    JAVA与MySQL连接并显示、管理表格实例
    2019沈阳网选——模拟
    CodeforcesRound#553(Div. 2)(A-D题解)
    CodeforcesRound#551(Div. 2)(A-C题解)
    CodeforcesGlobalRound2(Div.2)ABCE题解
    EducationalCodeforcesRound62(Div. 2)(A-D题解)
    博客搬家
    文本分类基本流程
    卡方检验应用-特征选择
  • 原文地址:https://www.cnblogs.com/youlin/p/dll_export_function_redirect.html
Copyright © 2011-2022 走看看