从内存中加载的DLL

从内存中加载的DLL

以下通过google翻译 

本教程介绍如何从内存中加载一个动态链接库(DLL)。

概括

默认的Windows API函数加载到程序的外部库(调用LoadLibrary,LoadLibraryEx)只适用于文件系统上的文件。因此,它不可能从内存中加载DLL。但有时,你需要的正是这样的功能(例如,你不希望发布多个的文件或删除困难)。这个问题的常见解决方法是写的DLL到一个临时文件,并从那里调用。当程序终止,临时文件被删除。

在本教程中,我将首先描述,DLL文件的结构,并提出了一些代码,可以完全从内存中加载DLL - 不存储在磁盘上。

Windows可执行文件 - PE格式

大多数Windows二进制文件,它可以包含可执行代码(EXE,DLL,SYS)共享一个通用的文件格式,由以下几部分组成:

DOS header

DOS stub

PE header
Section header
Section 1
Section 2
. . .
Section n

文件winnt.h中可以发现下面给出的所有结构。

DOS头/存根(stub)

DOS头只用于向后兼容。之前的DOS存根,通常只显示一条错误消息有关的文件不能在DOS模式下运行。

微软定义的DOS头如下:

 
  
typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
    WORD   e_magic;                     // Magic number
    WORD   e_cblp;                      // Bytes on last page of file
    WORD   e_cp;                        // Pages in file
    WORD   e_crlc;                      // Relocations
    WORD   e_cparhdr;                   // Size of header in paragraphs
    WORD   e_minalloc;                  // Minimum extra paragraphs needed
    WORD   e_maxalloc;                  // Maximum extra paragraphs needed
    WORD   e_ss;                        // Initial (relative) SS value
    WORD   e_sp;                        // Initial SP value
    WORD   e_csum;                      // Checksum
    WORD   e_ip;                        // Initial IP value
    WORD   e_cs;                        // Initial (relative) CS value
    WORD   e_lfarlc;                    // File address of relocation table
    WORD   e_ovno;                      // Overlay number
    WORD   e_res[4];                    // Reserved words
    WORD   e_oemid;                     // OEM identifier (for e_oeminfo)
    WORD   e_oeminfo;                   // OEM information; e_oemid specific
    WORD   e_res2[10];                  // Reserved words
    LONG   e_lfanew;                    // File address of new exe header
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

PE头

PE头中包含的信息。可执行文件的不同部分,用于存储代码和数据定义这个库提供的其他库或出口的进口。

它的定义如下:

typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

文件头描述的物理格式的文件,即内容,关于符号的信息,等:

