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. 良好的文案写作能力和活动组织协调能力。
简历投递至
设计师(实习生)
————————
【职位描述】
负责设计公司日常宣传图片、软文等与设计相关工作,负责产品品牌设计。
【职位要求】
1、从事平面设计相关工作1年以上,熟悉印刷工艺;具有敏锐的观察力及审美能力,及优异的创意设计能力;有 VI 设计、广告设计、画册设计等专长;
2、有良好的美术功底,审美能力和创意,色彩感强;
3、精通photoshop/illustrator/coreldrew/等设计制作软件;
4、有品牌传播、产品设计或新媒体视觉工作经历;
【关于岗位的其他信息】
企业名称:杭州安恒信息技术股份有限公司
办公地点:杭州市滨江区安恒大厦19楼
学历要求:本科及以上
工作年限:1年及以上,条件优秀者可放宽
简历投递至
安全招聘
————————
公司:安恒信息
岗位: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.具备良好的英语文档阅读能力。
简历投递至
岗位:红队武器化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.具备良好的英语文档阅读能力。
简历投递至
END
长按识别二维码关注我们