翻译——N种脱壳安卓恶意软件的方式

之前Anubis这个安卓恶意软件家族比较流行,而且通过官方的应用商店进行传播。

Android生态系统中的打包程序

下面说的打包程序就是我们常说的加固,但是呢,恶意软件也用,用于隐藏他们恶意的payloads。这包含反射,混淆,控制流平坦化和垃圾代码,当然Anubis 也是使用了这些来阻碍我们分析。

在运行时加载类

Android应用程序必须在AndroidManifest文件中定义其使用的服务,接收器和活动类才能使用它们。在Anubis示例中,很明显,清单文件中未定义的许多类仅存在于源代码中。

这意味着应在运行时将具有未定义类的文件加载到应用程序中。Android中有两种主要的运行时加载方式:

从文件:
1、API26之后,dalvik.system.DexFile.loadDex
2、dalvik.system.DexClassLoader
3、dalvik.system.PathClassLoader

从内存:
1、dalvik.system.InMemoryDexClassLoader(在恶意软件中不常见)

从文件加载需要在文件系统中存在一个dex / jar文件。Anubis解压缩加密的数据文件,然后删除解密的版本。后来,恶意软件继续将解密的dex加载到应用程序中。使用DexClassLoader加载后,恶意软件会删除解密的dex文件。跟踪dexClassLoader应该使加载例程清晰可见。由于dexClassLoader是dalvik.system的类,因此在代码中应包含“ dalvik.system.dexClassLoader”包,但找不到它。

反射

处理恶意软件时,另一个有用的方法是反射。反射是Java中的一个重要概念,它使您可以在不了解方法/类的情况下调用它们。有几种反映的类/方法。

  • java.lang.Class.forName
  • java.lang.ClassLoader.loadClass
  • java.lang.reflect.Method
  • java.lang.Class.getMethods

forName的用法示例

1
cObj = Class.forName("dalvik.system.dexClassLoader");

cObj变量保存dexClassLoader的类对象。这使程序可以调用任何给定类的方法。问题是找到对反射方法进行函数调用的位置。

使用Frida抓取packers

frida是几乎每个操作系统都支持的动态检测工具包。Frida使得可以注入一段代码来操纵目标程序并跟踪程序调用。在这种情况下,它将用于跟踪进行了哪些反射调用,从而分析线程。进行前面提到的函数调用时,将另外调用console.log。但是在此之前,让我们快速回顾一下如何在Android模拟器上设置Frida。

从以下位置下载适合您的仿真器的frida-server:(
例如Genymotion使用x86架构。)
https://github.com/frida/frida/releases。

1
2
3
4
5
adb push frida-server /data/local/tmp
adb shell
cd /data/local/tmp
chmod +x frida-server
./frida-server &

Frida工具在主机中安装

1
pip install frida-tools

设置完成后,我们可以编写一个脚本来挂钩目标方法。先定义一些类方法等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var classDef = Java.use('java.lang.Class');
var classLoaderDef = Java.use('java.lang.ClassLoader');
var loadClass = classLoaderDef.loadClass.overload('java.lang.String', 'boolean');
var forName = classDef.forName.overload('java.lang.String', 'boolean', 'java.lang.ClassLoader');
var reflect = Java.use('java.lang.reflect.Method')
var member = Java.use('java.lang.reflect.Member')
var dalvik = Java.use("dalvik.system.DexFile")
var dalvik2 = Java.use("dalvik.system.DexClassLoader")
var dalvik3 = Java.use("dalvik.system.PathClassLoader")
//var dalvik4 = Java.use("dalvik.system.InMemoryDexClassLoader")
var f = Java.use("java.io.File")
var url = Java.use("java.net.URL")
var obj = Java.use("java.lang.Object")
var fo = Java.use("java.io.FileOutputStream")

我们将使用此代码段更改方法的实现

1
2
3
4
5
class.targetmethod.implementation = function(){
console.log("[+] targetmethod catched !")
stackTrace()
return this.targetmethod()
}

console.log(“[+] {x} function catched !”) 将使我们能够查看该函数是否被调用。如果函数采用任何参数(例如字符串),则在分析过程中记录这些参数可能会有所帮助。然后,我们可以获得有关所处线程的更多信息。Frida可以调用任何Android函数,包括getStackTrace()。但这需要引用当前线程对象。让我们从获取线程类的实例开始:

