休止千鹤 | 我依旧是一名平凡的学生
上次写这个系列的文章已经过于很长时间了,整个系列也已经歪到研究静态绕过去了。
本文是对第三篇的补充。
我们之前介绍过:
在第三篇中,我们用了简单的思路:
就这样,隐藏了shellcode和IAT中的敏感函数,从而做到绕过部分杀软。
如果你对基础还不熟悉,可以看看之前的文章。
其实免杀还有一个思路:通常,由于自己制作的加载器小规模使用很难受到关注,所以自制工具。自己做的自己用。
2025年,那么之前提到的静态扫描规避思路真的还有任何实用价值吗?
当然啦,说回来,+1-1搞个exe还是太弱了。
基于上一篇文章的思路,我做了另一个工具。已经做成一个处理脚本放在GitHub上了:
GitHub: ShellcodeEncrypt2DLL
当然了,如果你想让工具活久一点,自制工具最好还是没事别放VT上了。这个我自己打算分享出来放Github上。被人丢VT上面是迟早的事。
那有小伙伴要问了:“啊,不放VT,还能放哪个上面测试呢?”
免费的antiscan.me已经没了。
用Kleenscan。他们有比较新的杀毒软件引擎。大概40个,他们不会把样本提交。
使用方法类似VT。有免费额度。
我放一个(有他们网站生成的推广链接)
Kleenscan
和之前那篇文章思路类似,我们对shellcode和敏感系统函数名称字符串进行了处理。只不过之前是+1-1,我们这次直接用AES进行加密,编译成DLL。(别看到DLL就没兴趣关网页了,下面我会解释为什么用DLL)
最初设计的时候考虑到为了一定程度上的防止被分析,所以我稍稍做了点”设计“。
生成的DLL分成了两种模式:
简而言之,这个脚本可以自动编译生成一个DLL. 其中的shellcode和需要用到的一些函数都会被AES加密。可以选择DLL中是否包含KEY。如果选择不包含key的话,那你需要在使用时想办法自己传入KEY。
当然这个东西主要还是在针对静态查杀的绕过。没有考虑内存扫描什么的。也就是说在运行起来以后内存里面还是有一个shellcode躺在内存里的…
+1-1所做的仅仅只是混淆,不那么直接的能被杀毒软件找到字符串识别。我们之前隐藏了shellcode和函数名称,很好。但是一旦被逆向,很轻松的就会发现这里只是对字节做+1-1,于是shellcode被逆向,文件标记为恶意,C2地址暴露。
用AES可以防止shellcode被逆向出来。当然,前提是不要把KEY硬编码进去。
我曾分析过一些XOR玩法,感觉更多的还是在做混淆而非加密。
常见的,用一个字节的密钥对着shellcode一个字节一个字节xor过去。密钥就一个字节也就256种可能,就不说你有没有密钥硬编码了,这暴力跑一遍其实也不费劲。
至于xor密钥重复使用,可能还是已知明文。
比如我看你这里加密字符串长度可能是VirtualAlloc,我直接明文和你密文xor一下就有可能得到正确密钥。
如果我猜对了,我会得到长度和字符串一致,每个字节都相同的输出,这个字节就是密钥。
如图,密钥k=69='E'
,明文p='VirtualAlloc'
,密文是c
。
那么你不打算复用,你用了一套PRG,套种子做密钥生成和shellcode,敏感函数长度一致的密钥,那也很麻烦…
另外,不负责任地说,XOR在循环里单字节处理一块内存似乎有时候也被当作特征。
虽然事后证明,其实AES也没那么好。系统AES的API调用在IAT中也被怀疑。
怎么办呢?
动态加载,函数名+1-1 XD
当时做出来,还是有3家杀毒软件报毒的。后面我有试图去绕过剩下的杀毒软件,
这三个:Cynet, Kaspersky, Rising。
怎么确定哪里敏感?
我用的方法是:在PE的段里填充成0. 比如原来的.text,我直接整个section全部填充覆盖成0x00
.
为了方便阅读,提前给各位复习一下:
测试:
后面填充过edata,没影响。
总之,kaspersky可能就是觉得.rdata里那些加密的数据可疑。而rising可能觉得.idata导入表是特征。
cynet我就不清楚了。
于是我把idata,rdata都填充掉。
这次这三个都没报毒。但是DeepInstinct报毒了…
我发现似乎每次都是Kaspersky先报毒,然后过一段时间Rising对相同样本才报。而且分类和Kaspersky大差不差。但是他们标记的特征是完全不同的。
当然Kaspersky是最坑的一个。我最初以为是.rdata的熵太高了。毕竟加密后的shellcode在里面。
如图,这里是shellcode,经过AES加密以后,数据几乎是随机的。熵7.5,逼近8。基本从熵就能看出来这里有加密数据。我想的是,这里搭配上解密函数调用,就很可疑。
熵逼近8, 意味着接近于一个字节的最大信息表达能力。一个字节有8bit,可以表达256种可能。这里的熵是这样来的$\log_2(256)=8$。 两个不同的字节,只有两种可能,1bit就能表示。所以两个不同的字节在这里计算出的熵是1.类似的,4个不同字节可以用2bit表达,熵是2. 而这里7.5几乎cover了256种可能了。正常的话(个人经验),英文字符串熵应该在4-5之间。所以我们可以在里面加入一样的字节来降低熵。大概需要shellcode两倍才能压到4.5左右。
于是我在shellcode里面每个字节之间添加一个字节的00。并且在结尾放上一段和shellcode等长的FF填充来进一步降低熵。
这样一折腾。恩…结果,没用。但是我没有移除这个功能。缺点就是:shellcode体积会变成原来的三倍。
所以到底是怎么回事?
Mingw-w64在编译后往这里塞了一个错误信息。里面有字符串VirtualProtect
。蛋疼的是这玩意儿我是真没找到怎么在编译的时候去掉。
所以我做了一个patch.py
。只能在编译好以后用python在.rdata里替换掉这个字符串了。
根据填充.idata后,Rising就安静了,说明特征可能在.idata里。简单测了一下,AES的API调用可能是问题根源。
那就,动态加载AES函数并且做了+1-1后让字符串认不出来后Rising绕过。
神奇的是Cynet同时也绕过了。Cynet貌似是依靠综合评分的。
还是那句话,其实对于静态扫描绕过而言,挺简单的。很多时候,字符串,函数名,藏好就行。
至于沙箱,内存扫描,以后再说。
过了一个星期,回头在看,这个工具也被针对了。已经被
Avast和AVG标记,最近又多了一个Trellix。
有些杀软迷和网管应该知道以后应该选择哪些杀毒软件了吧…
Views:
Comments
Furkan:
Isn't it possible to run the DLL on its own? Let's say I don't want to use it as "rundll32 <path_to_dll>,EPoint". Let's say I want to use it only as "rundll32 loader.dll". What can we do about this?
ReplyFurkan:
Can I use this as a standalone DLL just for the printnightmare vulnerability? That's what I was actually asking.
Replyrestkhz:(admin)
Furkan:
I will use standalone mode. Thank you very much for the information. Would it be better to run the "patch.py" script?
Reply