《简单的vshell绕过火绒内存查杀》
1.起因
好久没有做vshell的免杀了,一直在做的是cobalt strike的免杀,因为算是第一个接触的C2我个人也是比较有念旧情节的,就一直没有换。
但是最近在帮朋友做免杀的时候发现了一个事情,vshell能正常上线 但是执行命令的时候会被秒杀,本着在学校没有事情做的原理,就研究一下吧。
2.开始
首先火绒内存查杀 高级设置全部打开

首先我们上线一个vshell,观察一下情况

我们可以看到 shellcode的生成并不像cobaltstrike那样存在stager和stagerless的选项,只有固定的一个stager。
我们申请tcp的监听器的,shellcode进行上线
实际测试文件管理操作不会被动态查杀

截屏操作不会被查杀

屏幕操作不会被查杀

当我们点击交互命令的时候,就会秒删

点击非交互式的时候 则会发现执行一个命令过后就会被删除
3.简单探测
通过上面试验的规则我们应该大概了解了一些情况 执行命令的时候会被删除。那么到底是为什么呢??
我们再次研究一下
首先关闭火绒(看看马子运行原理),看看这两个交互式和非交互式具体做了什么
交互式
我们使用SystemInformer.exe 查看进程属性,发现当启动交互终端的时候创建了一个子进程 winpty-agent.exe

可以发现放在了临时目录下


至于这个到底是什么??这个是一个开源的项目,网上也有很多介绍,我就不多赘述了
https://github.com/rprichard/winpty

关闭终端则这个进程关闭
非交互式
我们使用非交互式执行命令的时候,则没有这个进程

这就证明是这个上线的agent充当了执行的终端
查看内存属性

我们执行查看内存属性会发现,rwx两片 rx一片 并且这里没有物理文件支撑,很大概率就是我们的马子了
第一片rwx属性是Mapped代表的是共享的,这是我们小马存放的地址,为什么呢?因为我的加载器就是申请的映射内存然后写入执行的。
第二片属性是private私有的,并且点开一看

4d5a(MZ标志) 5045(PE标志),就是一个PE头特征,妥妥的PE结构啊 这里应该就是大马的位置了。
后面我拿ida-mcp配合claude逆向这个内存,发现确实是大马,并且里面一堆特征,比如有关oss的字符串,还有那个开源终端,全部能逆向出来。
这片内存是一个很敏感的内存,为什么呢?这就是杀软和edr的一个最基本的检测了,详细可以看鸭哥的文章

4.确定思路
现在思路就很明确了 交互式是上传一个终端程序,非交互式是自己上线的agent来做命令执行。
再加上 vshell的一个不开源的 go语言编译的(很难逆向),所以我不能改他上传终端那个逻辑,而是选择改内存。
那到底怎么做内存免杀呢?cs那里用的是sleepmask,在鹏瑶大佬的udrl发展那个议题中介绍了一些c2的演进在这里面也介绍了一下手法。鸭哥的文章也有介绍一下手法


当然火绒不需要这么费力,本着轻量级的,让用户良好体验的,火绒没有hook机制,也没有堆栈检测机制。
(当然我也只是一个小白,哪里说错了,大佬们也多多包涵)。
我一开始的思路就是找到大内存那片地址,然后抹除PE头就可以了。
我是直接hook了一下 VirtualAlloc 看大内存rwx申请的地址是什么
0000028780000000

然后我们可以将这个地址 存入到全局变量后面再将其抹除PE头,我们可以看到第二次调用virtualAlloc的时候就是申请的这个大内存,那么第一次呢??第一次是我写的hook_loader申请的

随后我写的逻辑是在第二次调用申请内存的时候记录地址和大小放在全局变量中,第三次申请的时候 直接开始抹除第二次申请的PE头,写的有点直接,但是这只是初步试探的过程,后续确

此时我们再运行可以看到成功抹除了

但是运行命令一样会被查杀

那么为什么呢???
5.查看安全日志确定突破口
第一个查杀

第二个内存查杀

第三个内存查杀

发现了吗???他们的虚拟地址是一样的都是0x0000000067700000
我们再运行一下看看该内存

也是很明显的一个PE结构的内存,但是内存属性为RW。研究一下这个吧
加上或这个条件

再看一下0x0000000067700000
1 | |
以可读可写的权限先以0000000067700000为基质申请了一片大内存 然后再在这之中申请了一个小内存
再加上内存中这是一个PE程序,他应该是先申请了一个rw的程序 再将PE写入内存然后伸展修复,再修复各节的属性。
这也是很常规的一个操作了
于是我们再hook一下 virtualprotec api来查看
1 | |
| # | 地址 | 大小 | 新属性 | 解读 |
|---|---|---|---|---|
| 3 | 0x67701000 |
~3.1 MB | 0x20 (EXECUTE_READ) |
代码段:可执行+只读,典型的 .text 段属性 |
| 4 | 0x67A1F000 |
~256 KB | 0x04 (READWRITE) |
数据段:可读写,可能是 .data 或堆 |
| 5 | 0x67A60000 |
~4.0 MB | 0x02 (READONLY) |
只读数据:可能是 .rdata 或资源段 |
| 6 | 0x67E5C000 |
2.5 KB | 0x02 (READONLY) |
小块只读数据 |
| 7 | 0x67E5D000 |
2.0 KB | 0x02 (READONLY) |
同上,可能是导入表/重定位表的一部分 |
| 8 | 0x67E5E000 |
~390 KB | 0x04 (READWRITE) |
可读写数据段 |
| 9 | 0x67EC0000 |
512 B | 0x02 (READONLY) |
极小块,可能是某个表/结构的头部 |
| 10 | 0x67EC1000 |
5 KB | 0x04 (READWRITE) |
小块可读写数据 |
| 11 | 0x67EC3000 |
512 B | 0x04 (READWRITE) |
同上 |
| 12 | 0x67EC4000 |
512 B | 0x04 (READWRITE) |
同上 |
这能证明 这是PE 文件内存映射后的节区属性修复。

我们尝试抹除这片区域的PE头,因为这个地址申请的是固定的0000000067700000,这样就更简单了。
我直接通过CreateTimerQueueTimer创建一个时间回调执行,过5秒后抹除 该地址PE头


运行程序看见Hack The Planet 这个标志就代码已经成功抹除


此时再执行命令就不会被查杀

这就是很简单的手法达成的内存查杀,当然实战中我们还是最后利用veh+sleep做成sleepmask这种吧,毕竟vshell好像没有睡眠机制,为什么呢???因为我hook了sleep发现并没有运行sleep
与此同时交互式的终端运行命令也修复了,交互式运行命令也不会被查杀

9
6.最终演示视频
相比较原来的免杀的加载器 只添加一个回调函数

一个时间序列5秒后调用,就能简单绕过内存查杀

7.番外
我问了一下我朋友,不仅仅vshell的这片地址申请固定,cs的stager有一片地址申请的也是固定的

当然后续vshell的内存免杀,就可以仿造着sleepmask那样 通过veh异常处理+CreateTimerQueue创建时间序列实现 内存属性浮动和休眠加密 执行解密。毕竟这样直接抹除PE头的手法还是太暴力了。哈哈