1
2
var ThreadDef = Java.use('java.lang.Thread');
var ThreadObj = ThreadDef.$new();

ThreadObj保存Thread类的实例,currentThread()可用于获取线程。调用getStackTrace(),我们可以遍历stackElements来打印调用堆栈。

1
2
3
4
5
6
7
8
function stackTrace() {
console.log("------------START STACK---------------")
var stack = ThreadObj.currentThread().getStackTrace();
for (var i = 0; i < stack.length; i++) {
console.log(i + " => " + stack[i].toString());
}
console.log("------------END STACK---------------");
}

打印调用堆栈有助于识别反射和拆包机制的调用图。例如,dexClassLoader可能创建了反射。但是,当frida hook了dexClassLoader并打印调用堆栈时,我们可以在调用dexClassLoader之前看到这些函数。在应用程序的最开始就调用了解包例程。因此,应该尽快安装frida以赶上解包过程。幸运的是,frida中的-f选项使frida能够生成目标应用程序本身。frida接受带有-l参数的脚本。
frida -U -f appname -l dereflect.js
然后,frida等待用户输入继续。%resume将恢复该过程。完整脚本可在我的github存储库中找到。
https://github.com/eybisi/nwaystounpackmobilemalware/blob/master/dereflect.js

带stackTrace()的输出

您可以看到在write方法之前调用的函数。跟踪这些间隔函数后,您可以在它们之前看到RNlkfTEUX和lqfRafMrGew被调用。事实证明,它们是用于解密加密文件的非常重要的功能,稍后我们将再次介绍。

如何脱壳呢

  • 动态
    hooking:拦截file.delete (Java level),或者拦截unlink syscall (system level)
    从内存里dump:用gameguardian来转存内存或者使用自定义工具转储内存
  • 静态

    手动干(作者应该是写脚本的意思吧)

动态的拦截是最简单的办法

通过挂钩:Java级别

当我第一次遇到Anubis并意识到它正在删除文件时,我的第一个解决方案是挂钩到file.delete函数。

1
2
3
4
5
6
7
8
9
10
Java.perform(function() { 
var f = Java.use("java.io.File")
f.delete.implementation = function(a){
s = this.getAbsolutePath()
if(s.includes("jar")){
console.log("[+] Delete catched =>" +this.getAbsolutePath())
}
return true
}
})

这段代码始终将true返回给file.delete函数。截获后,我们就可以知道文件路径,获取到那个jar了

除此之外,我们还可以使用python调用frida使工作自动化,并浏览目标文件所在的文件夹。这些c&c服务器通常会生成数千个apk。由于它们每个都可以嵌入不同的IP地址,因此自动化工具可以使我们的生活更轻松。

具体参考这个:https://twitter.com/0xabc0/status/1072888987285630976

通过挂钩:系统级别

但是,如果恶意软件使用本机代码删除文件怎么办?我们不能总是钩在Java级别。我们需要更深入。

Unlink 函数有一个参数, 一个文件名的指针. 我们可以通过findExportByName来帮助我们hook. 代码来源于https://www.fortinet.com/blog/threat-research/defeating-an-android-packer-with-frida.html

但我稍微修改,会打印已删除的文件。

1
2
3
4
var unlinkPtr = Module.findExportByName(null, 'unlink');
Interceptor.replace(unlinkPtr, new NativeCallback( function (a){
console.log("[+] Unlink : " + Memory.readUtf8String(ptr(a)))
}, 'int', ['pointer']));

运行结果:

我们截获了unlink调用,因为我们的脚本只是用console.log替换了原始函数的代码,所以文件不会从文件系统中删除。

从内存dump

即使由于文件已加载到进程而从文件系统中删除文件,我们也可以从该进程的内存中获取已删除文件的痕迹。由于Android继承自Linux,因此我们可以使用/proc/pid文件夹为我们提供有关指定进程的内存区域的信息。让我们看一下cat /proc/pid/maps | grep dex过滤dex的目标。

我们发现了dex文件的踪迹。现在我们需要转储这些部分。

