前言
假期自学计划开启,就以分析 FART 源码开始。
FART 是看雪大牛 hanbingle 提出的一种基于主动调用开发的脱壳工具,工具的具体原理可以阅读帖子 FART:ART环境下基于主动调用的自动化脱壳方案,本文是结合该篇帖子和源码的学习笔记。
源码地址:https://github.com/hanbinglengyue/FART,该代码是基于 Android 6.0 源码基础上修改而成的,加入了主动调用并修复函数的模块。
代码结构
art/runtime
interpreter
interpreter.cc
native
dalvik_system_DexFile.cc
art_method.cc
art_method.h
frameworks/base/core/java/android/app
ActivityThread.java
libcore/dalvik/src/main/java/dalvik/system
DexFile.java
FART 大体上就修改了源码中的这六个文件,加上 fart.py
这个用于合并 Dex 结构体和 CodeItem 的脚本,共有七个文件。结合作者在帖子中的介绍,大概介绍下每个文件的作用:
-
DexFile.java
- 声明了 native 函数dumpMethodCode
,该函数的作用是构造主动调用链,完成方法体的dump。 -
dalvik_system_DexFile.cc
- 实现了dumpMethodCode
函数,该函数会去主动调用myfartInvoke
函数 -
interpreter.cc
- 增加判断,当处于<clinit>
阶段时,调用dumpDexFileByExecute
来进行 dump -
art_method.cc
- 增加了两个函数dumpDexFileByExecute
和dumpArtMethod
-
art_method.h
- 声明get_method_idx
,暴露出 dex_method_index_ -
ActivityThread.java
- 获得 ClassLoader,获得 mCookie,再利用 mCookie 获得所有的类,然后主动遍历类的所有函数,进而提取出每个函数体的 CodeItem,至于为何采用该类作为入口点,可以参考作者的解释。众所周知,对于一个正常的应用来说,最终都要由一个个的Activity来展示应用的界面并和用户完成交互,那么我们就可以选择在ActivityThread中的performLaunchActivity函数作为时机,来获取最终的应用的Classloader。选择该函数还有一个好处在于该函数和应用的最终的application同在ActivityThread类中,可以很方便获取到该类的成员。
CodeItem 抽取
从我对 Android 加固有限的认知来看,目前最为广泛普及的加固技术即为指令抽取,更为高级的 VMP 或者 Dex2C 由于性能问题并不会大范围应用,因此如果能有效地解决指令抽取问题,即可以解决 99% 的问题。
通常情况下,Java 类中的代码有两部分,一些代码只会在初始化阶段执行一次,因此针对 <clinit>
需要进行特判并在执行时直接抽取 CodeItem 部分,而其他部分可以在主动调用过程中进行抽取。
针对这两部分,作者设计了不同的 dump 逻辑,分别如下:
<clinit>
-Execute
=>dumpDexFileByExecute
- 正常函数 -
DexFile_dumpMethodCode
=>myfartInvoke
=>Invoke
=>dumpArtMethod
dumpDexFileByExecute
的逻辑如下,可以看到主要还是在执行时立刻保存 artmethod 所对应的 Dex 结构:
extern "C" void dumpDexFileByExecute(ArtMethod * artmethod)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
// 省略部分代码
if (szProcName[0]) {
const DexFile *dex_file = artmethod->GetDexFile();
const uint8_t *begin_ = dex_file->Begin(); // Start of data.
size_t size_ = dex_file->Size(); // Length of data.
// 继续省略代码
sprintf(dexfilepath,
"/sdcard/fart/%s/%d_dexfile_execute.dex",
szProcName, size_int_);
int dexfilefp = open(dexfilepath, O_RDONLY, 0666);
if (dexfilefp > 0) {
close(dexfilefp);
dexfilefp = 0;
} else {
dexfilefp =
open(dexfilepath, O_CREAT | O_RDWR,
0666);
if (dexfilefp > 0) {
write(dexfilefp, (void *) begin_,
size_);
fsync(dexfilefp);
close(dexfilefp);
}
}
}
}
继续看 dumpArtMethod
的逻辑:
extern "C" void dumpArtMethod(ArtMethod * artmethod)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
// 省略无关代码
if (szProcName[0]) {
// 省略代码
// 保存 Dex 结构
// 保存函数体
const DexFile::CodeItem * code_item =
artmethod->GetCodeItem();
if (LIKELY(code_item != nullptr)) {
int code_item_len = 0;
uint8_t *item = (uint8_t *) code_item;
if (code_item->tries_size_ > 0) {
const uint8_t *handler_data =
(const uint8_t *) (DexFile::
GetTryItems
(*code_item,
code_item->
tries_size_));
uint8_t *tail =
codeitem_end(&handler_data);
code_item_len =
(int) (tail - item);
} else {
code_item_len =
16 +
code_item->
insns_size_in_code_units_ * 2;
}
// 保存 codeitem 信息 <==> base64 形式
}
}
}
在正常保存 Dex 代码外,程序还会将一个函数的 CodeItem 抽取出来并保存。
编译运行
代码是基于 Android 6.0 的修改,因此我们可以自己编译 FART 工具并刷入自己的手机。然后找个带指令抽取的样本进行测试,脱壳结果如下:
root@angler:/sdcard/fart/de.k3b.android.androFotoFinder # ls -al
-rw-rw---- root sdcard_rw 10139916 1970-01-11 02:22 10139916_dexfile_execute.dex
-rw-rw---- root sdcard_rw 2499359 1970-01-11 02:25 1835412_5597.bin
-rw-rw---- root sdcard_rw 1936402 1970-01-11 02:26 1835412_5724.bin
-rw-rw---- root sdcard_rw 1832365 1970-01-11 02:26 1835412_5732.bin
-rw-rw---- root sdcard_rw 1835412 1970-01-11 02:24 1835412_dexfile.dex
-rw-rw---- root sdcard_rw 1835412 1970-01-11 02:22 1835412_dexfile_execute.dex
-rw-rw---- root sdcard_rw 270916 1970-01-11 02:22 270916_dexfile_execute.dex
-rw-rw---- root sdcard_rw 4307272 1970-01-11 02:22 4307272_dexfile_execute.dex
-rw-rw---- root sdcard_rw 4906 1970-01-11 02:25 5109544_5597.bin
-rw-rw---- root sdcard_rw 5109544 1970-01-11 02:25 5109544_dexfile.dex
-rw-rw---- root sdcard_rw 5109544 1970-01-11 02:22 5109544_dexfile_execute.dex
然后利用 adb 将文件 pull 出来后,利用官方提供 fart.py 脚本可以尝试合并 dex 文件和相应的函数指令,可以看到相应效果为显示修复前 smali 代码和修复后 smali 代码:
如果需要合并成新 dex 的话,需要结合 Dex 结构对脚本进行修改。