Windows动态库加载器实现(64位版)

功能需求

在最近写的软件中想要将采集的原始数据转换成其他软件能识别的格式,由于原始数据流根据需求变化,而非固定内容,因此需要一个数据模板转换引擎来执行这一过程,大体需求如下:

  • 需要能包含一些代码应对复杂的数据,能被主程序调用,内嵌执行。
  • 模板文件头部需要带一些自描述信息,例如创建日期,支持的数据流格式...
  • 单个文件,放在一个文件夹中,方便主程序用ListView展示出来。
  • 模板主体的数据处理代码要能支持加密,主程序载入并解密后运行

思考下来主要有几种方案可以实现:
脚本引擎方案:内嵌一个Lua解释器,脚本语言实现数据解析,JS解释器也可以
虚拟机方案:内嵌ARM或RISC-V虚拟机,执行交叉编译后的可执行文件
原生动态库:使用DLL动态库,运行时载入

综合来看,原生动态库需要增加的代码少而且性能较高,遂考虑用动态链接库(DLL)来实现。由于动态库头部加了一些自描述数据,且数据处理代码是加密混淆过的,因此不能移除信息头,解密可执行代码数据,写到磁盘,然后用windows自带的LoadLibraryA执行加载动作,这样就没有加密混淆的意义了,在主程序内部自行实现动态库的加载是比较合适的方案。

网上找了一圈,自定义动态库加载器的资料很多,但都是只适用于32位windows程序加载32位DLL的加载器代码;基本的流程是正确的,只是有一些细节点需要修改,这也是写完64位加载器后才发现的,这一点连deepseek这种AI助手也不能给出正确的代码。

DLL代码示例

本篇文章将展示实现动态库加载器的整个流程,因此也将DLL的函数声明放出来,给各位参考。

  • DLL头文件
#ifndef __MAIN_H__
#define __MAIN_H__

#include <windows.h>
#include <stdint.h>

/*  To use this exported function of dll, include this header
 *  in your project.
 */
#define DLL_EXPORT __declspec(dllexport)

#ifdef __cplusplus
extern "C"
{
#endif

uint32_t DLL_EXPORT GetHeaderCount(void);

uint32_t DLL_EXPORT GetHeaderName(uint32_t pos, char *buf, uint32_t bmax);

uint32_t DLL_EXPORT GetUnitsCount(void);

uint32_t DLL_EXPORT GetUnitsName(uint32_t pos, char *buf, uint32_t bmax);

uint32_t DLL_EXPORT GetColumnCount(void);

uint32_t DLL_EXPORT GetColumnName(uint32_t pos, char *buf, uint32_t bmax);

int DLL_EXPORT DecodeLine(uint8_t *buffer, uint32_t len1, char *output, uint32_t len2);

#ifdef __cplusplus
}
#endif

#endif // __MAIN_H__

  • DLL代码实现
#include "main.h"
#include <string.h>
#include <stdio.h>

#define HEADER_COUNT    5u

// 全局常量,加载器需要重定位的数据
const char * const HeaderNames[] = {
    "aa","bb","cc","hello","qwerty"
};

DLL_EXPORT BOOL APIENTRY DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    switch (fdwReason)
    {
        case DLL_PROCESS_ATTACH:
            // attach to process
            // return FALSE to fail DLL load
            break;

        case DLL_PROCESS_DETACH:
            // detach from process
            break;

        case DLL_THREAD_ATTACH:
            // attach to thread
            break;

        case DLL_THREAD_DETACH:
            // detach from thread
            break;
    }
    return TRUE; // succesful
}

uint32_t DLL_EXPORT GetHeaderCount() {
	// 硬编码的立即数,通常放在指令的立即数空间。
	// 如果指令空间放不下,则放在本函数代码段(.text)的末尾,
	// 为间接地址寻址,不需要重定位
    return HEADER_COUNT;
}

uint32_t DLL_EXPORT GetHeaderName(uint32_t pos, char *buf, uint32_t bmax) {
    uint32_t len = 0;
    const char *ptr = NULL;
    if(pos < HEADER_COUNT) {
        ptr = HeaderNames[pos];
		// 系统库函数strlen,声明在导入表中,加载器赋值实际的函数地址
        len = strlen(ptr);
        if(len > bmax) {
            len = bmax - 1;
        }
		// 系统库函数memcpy,声明在导入表中,加载器赋值实际的函数地址
        memcpy(buf, ptr, len);
        buf[len] = '\0';
        return len;
    }
    return 0;
}

uint32_t DLL_EXPORT GetUnitsCount(void) {
    return 0;
}

uint32_t DLL_EXPORT GetUnitsName(uint32_t pos, char *buf, uint32_t bmax) {
    (void)pos;
    (void)buf;
    (void)bmax;
    return 0;
}

uint32_t DLL_EXPORT GetColumnCount(void) {
    return 0;
}

uint32_t DLL_EXPORT GetColumnName(uint32_t pos, char *buf, uint32_t bmax) {
    (void)pos;
    (void)buf;
    (void)bmax;
    return 0;
}

int DLL_EXPORT DecodeLine(uint8_t *buffer, uint32_t len1, char *output, uint32_t len2) {
	// balabala...
    return 0;
}

将编译生成的DLL文件导入DependenciesGui程序,即可看到dll包含的具体信息,对调试有很大帮助。

  • 依赖的其他DLL,例如上述代码中的strlen,memcpy函数在ucrtbase.dll中
    image
  • 导出表,提供给外部调用的函数,可以看到每个函数的名称和虚拟地址
    image
  • 导入表,详细记录了需要导入的函数名称和来源DLL名称
    image

加载器类声明

先将整个类的结构显示出来,各部分的作用见代码注释,详细的实现将在后续分步骤讲解。

#include <stdint.h>
#include <stdio.h>
#include <windows.h>

// 人口函数的函数原型
// AddressOfEntryPoint: static int __cdecl pre_c_init(void);
typedef int (WINAPI *EntryFunc)(void);
// 动态库导出函数的函数指针声明,用于将DLL导出的函数包装到C++类的成员函数中
typedef bool (WINAPI *DllMainFunc)(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved);

typedef uint32_t (WINAPI *GetHeaderCountFunc)(void);
typedef uint32_t (WINAPI *GetHeaderNameFunc)(uint32_t pos, char *buf, uint32_t bmax);
typedef uint32_t (WINAPI *GetUnitsCountFunc)(void);
typedef uint32_t (WINAPI *GetUnitsNameFunc)(uint32_t pos, char *buf, uint32_t bmax);
typedef uint32_t (WINAPI *GetColumnCountFunc)(void);
typedef uint32_t (WINAPI *GetColumnNameFunc)(uint32_t pos, char *buf, uint32_t bmax);
typedef int (WINAPI *DecodeLineFunc)(uint8_t *buffer, uint32_t len1, char *output, uint32_t len2);

#define EXTRA_HANDLER_MAX    16u

