内存休眠时混淆
前言
2021年八月份cobalt strike发布的4.4版本中介绍了sleep mask/unmask技术,也就是内存休眠时加解密技术,可以加解密beacon的数据段和代码段,在一个月之后发布的4.5版本添加了对beacon堆内存加解密的支持。
这项技术的主要目的在于当beacon处于休眠状态时在内存中隐藏自身特征,避免被AV/EDR基于规则的扫描器发现。
这部分介绍可以在Cobalt strike的官方博客[1]中找到
友情提示:如果不想阅读本次内容,可以在此[2]获得所有代码 :)(仅支持64位)
概览
如下图所示就是基本的内存休眠时加解密示意图,因为我们不能在代码段中实现自身的加解密,所以我们需要一个“第三方”代码来协助我们完成这一过程,而一段可读可写可执行的堆内存和shellcode刚好可以满足这一需求
我们首先编写shellcode,在其中完成内存加密-休眠-再解密最后再返回到主程序继续执行。
ShellCode
如何使用C语言编写shellcode,已经有很多详细介绍文章,这里就简单提一下几个注意点
1:不要使用全局变量
2:不要使用字符串数组,改为使用字节数组
3:不要使用静态链接库,动态获取库函数地址
4:自定义入口函数代替mainCRTStartup
更为简单的是直接使用已有的shellcode编写模板,这里使用的是开源的Visual Stdio模板 [3](修改了一些东西)
-
• 有了模板之后,我们要先定义一个函数用来供主程序调用,最终也作为shellcode的入口函数执行
-
此函数接收模块基址和休眠时间(毫秒)作为参数传入,同时使用 __declspec 扩展属性告诉链接器应当将EntryPoint函数放置在代码段最前面(在上篇文章中有详细描述)
-
• 依据shellcode的编写规则,我们首先需要定义要使用的函数名字节数组以及动态获取函数地址
-
-
• 对指定模块进行加密不可避免的是需要解析PE,本文所有使用的解析代码均改写自
-
hasherezade的libpeconv [4],感谢其对社区做出的贡献。因为我们是对各个节加密,所以需要获得如下几个关键信息:
1:节的起始地址
2:节的大小
3:节的属性(方便后面进行恢复)
-
• 获得如上所述信息之后,正式进入加密阶段,由于各个节的属性通常不具备可写属性,所以我们需要先使用VirtualProtect将属性变为包含可写属性,之后使用基本的亦或对节进行加密
上述两部代码如下所示:
-
• 加密结束之后依据休眠时间参数调用sleep函数
wFunction.fnSleep(millseconds);
-
• 休眠结束后重复之前的步骤,依次对节进行解密,之后再次使用VirtualProtect恢复内存属性
-
编写完成之后,使用模板中提供format提取 .text段作为shellcode使用
主程序
主程序相对简单,我们只需要将刚才提取的shellcode作为全局变量内置在其中,然后申请一段可读可写可执行的内存,并将其拷贝其中,最后通过定义EntryPoint函数指针指向shellcode执行即可(类似一个基本的shellcode加载器)。
效果展示:
链接
[1]:https://www.cobaltstrike.com/blog/cobalt-strike-4-4-the-one-with-the-reconnect-button
[2]:代码仓库:https://github.com/jseclab/wechat_public
[3]:https://github.com/bypasscc/WinShellcode
[4]:https://github.com/hasherezade/libpeconv
原文始发于微信公众号(无名之):内存休眠时混淆技术一:相识
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论