翻译:胖胖秦
预估稿费:200RMB
投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿
前言
IBM X-Force安全研究团队在调查一起针对巴西银行的远程恶意软件攻击事件中,发现了一个恶意的反杀毒驱动,它作为恶意金融软件的一部分。而这个恶意软件是用来盗取受害者的银行帐户。
解码反杀毒驱动
当试图了解未知软件的形为时,主要有两种方法:动态调试,我们执行代码并使用sysinternals工具和调试器来观察它的形为;静态分析,我们使用反汇编或反编译来检测它的代码。
在这种情况下,恶意软件作为真实攻击的一部分,时间是最重要的。我的目标是尽可能快的了解这个恶意驱动的行为,并最终减小检测和响应的时间。由于这个特殊的驱动不包含大量的代码,所以我决定静态分析它。
反汇编
我已经打开一个IDA的实例来分析一段从来没有见过的代码。
我注意到的第一个问题就是函数比较少,只有12个需要分析。
查看导入表和字符串来推测恶意软件的功能是一个好办法,但是在这种情况下,查看字符串似乎不太可能。
但是,导入表却恰恰相反
现在,我发现了字符串创建和转换函数,有趣的是还有注册表设置函数。恶意软件的作者可能使用多种方法来隐藏导入表,因此我们必须考虑上面截图中没有出现的其他功能。
因为代码不多,所以我决定从入口点DriverEntry开始分析。
第一个函数是由编译器生成的,与安全cookie有关。我们对此不感兴趣,略过它,继续往下看。
从上图可知,函数多次调用sub_4011EA,每次调用都使用不同的参数,参数的范围在1至5之间。
让我们花点时间检查sub_4011EA的函数调用图:
我们看到sub_401160使用了注册表设置函数,让我们检查一下写入了什么。并将这个函数重命名成Write_To_Registry。
汇编代码往往很长,难以理解,所以我使用IDA的Hex-Rays插件,它能将汇编代码转换成c代码。这能明显减少代码量的显示。但请牢记,生成的代码并不是准确的。
如上所示,Hex-Rays近乎完美的翻译了代码。现在我们在MSDN文档的帮助下可以轻松了解发生了什么。反杀毒驱动试图访问一个由参数指定的注册表键。如果该键存在,反杀毒驱动会尝试向它写入值,IDA标识为ValueName。从前一张图可知,将要写入的数据是“4”。
但是为什么只是近乎完美呢?请注意,ZwOpenKey接收的第一个参数应该是指向HANDLE的指针,但在第一行代码中却被强制转换成Unicode字符串指针。对一个有经验的研究员,这是小事一桩。通过快速查看MSDN的ObjectAttributes说明,我们发现ObjectName的参数类型的确是字符串指针。从另一方面来看,HANDLE是一个数值又是一个字符串,那么这里发生了什么?应该是IDA对变量类型识别有误。
往下看
现在,有两个关键的问题:
1. ObjectName从何而来
2. ValueName从何而来
为了回答第一个问题,我们往回看,检查谁调用了Write_To_Registry函数。
从上图可知,我们感兴趣的UnicodeString参数是由sub_4010EA传入的。而ValueName参数则是由sub_4010EA确定。现在我们只需要检查一个函数就可以回答上述问题。
sub_4010EA函数有两个参数:a1和a2。a2是一个指向字符串的指针,根据前面看到的对这个函数的调用,可以知道它实际上是一个输出参数。
函数的第一部分是一个循环,循环0x1A7次,将byte_403000缓冲区的值依次跟0x8进行异或,结果填充byte_4031B0缓冲区,这是简单的解密。Byte_403357是解密标志,代表是否已解密,以保证不会进行重复的XOR解密操作。
二进制Blob
我提取二进制数据并且解密它。这是结果:
数据似乎包含两部分。蓝色部分看起来像是二进制数据。红色部分看起来像是已加密的数据。
仔细观察sub_4010EA,发现解密后的缓冲区传递到sub_401000中,同时还传入了参数a1。sub_401000的返回值会传递过v3。而v3最终会转换成输出参数a2,幸运的是,这是我们感兴趣的值。
在这一点上,我很想改变我的方法。我可以在虚拟机中执行驱动,连接内核调试器并检查sub_401000的返回值。但是我不知道哪种方法更快,所以我决定坚持使用静态分析,至少现在是这样。
Sub_401000
下一个函数sub_401000更加复杂。首先,让我们看看它是如何被调用的:
在此次调用中,它接收三个参数:a1,已解密的缓冲区以及常数1
上面标记的代码表示在循环中匹配a2参数,每次循环的索引加7。在这一点上,我不想进一步的分析二进制数据的解析过程,因为它可能耗时太多。那么我们看看解析过程的下一步是什么?
在第一个循环内,v9是对应a2参数的位置索引。而在这个函数的尾部,我们看到对两个额外函数的调用,其中第二个函数的参数取决于XORed缓冲区的偏移,偏移由v9决定。
合理的推测
我推测sub_4010EA和sub_401000这两个函数都涉及到加密或解密。
让我们来看第一个:
头部标记的部分是初始化一个长度为256(0x100)的数组,数组的每个成员都等于其索引的值。等同于a [i] = i。据此推测,我强烈怀疑这个调用可能是RC4加密。
看了下时钟,已经是11:30。如果推测是错误的,我可能需要花更多的时间去分析,同时我将考虑进行动态调试。但是我决定再试试静态分析。我假设这个函数与RC4算法有关,为了得到解密的数据和解密的密钥,我重新检查了sub_401000函数。
根据RC4算法,我修改这个函数的变量名:
密钥的长度是8个字节。我找到了保存密钥的地址和密钥生成部分:
上述代码遍历XORed缓冲区的前8个字节,将每个字节与0x8进行异或,最终生成RC4密钥,注意,这里的密钥长度是8,这符合我们之前的推测。
剩下的就是找到需要解密的是什么数据。同样,要准确回答这一问题需要一段时间。目前我们所知的,或者通过推测,需要解密的数据可能是XORed_buff的某些部分。
一个捷径
为了节省时间,我决定不去关心哪些数据需要被解密。相反,我尝试去解密一切数据。RC4是流算法,因此每个字符的解密取决于算法的当前状态。要解密所有内容,我必须先从头开始开始解密缓冲区,直到0x1A7。
伪代码应该是这样的
外层循环从密钥的尾部开始,密钥位于XORed缓冲区的头部,依次处理缓冲区的其余部分。内部循环执行RC4解密。
正如所料,解密后的结果包含大量的垃圾数据,但是也含有许多的有价值的数据:
事实上,有五个这样的字符串; 每个字符串对应着不同的杀毒软件。字符串的路径表示杀毒软件驱动的服务路径。
进一退二
让我们回过头来看看第一个函数overwrite_AV_reg_service,它被调用5次,参数的范围是1-5:
函数接收参数,使用get_string函数从加密缓冲区中解密相应参数的字符串,并使用set_registry_value函数将数据写入已解密的注册表键中。
拼图
现在我们知道驱动的不同函数都做了什么,我们可以查看函数调用图:
main_func中的第一个函数被调用5次,参数为1-5。对于每个参数,get_string函数解密不同的反病毒软件相关路径。然后调用set_registry_if_key_exists覆写反病毒驱动的注册表路径,并阻止它加载到系统中。
禁用杀软,重启加载
我们还注意到,系统在加载完恶意驱动之后会重新启动。导致在系统重启之后不加载相关的反病毒软件,使得恶意软件能够畅通无阻的执行。
由于反病毒软件的自我保护功能,用户模式不能覆写反病毒软件的注册表项,所以才使用驱动;执行驱动可以在更高的权限下执行更多的恶意操作,所以防止这种恶意操作是相当困难的。
就是这样。我们现在知道驱动都做什么,我甚至还有几分钟的时间来吃午饭!
最终免责声明
如果要在动态调试还是静态分析中作出选择,我选择了后一种方法。因为它可以让我理解代码内部是如何工作的。通过动态调试虽然可以直接获取字符串,但是不知道它是怎么做到的。
在我进行的大部分的研究中,都没有使用Hex-Rays插件,因为插件具有不准确性。我显示代码大多数使用Hex-Rays,只是因为比较方便。
发表评论
您还未登录,请先登录。
登录