class TemplateLoader {
private:
	// 存储DLL文件的内存,使用malloc分片,无特殊属性要求
    void *libmem;
	// DLL展开并映射到exemem,使用VirtualAlloc,指定属性可读可写可执行
	// exemem占用空间比libmen大
    void *exemem;
	// 记录导入表使用的当前进程未载入的DLL句柄,以便使用结束时卸载
	// 导入表导入的函数所属的DLL有两种情况,
	// 1:已经被当前进程加载过了,直接使用GetModuleHandleA获取DLL句柄即可。
	// 2:没被加载过,使用LoadLibraryA加载一次,并在当前DLL使用完毕后释放之前加载的依赖动态库。
    size_t extraHandler[EXTRA_HANDLER_MAX];
    size_t extraHandlerSize;
    int loaded;
    
    DllMainFunc fpDllMain;
    GetHeaderCountFunc fpGetHeaderCount;
    GetHeaderNameFunc fpGetHeaderName;
    GetUnitsCountFunc fpGetUnitsCount;
    GetUnitsNameFunc fpGetUnitsName;
    GetColumnCountFunc fpGetColumnCount;
    GetColumnNameFunc fpGetColumnName;
    DecodeLineFunc fpDecodeLine;
    
    void memmapFile(IMAGE_DOS_HEADER *doshdr, IMAGE_NT_HEADERS *nthdr);
    
    void relocationTable(IMAGE_DOS_HEADER *doshdr, IMAGE_NT_HEADERS *nthdr);
    bool importTable(IMAGE_DOS_HEADER *doshdr, IMAGE_NT_HEADERS *nthdr);
    uint32_t rva2foa(IMAGE_DOS_HEADER *doshdr, IMAGE_NT_HEADERS *nthdr, uint32_t rva);
    void matchFuncPointer(const char *pfname, ULONGLONG base, ULONGLONG offset);
    bool callDllMain(IMAGE_DOS_HEADER *doshdr, IMAGE_NT_HEADERS *nthdr);
public:
    TemplateLoader();
    ~TemplateLoader();
    
    bool isLibraryLoad();
    bool loadLibrary(const char *path);
    void unloadLibrary();
    
    // library's export function wrapper
    uint32_t getHeaderCount();
    uint32_t getHeaderName(uint32_t pos, char *buf, uint32_t bmax);
    uint32_t getUnitsCount(void);
    uint32_t getUnitsName(uint32_t pos, char *buf, uint32_t bmax);
    uint32_t getColumnCount(void);
    uint32_t getColumnName(uint32_t pos, char *buf, uint32_t bmax);
    int decodeLine(uint8_t *buffer, uint32_t len1, char *output, uint32_t len2);
};

载入DLL文件

将DLL文件读入内存不是必须的,因为后续还会在当前进程空间为该DLL开辟一块RWX属性的内存,这块内存才是DLL执行的真正内存;但DLL文件预先读入内存也有好处,随机访问各个信息段直接使用地址加减配合强制指针转换,对于一些需要遍历的操作能简化流程。因此对于文件长度不大的DLL推荐预先读入内存,本篇的实现方法也是将DLL文件预载入内存。

FILE *fplib = nullptr;
uint32_t fileSize, metaSize, libSize;
size_t readin;
do {
	fseek(fplib, 0, SEEK_END);
	fileSize = (uint32_t)(ftell(fplib) & 0xFFFFFFFF);
	if(fileSize < sizeof(IMAGE_DOS_HEADER)) {
		break;
	}
	LOG_I("file size:%d\n", fileSize);
	// 头部信息验证代码已经移除,载入的是原始DLL文件
	metaSize = 0;
	LOG_I("meta size:%d\n", metaSize);

	libSize = (fileSize - metaSize);
	libmem = malloc(sizeof(uint8_t) * libSize);
	if(libmem == nullptr) {
		break;
	}
	LOG_I("lib size:%d\n", libSize);
	fseek(fplib, metaSize, SEEK_SET);
	readin = fread(libmem, 1, libSize, fplib);
	if((readin != libSize) ) {
		free(libmem); libmem = nullptr;
		break;
	}
	// 数据解密已移除,载入的是原始DLL文件
} while(0);

验证DLL头部数据

DLL头部数据由DOS头开始,该结构体下的大部分字段只适用于16位DOS程序,对于32/64位程序没什么用,DOS头只需要关注e_magice_lfanew这两个成员即可,由e_lfanew成员可以索引到我们需要NT头。
NT头内部的SizeOfImage表示DLL展开后的大小,申请可执行内存时按这个大小来分配;
ImageBase表示链接时的基址,修改重定位表时需要根据链接基址和加载的实际地址计算偏移差值;
AddressOfEntryPoint是C运行时库的入口点,主要对全局有值的变量赋初值,没值的变量清零,如果是C++程序,还要对于全局对象的执行它的构造函数,执行完这些操作后,再调用DLL程序的入口函数DllMain
在加载DLL到可执行内存后,以下几种情况可以忽略执行AddressOfEntryPoint函数:
1:只有功能函数或只存在全局常量,无需初始化过程。
2:全局变量初值都是0或全局类对象构造函数无功能,这是由于分配可执行内存的系统调用VirtualAlloc已经将内存清零。
3:全局变量初始化或全局类对象初始化由用户的导出函数负责。
AddressOfEntryPoint的函数实现可以参考https://github.com/cansou/msvcrt/blob/master/src/crtdll.c

PIMAGE_DOS_HEADER pDosHeader;
PIMAGE_NT_HEADERS pNtHeader;
//获取Dos头
pDosHeader = (PIMAGE_DOS_HEADER)libmem;
//判断是否是有效的PE文件 0x5A4D
if(pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
	break;
}
LOG_I("dos magic:0x%04x\n", pDosHeader->e_magic);
//获取NT头
pNtHeader = (PIMAGE_NT_HEADERS)((ULONGLONG)pDosHeader + pDosHeader->e_lfanew);
//判断是否是有效的PE文件 0x00004550
if(pNtHeader->Signature != IMAGE_NT_SIGNATURE) {
	break;
}
LOG_I("nt sign:0x%08x\n", pNtHeader->Signature);
LOG_I("image size:%d\n", pNtHeader->OptionalHeader.SizeOfImage);
LOG_I("image base:0x%I64x\n", pNtHeader->OptionalHeader.ImageBase);
LOG_I("entry point:0x%08x\n", pNtHeader->OptionalHeader.AddressOfEntryPoint);

分配进程空间

在NT头中,通过pNtHeader->OptionalHeader.SizeOfImage字段可知该DLL需要的可执行内存大小,DLL各个段(.text, .rodata, .data ...)之间对齐大小由pNtHeader->OptionalHeader.SectionAlignment指定,通常是4KB,是一个页面的大小,使用VirtualAlloc为其分片可读可写可执行的内存。
image

