关于内存加载DLL后修复重定位的问题

看网上的代码好累,摸索整理了一下,顺便巩固下PE

首先介绍下PE头,分为2个部分

1、DOS头 (IMAGE_DOS_HEADER)

typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
    WORD   e_magic;                     // ASCII字符MZ  
    WORD   e_cblp;                      // 文件最后页的字节数
    WORD   e_cp;                        // 文件页数
    WORD   e_crlc;                      // 重定位元素个数
    WORD   e_cparhdr;                   // 以段落为单位的头部大小
    WORD   e_minalloc;                  // 所需的最小附加段
    WORD   e_maxalloc;                  // 所需的最大附加段
    WORD   e_ss;                        // 初始的堆栈段(SS)相对偏移量值
    WORD   e_sp;                        // 初始的堆栈指针(SP)值
    WORD   e_csum;                      // 校验和
    WORD   e_ip;                        // 初始的指令指针(IP)值
    WORD   e_cs;                        // 初始的代码段(CS)相对偏移量值
    WORD   e_lfarlc;                    // 重定位表在文件中的偏移地址
    WORD   e_ovno;                      // 覆盖号
    WORD   e_res[4];                    // 保留字(一般都是为确保对齐而预留)
    WORD   e_oemid;                     // OEM 标识符(相对于 e_oeminfo) 
    WORD   e_oeminfo;                   // OEM 信息,即 e_oemid 的细节
    WORD   e_res2[10];                  // 保留字(一般都是为确保对齐而预留) 
    LONG   e_lfanew;                    // NT头在文件中的偏移地址
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;


2、NT头 (IMAGE_NT_HEADERS)

NT头还区分为32位和64位,64位就不做介绍了,重点说下32位

typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature;                         //签名(文件类型标志)
    IMAGE_FILE_HEADER FileHeader;            //PE 文件头结构(占用20个字节)
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;  //可选头结构(占用224个字节)
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

- Signature的值为:

#define IMAGE_DOS_SIGNATURE                 0x5A4D      // MZ
#define IMAGE_OS2_SIGNATURE                 0x454E      // NE
#define IMAGE_OS2_SIGNATURE_LE              0x454C      // LE
#define IMAGE_VXD_SIGNATURE                 0x454C      // LE
#define IMAGE_NT_SIGNATURE                  0x00004550 // PE00
-FileHeader的结构