使用Gameguardian来转储内存:

这种方法是“作弊”,有一个称为GameGuardian的工具可用于游戏黑客。您可以使用GameGuardian做很多有趣的事情,但是我们现在仅使用转储机制。

让我们从安装和运行APK开始。然后启动GameGuardian,然后从左上方的按钮中选择应用程序名称。选择最右边的按钮及其下面的按钮。现在,您可以在菜单中看到转储内存选项。通过单击箭头按钮放置区域的十六进制代码或选择区域,然后按保存。

我们可以使用以下方法拉出转储的东西

1
adb pull /storage/emulated/0/packer .

然后您将在packer文件夹中看到2个文件

1
com.eqrxhpdv.cbunlkwsqtz-dfb5a000-e0080000.bin com.eqrxhpdv.cbunlkwsqtz-maps.txt

当使用file命令检查时,它会将我们的dex文件检测为数据文件,所以我们需要删除不属于dex文件的部分来修复。

使用自定义工具转储内存:

感谢@theempire_h,我们可以使用C程序转储目标应用程序的内存区域。
https://github.com/CyberSaxosTiGER/androidDump

1
2
3
4
5
adb push androidDump /data/local/tmp
adb shell
cd /data/local/tmp
chmod +x androidDump
./androidDump appname

它转储3个数据块

但是转储后,file命令仍然没有为我们提供正确的类型we事实证明,我们应该对文件进行一些修改。为了找到dex的魔术数字,我编写了此脚本。

https://github.com/eybisi/nwaystounpackmobilemalware/blob/master/deDex.py

1
2
3
4
5
6
7
8
9
10
11
import binascii
import sys
filename = sys.argv[1]
with open(filename, 'rb') as f:
content = f.read()
h = binascii.hexlify(content).split(b'6465780a')
h.pop(0)
h = b'6465780a' + b''.join(h)
dex = open(sys.argv[1][:-4]+".dex","wb")
dex.write(binascii.a2b_hex(h))
dex.close()

修复后我们就可以打开了

我们找到了失去了的class

静态方法:

这是一篇博客文章,从不同的角度解释了解包过程。

https://sysopfb.github.io/malware,/reverse-engineering/2018/08/30/Unpacking-Anubis-APK.html

我在stackTrace的帮助下找到了rc4密钥。但是显然,寻找^这个符号是从Anubis找到RC4例程的一种非常有效的方法。

在JADX中轻松找到rc4密钥,这里有个tips

  • 搜索 “% length”
  • 右键单击要使用的方法,然后点 find Usage
  • 下面的bArr2用作rc4密钥进行解密

有了密钥,我们可以从APK的images文件夹中解密加密的文件。脚本带有2个参数,即bArr2和加密的文件

https://github.com/eybisi/nwaystounpackmobilemalware/blob/master/anubis_manual.py

解密并解压缩后,我们得到了dex

提取配置文件后,还有一个步骤来获取c&c服务器的地址。恶意软件从电报地址的页面获取,并用ASCII字母更改汉字。然后,它处理base64字符串。解码base64后,它用于service解密使用rc4方案加密的数据。这是一个将中文字符解密为c&c地址的代码段。(就是将中文一一对应数字字母,还原出数字字母是base64加密的,解密一下就好)

https://github.com/eybisi/nwaystounpackmobilemalware/blob/master/solve_chinese.py

我设法用Androguard在没有在模拟器中运行APK,去解密了Anubis payloads。转储dex文件后,我的脚本将找到打印c2和加密密钥的config类。Config Class位于a,b or c 或者较新版本的ooooooooooooo{0,2}o中

通过检查类源代码中“ this”关键字的计数,我设法解密了所有版本的anubis。这是我的脚本的输出,用于从Anubis示例中获取c2和密钥。

结论

有很多解压缩安卓恶意软件和跟踪pack机制的方法,我们会在未来看到恶意软件使用dalvik.system.InMemoryDexClassLoader,如果使用此选项,则删除挂钩将无法捕获已删除的文件,因为一切都将在内存中完成,但是转储内存将捕获这些方法。知道不同的方式总是有帮助的。

参考链接

https://pentest.blog/n-ways-to-unpack-mobile-malware/

自愿打赏专区