// 在进程中申请一个可读可写可执行的内存块, 此函数分配的内存会自动初始化为零。
exemem = VirtualAlloc(NULL, pNtHeader->OptionalHeader.SizeOfImage, (MEM_COMMIT | MEM_RESERVE), PAGE_EXECUTE_READWRITE);
if(exemem == NULL) {
	break;
}
LOG_I("virtual alloc success:0x%I64x\n", (ULONGLONG)exemem);

解压并载入DLL

DLL文件在存储时,各个section之间的对齐大小由pNtHeader->OptionalHeader.FileAlignment指定,通常是512字节,和磁盘的扇区大小一致;实际载入到可执行内存时,需要按照页面大小4KB进行对齐放置,占用空间和原始文件尺寸相比大了许多。
image

// @brief 将内存DLL数据(libmem)按映像对齐大小(SectionAlignment)映射到exemem内存中
// @param[in] doshdr Dos头,来自DLL文件基址
// @param[in] nthdr NT头,来自DLL文件基址
void TemplateLoader::memmapFile(IMAGE_DOS_HEADER *doshdr, IMAGE_NT_HEADERS *nthdr) {
    LPVOID lpSrcMem;
    LPVOID lpDestMem;
    DWORD dwSizeOfRawData;
    
    (void)doshdr;
    //获取所有头部+区段表的大小
    DWORD dwSizeOfHeaders = nthdr->OptionalHeader.SizeOfHeaders;
    //获取区段数量
    WORD wNumberOfSections = nthdr->FileHeader.NumberOfSections;
    //获取区段表数组的首元素
    PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(nthdr);
    //将头部(包括区段表)拷贝到内存
    RtlCopyMemory(this->exemem, this->libmem, dwSizeOfHeaders);

    //循环加载所有区段
    for (WORD i = 0; i < wNumberOfSections; i++) {
        //过滤掉无效区段
        if((0 == pSectionHeader->VirtualAddress) || (0 == pSectionHeader->SizeOfRawData)) {
            pSectionHeader++;
            continue;
        }
        //获取区段在文件中的位置,PointerToRawData指明段数据在文件中的偏移量,加上文件在内存的基地址libmem就是实际的数据位置
        lpSrcMem = (LPVOID)((ULONGLONG)this->libmem + pSectionHeader->PointerToRawData);
        //获取区段映射到内存中的位置,这里的VirtualAddress是相对ImageBase的偏移量,由于ImageBase是分配出来的exemem,因此加上exemem就是可执行内存的目标位置
        lpDestMem = (LPVOID)((ULONGLONG)this->exemem + pSectionHeader->VirtualAddress);
        //获取区段在文件中的大小
        dwSizeOfRawData = pSectionHeader->SizeOfRawData;
        //将区段数据拷贝到内存中
        RtlCopyMemory(lpDestMem, lpSrcMem, dwSizeOfRawData);
        //获取下一个区段头(属性)
        pSectionHeader++;
    }
}

修改重定位表

对于DLL程序函数中对自身全局变量/常量读写操作,在生成代码时是按照链接地址作为基址加上偏移量得到的地址(绝对地址)进行访问,而加载后,可执行内存地址不一定和链接时基址相同,这里就需要对重定位表的中涉及的地址值进行修改,实质上是一个链接地址和加载地址的区别。

