浅析Pe2shellcode

编者注:本文仅供学习研究,严禁从事非法活动,任何后果由使用者本人负责。

前言

众所周知,对shellcode免杀是很流行的技术,但是直接对exe的免杀方法相对稀缺,如果我们能将exe转为shellcode,然后用shellcode免杀那一套就会简单许多。所以shellcode转exe是一个很值得研究的课题。下面我将大概讲解一下pe2shellcode这个开源项目的思路。

Github项目地:

https://github.com/hasherezade/pe_to_shellcode。

代码分析

我们先看看它的项目结构,主要模块有Injector、Runshc、pe2shr。

图片

1 Injector

该文件夹下存在一个main.cpp。

图片

这是一个简单的远程shellcode注入的代码,用于将我们的恶意代码注入到其它进程。

关于进程注入这里就不赘述了。

2 Runshc

主文件main.cpp。

图片

这是个最基本shellcode加载器,可以看出来Injector和Runshc都是给PE转成shellcode之后测试用的,测试shellcode是否能成功运行。

然后就是libpeconv。

图片

这个工具也是非常经典,拿来用和学习pe结构都特别好(https://github.com/hasherezade/libpeconv),这个工具可以用来处理解析PE文件,可以用以替代windows的Createprocess制作Runshc和pe2shr。很巧的是,pe2shr里面的stub.bin是作者用汇编实现的一个简易版的libpeconv。

3 pe2shr

然后就是文章主角pe2shc,利用了stub32.bin、stub64.bin和main.cpp。

stub32.bin和stub64.bin是一个作者用汇编写的一个peloader。

图片

接着打开main.cpp看看作者怎么实现的pe2shellcode。

图片

前面就是对参数的处理,比如获取需要处理的原生pe路径,设置输出的shellcode路径,然后就是步入is_supporte_pe函数。

图片

可以看到有三点:

1、必须要有重定位表

2、不能是控制台应用程序

3、不能是.net程序,.net的pe结构不同之处在于pe头中的IMAGE_OPTIONAL_HEARDER:这个结构中的数据目录DataDirectory包括了映像文件中的CLR头的RVA和大小。而stub并没有处理.net形式PE的代码。

其实除了作者在代码里列出的三种pe无法执行之外,有些有tls表的pe以及某些使用了延迟重定位表的pe也是转换不了的(或者说转换了也运行不了),因为作者的stub并没有对这些表处理,后面讲到stub的时候就可以发现了。

图片

接下来将原生pe加载进内存处理后再将更改后的shellcode另存为(或者叫它pe?)。

关键是处理这一步:shellcodeify函数。

图片

可以看到大致逻辑是判断pe位数后获取资源里的资源段,这个资源段是pe2shc.exe里本身的资源,且x64和x86各有一套。

图片

RCData里有存储着stub,resourceID 101是32位的stub,102是64位的stub。

这个stub其实就是作者用汇编写的一段PEloader,后面会详细讲。

接下来分析overwrite_hdr这个函数。

图片

Raw是原生pe的大小,将最后调用的eax赋值成ImageBase+sizeof(原生pe),让eip跳到尾部的stub执行。

这里可以看到有一个redir_code,这个redir_code是用来覆盖原生pe头的,个人认为这个redir_code涉及的十分巧妙,我们来分析一下32位的redir_code。

redir_code32[] = "\x4D" //dec ebp   ‘M’"\x5A" //pop edx ’Z‘"\x45" //inc ebp   消除 dec ebp的影响"\x52" //push edx 消除pop edx 的影响"\xE8\x00\x00\x00\x00" //短call 下一条指令"\x58" // pop eax  把这条指令地址弹到eax"\x83\xE8\x09" // sub eax,9     eax-9指向的就是imageBase也就是加载进内存中pe的基址"\x50" // push eax (Image Base)  将eax压到栈顶"\x05" // add eax,   把shellcode地址放到eax当中   eax+ 原生PE的buffer大小就是shellcode的地址"\x59\x04\x00\x00" // value"\xFF\xD0" // call eax    将下一条指令压栈"\xc3"; // ret                       == // pop eip  又回到最初的起点

 \x4D\x5A对应的是PE文件的魔术字段“MZ”,作者将MZ当成指令处理,CreateProcess底层函数MiVerifyImageHeader检测到文件没有某些字段便会抛出异常不去执行,这些字段就包括魔术字段,也就是说,作者很巧妙地让转成shellcode的文件仍然能当作正常的PE文件运行,这种手法让我想到了0x0C0C0C0C,既可以是地址也可以是opcode。

MZ字符当成指令在msf的stager里也用到过。

图片

msf里是为了防止发生Bootstrap直接写入PE头部而导致破坏dos头的情况,这里就不作赘述了。

下图是windows针对PE头的必检字段。

图片

图片

图片

有些安全研究员会更改这些非必检字段制作畸形pe,一旦edr或者杀软的校验逻辑与windows不一致,那就很可能将其当成非可执行文件从而达到绕过检测的目的,但并不会影响其在windows上的正常运行。

"\x45" //inc ebp
"\x52" //push edx

接着这两条指令是为了平衡“M”,“Z”作为指令造成的影响,插在e_cblp上。

"\xE8\x00\x00\x00\x00" //call <next_line
"\x58" // pop eax

然后是call 0,意思是call 下一条指令的地址,也就是call pop eax的地址。

pop eax 把该指令地址弹到eax里。

"\x83\xE8\x09" // sub eax,9
"\x50" // push eax (Image Base)

sub eax ,9 就是让eax指向imageBase,把eax压栈。

"\x05" // add eax,
"\x59\x04\x00\x00" // value

前面讲过是将eax赋值成尾部stub的地址。

我们拿mimikatz_x86转shellcode进行验证。

图片

010editor打开 mimi86.exe,可以看到rdi_code覆盖了原生pe头,而"\x59\x04\x00\x00"也变成了\x00\x80\x09\x00。

图片

转到该地址,发现这确实是stu32.bin。

图片

图片

"\xFF\xD0" // call eax
"\xc3"; // ret      

​​​​​​接着最后两条指令跳到stub执行,然后ret 回到原来的地方。

stub32.bin

接着我们看一下stub32.bin,由于篇幅问题,我就不一个指令一个指令分析了。

可以看到asm文件包含了一个inc文件。

图片

Inc文件里把一些固定值用伪指令equ对应起来。

图片

像PAGE_EXECUTE_READWRITE equ 40h、lfanew equ 3ch,增强了代码的可读性

1 获取kernel32地址

我们看看第一段。

图片

很经典的shellcode写法,fs\gs寄存器寻找PEB基址,通过PEB找到ldr链,然后ldr链里存放着dll名称和地址,hldr_begin的功能就是寻找kernel32.dll的地址。

2 获取导出函数地址

图片

获取IMAGE_DIRECTORY_ENTRY_EXPORT地址->从IMAGE_DIRECTORY_ENTRY_EXPORT获取函数名地址(edAddressOfNames)->根据RVA在AddressOfNames表中的排序序号,获取AddressOfNameOrdinals表中相同排序序号的值->获取函数地址(edAddressOfFunctions)。

本质上实现了一个GetProAddress的功能。如下是转成C语言之后的代码。

DWORD GetFuncAddress() {DWORD dwProcAddress = 0;PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)hModule;PIMAGE_NT_HEADERS pNtHdr = (PIMAGE_NT_HEADERS)((DWORD)pDosHdr + pDosHdr->e_lfanew);PIMAGE_EXPORT_DIRECTORY pImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((DWORD)pDosHdr + pNtHdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);PDWORD pAddressOfFunc = (PDWORD)((DWORD)hModule + pImageExportDirectory->AddressOfFunctions);PDWORD pAddressOfName = (PDWORD)((DWORD)hModule + pImageExportDirectory->AddressOfNames);PWORD pAddressOfNameOrdinal = (PWORD)((DWORD)hModule + pImageExportDirectory->AddressOfNameOrdinals);if ((DWORD)lpProcName & 0xFFFF0000){DWORD dwSize = pImageExportDirectory->NumberOfNames;for (DWORD i = 0; i < dwSize; i++){DWORD dwAddrssOfName = (DWORD)hModule + pAddressOfName[i];int nRet = strcmp(lpProcName, (char*)dwAddrssOfName);if (nRet == 0){WORD wHint = pAddressOfNameOrdinal[i];dwProcAddress = (DWORD)hModule + pAddressOfFunc[wHint];return dwProcAddress;}}dwProcAddress = 0;}else{DWORD nId = (DWORD)lpProcName - pImageExportDirectory->Base;dwProcAddress = (DWORD)hModule + pAddressOfFunc[nId];}return dwProcAddress;
}