typedef struct _IMAGE_FILE_HEADER {
    WORD    Machine;                   //该文件运行所要求的CPU
    WORD    NumberOfSections;          //文件的节数目
    DWORD   TimeDateStamp;             //文件创建日期和时间
    DWORD   PointerToSymbolTable;      //COFF 符号表格的偏移位置
    DWORD   NumberOfSymbols;           //COFF 符号表格中的符号个数
    WORD    SizeOfOptionalHeader;      //Optional Header(IMAGE_OPTIONAL_HEADER)结构大小
    WORD    Characteristics;           //0x0001 文件中没有重定位(relocation)、0x0002 文件是一个可执行程序exe(也就是說不是OBJ 或LIB)、0x2000 文件是dll
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
-Optional Header 包含了PE文件的逻辑分布信息

typedef struct _IMAGE_OPTIONAL_HEADER {
    WORD    Magic;                               //用来定义 image 的状态:0x0107 一个 ROM image,0x010B 一个正常的(一般的)EXE
    BYTE    MajorLinkerVersion;                  //产生此PE文件的链接器的版本
    BYTE    MinorLinkerVersion;                  //产生此PE文件的链接器的版本
    DWORD   SizeOfCode;                          //所有code section 的总和大小
    DWORD   SizeOfInitializedData;               //所有包含初始化内容的 sections(但不包括 code section)的总和大小
    DWORD   SizeOfUninitializedData;             //所有需要PE装载器将内存地址空间赋予它但是却不占用硬盘空间的所有 sections 的大小总和
    DWORD   AddressOfEntryPoint;                 //这是PE文件开始执行的位置
    DWORD   BaseOfCode;                          //一个RVA,表示程序中的 code section 从何开始
    DWORD   BaseOfData;                          //一个RVA,表示程序中的 data section 从何开始

    DWORD   ImageBase;                           //PE文件的优先装载地址(Base Address)
    DWORD   SectionAlignment;                    //内存中节对齐的粒度
    DWORD   FileAlignment;                       //文件中节对齐的粒度
    WORD    MajorOperatingSystemVersion;         //使用此可执行程序的操作系统的最小版本
    WORD    MinorOperatingSystemVersion;         //使用此可执行程序的操作系统的最小版本
    WORD    MajorImageVersion;                   //WIN32子系统版本
    WORD    MinorImageVersion;                   //WIN32子系统版本
    WORD    MajorSubsystemVersion;               //使用者自定义的域,允许你拥有不同版本的exe或dll
    WORD    MinorSubsystemVersion;               //使用者自定义的域,允许你拥有不同版本的exe或dll
    DWORD   Win32VersionValue;                   //似乎总是0
    DWORD   SizeOfImage;                         //内存中整个PE映像体的尺寸
    DWORD   SizeOfHeaders;                       //所有头 + 节表的大小,也就等于文件尺寸减去文件中所有节的尺寸
    DWORD   CheckSum;                            //此程序的一个CRC 校验和
    WORD    Subsystem;                           //用来识别PE文件属于哪个子系统
    WORD    DllCharacteristics;                  //一组标志位,用来指出dll的初始化函数(例如 DllMain)在什么环境下被调用
    DWORD   SizeOfStackReserve;                  //线程初始堆栈的保留大小
    DWORD   SizeOfStackCommit;                   //一开始就被指定给执行线程初始堆栈的内存数量
    DWORD   SizeOfHeapReserve;                   //保留给最初的进程堆(process heap)的虚拟内存数量
    DWORD   SizeOfHeapCommit;                    //一开始就被指定给进程堆(process heap)的内存数量
    DWORD   LoaderFlags;                         //Debug用
    DWORD   NumberOfRvaAndSizes;                 //在DataDirectory(下一个域)数组的成员结构个数
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];  //一个IMAGE_DATA_DIRECTORY 结构数组。每个结构给出一个重要数据结构的RVA。
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

IMAGE_DATA_DIRECTORY的结构如下:

typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;                         //虚拟地址
    DWORD   Size;                                   //长度
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

注释:

RAV 代表相对虚拟地址。RVA是虚拟空间中到参考点的一段距离。RVA就是类似文件偏移量的东西。当然它是相对虚拟空间里的一个地址,而不是文件头部


重定位表在DataDirectory数组的第5个表内(IMAGE_DIRECTORY_ENTRY_BASERELOC)

typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;                         //表起始偏移值
    DWORD   Size;                                   //表大小
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

即:模块基地址  + 表起始偏移值 = 重定位表数据

重定位表有好几块,每个块的头8个字节为2个DWORD,存放偏移值 和 当前块的大小,因此可以计算出当前块中需要修正的偏移(Word类型)的数量。

即:(当前块的大小 - 8) / 2 = 修正数量

修正偏移 = 原值  & 0xFFF

最后修正直接 将当前载入的基地址  + 修正偏移 就得到的修正数据的地址,直接将地址内的数据加上修正值。

修正值  = 当前载入的基地址 - imagebase

接下来直接给出代码,内存中加载DLL,并且修复重定位(RAV)

byte *MemModule = Null;   //用于存放加载的模块
//参数为模块路径,加载成功返回内存地址,失败返回-1
int LoadModuleInMem(char *ModuleName)
{
    int iRet = -1;
    HANDLE hFile = CreateFile(ModuleName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL);  //读取文件
    if (hFile != INVALID_HANDLE_VALUE)
    {
        DWORD dwFileSilze= GetFileSize(hFile,NULL);   //获取个文件长度
        MemModule = (BYTE*)malloc(dwFileSize);        //申请内存
        if (ReadFile(hFile, OrgCode, dwFileSize, &dwRead, NULL))
        {
            IMAGE_DOS_HEADER* DosHeader = (IMAGE_DOS_HEADER*)MemModule;
            IMAGE_NT_HEADERS* PEHeader = (IMAGE_NT_HEADERS*)((DWORD)DosHeader + DosHeader->e_lfanew);
            PIMAGE_DATA_DIRECTORY        pRelocDir = NULL;
            PIMAGE_BASE_RELOCATION       pBaseReloc = NULL;
            UINT                         nRelocSize = NULL;
            DWORD ImageBase = PEHeader->OptionalHeader.ImageBase;  //相对文件代码段偏移        
            pRelocDir = &PEHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]; //获取重定位表结构
            //判断是否存在重定位
            if (pRelocDir->VirtualAddress != 0)
            {
                int Delta = 0;
                if ((DWORD)OrgCode != ImageBase)
                {
                   //修正值
                   Delta =(DWORD)MemModule - ImageBase ;
                }
                //获取重定位表数据地址
                DWORD pRelocBase = pRelocDir->VirtualAddress+(DWORD)hSrcModule;

                //取出重定位表的第一块 偏移 和 长度
                pBaseReloc =(PIMAGE_BASE_RELOCATION)(pRelocBase);

                //判断是否存在偏移
                if (Delta != 0)
                {
                    //修正重定位
                    while (pBaseReloc->VirtualAddress + pBaseReloc->SizeOfBlock != 0) 
                    {
                       //0x1000 块内的 重定位偏移数量
                       int NumberOfReloc = (pBaseReloc->SizeOfBlock - 8 ) / 2;
                       for( int i=0 ; i < NumberOfReloc; i++)
                       {
                          WORD pLocData = *(WORD*)(pRelocBase + 8 + i * 2) & 0xFFF;
                          //内存中代码的地址 = 内存基地址 + 重定位RVA偏移 + 偏移;
                          //获取源值
                          WORD OldCode = *(WORD*)(MemModule + pBaseReloc->VirtualAddress + pLocData);
                          //将修正值写回
                          *(WORD*)(MemModule + pBaseReloc->VirtualAddress + pLocData) = OldCode + Delta;
                       }
                       //移动指针,指向下个块
                       pRelocBase = pRelocBase + pBaseReloc->SizeOfBlock;
                       //获取下个块的结构
                       pBaseReloc = (PIMAGE_BASE_RELOCATION)((DWORD)pBaseReloc +pBaseReloc->SizeOfBlock);        
                    }
                    iRet = MemModule;
                }
            }
        }
    }
    return iRet;
 }


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在Delphi中,可以使用`LoadLibrary`函数来加载一个动态链接库(DLL)到内存中。以下是一个简单的示例: ```delphi var hDll: THandle; // 动态链接库句柄 begin // 加载动态链接库 hDll := LoadLibrary('path\to\your\dll.dll'); if hDll = 0 then begin ShowMessage('无法加载动态链接库'); Exit; end; // 使用动态链接库中的函数或过程 // ... // 卸载动态链接库 FreeLibrary(hDll); end; ``` 首先,我们声明了一个变量`hDll`,用于存储动态链接库的句柄。然后,使用`LoadLibrary`函数加载指定路径下的DLL文件。如果加载成功,`LoadLibrary`函数会返回该DLL的句柄;如果加载失败,函数会返回0。 加载成功后,我们可以通过该动态链接库的句柄`hDll`来调用其内部的函数或过程。这些函数或过程的声明通常包含在相应的DLL头文件中。通过调用这些函数或过程,我们可以实现与DLL相关的功能。 最后,在不再需要使用动态链接库时,我们调用`FreeLibrary`函数来卸载该DLL并释放其内存。 需要注意的是,在使用动态链接库时,我们应该遵循相应的DLL文件的使用规则和API文档,确保正确使用并处理异常情况。 ### 回答2: 在Delphi中,我们可以使用LoadLibrary函数来加载DLL文件,并使用GetProcAddress函数来获取DLL中的函数地址。 使用LoadLibrary函数加载DLL文件时,我们需要提供DLL文件的完整路径。可以使用函数的返回值来检查是否成功加载DLL。如果返回值为0,表示加载失败;否则,返回一个非零的句柄,表示成功加载DLL加载成功后,我们可以使用GetProcAddress函数来获取DLL中函数的地址。该函数需要传入DLL的句柄和函数的名称,返回一个指向函数的指针。如果函数不存在,则返回空指针。 完成以上步骤后,我们就可以使用获取到的函数地址来调用DLL中的函数。可以通过指针类型转换来将函数指针赋值给相应的函数变量,然后通过调用该函数变量来调用DLL函数。需要注意的是,调用DLL中的函数需要使用stdcall调用约定,以确保参数传递和堆栈清理的正确性。 在使用完DLL后,我们需要使用FreeLibrary函数来释放DLL所占用的内存。该函数需要传入DLL的句柄,释放成功返回非零值,失败返回0。 通过以上步骤,我们可以在Delphi中成功加载和调用DLL函数,实现内存加载DLL的功能。 ### 回答3: Delphi 是一种流行的编程语言,可以通过内存加载 DLL(Dynamic Link Library)来实现一些功能。内存加载 DLL 意味着将 DLL 文件中的程序代码和数据加载内存中,并通过操作内存中的数据来调用 DLL 中的函数和方法。 在 Delphi 中,可以使用 TMemoryStream 类来实现内存加载 DLL。以下是一个简单的例子: 1. 首先,需要在 Delphi 项目中添加一个引用到 "Classes" 单元。 2. 创建一个 TMemoryStream 对象,并加载 DLL 文件的内容到这个对象中。 ```delphi var MemStream: TMemoryStream; begin MemStream := TMemoryStream.Create; try MemStream.LoadFromFile('path_to_dll.dll'); except // 处理加载 DLL 文件失败的情况 end; // 现在,DLL 文件的内容已经加载内存中 // 可以通过 MemStream 来访问内存中的数据 end; ``` 3. 接下来,可以使用 GetProcAddress 函数来获取内存DLL 文件中指定函数的地址,并将其的地址转换为相应的函数指针。然后,就可以调用该函数了。 ```delphi type TMyFunction = function(params): returnType; var MyFunction: TMyFunction; begin MyFunction := TMyFunction(GetProcAddress(MemStream.Memory, 'function_name')); if Assigned(MyFunction) then begin // 调用函数 MyFunction(params); end; end; ``` 通过以上步骤,我们可以在 Delphi 中实现内存加载 DLL 的功能。这种方法可以用于动态加载 DLL,特别是在某些情况下无法直接从文件系统加载 DLL 文件时。需要注意的是,在使用内存加载 DLL 时要小心内存泄漏和错误处理,以保证程序的稳定性和安全性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值