记录黑客技术中优秀的内容,传播黑客文化,分享黑客技术精华

从内存中加载dll

2021-08-25 16:47


STATEMENT

声明

由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,雷神众测及文章作者不为此承担任何责任。

雷神众测拥有对此文章的修改和解释权。如欲转载或传播此文章,必须保证此文章的完整性,包括版权声明等全部内容。未经雷神众测允许,不得任意修改或者增减此文章内容,不得以任何方式将其用于商业目的。



NO.1 前言

之前我们看的dll劫持,都是必须有个dll文件。然后主程序通过Windows提供的API(LoadLibrary, LoadLibraryEx)从文件系统上加载DLL文件。


现在去学习一种新的dll加载方式,从内存加载。


为什么需要这种加载方式呢?因为从文件系统上去加载dll,恶意的dll可能会被安全软件直接杀掉,然而内存加载就直接内存中展开,无需.dll文件存在,没有通过LoadLibrary等API直接加载,ProcessExplorer、procexp64等工具无法检测到这个dll。


那么从内存中加载dll的过程是什么样子的呢?从内存中加载DLL其实就是解析PE格式并将DLL内容按照该格式要求存放到进程的虚拟地址空间的过程。



NO.2 PE结构简述

那么就不得不介绍下PE格式,大多的可执行代码(.exe .dll .sys)的二进制文件都有一个通用的文件格式。

在程序员眼中

下面给出的所有结构都可以在头文件winnt.h 中找到。
比如下面用vs2019随便打开一个可执行代码的项目中 外部依赖项->winnt.h 可以看见有DOS header。

DOS header DOS stub

每一个PE文件都是以一个Dos程序开始的,一旦程序在Dos下执行,Dos就能识别出这是有效的执行体。


DOS头部分的存在见证了PE的强大兼容性。为了保持与16位系统的兼容,在PE里依旧保留了16位系统下标准可执行程序执行时,所必要的文件头部(DOS MZ)和指令代码(DOS Stub),不过在对从内存加载dll的研究时,只需要了解一个字段就行了。

· e_lfanew字段,指出了真正的PE文件头在文件中的位置 (它表示PE头的偏移位置,我们用这个字段来定位PE头的起始地址。)


DOS MZ头部占据了PE文件的头64个字节,描述它内容的结构如下:

typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header    WORD   e_magic;                     // EXE标志。“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 标识符    WORD   e_oeminfo;                   // OEM 信息    WORD   e_res2[10];                  // 保留字    LONG   e_lfanew;                    // PE头相对于文件的偏移地址  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

DOS Stub 部分是该程序在DOS系统下运行的指令字节码。它的大小不固定,因此DOS头的大小也是不固定的。

NT header (PE头)

PE头的结构体定义如下:
可以看出PE头包含三个字段Signature, FileHeader ,OptionalHeader

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

Signature

· 4个字节的标识符号(Signature) 可以用来检查PE内容是否合法。


FileHeader

· 20个字节的基本头信息(IMAGE_FILE_HEADER)FileHeader字段包含了可执行文件的物理格式或属性,如符号信息,所需CPU,文件信息标志(dll还是exe)


结构定义如下:

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

· 216个字节的扩展头信息(IMAGE_OPTIONAL_HEADER32) OptionalHeader字段包含一些逻辑上的信息,如操作系统版本、入口点、基地址、映像大小等。


下面代码是FileHeader的数据类型的定义。

结构定义如下:

typedef struct _IMAGE_OPTIONAL_HEADER64 {    WORD        Magic;    BYTE        MajorLinkerVersion;    BYTE        MinorLinkerVersion;    DWORD       SizeOfCode;    DWORD       SizeOfInitializedData;    DWORD       SizeOfUninitializedData;    DWORD       AddressOfEntryPoint;    DWORD       BaseOfCode;    ULONGLONG   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;    ULONGLONG   SizeOfStackReserve;    ULONGLONG   SizeOfStackCommit;    ULONGLONG   SizeOfHeapReserve;    ULONGLONG   SizeOfHeapCommit;    DWORD       LoaderFlags;    DWORD       NumberOfRvaAndSizes;    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;

OptionalHeader最后的DataDirectory包含了16(IMAGE_NUMBEROF_DIRECTORY_ENTRIES)IMAGE_DATA_DIRECTORY逻辑组件,但是对于从内存中加载DLL,我们只需要关注Index为0,1,5的组件。


0 Exported functions 导出功能
1 Imported functions 导入功能
5 Base relocation table 基址重定位表


Section header

Section头存储在 PE 头中的 OptionalHeader 结构之后,这一部分可以包含代码、数据、重定位信息、资源、导出或导入定义等。


Section头包含n个IMAGE_SECTION_HEADER结构体,具体的个数可以通过PEHeader.FileHeader.NumberOfSections字段得到。


微软提供了IMAGE_FIRST_SECTION宏来获取第一个IMAGE_SECTION_HEADER结构体的地址,这样我们就可以遍历到所有Section。


IMAGE_SECTION_HEADER结构体定义如下:

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;



NO.3 从内存加载dll过程

下面我们会去了解当发出api loadlibrary的时候,Windows操作系统做的步骤:


1、首先是寻找dll,找到之后检查DOS和PE header。


2、尝试在PEHeader.OptionalHeader.ImageBase位置分配PEHeader.OptionalHeader.SizeOfImage字节的内存区域。


3、解析Section header中的每个Section,并将它们的实际内容拷贝到第2步分配的地址空间中。拷贝的目的地址的计算方法为:

IMAGE_SECTION_HEADER.VirtualAddress偏移 + 第二步分配的内存区域的起始地址。


4、检查加载到进程地址空间的位置和之前PE文件中指定的基地址是否一致,如果不一致,则需要重定位。重定位就需要用到1.2节中的IMAGE_OPTIONAL_HEADER64.DataDirectory[5]也就是Base relocation table 基址重定位表。


5、加载该DLL依赖的其他dll,并构建"PEHeader.OptionalHeader.DataDirectory.Image_directory_entry_import"导入表。


6、根据每个Section的"PEHeader.Image_Section_Table.Characteristics"属性来设置内存页的访问属性;如果被设置为”discardable”属性,则释放该内存页。


7、获取DLL的入口函数指针,并使用DLL_PROCESS_ATTACH参数调用。



NO.4 反射dll

上面抽象的介绍了,从内存中加载dll的一些过程。从内存中加载dll有很多方法,常见的就有反射dll注入。以反射dll注入为例子,不仅了解反射dll注入手法,也会对从内存加载dll的理解更加深刻。

反射dll注入的利用

github的一个开源项目做的很好,其他的大部分开源项目都参考了这个项目。
https://github.com/stephenfewer/ReflectiveDLLInjection
在应用层面我们直接拿过来使用,用vs2019打开.sln

原本的dll程序是弹出一个窗口

我们再加点东西,比如去弹个计算器。下面是需要改动的内容。

然后生成dll

再创建一个.cna的脚本

alias reflective_dll {    btask($1, "Task Beacon to run reflective_dll...");    bdllspawn!($1, script_resource("reflective_dll.dll"), $null, "MessageBox", 1000);}

然后得去cs上load 这个脚本

反射dll注入的简单分析

通过对反射dll注入的利用,可以明显的看到,并没有将dll文件传入到被害机器,dll文件只是在我们本机中,但是却在被害机器上执行了dll中的恶意代码。这一方式 相比将恶意dll上传到被害机器上来说,动作要小的多。


并且在进程中看不到任何dll的进程。


再进一步的思考,反射dll是如何运作的,为什么在https://github.com/stephenfewer/ReflectiveDLLInjection是有个ReflectiveLoader.c和LoadLibraryR.c。

先让我们看看https://github.com/stephenfewer/ReflectiveDLLInjection/blob/master/dll/src/ReflectiveLoader.c和https://github.com/stephenfewer/ReflectiveDLLInjection/blob/178ba2a6a9feee0a9d9757dcaa65168ced588c12/inject/src/LoadLibraryR.c是如何实现的,当然这里面涉及的知识相当复杂,所以这里也只是简单去看看代码。

LoadLibraryR.c

https://github.com/stephenfewer/ReflectiveDLLInjection/blob/178ba2a6a9feee0a9d9757dcaa65168ced588c12/inject/src/LoadLibraryR.c


首先是初始化相关参数,然后用GetReflectiveLoaderOffset()去判断dll中有没有ReflectiveLoader这个导出函数。

简单跟下GetReflectiveLoaderOffset()

先去通过uiBaseAddress和Rva2Offset()函数获取dll的结构信息。
然后遍历所有的导出函数找到ReflectiveLoader()。

然后再进程中开辟一块可读可写可执行的内存空间,并将dll写入。

然后以ReflectiveLoader 调用作为线程函数创建远程线程,调用lpReflectiveLoader加载DLL。

ReflectiveLoader.c

https://github.com/stephenfewer/ReflectiveDLLInjection/blob/master/dll/src/ReflectiveLoader.c

大概是LoadLibraryA() :加载DLL所需的导入模块 ,getprocaddress 检索指定的动态链接库(DLL)中的输出库函数地址, virtualalloc分配内存,ntflushInstructionCache()函数:刷新指令,清空指定进程的指令缓冲区,让cpu执行新的指令等。


然后经历了一个类似初始化的过程,获取一些初始变量和地址。

第0 步: 先去寻找dll,获得dll的起始地址(类比从内存中加载dll过程的第一步)

uiLibraryAddress = caller();while( TRUE )  {    if( ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_magic == IMAGE_DOS_SIGNATURE )    {      uiHeaderValue = ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;      //检查DOS和PE header          if( uiHeaderValue >= sizeof(IMAGE_DOS_HEADER) && uiHeaderValue < 1024 )      {        uiHeaderValue += uiLibraryAddress;        // 如果我们已经找到一个有效的MZ/PE头 则break跳出        if( ((PIMAGE_NT_HEADERS)uiHeaderValue)->Signature == IMAGE_NT_SIGNATURE )          break;      }    }    uiLibraryAddress--;  }

我们可以看见有个caller()然后跟下函数

__declspec(noinline) ULONG_PTR caller( VOID ) { return (ULONG_PTR)_ReturnAddress(); }

这行代码作用就是ReflectiveLoader首先利用一个重定位技巧找到自身所在的大致位置。


通过e_lfanew字段然后向上遍历去寻找有效的DOS 头指,直到找到有效的MZ/PE 标头。


第一步: 处理内核导出,以满足加载器所需的功能。
我们可以看到经历一个非常多的过程去寻找一些东西,具体寻找哪些东西,这行代码告诉了我们。

// 当我们找到了所需的一切时,就停止寻找if( pLoadLibraryA && pGetProcAddress && pVirtualAlloc && pNtFlushInstructionCache )    break;

再根据这些参数依次往上回溯看具体是什么

pLoadLibraryA :存储LoadLibrary的VA(虚拟地址)pGetProcAddress :存储GetProcAddress的VA(虚拟地址)pVirtualAlloc :存储VirtualAlloc的VA(虚拟地址)pNtFlushInstructionCache :存储pNtFlushInstructionCache函数的VA(虚拟地址)

其实是需要用到kernel32.dll和ntdll.dll中的导出函数,所以需要先找到这些函数的va(虚拟地址)。这些系统模块都是已经加载了的,可以在进程环境信息块中找到其加载的位置。


可以看到代码中先用hash去比对:

然后再去获取这些导出函数的va值。


第二步和第三步:用pVirtualAlloc将映像装到一个新的位置,并加载所有的部分。将section复制到新内存的对应位置上。

遍历所有的sections 把所有的va加载进内存。

第四步: 处理pe文件中用到的动态链接库的集合,也就是导入表,遍历导入表,依次加载对应的dll以及需要的dll的导出函数,并填写对应的导入地址表也就是iat。


双层循环 外层while循环遍历所有导入表,内层while循环遍历每个导入表中的所有导入函数。

第五步: 在NT header结构中OptionalHeader最后的DataDirectory包含了16(IMAGE_NUMBEROF_DIRECTORY_ENTRIES)个IMAGE_DATA_DIRECTORY逻辑组件,其中第5个组件是 Base relocation table 基址重定位表。


然而第五步的操作就是,遍历每一个重定位表,然后并且遍历每个重定位表中的表项,根据其重定位的类型,执行重定位的操作。


我们可以看到,这里同样用两层循环实现。

第六步:调用新加载的dll的入口点的虚拟地址。


首先是获取dllmain的入口点的地址,并且通过pNtFlushInstructionCache()函数去刷新指令集。

uiValueA = (uiBaseAddress + ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.AddressOfEntryPoint);

第7步:返回新入口点的地址,这样可以随时调用dllmain()



NO.5 总结

通过对代码的大致分析,我们会搞清楚这个反射dll的实现以便更加深入理解从内存中加载dll的手法。


LoadLibraryR.c(注射器):主要做的事情就是将dll文件写到目标的进程空间,然后获取导出函数ReflectiveLoader,创建远程线程,执行反射加载函数。


ReflectiveLoader.c(反射加载器):主要做的就是在被注入进程的新建线程中,把之前内存中的dll找到,并且搬到新建线程中,重定位,找到dll的入口函数地址。


《windows PE 权威指南》

https://blog.csdn.net/china_jeffery/article/details/79867801

https://www.joachim-bauch.de/tutorials/loading-a-dll-from-memory/

https://payloads.online/archivers/2020-03-02/1

http://blog.opensecurityresearch.com/2013/01/windows-dll-injection-basics.html

https://bbs.pediy.com/thread-266929.htm
https://www.cnblogs.com/iBinary/p/7653693.html





RECRUITMENT

招聘启事

安恒雷神众测SRC运营(实习生)
————————
【职责描述】
1.  负责SRC的微博、微信公众号等线上新媒体的运营工作,保持用户活跃度,提高站点访问量;
2.  负责白帽子提交漏洞的漏洞审核、Rank评级、漏洞修复处理等相关沟通工作,促进审核人员与白帽子之间友好协作沟通;
3.  参与策划、组织和落实针对白帽子的线下活动,如沙龙、发布会、技术交流论坛等;
4.  积极参与雷神众测的品牌推广工作,协助技术人员输出优质的技术文章;
5.  积极参与公司媒体、行业内相关媒体及其他市场资源的工作沟通工作。

【任职要求】 
 1.  责任心强,性格活泼,具备良好的人际交往能力;
 2.  对网络安全感兴趣,对行业有基本了解;
 3.  良好的文案写作能力和活动组织协调能力。


简历投递至 

bountyteam@dbappsecurity.com.cn

设计师(实习生)

————————

【职位描述】
负责设计公司日常宣传图片、软文等与设计相关工作,负责产品品牌设计。

【职位要求】
1、从事平面设计相关工作1年以上,熟悉印刷工艺;具有敏锐的观察力及审美能力,及优异的创意设计能力;有 VI 设计、广告设计、画册设计等专长;
2、有良好的美术功底,审美能力和创意,色彩感强;

3、精通photoshop/illustrator/coreldrew/等设计制作软件;
4、有品牌传播、产品设计或新媒体视觉工作经历;

【关于岗位的其他信息】
企业名称:杭州安恒信息技术股份有限公司
办公地点:杭州市滨江区安恒大厦19楼
学历要求:本科及以上
工作年限:1年及以上,条件优秀者可放宽


简历投递至 

bountyteam@dbappsecurity.com.cn

安全招聘

————————

公司:安恒信息
岗位:Web安全 安全研究员
部门:战略支援部
薪资:13-30K
工作年限:1年+
工作地点:杭州(总部)、广州、成都、上海、北京

工作环境:一座大厦,健身场所,医师,帅哥,美女,高级食堂…

【岗位职责】
1.定期面向部门、全公司技术分享;
2.前沿攻防技术研究、跟踪国内外安全领域的安全动态、漏洞披露并落地沉淀;
3.负责完成部门渗透测试、红蓝对抗业务;
4.负责自动化平台建设
5.负责针对常见WAF产品规则进行测试并落地bypass方案

【岗位要求】
1.至少1年安全领域工作经验;
2.熟悉HTTP协议相关技术
3.拥有大型产品、CMS、厂商漏洞挖掘案例;
4.熟练掌握php、java、asp.net代码审计基础(一种或多种)
5.精通Web Fuzz模糊测试漏洞挖掘技术
6.精通OWASP TOP 10安全漏洞原理并熟悉漏洞利用方法
7.有过独立分析漏洞的经验,熟悉各种Web调试技巧
8.熟悉常见编程语言中的至少一种(Asp.net、Python、php、java)

【加分项】
1.具备良好的英语文档阅读能力;
2.曾参加过技术沙龙担任嘉宾进行技术分享;
3.具有CISSP、CISA、CSSLP、ISO27001、ITIL、PMP、COBIT、Security+、CISP、OSCP等安全相关资质者;
4.具有大型SRC漏洞提交经验、获得年度表彰、大型CTF夺得名次者;
5.开发过安全相关的开源项目;
6.具备良好的人际沟通、协调能力、分析和解决问题的能力者优先;
7.个人技术博客;
8.在优质社区投稿过文章;


岗位:安全红队武器自动化工程师
薪资:13-30K
工作年限:2年+
工作地点:杭州(总部)

【岗位职责】
1.负责红蓝对抗中的武器化落地与研究;
2.平台化建设;
3.安全研究落地。

【岗位要求】
1.熟练使用Python、java、c/c++等至少一门语言作为主要开发语言;
2.熟练使用Django、flask 等常用web开发框架、以及熟练使用mysql、mongoDB、redis等数据存储方案;
3:熟悉域安全以及内网横向渗透、常见web等漏洞原理;
4.对安全技术有浓厚的兴趣及热情,有主观研究和学习的动力;
5.具备正向价值观、良好的团队协作能力和较强的问题解决能力,善于沟通、乐于分享。

【加分项】
1.有高并发tcp服务、分布式等相关经验者优先;
2.在github上有开源安全产品优先;
3:有过安全开发经验、独自分析过相关开源安全工具、以及参与开发过相关后渗透框架等优先;
4.在freebuf、安全客、先知等安全平台分享过相关技术文章优先;
5.具备良好的英语文档阅读能力。


简历投递至

bountyteam@dbappsecurity.com.cn

岗位:红队武器化Golang开发工程师

薪资:13-30K
工作年限:2年+
工作地点:杭州(总部)

【岗位职责】
1.负责红蓝对抗中的武器化落地与研究;
2.平台化建设;
3.安全研究落地。

【岗位要求】
1.掌握C/C++/Java/Go/Python/JavaScript等至少一门语言作为主要开发语言;
2.熟练使用Gin、Beego、Echo等常用web开发框架、熟悉MySQL、Redis、MongoDB等主流数据库结构的设计,有独立部署调优经验;
3.了解docker,能进行简单的项目部署;
3.熟悉常见web漏洞原理,并能写出对应的利用工具;
4.熟悉TCP/IP协议的基本运作原理;
5.对安全技术与开发技术有浓厚的兴趣及热情,有主观研究和学习的动力,具备正向价值观、良好的团队协作能力和较强的问题解决能力,善于沟通、乐于分享。

【加分项】
1.有高并发tcp服务、分布式、消息队列等相关经验者优先;
2.在github上有开源安全产品优先;
3:有过安全开发经验、独自分析过相关开源安全工具、以及参与开发过相关后渗透框架等优先;
4.在freebuf、安全客、先知等安全平台分享过相关技术文章优先;
5.具备良好的英语文档阅读能力。

简历投递至

bountyteam@dbappsecurity.com.cn



END

长按识别二维码关注我们



知识来源: https://mp.weixin.qq.com/s?__biz=MzI0NzEwOTM0MA==&mid=2652493809&idx=1&sn=dd8cad54a79f11e97cd386d3aced2a12

阅读:15787 | 评论:0 | 标签:内存

想收藏或者和大家分享这篇好文章→复制链接地址

“从内存中加载dll”共有0条留言

发表评论

姓名:

邮箱:

网址:

验证码:

公告

永久免费持续更新精选优质黑客技术文章Hackdig,帮你成为掌握黑客技术的英雄

求赞助求支持💖

标签云