typedef struct _IMAGE_FILE_HEADER {
    WORD    Machine;
    WORD    NumberOfSections;
    DWORD   TimeDateStamp;
    DWORD   PointerToSymbolTable;
    DWORD   NumberOfSymbols;
    WORD    SizeOfOptionalHeader;
    WORD    Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

可选头包含的逻辑格式的库的信息,包括所需的操作系统版本,内存需求和切入口:

的typedef的结构_IMAGE_OPTIONAL_HEADER {
    / /
    / /标准领域。
    / /

    WORD魔术;
    BYTE MajorLinkerVersion;
    BYTE MinorLinkerVersion;
    DWORD SizeOfCode;
    DWORD SizeOfInitializedData;
    DWORD SizeOfUninitializedData;
    DWORD AddressOfEntryPoint;
    DWORD BaseOfCode;
    DWORD BaseOfData;

    / /
    / / NT额外的字段。
    / /

    DWORD基址;
    DWORD SectionAlignment;
    DWORD FileAlignment;
    WORD MajorOperatingSystemVersion;
    WORD MinorOperatingSystemVersion;
    WORD MajorImageVersion;
    WORD MinorImageVersion;
    WORD MajorSubsystemVersion;
    WORD MinorSubsystemVersion;
    DWORD Win32VersionValue;
    DWORD SizeOfImage;
    DWORD SizeOfHeaders;
    DWORD校验和;
    WORD子系统;
    WORD DllCharacteristics;
    DWORD SizeOfStackReserve;
    DWORD SizeOfStackCommit;
    DWORD SizeOfHeapReserve;
    DWORD SizeOfHeapCommit;
    DWORD LoaderFlags;
    DWORD NumberOfRvaAndSizes;
    IMAGE_DATA_DIRECTORY使用DataDirectory [IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32,* PIMAGE_OPTIONAL_HEADER32;

数据目录包含16个的(IMAGE_NUMBEROF_DIRECTORY_ENTRIES)项定义的逻辑组件的库:

指数描述
0导出的函数
1进口功能
2资源
3异常信息
4安全信息
5基地搬迁表
6调试信息
7架构体系数据
8全局指针
9线程局部存储
10加载配置
11绑定进口
12导入地址表
13延迟加载进口
14COM运行时描述符


对于导入的DLL,我们只需要导入项描述和基地重定位表项。为了提供访问导出的函数,导出项是必需的

节头

节头后存储在PE头的可选头结构。微软提供宏观IMAGE_FIRST_SECTION, PE头的基础上得 ​​到的起始地址。

其实,节头信息的文件中的每个部分的列表:

typedef struct _IMAGE_SECTION_HEADER {
    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];
    union {
            DWORD   PhysicalAddress;
            DWORD   VirtualSize;
    } Misc;
    DWORD   VirtualAddress;
    DWORD   SizeOfRawData;
    DWORD   PointerToRawData;
    DWORD   PointerToRelocations;
    DWORD   PointerToLinenumbers;
    WORD    NumberOfRelocations;
    WORD    NumberOfLinenumbers;
    DWORD   Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

一节可以包含代码,数据,重定位信息,资源,出口或入口的定义,等等。

加载库。

为了模拟PE加载器,我们必须先了解哪些步骤,都需要将文件加载到内存中,并准备结构,使他们能够从其他程序调用。

当我们调用LoadLibrary的,Windows主要做了以下任务:

  1. 打开指定的文件,并检查DOS和PE头。
  2. 尝试的PE头.可选头.映像大小字节分配一个内存块的位置PE头.可选头.映像基址
  3. 解析部分的页眉和部分复制到自己的地址。VirtualAddress的属性的IMAGE_SECTION_HEADER结构的每个部分中,相对于分配的内存块的基础上,被存储在目的地地址。
  4. 如果分配的内存块不同于基址,各种参考文献中的代码和/或数据段必须被调整。这就是所谓的基本搬迁
  5. 需要进口的库加载相应的库,必须解决。
  6. 必须被保护的存储器区域的不同的部分,根据部分的特性。的某些部分被标记为“ 舍弃,因此可以安全地释放在这一点上。这些部分通常包含临时数据,只需要在导入过程中,类似的信息为基地的搬迁。
  7. 现在的图书馆是完全加载。必须通过调用入口点使用标志DLL_PROCESS_ATTACH通知。

在下面的段落中,每个步骤进行说明。

分配内存

库所需的所有内存,必须保留/分配使用VirtualAlloc的,因为Windows提供的功能来保护这些内存块。这是必需的,如阻塞写的代码或常量数据的访问限制对内存的访问。

可选头结构定义为库所需的存储器块的大小。它必须被保留的地址所指定的映像基址如果可能的话:

memory = VirtualAlloc((LPVOID)(PEHeader->OptionalHeader.ImageBase),
    PEHeader->OptionalHeader.SizeOfImage,
    MEM_RESERVE,
    PAGE_READWRITE);

如果保留的内存的地址在基址不同(如下文所述),基地重定位必须做的。

复制部分

一旦存储器已经被保留,该文件的内容可以被复制到系统中。节标头必须得到评估,以确定在内存中的文件和目标区域的位置。

在复制数据的内存块必须得到承诺:

dest = VirtualAlloc(baseAddress + section->VirtualAddress,
    section->SizeOfRawData,
    MEM_COMMIT,
    PAGE_READWRITE);

没有文件中的数据(如所使用的变量的数据部分)部分的SizeOfRawData0,所以您可以使用SizeOfInitializedData或可选头的SizeOfUninitializedData选择哪一个取决于上的的位标志IMAGE_SCN_CNT_INITIALIZED_DATAIMAGE_SCN_CNT_UNINITIALIZED_DATA节“的特点,可以被设置在。

基地重定位

所有的内存地址被存储在库中的代码/数据段的相对在可选头定义的地址由基址。如果该库不能被导入到这个内存地址,都必须进行调整=> 重新定位。帮助基地搬迁表,可以发现目录中的5项的数据目录在可选头所有这些引用在存储信息的文件格式。

此表包含此结构的一系列

typedef struct _IMAGE_BASE_RELOCATION {
    DWORD   VirtualAddress;
    DWORD   SizeOfBlock;
} IMAGE_BASE_RELOCATION;

它包含(SizeOfBlock - IMAGE_SIZEOF_BASE_RELOCATION)/ 2,每个项目的16位。高4位的低12位定义的重定位类型,定义偏移相对的虚拟地址

唯一的类型,似乎是在DLL文件

IMAGE_REL_BASED_ABSOLUTE
无操作重定位。用于填充。
IMAGE_REL_BASED_HIGHLOW
基址 和分配的内存块添加增量的32位地址的偏移。

解决入口

1的DataDirectory在OptionalHeader的指定目录项的列表导入符号库。在该列表中的每个条目定义如下:

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;            // 0 for terminating null import descriptor
        DWORD   OriginalFirstThunk;         // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
    };
    DWORD   TimeDateStamp;                  // 0 if not bound,
                                            // -1 if bound, and real date\time stamp
                                            //     in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
                                            // O.W. date/time stamp of DLL bound to (Old BIND)

    DWORD   ForwarderChain;                 // -1 if no forwarders
    DWORD   Name;
    DWORD   FirstThunk;                     // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;

名称条目描述的偏移量,以NULL结尾的字符串库的名称(如KERNEL32.DLL)。将OriginalFirstThunk入口点从外部导入库的函数名的列表。FirstThunk项保存点得到填补进口的符号指针的地址的列表。

当我们解决了进口,我们在并行访问这两个表,导入第一个列表中定义的函数的名称和存储的指针符号在第二个列表:

nameRef = (DWORD *)(baseAddress + importDesc->OriginalFirstThunk);
symbolRef = (DWORD *)(baseAddress + importDesc->FirstThunk);
for (; *nameRef; nameRef++, symbolRef++)
{
    PIMAGE_IMPORT_BY_NAME thunkData = (PIMAGE_IMPORT_BY_NAME)(codeBase + *nameRef);
    *symbolRef = (DWORD)GetProcAddress(handle, (LPCSTR)&thunkData->Name);
    if (*funcRef == 0)
    {
        handleImportError();
        return;
    }
}

保护内存

每一部分都有它的特点进入指定的权限标志。这些标记可以是一个或它们的组合

IMAGE_SCN_MEM_EXECUTE
本节包含的数据,可以执行。
IMAGE_SCN_MEM_READ
本节包含的数据是可读的。
IMAGE_SCN_MEM_WRITE
本节包含的数据是可写的。

这些标志必须被映射到保护标志

  • PAGE_NOACCESS
  • PAGE_WRITECOPY
  • PAGE_READONLY
  • PAGE_READWRITE
  • PAGE_EXECUTE
  • PAGE_EXECUTE_WRITECOPY
  • PAGE_EXECUTE_READ
  • PAGE_EXECUTE_READWRITE

现在,该函数的VirtualProtect可以被用于限制对存储器的访问。如果程序试图访问它在未经授权的方式,将会抛出一个异常的Windows。

除了上述的部分标志,可以添加以下:

IMAGE_SCN_MEM_DISCARDABLE
本节中的数据可以被释放后的进口。这通常是指定的重定位数据。
IMAGE_SCN_MEM_NOT_CACHED
本节中的数据不能由Windows缓存。上面的保护标志位标志 PAGE_NOCACHE的

通知库

做的最后一件事是调用的DLL入口点(定义为AddressOfEntryPoint),通知与库连接到某个进程。

的入口点处的功能被定义为

typedef BOOL (WINAPI *DllEntryProc)(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved);

所以最后我们要执行的代码是

DllEntryProc entry = (DllEntryProc)(baseAddress + PEHeader->OptionalHeader.AddressOfEntryPoint);
(*entry)((HINSTANCE)baseAddress, DLL_PROCESS_ATTACH, 0);

之后,我们可以使用任何正常的库导出的函数。

导出的函数

如果你想访问的库导出的函数,你需要找到切入点,以一个符号,即要调用的函数的名称。

0的DataDirectory在OptionalHeader的包含目录项有关导出的函数的信息。它的定义如下:

typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics;
    DWORD   TimeDateStamp;
    WORD    MajorVersion;
    WORD    MinorVersion;
    DWORD   Name;
    DWORD   Base;
    DWORD   NumberOfFunctions;
    DWORD   NumberOfNames;
    DWORD   AddressOfFunctions;     // RVA from base of image
    DWORD   AddressOfNames;         // RVA from base of image
    DWORD   AddressOfNameOrdinals;  // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

首先要做的事,是名映射的序号导出的符号的功能。所以,我走在定义的阵列的AddressOfNamesAddressOfNameOrdinals平行,直到你找到所需的名称。

现在你可以用序数读通过评估的第n个元素的AddressOfFunctions数组的地址。

释放库

要释放自定义加载的库,请执行下列步骤

  • 调用入口点通知被分离的库:
    DllEntryProc entry = (DllEntryProc)(baseAddress + PEHeader->OptionalHeader.AddressOfEntryPoint);
    (*entry)((HINSTANCE)baseAddress, DLL_PROCESS_ATTACH, 0);
  • 免费使用的外部库,以解决进口。
  • 释放分配的内存。

MemoryModule

MemoryModule是C库,可用于从内存中加载的DLL。

该接口是非常类似的标准方法加载的库:

typedef void *HMEMORYMODULE;

HMEMORYMODULE MemoryLoadLibrary(const void *);
FARPROC MemoryGetProcAddress(HMEMORYMODULE, const char *);
void MemoryFreeLibrary(HMEMORYMODULE);

下载

最新的开发版本总是可以从Github抓住在http://github.com/fancycode/MemoryModule

所有标记的版本,可以从GitHub上下载。

已知问题

  • 所有的内存,并不受部分标志是得到承诺PAGE_READWRITE。我不知道这是否是正确的。

许可证

自从版本0.0.2中,MemoryModule库下发布的Mozilla公共许可证(MPL)。版本0.0.1已经发布的Unter较宽松通用公共许可证(LGPL)。

它提供的是不提供任何担保。您就可以使用您自己的风险。

端口

托马斯·海勒说MemoryModule py2exe的创建单个文件的可执行文件,从Python脚本。

马丁·Offenwanger做德尔福可在此页面的端口。

内存加载动态库 MemoryLoadLibrary 有例子。 /* * Memory DLL loading code * Version 0.0.3 * * Copyright (c) 2004-2013 by Joachim Bauch / mail@joachim-bauch.de * http://www.joachim-bauch.de * * The contents of this file are subject to the Mozilla Public License Version * 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is MemoryModule.h * * The Initial Developer of the Original Code is Joachim Bauch. * * Portions created by Joachim Bauch are Copyright (C) 2004-2013 * Joachim Bauch. All Rights Reserved. * */ #ifndef __MEMORY_MODULE_HEADER #define __MEMORY_MODULE_HEADER #include typedef void *HMEMORYMODULE; typedef void *HMEMORYRSRC; typedef void *HCUSTOMMODULE; #ifdef __cplusplus extern "C" { #endif typedef HCUSTOMMODULE (*CustomLoadLibraryFunc)(LPCSTR, void *); typedef FARPROC (*CustomGetProcAddressFunc)(HCUSTOMMODULE, LPCSTR, void *); typedef void (*CustomFreeLibraryFunc)(HCUSTOMMODULE, void *); /** * Load DLL from memory location. * * All dependencies are resolved using default LoadLibrary/GetProcAddress * calls through the Windows API. */ HMEMORYMODULE MemoryLoadLibrary(const void *); /** * Load DLL from memory location using custom dependency resolvers. * * Dependencies will be resolved using passed callback methods. */ HMEMORYMODULE MemoryLoadLibraryEx(const void *, CustomLoadLibraryFunc, CustomGetProcAddressFunc, CustomFreeLibraryFunc, void *); /** * Get address of exported method. */ FARPROC MemoryGetProcAddress(HMEMORYMODULE, LPCSTR); /** * Free previously loaded DLL. */ void MemoryFreeLibrary(HMEMORYMODULE); /** * Find the location of
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值