// @brief 修改PE文件的重定位表信息
// @param[in] doshdr Dos头,来自映像对齐后的内存基址
// @param[in] nthdr NT头,来自映像对齐后的内存基址
void TemplateLoader::relocationTable(IMAGE_DOS_HEADER *doshdr, IMAGE_NT_HEADERS *nthdr) {
    DWORD nNumberOfReloc;
    WORD* pRelocData;
    WORD relocFlag;
    ULONGLONG* pAddress;
    
    //获取重定位表的地址
    PIMAGE_BASE_RELOCATION pReloc = (PIMAGE_BASE_RELOCATION)((ULONGLONG)doshdr
        + nthdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);

    //注意重定位表的位置可能和硬盘文件中的偏移地址不同,应该使用加载后的地址

    //判断是否有重定位表
    if((PVOID)pReloc == (PVOID)doshdr) {
        LOG_I("no reloc table\n");
        return; //没有重定位表
    }

    //开始修复重定位
    while ((pReloc->VirtualAddress != 0) && (pReloc->SizeOfBlock != 0)) {
        // SizeOfBlock(重定位表大小) = (VirtualAddress(4B) + SizeOfBlock(4B) + 各偏移的总大小), 每个偏移数据占据2个字节
        nNumberOfReloc = (pReloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
        //获取IMAGE_BASE_RELOCATION结构后面的数据的地址
        pRelocData = (WORD *)((ULONGLONG)pReloc + sizeof(IMAGE_BASE_RELOCATION));
        LOG_I("num reloc:%d\n", nNumberOfReloc);
        
        for (DWORD i = 0; i < nNumberOfReloc; i++) {
            // 每个WORD由两部分组成,高4位指出了重定位的类型,WINNT.H中的一系列IMAGE_REL_BASED_xxx定义了重定位类型的取值。
            // 32位DLL重定位属性值是IMAGE_REL_BASED_HIGHLOW
            // 64位DLL重定位属性值是IMAGE_REL_BASED_DIR64
            // 低12位是相对于IMAGE_BASE_RELOCATION中第一个元素VirtualAddress描述位置的偏移
            relocFlag = (WORD)((pRelocData[i] >> 12u) & 0xFu);
            LOG_I("relocFlag:0x%x\n", relocFlag);
            if(relocFlag == IMAGE_REL_BASED_DIR64) {
                // 获取需要被修正数据的地址 = 加载后的内存地址 + 偏移数据的基址 + 数据项偏移
                // 64位DLL中,pAddress需要是ULONGLONG指针
                pAddress = (ULONGLONG *)((ULONGLONG)doshdr + pReloc->VirtualAddress + (pRelocData[i] & 0x0FFF));
                // 判断加载后的实际内存地址和DLL库链接时的原始基址,差值为正:相加,差值为负:相减
                // 地址是无符号数(ULONGLONG),只能大数值减去小数值,避免出现下溢导致计算出错
                if((ULONGLONG)doshdr > nthdr->OptionalHeader.ImageBase) {
                    *pAddress += ((ULONGLONG)doshdr - nthdr->OptionalHeader.ImageBase);
                }else {
                    *pAddress -= (nthdr->OptionalHeader.ImageBase - (ULONGLONG)doshdr);
                }
                LOG_I("addr:0x%I64x,value:0x%I64x\n", (ULONGLONG)pAddress, *pAddress);
            }
        }
        //下一个重定位块
        pReloc = (PIMAGE_BASE_RELOCATION)((ULONGLONG)pReloc + pReloc->SizeOfBlock);
    }
}

填写导入表

当前DLL使用的一些函数可能是外部其他DLL提供的,需要在这里补齐函数地址。需要注意的是这些被依赖的DLL如果没有被系统或当前进程加载过,手动加载后需要记录下返回的句柄,在当前DLL使用完毕后一起释放。

// @brief 填写PE文件的导入表信息
// @param[in] doshdr Dos头,来自映像对齐后的内存基址
// @param[in] nthdr NT头,来自映像对齐后的内存基址
bool TemplateLoader::importTable(IMAGE_DOS_HEADER *doshdr, IMAGE_NT_HEADERS *nthdr) {
    char* pDllName;
    HMODULE hDll;
    PIMAGE_THUNK_DATA pIat;
    FARPROC pFuncAddress;
    PIMAGE_IMPORT_BY_NAME pImportByName ;
    
    //获取导入表地址
    PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)((ULONGLONG)doshdr
        + nthdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
        
    this->extraHandlerSize = 0;
    
    //循环遍历导入表
    while (pImport->Name) {
        //获取导入表中的Dll名称
        pDllName = (char *)((ULONGLONG)doshdr + pImport->Name);
        //检索Dll模块获取模块句柄
        hDll = GetModuleHandleA(pDllName);
        LOG_I("Get lib:%s\n", pDllName);
        if(NULL == hDll) {
            if(extraHandlerSize >= EXTRA_HANDLER_MAX) {
                return false;
            }
            //加载Dll模块获取模块句柄
            hDll = LoadLibraryA(pDllName);
            LOG_I("Load lib:%s\n", pDllName);
            if(NULL == hDll) {
                return false;
            }
            extraHandler[extraHandlerSize] = (ULONGLONG)hDll;
            extraHandlerSize++;
        }
        
        //获取IAT
        pIat = (PIMAGE_THUNK_DATA)((ULONGLONG)doshdr + pImport->FirstThunk);

        //遍历IAT中函数
        while (pIat->u1.Ordinal) {
            //判断导入的函数是名称导入还是序号导入
            //判断最高位是否为1,如果是1那么是序号导入
            if IMAGE_SNAP_BY_ORDINAL64(pIat->u1.Ordinal) {
                //获取函数地址
                pFuncAddress = GetProcAddress(hDll, (LPCSTR)IMAGE_ORDINAL64(pIat->u1.Ordinal));
            }else {
                //名称导入,获取IMAGE_IMPORT_BY_NAME结构
                pImportByName = (PIMAGE_IMPORT_BY_NAME)((ULONGLONG)doshdr + pIat->u1.AddressOfData);
                //获取函数地址
                pFuncAddress = GetProcAddress(hDll, pImportByName->Name);
            }
            //将函数地址填入到IAT中
            pIat->u1.Function = (ULONGLONG)pFuncAddress;
            pIat++;
        }
        
        pImport++;
    }
    return true;
}

获取导出函数地址

导出表的几个表头AddressOfFunctions, AddressOfNames, ordinalTable
涉及到相对虚拟偏移和文件偏移的转换,本质上就是因为DLL文件存储对齐大小和DLL展开对齐大小不一样,因此需要拿着RVA到DLL文件的每个节中遍历查找。
详细可参考这篇博客:https://www.cnblogs.com/iBinary/p/9733703.html
导出表内的函数地址是相对链接基址的偏移量,可调用的函数地址是加载地址+函数偏移量,调试时打印出函数地址,看最后几个数字是否相符
image
image

DWORD virtualAddr;
PIMAGE_EXPORT_DIRECTORY pExportDir;
DWORD *addrTable;
DWORD *nameTable;
WORD *ordinalTable;
const char *pfname;

// 从数据目录表的导出表项找到rva
virtualAddr = pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
// 导出表结构体
pExportDir = (PIMAGE_EXPORT_DIRECTORY)(rva2foa(pDosHeader, pNtHeader, virtualAddr) + fileBase);
if((pExportDir == NULL) || (pExportDir->NumberOfFunctions == 0)) {
	break;
}
LOG_I("func count:%d\n", pExportDir->NumberOfFunctions);
LOG_I("name count:%d\n", pExportDir->NumberOfNames);

// 地址表,名称表,序号表,其中序号表是WORD*
addrTable = (DWORD *)(rva2foa(pDosHeader, pNtHeader, pExportDir->AddressOfFunctions) + fileBase);
nameTable = (DWORD *)(rva2foa(pDosHeader, pNtHeader, pExportDir->AddressOfNames) + fileBase);
ordinalTable = (WORD *)(rva2foa(pDosHeader, pNtHeader, pExportDir->AddressOfNameOrdinals) + fileBase);

for(DWORD i = 0; i < pExportDir->NumberOfFunctions; i++) {
	// 判断是否有名字,有名字的话,下标会存在序号表中
	for(DWORD j = 0; j < pExportDir->NumberOfNames; j++) {
		// 如果有名字则执行此处
		if(i == ordinalTable[j]) {
			// 对应序号表下标的名称表内保存的是名字
			pfname = (const char *)(rva2foa(pDosHeader, pNtHeader, nameTable[j]) + fileBase);
			matchFuncPointer(pfname, (ULONGLONG)exemem, addrTable[i]);
			break;
		}
	}
}
	

调用入口函数(可选)

EntryFunc entry;
entry = (EntryFunc)((ULONGLONG)exemem + pNtHeader->OptionalHeader.AddressOfEntryPoint);
entry();
LOG_I("call entry\n");

完整代码

#include "TemplateLoader.h"

#define LOADER_DBG_ENABLE    1

#if (LOADER_DBG_ENABLE == 1)
#define LOG_I(fmt, ...)    printf(fmt, ##__VA_ARGS__)
#else
#define LOG_I(...)
#endif

TemplateLoader::TemplateLoader() {
    libmem = nullptr;
    exemem = nullptr;
    extraHandlerSize = 0;
    loaded = 0;
    fpDllMain = nullptr;
    fpGetHeaderCount = nullptr;
    fpGetHeaderName = nullptr;
    fpGetUnitsCount = nullptr;
    fpGetUnitsName = nullptr;
    fpGetColumnCount = nullptr;
    fpGetColumnName = nullptr;
    fpDecodeLine = nullptr;
}

TemplateLoader::~TemplateLoader() {
    if(libmem != nullptr) {
        free(libmem);
        libmem = nullptr;
    }
    if(exemem != nullptr) {
        VirtualFree(exemem, 0, MEM_RELEASE);
        exemem = nullptr;
    }
    for(size_t i = 0; i < extraHandlerSize; i++) {
        FreeLibrary((HMODULE)extraHandler[i]);
    }
    extraHandlerSize = 0;
}

bool TemplateLoader::isLibraryLoad() {
    return this->loaded;
}

bool TemplateLoader::loadLibrary(const char *path) {
    FILE *fplib = nullptr;
    bool success = false;
    uint32_t fileSize, metaSize, libSize;
    size_t readin;
    
    if(loaded) {
        return false;
    }
    if((path == nullptr) || (path[0] == '\0')) {
        return false;
    }
    fplib = fopen(path, "rb+");
    if(fplib == nullptr) {
        return false;
    }
    LOG_I("filename:%s\n", path);
    
    do {
        fseek(fplib, 0, SEEK_END);
        fileSize = (uint32_t)(ftell(fplib) & 0xFFFFFFFF);
        if(fileSize < sizeof(IMAGE_DOS_HEADER)) {
            break;
        }
        LOG_I("file size:%d\n", fileSize);
		
		// 头部信息代码已经移除,载入的是原始DLL文件

		metaSize = 0;
        LOG_I("meta size:%d\n", metaSize);
        
        libSize = (fileSize - metaSize);
        libmem = malloc(sizeof(uint8_t) * libSize);
        if(libmem == nullptr) {
            break;
        }
        LOG_I("lib size:%d\n", libSize);
        fseek(fplib, metaSize, SEEK_SET);
        readin = fread(libmem, 1, libSize, fplib);
        if((readin != libSize) ) {
            free(libmem); libmem = nullptr;
            break;
        }
		
		// 数据解密已移除,载入的是原始DLL文件

        success = true;
    } while(0);
    
    fclose(fplib);
    if(!success) {
        return false;
    }
    LOG_I("load dll file success\n");
    
    // clear success flag
    success = false;
    
    PIMAGE_DOS_HEADER pDosHeader;
    PIMAGE_NT_HEADERS pNtHeader;
    ULONGLONG fileBase;
    DWORD virtualAddr;
    PIMAGE_EXPORT_DIRECTORY pExportDir;
    DWORD *addrTable;
    DWORD *nameTable;
    WORD *ordinalTable;
    const char *pfname;
	EntryFunc entry;
    do {
        //获取Dos头
        pDosHeader = (PIMAGE_DOS_HEADER)libmem;
        //判断是否是有效的PE文件 0x5A4D
        if(pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
            break;
        }
        LOG_I("dos magic:0x%04x\n", pDosHeader->e_magic);
        //获取NT头
        pNtHeader = (PIMAGE_NT_HEADERS)((ULONGLONG)pDosHeader + pDosHeader->e_lfanew);
        //判断是否是有效的PE文件 0x00004550
        if(pNtHeader->Signature != IMAGE_NT_SIGNATURE) {
            break;
        }
        LOG_I("nt sign:0x%08x\n", pNtHeader->Signature);
        LOG_I("image size:%d\n", pNtHeader->OptionalHeader.SizeOfImage);
        LOG_I("image base:0x%I64x\n", pNtHeader->OptionalHeader.ImageBase);
        LOG_I("entry point:0x%08x\n", pNtHeader->OptionalHeader.AddressOfEntryPoint);
        
        //LPVOID lpAddress = (void *)0x261440000;
        // 在进程中申请一个可读可写可执行的内存块
        //exemem = VirtualAlloc(lpAddress, pNtHeader->OptionalHeader.SizeOfImage, (MEM_COMMIT | MEM_RESERVE), PAGE_EXECUTE_READWRITE);
        exemem = VirtualAlloc(NULL, pNtHeader->OptionalHeader.SizeOfImage, (MEM_COMMIT | MEM_RESERVE), PAGE_EXECUTE_READWRITE);
        if(exemem == NULL) {
            break;
        }
        LOG_I("virtual alloc success:0x%I64x\n", (ULONGLONG)exemem);
        
        // 将内存DLL数据按映像对齐大小(SectionAlignment)映射到刚刚申请的内存中
        memmapFile(pDosHeader, pNtHeader);
        LOG_I("memmap success\n");
        
        // 切换头部结构指针基址
        pDosHeader = (PIMAGE_DOS_HEADER)exemem;
        pNtHeader = (PIMAGE_NT_HEADERS)((ULONGLONG)exemem + pDosHeader->e_lfanew);
        LOG_I("reassign header pointer\n");
        
        // 修改PE文件的重定位表信息
        relocationTable(pDosHeader, pNtHeader);
        LOG_I("relocationTable ok\n");
        
        //填写PE文件的导入表信息
        success = importTable(pDosHeader, pNtHeader);
        if(!success) {
            break;
        }
        LOG_I("importTable ok\n");
        // 修改PE文件的加载基址
        fileBase = (ULONGLONG)libmem;
        pNtHeader->OptionalHeader.ImageBase = (ULONGLONG)exemem;
        LOG_I("reedit ImageBase\n");
        
        // 从数据目录表的导出表项找到rva
        virtualAddr = pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
        // 导出表结构体
        pExportDir = (PIMAGE_EXPORT_DIRECTORY)(rva2foa(pDosHeader, pNtHeader, virtualAddr) + fileBase);
        if((pExportDir == NULL) || (pExportDir->NumberOfFunctions == 0)) {
            break;
        }
        LOG_I("func count:%d\n", pExportDir->NumberOfFunctions);
        LOG_I("name count:%d\n", pExportDir->NumberOfNames);
        
        // 地址表,名称表,序号表,其中序号表是WORD*
        addrTable = (DWORD *)(rva2foa(pDosHeader, pNtHeader, pExportDir->AddressOfFunctions) + fileBase);
        nameTable = (DWORD *)(rva2foa(pDosHeader, pNtHeader, pExportDir->AddressOfNames) + fileBase);
        ordinalTable = (WORD *)(rva2foa(pDosHeader, pNtHeader, pExportDir->AddressOfNameOrdinals) + fileBase);
        
        for(DWORD i = 0; i < pExportDir->NumberOfFunctions; i++) {
            // 判断是否有名字,有名字的话,下标会存在序号表中
            for(DWORD j = 0; j < pExportDir->NumberOfNames; j++) {
                // 如果有名字则执行此处
                if(i == ordinalTable[j]) {
                    // 对应序号表下标的名称表内保存的是名字
                    pfname = (const char *)(rva2foa(pDosHeader, pNtHeader, nameTable[j]) + fileBase);
                    matchFuncPointer(pfname, (ULONGLONG)exemem, addrTable[i]);
                    break;
                }
            }
        }
        if(fpDllMain == nullptr
            || fpGetHeaderCount == nullptr
            || fpGetHeaderName == nullptr
            || fpGetUnitsCount == nullptr
            || fpGetUnitsName == nullptr
            || fpGetColumnCount == nullptr
            || fpGetColumnName == nullptr
            || fpDecodeLine == nullptr) {
            break;
        }
        // invoke entry function
        entry = (EntryFunc)((ULONGLONG)exemem + pNtHeader->OptionalHeader.AddressOfEntryPoint);
        entry();
        LOG_I("call entry\n");
        success = true;
    }while(0);

    free(libmem);
    libmem = nullptr;
    if(!success) {
        if((exemem != nullptr)) {
            VirtualFree(exemem, 0, MEM_RELEASE);
            exemem = nullptr;
        }
        for(readin = 0; readin < extraHandlerSize; readin++) {
            FreeLibrary((HMODULE)extraHandler[readin]);
        }
        extraHandlerSize = 0;
    }
    loaded = int(success);
    return success;
}

void TemplateLoader::unloadLibrary() {
    if(loaded) {
        if((exemem != nullptr)) {
            VirtualFree(exemem, 0, MEM_RELEASE);
            exemem = nullptr;
        }
        for(size_t i = 0; i < extraHandlerSize; i++) {
            FreeLibrary((HMODULE)extraHandler[i]);
        }
        extraHandlerSize = 0;
        
        fpDllMain = nullptr;
        fpGetHeaderCount = nullptr;
        fpGetHeaderName = nullptr;
        fpGetUnitsCount = nullptr;
        fpGetUnitsName = nullptr;
        fpGetColumnCount = nullptr;
        fpGetColumnName = nullptr;
        fpDecodeLine = nullptr;
        loaded = 0;
    }
}

// @brief 将内存DLL数据(libmem)按映像对齐大小(SectionAlignment)映射到exemem内存中
// @param[in] doshdr Dos头,来自DLL文件基址
// @param[in] nthdr NT头,来自DLL文件基址
void TemplateLoader::memmapFile(IMAGE_DOS_HEADER *doshdr, IMAGE_NT_HEADERS *nthdr) {
    LPVOID lpSrcMem;
    LPVOID lpDestMem;
    DWORD dwSizeOfRawData;
    
    (void)doshdr;
    //获取所有头部+区段表的大小
    DWORD dwSizeOfHeaders = nthdr->OptionalHeader.SizeOfHeaders;
    //获取区段数量
    WORD wNumberOfSections = nthdr->FileHeader.NumberOfSections;
    //获取区段表数组的首元素
    PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(nthdr);
    //将头部(包括区段表)拷贝到内存
    RtlCopyMemory(this->exemem, this->libmem, dwSizeOfHeaders);

    //循环加载所有区段
    for (WORD i = 0; i < wNumberOfSections; i++) {
        //过滤掉无效区段
        if((0 == pSectionHeader->VirtualAddress) || (0 == pSectionHeader->SizeOfRawData)) {
            pSectionHeader++;
            continue;
        }
        //获取区段在文件中的位置
        lpSrcMem = (LPVOID)((ULONGLONG)this->libmem + pSectionHeader->PointerToRawData);
        //获取区段映射到内存中的位置
        lpDestMem = (LPVOID)((ULONGLONG)this->exemem + pSectionHeader->VirtualAddress);
        //获取区段在文件中的大小
        dwSizeOfRawData = pSectionHeader->SizeOfRawData;
        //将区段数据拷贝到内存中
        RtlCopyMemory(lpDestMem, lpSrcMem, dwSizeOfRawData);
        //获取下一个区段头(属性)
        pSectionHeader++;
    }
}

// @brief 修改PE文件的重定位表信息
// @param[in] doshdr Dos头,来自映像对齐后的内存基址
// @param[in] nthdr NT头,来自映像对齐后的内存基址
void TemplateLoader::relocationTable(IMAGE_DOS_HEADER *doshdr, IMAGE_NT_HEADERS *nthdr) {
    DWORD nNumberOfReloc;
    WORD* pRelocData;
    WORD relocFlag;
    ULONGLONG* pAddress;
    
    //获取重定位表的地址
    PIMAGE_BASE_RELOCATION pReloc = (PIMAGE_BASE_RELOCATION)((ULONGLONG)doshdr
        + nthdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);

    //注意重定位表的位置可能和硬盘文件中的偏移地址不同,应该使用加载后的地址

    //判断是否有重定位表
    if((PVOID)pReloc == (PVOID)doshdr) {
        LOG_I("no reloc table\n");
        return; //没有重定位表
    }

    //开始修复重定位
    while ((pReloc->VirtualAddress != 0) && (pReloc->SizeOfBlock != 0)) {
        // SizeOfBlock(重定位表大小) = (VirtualAddress(4B) + SizeOfBlock(4B) + 各偏移的总大小), 每个偏移数据占据2个字节
        nNumberOfReloc = (pReloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
        //获取IMAGE_BASE_RELOCATION结构后面的数据的地址
        pRelocData = (WORD *)((ULONGLONG)pReloc + sizeof(IMAGE_BASE_RELOCATION));
        LOG_I("num reloc:%d\n", nNumberOfReloc);
        
        for (DWORD i = 0; i < nNumberOfReloc; i++) {
            // 每个WORD由两部分组成,高4位指出了重定位的类型,WINNT.H中的一系列IMAGE_REL_BASED_xxx定义了重定位类型的取值。
            // 32位DLL重定位属性值是IMAGE_REL_BASED_HIGHLOW
            // 64位DLL重定位属性值是IMAGE_REL_BASED_DIR64
            // 低12位是相对于IMAGE_BASE_RELOCATION中第一个元素VirtualAddress描述位置的偏移
            relocFlag = (WORD)((pRelocData[i] >> 12u) & 0xFu);
            LOG_I("relocFlag:0x%x\n", relocFlag);
            if(relocFlag == IMAGE_REL_BASED_DIR64) {
                // 获取需要被修正数据的地址 = 加载后的内存地址 + 偏移数据的基址 + 数据项偏移
                // 64位DLL中,pAddress需要是ULONGLONG指针
                pAddress = (ULONGLONG *)((ULONGLONG)doshdr + pReloc->VirtualAddress + (pRelocData[i] & 0x0FFF));
                // 判断加载后的实际内存地址和DLL库链接时的原始基址,差值为正:相加,差值为负:相减
                // 地址是无符号数(ULONGLONG),只能大数值减去小数值,避免出现下溢导致计算出错
                if((ULONGLONG)doshdr > nthdr->OptionalHeader.ImageBase) {
                    *pAddress += ((ULONGLONG)doshdr - nthdr->OptionalHeader.ImageBase);
                }else {
                    *pAddress -= (nthdr->OptionalHeader.ImageBase - (ULONGLONG)doshdr);
                }
                LOG_I("addr:0x%I64x,value:0x%I64x\n", (ULONGLONG)pAddress, *pAddress);
            }
        }
        //下一个重定位块
        pReloc = (PIMAGE_BASE_RELOCATION)((ULONGLONG)pReloc + pReloc->SizeOfBlock);
    }
}

// @brief 填写PE文件的导入表信息
// @param[in] doshdr Dos头,来自映像对齐后的内存基址
// @param[in] nthdr NT头,来自映像对齐后的内存基址
bool TemplateLoader::importTable(IMAGE_DOS_HEADER *doshdr, IMAGE_NT_HEADERS *nthdr) {
    char* pDllName;
    HMODULE hDll;
    PIMAGE_THUNK_DATA pIat;
    FARPROC pFuncAddress;
    PIMAGE_IMPORT_BY_NAME pImportByName ;
    
    //获取导入表地址
    PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)((ULONGLONG)doshdr
        + nthdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
        
    this->extraHandlerSize = 0;
    
    //循环遍历导入表
    while (pImport->Name) {
        //获取导入表中的Dll名称
        pDllName = (char *)((ULONGLONG)doshdr + pImport->Name);
        //检索Dll模块获取模块句柄
        hDll = GetModuleHandleA(pDllName);
        LOG_I("Get lib:%s\n", pDllName);
        if(NULL == hDll) {
            if(extraHandlerSize >= EXTRA_HANDLER_MAX) {
                return false;
            }
            //加载Dll模块获取模块句柄
            hDll = LoadLibraryA(pDllName);
            LOG_I("Load lib:%s\n", pDllName);
            if(NULL == hDll) {
                return false;
            }
            extraHandler[extraHandlerSize] = (ULONGLONG)hDll;
            extraHandlerSize++;
        }
        
        //获取IAT
        pIat = (PIMAGE_THUNK_DATA)((ULONGLONG)doshdr + pImport->FirstThunk);

        //遍历IAT中函数
        while (pIat->u1.Ordinal) {
            //判断导入的函数是名称导入还是序号导入
            //判断最高位是否为1,如果是1那么是序号导入
            if IMAGE_SNAP_BY_ORDINAL64(pIat->u1.Ordinal) {
                //获取函数地址
                pFuncAddress = GetProcAddress(hDll, (LPCSTR)IMAGE_ORDINAL64(pIat->u1.Ordinal));
            }else {
                //名称导入,获取IMAGE_IMPORT_BY_NAME结构
                pImportByName = (PIMAGE_IMPORT_BY_NAME)((ULONGLONG)doshdr + pIat->u1.AddressOfData);
                //获取函数地址
                pFuncAddress = GetProcAddress(hDll, pImportByName->Name);
            }
            //将函数地址填入到IAT中
            pIat->u1.Function = (ULONGLONG)pFuncAddress;
            pIat++;
        }
        
        pImport++;
    }
    return true;
}

// RVA到文件偏移转换函数
uint32_t TemplateLoader::rva2foa(IMAGE_DOS_HEADER *doshdr, IMAGE_NT_HEADERS *nthdr, uint32_t rva) {
    (void)doshdr;
	//获得区段表 = Nt头的起始位置 + 可选头到Nt头的距离 + 可选头的大小
    PIMAGE_SECTION_HEADER pSelctionHeader = IMAGE_FIRST_SECTION(nthdr);
	
	// 遍历每一个区段,看看 RVA(在内存中的偏移地址)是否落在某个区段上
	for(WORD i = 0; i < nthdr->FileHeader.NumberOfSections; i++) {
		/*
		数据的RVA - 区段的RVA = 数据 在内存中距离区段头的距离 s1
		数据的FOA - 区段的FOA = 数据 在文件中距离区段头的距离 s2
		s1=s2
		数据的FOA - 区段的FOA = 数据的RVA - 区段的RVA
		所以:已知数据的RVA求数据的FOA: 数据的FOA = 数据的RVA - 区段的RVA + 区段的FOA
		*/
		if((rva >= pSelctionHeader->VirtualAddress)
		    && (rva < pSelctionHeader->VirtualAddress + pSelctionHeader->Misc.VirtualSize)) {
			//rva必须在某一个区段的里面, 大于区段头部偏移,小于区段头加大小
			return rva - pSelctionHeader->VirtualAddress + pSelctionHeader->PointerToRawData;
		}
		pSelctionHeader++;
	}
	// 如果RVA位于头部区域(未映射到任何节区)
    if (rva < nthdr->OptionalHeader.SizeOfHeaders) {
        return rva; // 头部在文件和内存中的布局相同
    }
	return 0;
}

void TemplateLoader::matchFuncPointer(const char *pfname, ULONGLONG base, ULONGLONG offset) {
    if(strcmp(pfname, "DllMain") == 0) {
        fpDllMain = (DllMainFunc)(base + offset);
        LOG_I("DllMain:0x%I64x\n",fpDllMain);
    }else if(strcmp(pfname, "GetHeaderCount") == 0) {
        fpGetHeaderCount = (GetHeaderCountFunc)(base + offset);
        LOG_I("GetHeaderCount:0x%I64x\n", fpGetHeaderCount);
    }else if(strcmp(pfname, "GetHeaderName") == 0) {
        fpGetHeaderName = (GetHeaderNameFunc)(base + offset);
        LOG_I("GetHeaderName:0x%I64x\n", fpGetHeaderName);
    }else if(strcmp(pfname, "GetUnitsCount") == 0) {
        fpGetUnitsCount = (GetUnitsCountFunc)(base + offset);
        LOG_I("GetUnitsCount:0x%I64x\n", fpGetUnitsCount);
    }else if(strcmp(pfname, "GetUnitsName") == 0) {
        fpGetUnitsName = (GetUnitsNameFunc)(base + offset);
        LOG_I("GetUnitsName:0x%I64x\n", fpGetUnitsName);
    }else if(strcmp(pfname, "GetColumnCount") == 0) {
        fpGetColumnCount = (GetColumnCountFunc)(base + offset);
        LOG_I("GetColumnCount:0x%I64x\n", fpGetColumnCount);
    }else if(strcmp(pfname, "GetColumnName") == 0) {
        fpGetColumnName = (GetColumnNameFunc)(base + offset);
        LOG_I("GetColumnName:0x%I64x\n", fpGetColumnName);
    }else if(strcmp(pfname, "DecodeLine") == 0) {
        fpDecodeLine = (DecodeLineFunc)(base + offset);
        LOG_I("DecodeLine:0x%I64x\n", fpDecodeLine);
    }
}

bool TemplateLoader::callDllMain(IMAGE_DOS_HEADER *doshdr, IMAGE_NT_HEADERS *nthdr) {
    bool result = false;
    (void)nthdr;
    //调用入口函数,附加进程DLL_PROCESS_ATTACH
    if(fpDllMain == nullptr) return false;
    result = fpDllMain((HINSTANCE)doshdr, DLL_PROCESS_ATTACH, NULL);
    LOG_I("DllMain ret:%d\n", result);
    return result;
}

uint32_t TemplateLoader::getHeaderCount() {
    uint32_t result;
    // check function pointer, return err code or throw exception if fail
    if(fpGetHeaderCount == nullptr) return 0;
    result = fpGetHeaderCount();
    return result;
}

uint32_t TemplateLoader::getHeaderName(uint32_t pos, char *buf, uint32_t bmax) {
    uint32_t result;
    if(fpGetHeaderName == nullptr) return 0;
    result = fpGetHeaderName(pos, buf, bmax);
    return result;
}

uint32_t TemplateLoader::getUnitsCount(void) {
    uint32_t result;
    if(fpGetUnitsCount == nullptr) return 0;
    result = fpGetUnitsCount();
    return result;
}

uint32_t TemplateLoader::getUnitsName(uint32_t pos, char *buf, uint32_t bmax) {
    uint32_t result;
    if(fpGetUnitsName == nullptr) return 0;
    result = fpGetUnitsName(pos, buf, bmax);
    return result;
}

uint32_t TemplateLoader::getColumnCount(void) {
    uint32_t result;
    if(fpGetColumnCount == nullptr) return 0;
    result = fpGetColumnCount();
    return result;
}

uint32_t TemplateLoader::getColumnName(uint32_t pos, char *buf, uint32_t bmax) {
    uint32_t result;
    if(fpGetColumnName == nullptr) return 0;
    result = fpGetColumnName(pos, buf, bmax);
    return result;
}

int TemplateLoader::decodeLine(uint8_t *buffer, uint32_t len1, char *output, uint32_t len2) {
    int result;
    if(fpDecodeLine == nullptr) return 0;
    result = fpDecodeLine(buffer, len1, output, len2);
    return result;
}

测试日志

filename:D:\examples\decode\output.tpl
file size:15236
meta size:388
file magic:0x4354504c
lib size:14848
load dll file success
dos magic:0x5a4d
nt sign:0x00004550
image size:53248
image base:0x361440000
entry point:0x00001320
virtual alloc success:0x1c5b10d0000
memmap success
reassign header pointer
num reloc:2
relocFlag:0xa
addr:0x1c5b10d27b8,value:0x1c5b10d27a0
relocFlag:0x0
num reloc:14
relocFlag:0xa
addr:0x1c5b10d3010,value:0x1c5b10d27d0
relocFlag:0xa
addr:0x1c5b10d3040,value:0x1c5b10d22f0
relocFlag:0xa
addr:0x1c5b10d3048,value:0x1c5b10d2330
relocFlag:0xa
addr:0x1c5b10d3050,value:0x1c5b10d2180
relocFlag:0xa
addr:0x1c5b10d3058,value:0x1c5b10d22b0
relocFlag:0xa
addr:0x1c5b10d3060,value:0x1c5b10d2200
relocFlag:0xa
addr:0x1c5b10d3068,value:0x1c5b10d2190
relocFlag:0xa
addr:0x1c5b10d3070,value:0x1c5b10d3088
relocFlag:0xa
addr:0x1c5b10d3078,value:0x1c5b10d308c
relocFlag:0xa
addr:0x1c5b10d3080,value:0x1c5b10d3090
relocFlag:0xa
addr:0x1c5b10d3090,value:0x1c5b10d30a4
relocFlag:0xa
addr:0x1c5b10d3098,value:0x1c5b10d30a0
relocFlag:0xa
addr:0x1c5b10d30a8,value:0x1c5b10d2290
relocFlag:0xa
addr:0x1c5b10d30b0,value:0x1c5b10d2270
num reloc:28
relocFlag:0xa
addr:0x1c5b10d4060,value:0x1c5b10d1470
relocFlag:0xa
addr:0x1c5b10d4080,value:0x1c5b10db000
relocFlag:0xa
addr:0x1c5b10d4088,value:0x1c5b10db008
relocFlag:0xa
addr:0x1c5b10d4090,value:0x1c5b10d704c
relocFlag:0xa
addr:0x1c5b10d4098,value:0x1c5b10da030
relocFlag:0xa
addr:0x1c5b10d4240,value:0x1c5b10d3030
relocFlag:0xa
addr:0x1c5b10d4250,value:0x1c5b10d27b0
relocFlag:0xa
addr:0x1c5b10d4260,value:0x1c5b10d43a0
relocFlag:0xa
addr:0x1c5b10d4270,value:0x1c5b10d43a0
relocFlag:0xa
addr:0x1c5b10d4280,value:0x1c5b10d4060
relocFlag:0xa
addr:0x1c5b10d4290,value:0x1c5b10d0000
relocFlag:0xa
addr:0x1c5b10d42a0,value:0x1c5b10d9314
relocFlag:0xa
addr:0x1c5b10d42b0,value:0x1c5b10d93b4
relocFlag:0xa
addr:0x1c5b10d42c0,value:0x1c5b10d7050
relocFlag:0xa
addr:0x1c5b10d42d0,value:0x1c5b10d3000
relocFlag:0xa
addr:0x1c5b10d42e0,value:0x1c5b10d3024
relocFlag:0xa
addr:0x1c5b10d42f0,value:0x1c5b10d7030
relocFlag:0xa
addr:0x1c5b10d4300,value:0x1c5b10d7038
relocFlag:0xa
addr:0x1c5b10d4310,value:0x1c5b10da000
relocFlag:0xa
addr:0x1c5b10d4320,value:0x1c5b10da008
relocFlag:0xa
addr:0x1c5b10d4330,value:0x1c5b10da010
relocFlag:0xa
addr:0x1c5b10d4340,value:0x1c5b10da020
relocFlag:0xa
addr:0x1c5b10d4360,value:0x1c5b10d401e
relocFlag:0xa
addr:0x1c5b10d4368,value:0x1c5b10d4024
relocFlag:0xa
addr:0x1c5b10d4370,value:0x1c5b10d402e
relocFlag:0xa
addr:0x1c5b10d4378,value:0x1c5b10d4037
relocFlag:0xa
addr:0x1c5b10d4380,value:0x1c5b10d4041
relocFlag:0x0
num reloc:4
relocFlag:0xa
addr:0x1c5b10da018,value:0x1c5b10d1000
relocFlag:0xa
addr:0x1c5b10da030,value:0x1c5b10d1470
relocFlag:0xa
addr:0x1c5b10da038,value:0x1c5b10d1440
relocFlag:0x0
relocationTable ok
Get lib:KERNEL32.dll
Get lib:api-ms-win-crt-environment-l1-1-0.dll
Get lib:api-ms-win-crt-heap-l1-1-0.dll
Get lib:api-ms-win-crt-private-l1-1-0.dll
Get lib:api-ms-win-crt-runtime-l1-1-0.dll
Get lib:api-ms-win-crt-stdio-l1-1-0.dll
Get lib:api-ms-win-crt-string-l1-1-0.dll
Get lib:api-ms-win-crt-time-l1-1-0.dll
importTable ok
reedit ImageBase
func count:8
name count:8
DecodeLine:0x1c5b10d24e0
DllMain:0x1c5b10d2630
GetColumnCount:0x1c5b10d2640
GetColumnName:0x1c5b10d2650
GetHeaderCount:0x1c5b10d26b0
GetHeaderName:0x1c5b10d26c0
GetUnitsCount:0x1c5b10d2720
GetUnitsName:0x1c5b10d2730
call entry
result:1
<--- Finished in (0:24.90), exitcode: 0 --->

参考资料

在编写动态库加载器的过程中参考了以下博客,感谢这些前辈的辛勤工作。
https://www.cnblogs.com/ndyxb/p/12896872.html
https://www.cnblogs.com/LyShark/p/17684107.html
https://blog.csdn.net/tutucoo/article/details/83828713
https://www.cnblogs.com/helloylh/p/17209673.html

posted @ 2025-06-06 23:03  Yanye  阅读(39)  评论(0)    收藏  举报