网站建设、公众号开发、微网站、微商城、小程序就找牛创网络 !

7*24小时服务专线: 152-150-65-006 023-68263070 扫描二维码加我微信 在线QQ

无线安全团结互助,让我们共同进步!

当前位置:主页 > 技术资讯 > 网络安全 > 无线安全 >

我们的优势: 10年相关行业经验,专业设计师量身定制 设计师一对一服务模式,上百家客户案例! 企业保证,正规流程,正规合作 7*24小时在线服务,售后无忧

如何从内存加载动态链接库(DLL)

文章来源:重庆网络安全 发布时间:2020-03-08 02:59:58 围观次数:
分享到:

摘要:如何从内存加载动态链接库(DLL)。

Windows可执行文件-PE格式


  首先我们看一下pe的结构


                DOS headerDOS stub            

                PE header            

                Section header            

                Section 1            

                Section 2            

                . . .            

                Section n         

  下面给出的所有结构都可以在头文件winnt.h中找到。


DOS header


  DOS header仅用于向后兼容。它位于DOS stub之前。


  Microsoft定义DOS标头如下:

typedef struct _IMAGE_DOS_HEADER {// DOS .EXE标头    WORD e_magic; //Magic number    字e_cblp; //文件最后一页上的字节    字e_cp; //文件中的页面    WORD e_crlc; //Relocations    字e_cparhdr; //段落中header的大小    字e_minalloc; //所需的最少额外段落    字e_maxalloc; //所需的最大额外段落数    WORD e_ss; //初始(相对)SS值    WORD e_sp; //初始SP值    WORD e_csum; //校验和    WORD e_ip; //初始IP值    WORD e_cs; //初始(相对)CS值    字e_lfarlc; //重定位表的文件地址    WORD e_ovno; //覆盖数    WORD e_res [4]; //保留字    WORD e_oemid; // OEM标识符(用于e_oeminfo)    WORD e_oeminfo; // OEM信息;特定于e_oemid    字e_res2 [10]; //保留字    LONG e_lfanew; //新的exe标头的文件地址  } IMAGE_DOS_HEADER,* PIMAGE_DOS_HEADER;


PE header


  PE header包含有关可执行文件中不同部分的信息,这些不同部分用于存储代码和数据或定义从其他库导入或由此库提供的导出。


  定义如下:

typedef struct _IMAGE_NT_HEADERS {    DWORD签名;    IMAGE_FILE_HEADER FileHeader;    IMAGE_OPTIONAL_HEADER32可选标题; } IMAGE_NT_HEADERS32,* PIMAGE_NT_HEADERS32;


 FileHeader中描述的physical 文件的格式,例如目录符号和其他信息:

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;


 此OptionalHeader中包含的信息逻辑库的格式,包括所需的操作系统版本,内存要求和入口点:

typedef struct _IMAGE_OPTIONAL_HEADER {    //    //标准字段。    //WORD    Magic;    BYTE    MajorLinkerVersion;    BYTE    MinorLinkerVersion;    DWORD   SizeOfCode;    DWORD   SizeOfInitializedData;    DWORD   SizeOfUninitializedData;    DWORD   AddressOfEntryPoint;    DWORD   BaseOfCode;    DWORD   BaseOfData;    //    // NT其他字段。    //    DWORD   ImageBase;    DWORD   SectionAlignment;    DWORD   FileAlignment;    WORD    MajorOperatingSystemVersion;    WORD    MinorOperatingSystemVersion;    WORD    MajorImageVersion;    WORD    MinorImageVersion;    WORD    MajorSubsystemVersion;    WORD    MinorSubsystemVersion;    DWORD   Win32VersionValue;    DWORD   SizeOfImage;    DWORD   SizeOfHeaders;    DWORD   CheckSum;    WORD    Subsystem;    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;


 DataDirectory目录包含16个(IMAGE_NUMBEROF_DIRECTORY_ENTRIES定义库的逻辑组件)条目:

blob.png

 对于导入DLL,我们仅需要描述导入表和基本重定位表的条目。为了提供对导出功能的访问,需要导出条目。


Section header


  段头存储在PE头中的OptionalHeader结构之后。Microsoft提供了宏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发出API调用时,Windows基本上执行以下任务:

blob.png

 在以下段落中,将描述每个步骤。


分配内存


  由于Windows提供了保护这些内存块的功能,因此必须使用VirtualAlloc来保留/分配该库所需的所有内存。 这对于限制对内存的访问(例如防止对代码或常量数据的写访问)是必要的。


  OptionalHeader结构定义该库所需的存储块的大小。如果可能,必须将其保存在ImageBase指定的地址上:

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


 如果保留的内存与ImageBase中指定的地址不同,则必须执行以下基本重定位。


复制sections


  保留内存后,即可将文件内容复制到系统中。 必须评估section header,以确定文件中的位置和内存中的目标区域。


  复制数据之前,必须首先提交内存块:

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


Base relocation


  库的代码/数据部分中的所有内存地址都相对于ImageBase在OptionalHeader中定义的地址进行存储。如果无法将库导入此内存地址,则必须调整引用=> relocated。文件格式通过将有关所有这些引用的信息存储在基本重定位表中来帮助实现此目的,该表可在OptionalHeader的DataDirectory的目录条目5中找到。


  该表由一系列此类结构组成

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


 它包含(SizeOfBlock-IMAGE_SIZEOF_BASE_RELOCATION)/ 2个条目,每个条目为16位。高4位定义重定位的类型,低12位定义与VirtualAddress的偏移量。


  似乎在DLL中使用的唯一类型是

 

  IMAGE_REL_BASED_ABSOLUTE

  用于填充。

  IMAGE_REL_BASED_HIGHLOW

  将ImageBase和分配的内存块之间的增量添加到在偏移处找到的32位。


解决导入部分


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

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;


 名称条目描述了偏移到库的名称(例如,以空字符结尾的字符串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添加到上面的保护标志中。


Notify library


  最后要做的是调用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);

 之后,我们可以像使用任何普通库一样使用导出的函数。


导出功能


  如果要访问库导出的函数,则需要找到符号的入口点,即要调用的函数的名称。


  OptionalHeader中DataDirectory的目录条目包含有关导出函数的信息。定义如下:

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;

 首先要做的是将函数名称映射到导出符号的序列号。因此,只需并行遍历AddressOfNames和AddressOfNameOrdinals定义的数组,直到找到所需的名称。


  现在,您可以通过评估AddressOfFunctions数组的第n个元素来使用序列号读取地址。


释放


  要发布自定义加载的库,请按照下列步骤操作


  调用入口点以通知库有关分离的信息:

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

  用于解析导入的免费外部库。


  释放已分配的内存。


内存模块


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


  此接口与加载库的标准方法非常相似:

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

本文由 重庆网络安全 整理发布,转载请保留出处,内容部分来自于互联网,如有侵权请联系我们删除。

相关热词搜索:内存 加载动态链接库 DLL 重庆网络安全

上一篇:映射攻击者活动技术:LNK文件构建关联用户搜索
下一篇:绕过Android SDK网络安全配置:Frida脚本利用,测试脚本分析脚本的运行机制

热门资讯

鼠标向下滚动