一种绕开反病毒引擎的方法

2,734 阅读7分钟
原文链接: zhuanlan.zhihu.com

前段时间,我整出了一个 无需Root也能使用Xposed 的方法,简单来说,就是通过 双开技术(VirtualApp)结合本进程内的Java Method Hook(epic)实现加载Xposed模块,从而任意修改内部App的方法。项目刚 开源 不久,有大量用户指责 VirtualXposed 是恶意软件,有病毒!比如说:

以及 VirusTotal的检测结果:

(检测报告地址:VirusTotal )

我当时不以为然,所以没放在心上;毕竟没有什么病毒能这么明目张胆公开把自己的代码放出来给别人用吧?

直到后来,面对部分XDA用户的质疑,以及一些国外媒体的报道,我意识到,在部分用户眼中,「病毒」这个词对他们非常敏感,16家杀毒引擎说你有病毒,就算你觉得清者自清,别人也是敬而远之。于是我就开始了“洗脱罪名”之旅。

刚开始的时候我也是无从下手,去网上搜索这些病毒的名字,什么 `Android:Agent-QBQ [PUP]`, `Tool.SilentInstaller.6.origin` 基本没有什么有意义的结果;整个代码那么多,怎么判断是哪一部分有问题,我到底应该从哪里查起?

然后我就找了几个做过病毒分析的工程师聊了聊,他们告诉我,目前一般对Android APK的静态分析方法是采用特征分析,就是提取你APK安装包里面的特征,然后拿起和病毒库做比对,如果有匹配,那么就认为你可能是有害程序。

于是我就想,如果我是病毒分析工程师,我应该提取哪些特征呢?动态加载代码,运行可执行文件,敏感包名?顺着这个想法,我做了几件事:

  1. VirtualApp中有大量的 `odex`, `dex` 等字符串,针对这些串用硬编码的base64加密串替代,运行时解密然后使用。
  2. 把所有硬编码的类名,如微信,QQ里面的类(对这些App做过特殊处理)也用base64串代替。
  3. 修改manifest中注册的组件名,避免被匹配为VirtualApp。

这么做之后,依然有14家反病毒引擎认为是病毒(少了2家);当时我有点懵,心里一万只草泥马奔腾而过。。。

这么光靠猜实在不是办法,需要有一种科学的方法去分析和解决这个问题。既然我们知道杀毒引擎会匹配特征,正向去猜测它提取的特征行不通,那么我们可以反过来,看看什么“东西”会命中它的哪些特征。具体来说,APK文件分为几个部分:Manifest,资源,dex文件(Java代码),so文件(native 代码)以及其他文件;我们可以把APK中的一部分文件剔除,然后把这个缺失某部分的APK文件拿去给反病毒引擎检测,如果前后检测结果有不同,那么说明被剔除的那部分可能就是命中了某些病毒的特征,从而被误判。当然,这种方法不一定正确,这取决于杀毒引擎如何提取特征;但是可以一试。

尝试之后发现,只有java code才会导致杀毒引擎被判为病毒;其他之前猜想的manifest组件,so文件实际上都没有问题。那么,到底是哪部分Java代码被判为有问题呢?起初我打算从依赖库的角度去做代码剔除,不过立马就被否定了——虽然我依赖了十几个三方库,但是很显然,很有可能查到最后会发现 VirtualXposed 本身的代码才是被判为病毒的根源。最后,我使用的方法是这样的:


  1. 取出所有的Java类,根据全限定名排序。
  2. 按顺序删掉一半的类,然后打包成APK给反病毒引擎检测;如果有变化,那么说明被删的那部分类有问题;于是用被删的那部分类再做二分(再删除一半)继续检测,直到被锁定为一个或几个特定的类。如果没有变化,那用同样的办法对保留的那部分类做二分。
  3. 锁定到某些特定的类还不够,最好能锁定到具体的行。于是,我们可以继续以方法为粒度进行二分测试,直到定位到所有的方法。

但是,光有思路是不行的。具体如何实施这个方案呢?你要删除一半的类,怎么删?很显然,如果直接删Java源码,是无法编译成功拿到最终的apk文件的。我们应该对编译产物做处理。在D8编译器出来之前,APK的打包流程是这样的:


观察Java 源码以及第三方库被打包为dex的过程,我们可以知道,删除被编译过后的class文件是可以达到目的的;而如果要控制这个过程,gradle的Transform API是个极佳的选择。

另外,其实还有一条路:Android虚拟机有一套自己的虚拟机指令smali,我们也可以反编译APK得到smali文件,删掉部分smali文件然后重打包;这样也可以达到目的,不过由于 gradle Transform API的存在,删class文件的过程实现起来简单很多,并且可操作性也强很多;再结合字节码操作工具,我们可以对apk做各种修改来试探反病毒引擎。

最终使用的代码在这:fuck_anti_virus.gradle。这是一个gradle插件,在项目里面直接引用即可。


经过数天对16个反病毒引擎的探测,我解决了所有被误报的问题;最终的检测结果长这样:


不过这里面被误判为病毒的代码,实在是让人啼笑皆非。不妨来看一下:


1. 敏感API调用, `openDexFileNative`, `openDexFile`,用base64编码解决

2. 敏感API调用,`System.setProperty`,通过用反射调用绕过

3. 打印日志以及异常堆栈被误判(这个我没弄清楚原因,有点莫名其妙)

4. 替换Mainifest组件名

5. 修改类名,修改输入日志的姿势

如果第一个和第二个还能扯得上点关系,后面那几个以及我其他的修改,我想说,不是我针对谁,这些反病毒引擎简直都是智障。。。


然后分享一个最近无意中发现的代码:


这是某双开软件,动态加载的插件逆向之后的代码。刚开始看到的时候,我惊呆了。这尼玛是在直接查被双开的微信内部的数据库啊!我记得之前说过,双开软件是非常不安全的,双开软件本身对内部的APP有着完全的控制权,而且双开内部的APP对其他被双开APP的数据有着完全的访问权(比如说你在里面装了一个恶意软件,这个软件可以访问微信,支付宝的所有数据;所以把双开软件成为沙盒是不恰当的,这里面并不安全)。但是我没想到,竟然真的有人这么做,而且还是双开软件自己干的。但如果你把这个软件丢到杀毒软件里面检测呢,没错,很安全。就连 `Android:Agent-QBQ [PUP]`(Potentially Unwanted Program) 都没有。


最后我还是想说,现在的反病毒引擎真的是弱鸡。当然也可能是因为 VirusTotal是免费的,各大杀毒引擎给出的API都是比较弱鸡的版本,非常欢迎各路安全工程师前来打脸 ^_^ 还有,使用双开软件以及Xposed等功能的时候,一定要注意安全哦~