3 处理导入表

图片

循环遍历DLL导入表中的DLL及获取导入表中的函数地址-> 获取导入表中DLL的名称并加载DLL->判断是否加载了dll没有的话则加载之->获取OriginalFirstThunk以及对应的导入函数名称表首地址->获取FirstThunk以及对应的导入函数地址表首地址-> 判断导出函数是序号导出还是函数名称导出->获取函数地址。

转成C大概就是这样:

ImportTable()
{PIMAGE_DOS_HEADER DosAddr = (PIMAGE_DOS_HEADER)BaseAddr;PIMAGE_NT_HEADERS NtAddr = (PIMAGE_NT_HEADERS)(BaseAddr + DosAddr->e_lfanew);PIMAGE_IMPORT_DESCRIPTOR pImportTable = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)DosAddr +NtAddr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);char* DllName = NULL;HMODULE DllAddr = NULL;PIMAGE_THUNK_DATA lpImportNameArray = NULL;PIMAGE_IMPORT_BY_NAME lpImportByName = NULL;PIMAGE_THUNK_DATA lpImportFuncAddrArray = NULL;FARPROC FuncAddress = NULL;DWORD i = 0;while (TRUE){if (0 == pImportTable->OriginalFirstThunk){break;}DllName = (char*)((DWORD)DosAddr + pImportTable->Name);DllAddr = GetModuleHandleA(DllName);
if (NULL == hDll)
{hDll = LoadLibraryA(DllName);if (NULL == hDll){pImportTable++;continue;}
}i = 0;lpImportNameArray = (PIMAGE_THUNK_DATA)((DWORD)DosAddr + pImportTable->OriginalFirstThunk);lpImportFuncAddrArray = (PIMAGE_THUNK_DATA)((DWORD)DosAddr + pImportTable->FirstThunk);while (TRUE){if (0 == lpImportNameArray[i].u1.AddressOfData){break;}lpImportByName = (PIMAGE_IMPORT_BY_NAME)((DWORD)DosAddr + lpImportNameArray[i].u1.AddressOfData);if (0x80000000 & lpImportNameArray[i].u1.Ordinal){FuncAddress = GetProcAddress(DllAddr, (LPCSTR)(lpImportNameArray[i].u1.Ordinal & 0x0000FFFF));}else{FuncAddress = GetProcAddress(DllAddr, (LPCSTR)lpImportByName->Name);}lpImportFuncAddrArray[i].u1.Function = (DWORD)FuncAddress;i++;}pImportTable++;}return TRUE;
}
 

4 处理重定位

图片

获取IMAGE_DIRECTORY_ENTRY_RELOC地址->计算需要修正的重定位项(地址)的数目(reSizeofBlock)->计算需要修正的重定位项的地址->把移动的距离在原地址加上去->转到下一个节进行处理直到所有节处理完毕。

转成C就是这样:

ProcessRelocation()
{
PIMAGE_DOS_HEADER DosAddr = (PIMAGE_DOS_HEADER)BaseAddr;
PIMAGE_NT_HEADERS NtAddr = (PIMAGE_NT_HEADERS)(BaseAddr + DosAddr->e_lfanew);
PIMAGE_BASE_RELOCATION LocAddr = (PIMAGE_BASE_RELOCATION)(BaseAddr + NtAddr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
while ((LocAddr->VirtualAddress + LocAddr->SizeOfBlock) != 0)
{
WORD* LocAddrData = (WORD*)((PBYTE)LocAddr + sizeof(IMAGE_BASE_RELOCATION));
int NumberOfReloc = (LocAddr->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
int i;
for (i = 0; i < NumberOfReloc; i++)
{
if ((DWORD)(LocAddrData[i] >> 12)==3)
{
DWORD* pAddress = (DWORD*)((PBYTE)DosAddr + LocAddr->VirtualAddress + (LocAddrData[i] & 0x0FFF));
DWORD dwDelta = (DWORD)DosAddr - NtAddr->OptionalHeader.ImageBase;
*pAddress += dwDelta;
}
}
LocAddr = (PIMAGE_BASE_RELOCATION)((PBYTE)LocAddr + LocAddr->SizeOfBlock);
}

5 跳到OPE执行

图片

bool GetEntry()
{PIMAGE_DOS_HEADER DosAddr = (PIMAGE_DOS_HEADER)BaseAddress;PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(BaseAddress + DosAddr->e_lfanew);char* Entry = (char*)(BaseAddress + pNt->OptionalHeader.AddressOfEntryPoint);((void(*)())ExeEntry();return TRUE;
}

可以看到stub32.bin其实就是一个PEloader。

总结

pe2shellcode并不是真正意义上的将pe文件转成shellcode,它其实更像个加壳器,利用尾部的“peloader”加载头部的原生pe,并且让转换后的pe仍然是个合法的pe。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/624695.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于ssm的中文学习系统的设计与实现+jsp论文

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本中文学习系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据信息&am…

电脑桌面便签在哪设置?备忘录软件哪个好?

好记性不如烂笔头&#xff01;相信很多打工族在电脑面前办公的时候&#xff0c;都需要随时记录工作中的事项&#xff0c;有的用TXT记录&#xff0c;有的手写笔记&#xff0c;还有一些用电脑桌面便签类软件。而当我们待办事项繁多的时候&#xff0c;手写或文本记录并不能有效帮我…

去不了哈尔滨? 来看这里VR全景线上云体验

如果你无法亲自前往哈尔滨&#xff0c;那么不要失望&#xff0c;因为现在有一种全新的方式让你在家就能领略到哈尔滨的美丽景色。 冰城客户端、哈尔滨新闻网承办的“激情迎亚冬 冰雪暖世界——2024年哈尔滨冰雪乐园” 运用720云VR打造的沉浸式体验产品正式上线&#xff0c;将带…

[我的Rust库更新]get_local_info 0.2.3

今天收到西安城市开发者社区的文章收录通知&#xff0c;谢谢社区的肯定。 随即发布0.2.3版本&#xff0c;增加峰值算法。 get_local_info是一个获取linux本地信息的Rust三方库&#xff0c;其目标是降低获取本地linux系统信息的难度。支持银河麒麟10、UOS、鸿蒙等国产系统。 项…

利用HTML和CSS实现的浮动布局

代码如下 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><style>*{m…

MPU机制与实现详解

目录 MPU机制与实现详解 Partition元素-MPU Partition实现元素OSApplication Partition元素-RTE MPU机制与实现详解 1、freedom from interference 此概念来自ISO26262-1&#xff1a;多个元素之间没有可能导致违反安全目标的级联故障&#xff0c;称之为免于干涉。 在左侧的…

案例129:基于微信小程序的外卖商城平台设计与实现

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

pybind11实现numpy和OpenCV Mat的数据交互

1、编译安装pybind11 下载源代码&#xff1a;https://github.com/pybind/pybind11&#xff0c; 文档&#xff1a;https://pybind11.readthedocs.io/en/stable/compiling.html 解压后进入到命令行&#xff0c;如果有conda环境&#xff0c;就先进入想要的conda环境&#xff0c…

Poi实现根据word模板导出-文本段落篇

最近在做word模板导出的需求&#xff0c;本来意为是很简单&#xff0c;做起来才发现细节上有很多东西处理起来还是比较麻烦的&#xff08;客户要求太多&#xff01;&#xff01;&#xff01;&#xff09; 因此我把涉及到基于word模板导出的这部分整理了一下&#xff0c;大家直…

2024年,前端开发者,不妨看看鸿蒙开发

从大环境也好&#xff0c;行业发展也好&#xff0c;过去10年的前端技术晋级路径已经彻底失效。我在去年看了一眼考公的职位&#xff0c;没有任何一个岗位可以由前端胜任&#xff0c;而如果在后端领域比较资深的话&#xff0c;进可以做技术咨询&#xff0c;退可以考架构公务编。…

【基于 InternLM 和 LangChain 搭建你的知识库】学习笔记

学习参考文档【基于 InternLM 和 LangChain 搭建你的知识库】 学习参考链接【书生・浦语大模型实战营第三课作业(基础进阶)】 理论 实战 收集原始数据 收集2018年-2020年几年间的优秀数学建模论文 修改脚本文件&#xff0c;测试文件 作业 复现课程知识库助手搭建过程 La…

PDF文件的创建时间可以修改吗?分享你一个简单的小技巧

PDF文件的创建时间能修改吗&#xff1f; PDF文件的创建时间是指该文件首次被创建或生成的日期和时间。这个时间被存储在PDF文件的元数据中&#xff0c;可以通过某些软件查看和修改。 在某些情况下&#xff0c;我们需要将PDF文件的创建时间修改为特定的日期和时间。例如&#…

如何使用Java采集汽车之家车辆配置参数信息

目录 一、引言 二、采集工具选择 三、采集流程设计 1、确定采集目标 2、确定采集URL 3、发送HTTP请求 4、解析HTML页面 5、CSS选择器或jQuery选择器。 6、异常处理和日志记录 四、代码实现示例 五、结果与分析 六、结论 随着互联网的普及和信息技术的不断发展&…

人人都在用的PDF软件,也要接入ChatGPT了

随着人工智能技术的不断进步和发展&#xff0c;Chatbot技术的应用已经逐渐从娱乐和社交领域扩展到了更多的实际场景中。在办公软件领域&#xff0c;聊天机器人已经成为了提升工作效率、减少人力投入的重要工具&#xff0c;多家头部企业都在探讨将AI接入到软件及应用中的使用方案…

ctfshow元旦水友赛 misc 以假换真wp

记录一下根据官方wp的复现过程 目录 1.解压2.得到新的压缩包3.明文攻击4.上传baidu.jpg至百度网盘得到flag 1.解压 1&#xff09;下载题目&#xff0c;得到一个名为6.zip的文件 2&#xff09;尝试直接用360解压&#xff0c;发现需要密码 3&#xff09;那就常规思路用010打…

便携式VCI汽车售后诊断仪的优点

汽车诊断工具的优点主要包括以下几个方面&#xff1a; 故障检测智能化:汽车诊断I具通过与车辆的紧密连接,能够实时获取车辆的数据流Q参数和诊断信息,实现故障检测的智能化和精确化。快速诊断:汽车诊断I具能够在短时间内完成对车辆的全面检测,提供准确的故障诊断Q结果,帮助维修…

后端杂七杂八系列篇三

后端杂七杂八系列篇三 ① Spring Event用法① 同步代码的用法① 自定义事件② 定义监听器③ 定义发布者④ 发布消息后&#xff0c;接口收到消息 ② 异步代码的用法① 开启异步② 自定义事件③ 自定义监听器(推荐使用 EventListener 注解)&#xff0c;使用Async注解④ 定义发布者…

存储卷(数据卷)—主要是nfs方式挂载

1、定义 容器内的目录和宿主机的目录进行挂载 容器在系统上的生命周期是短暂的&#xff0c;一旦容器被删除&#xff0c;数据会丢失。k8s基于控制器创建的pod&#xff0c;delete相当于重启&#xff0c;容器的状态会恢复到原始状态。一旦回到原始状态&#xff0c;后天编辑的文件…

写一个判断鼠标进入方向切换图片的效果

直接看代码&#xff1a; <template><div class"mainrouter centerWindi"><div ref"mouse" class"mouse" mouseenter"handleMouse"></div></div> </template> <script setup> import { onMo…

erlang/OTP 平台(学习笔记)(四)

Erlang语言精要 Erlang shell 相较于日常惯用的系统&#xff0c;Erlang系统是一套更富交互性的环境。使用大部分编程语言时&#xff0c;要么把程序编译成OS可执行文件后运行&#xff0c;要么用解释器来执行一堆脚本文件或编译后的字节码文件。无论哪种情况&#xff0c;都是让…