查看原文
其他

来自高纬的对抗:定制ART解释器脱所有一二代壳

r0ysue OPPO安全应急响应中心 2023-08-22





以上文章由作者【r0ysue】的连载有赏投稿,共有五篇,每周更新一篇;也欢迎广大朋友继续投稿,详情可点击OSRC重金征集文稿!!!了解~~
温馨提示:建议投稿的朋友尽量用markdown格式,特别是包含大量代码的文章




缘·起

拜读ThomasKing的那篇著名文章《来自高纬的对抗 - 逆向TinyTool自制》之后,对编译源码和修改源码来进行高维度对抗、进而实现降维打击的思路产生了浓厚的兴趣。

大佬在文中修改安卓内核源码,实现了在内核层面对app的内存数据进行dump;在内核里用jprobe来监控syscall系统调用,即使加固厂商使用静态编译的bin文件或通过svc汇编指令在用户态直接进行系统调用时,用户态hook已经失效并且没有意义的情况下,从内核里打出一份log,为分析app的行为提供依据。

据。

<6>[34728.283575] REHelper device open success!
<6>[34728.285504] Set monitor pid: 3851
<6>[34728.287851] [openat] dirfd: -100, pathname /dev/__properties__, flags: a8000, mode: 0
<6>[34728.289348] [openat] dirfd: -100, pathname /proc/stat, flags: 20000, mode: 0
<6>[34728.291325] [openat] dirfd: -100, pathname /proc/self/status, flags: 20000, mode: 0
<6>[34728.292016] [inotify_add_watch]: fd: 4, pathname: /proc/self/mem, mask: 23
<6>[34729.296569] PTRACE_PEEKDATA: [src]pid = 3851 --> [dst]pid = 3852, addr: 40000000, data: be919e38

本系列文章五篇的核心灵感也是来源于此,从高纬度发起降维攻击,让低纬度手段无法或难以对抗,分别立意于:

1.《来自高纬的对抗:定制ART解释器脱所有一二代壳》

2.《来自高纬的对抗:魔改XPOSED过框架检测(上)》

3.《来自高纬的对抗:魔改XPOSED过框架检测(下)》

4.《来自高纬的对抗:定制安卓内核过反调试》

5.《来自高纬的对抗:替换安卓内核解封Linux内核命令》

接下来我们来逐个分析详细思路和技术实现的细节。

FART简介

如果还有人不知道FART是啥,在这里稍微科普下,FARTART环境下基于主动调用的自动化脱壳方案FART的创新主要在两个方面:

  • 之前所有的内存脱壳机都是基于Dalvik虚拟机做的,比如F8LEFT大佬的FUPK3,只能在安卓4.4之下使用,FART开创了ART虚拟机内存脱壳的“新纪元”,上至最新的安卓10甚至还在preview的安卓11都可以。

  • ART虚拟机中彻底解决函数抽取型壳的问题;构造主动调用链,主动调用类中的每一个方法,并实现对应CodeItemdump,最终实现完整dex的修复和重构。

详细的介绍和源码下载地址当然是在寒冰大佬的github:https://github.com/hanbinglengyue/FART,以及他在看雪的三篇文章。

  • 组件一:可以整体dump目前已知的所有整体型壳,也就是通杀一代壳;对于未做函数体清场的二代壳也被整体扒下来了,这部分也占很大比例;

  • 组件二:构造主动调用链,欺骗壳去自己解密函数体,然后dump函数体,通杀二代壳;

  • 组件三:将解密出来的函数体与类和函数名索引一一对应,恢复函数体可读性;

本文的基本流程为:

  • 介绍编译AOSP源码的流程

  • 在源码中添加FART脱壳代码继续编译

  • 实现脱壳机镜像并刷入手机

  • 用这个脱壳手机来脱壳并dump和修复函数体

  • 实现通杀一代整体型壳和二代函数抽取型壳的目标

FART脱壳机从系统框架层源码的高维、来“降维”打击App用户层的低维技术,对于实际逆向分析工作帮助重大,可谓在ART虚拟机上的一项重大突破。

本文所涉及的环境、实验数据结果及代码资料等都在我的Github:https://github.com/r0ysue/AndroidSecurityStudy上,欢迎大家取用和star,蟹蟹。

编译AOSP源码

其实更加喜欢在Kali Linux上进行操作,显得更像一名“黑客”,只是Kali上的openjdk最小只支持到8,而我们编译安卓6必须要openjdk7,所以只能选择Ubuntu 1604 LTS了。

在官网上下载


注意下载完成后检查下md5是否匹配。

使用该镜像新建虚拟机时,有一点就是硬盘要给300G,这项操作不会真的占用300G的硬盘,真实占用空间会随着虚拟机内部系统占用空间大小而浮动,笔者最终编译完也只不到百G

其余CPU建议给宿主机真实核心数的一半,内存建议10G及以上即可。如果内存还不够,可以增加一些swap,详见这篇文章。

系统开机后就可以把源码包拷贝进去,源码包位于百度云盘中的aosp_pure_souce_code目录下,解压完成后有md5.txt文件的,记得也要比对md5噢。

解压要用到7z,所以安装一波常用工具:

$ sudo apt install p7zip-full htop jnettop git

安装完成后进行解压:

$ 7z x aosp600r1.7z

新开一个终端,准备编译的环境:

$ apt install bison tree curl
$ dpkg --add-architecture i386
$ apt update
$ apt install libc6:i386 libncurses5:i386 libstdc++6:i386 libxml2-utils

安装openjdk-7

sudo add-apt-repository ppa:openjdk-r/ppa
sudo apt-get update
sudo apt-get install openjdk-7-jdk

在源码目录中,下载设备驱动:

$ cd /home/vx-r0ysue/Desktop/aosp600r1
$ wget https://dl.google.com/dl/android/aosp/broadcom-hammerhead-mra58k-bed5b700.tgz
$ wget https://dl.google.com/dl/android/aosp/lge-hammerhead-mra58k-25d00e3d.tgz
$ wget https://dl.google.com/dl/android/aosp/qcom-hammerhead-mra58k-ff98ab07.tgz

解压和安装设备驱动,安装时最后要键入I ACCEPT来表示接受条款:

$ tar zxvf broadcom-hammerhead-mra58k-bed5b700.tgz
$ ./extract-broadcom-hammerhead.sh
$ tar zxvf lge-hammerhead-mra58k-25d00e3d.tgz
$ ./extract-lge-hammerhead.sh
$ tar zxvf qcom-hammerhead-mra58k-ff98ab07.tgz
$ ./extract-qcom-hammerhead.sh

最后就是设置编译环境变量、选择编译设备、开始编译三板斧:

$ source build/envsetup.sh
$ lunch (选择17`hammerhead,也就是N5`)
$ make -j8

然后就会开始编译,可疑使用htop命令来查看CPU、内存等信息。

编译过程中出现一次unsupported reloc 43的错误,可以通过如下命令解决:

$ cp /usr/bin/ld.gold prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/x86_64-linux/bin/ld

最终等到编译成功即可。

添加FART脱壳代码

FARTgithub发布页上可以得到FART的源码,我们来解压并下载。

$ git clone https://github.com/hanbinglengyue/FART.git
$ cd FART/
$ unzip FART_6.0_sourcecode.zip
$ tree
.
├── art
│   └── runtime
│   ├── art_method.cc
│   ├── art_method.h
│   ├── interpreter
│   │   └── interpreter.cc
│   └── native
│   └── dalvik_system_DexFile.cc
├── FART_6.0_sourcecode.zip
├── fart.py
├── frameworks
│   └── base
│   └── core
│   └── java
│   └── android
│   └── app
│   └── ActivityThread.java
├── libcore
│   └── dalvik
│   └── src
│   └── main
│   └── java
│   └── dalvik
│   └── system
│   └── DexFile.java
├── LICENSE
└── README.md

17 directories, 10 files

需要打开图中这六个文件,到末尾去敲两行Enter即可,目的是为了保证这六个文件的修改日期是新于安卓源码的,否则无法被编译系统识别为新代码,也就不会激活重新编译的流程。

$ nano art/runtime/native/dalvik_system_DexFile.cc
PageDown键翻到最下页
敲两行Enter
Ctrl X 关闭文件
y 保存

然后直接用文件管理器打开这个目录,将这个目录下的artframeworklibcore三个目录直接复制黏贴到安卓源码根目录中去,文件夹一路点击Merge,文件一路点击Replace

注意,我们这里可以直接复制黏贴的原因在于,使用的是跟作者一模一样的安卓源码版本,即6.0.0_r1;如果读者使用的不是aosp6.0.0_r1,切记千万不可以直接替换,会造成编译无法通过,即使编译通过,最严重情况下可能手机会变砖。

然后就可以到安卓源码目录下,sourcelunchmake三板斧了,流程和命令与上一节相同,大概需要几十分钟至一两小时,视电脑性能而定。

编译完该终端不要关闭,该终端中已经将随着源码一起编译出来的fastboot添加到了路径中,可以直接用该终端进行刷机。

随着源码一起编译出来的fastboot是对Nexus5手机的兼容性最好的。如果是官网下载的最新版的fastboot可能无法兼容较老设备。

下载官网相同版本刷机包,解压:

$ wget https://dl.google.com/dl/android/aosp/hammerhead-mra58k-factory-ccbc4611.zip
$ unzip hammerhead-mra58k-factory-ccbc4611.zip

打开①处的压缩包,用②处新编译出来的各种img替换掉压缩包内的img镜像,直接拖过去就可以。

最终在刚刚编译完源码的终端中,直接进行刷机。注意要确保手机连接到虚拟机,并且成功被虚拟机识别喔。

如果遇到虚拟机内部的Ubuntu不认手机的情况,即fastboot devices命令返回是no permissions,可以按照这篇51-android来设置一下系统的手机型号识别即可。

刷机完成重启之后,手机再次连接到虚拟机里的Ubuntu系统,可以看到在sd卡根目录下已经有了fart文件夹,里面已经有一些脱下来的系统应用的dex,而且终端也是有root权限的。

到这里我们的源码编译和加入脱壳机代码就结束了,接下来进入脱壳阶段。

FART完整脱壳步骤

跟作者沟通后发现目前在开放源码的版本之后,作者又做了更新和更改,最新的版本是在pixel上的8.0,我们以最新版本为例,来演示脱壳的全过程。

首先用jadx打开我们的测试app看下,这明显是个加壳了的app,具体哪家有经验的读者一眼其实就会看出。

  • 安装App,给SD卡读写权限

App安装到手机上:

# adb install aipao.apk
Performing Streamed Install
Success

建议使用较新的Pixel手机(谷歌代号:Sailfish),其采用的UFS磁盘系统速度会非常快,还在用Nexus 5做调试机的可以升上来了。

然后先不用打开app,先去应用的权限设置中,将存储卡读写权限打开,如果权限没有打开,则无法将脱下来的文件,写到/sdcard/fart/的目录中去。

还有一点需要注意的就是,FART脱壳机对App进行脱壳时,是不需要连接外网的。如果担心脱壳的完整性,也可以连接WIFI上网,此时会发现一个系统bug就是系统自带的输入法会崩溃,其实安装个第三方输入法就可以了,比如搜狗输入法。

接下来就是点击App,启动它,系统就会自动开始脱壳。脱壳的过程中可以使用adb logcat |grep com.aipao.hanmoveschool来看下后台的日志,FART中的日志信息非常多,比如当前正在对哪个类进行加载和调用,类后面的数字代表当前类里面函数的个数,都会有日志输出。具体可以参考笔者写的另一篇源码解读的文章:《FART源码解析及编译镜像支持到Pixel2(xl)》。

如果日志输出中断可以多试几次,或者有时候出现很慢也可以多等一会儿。

虽然此App需要联网才能正常运行,但是其实在它启动的一瞬间,我们的脱壳流程已经结束了,并不会影响到具体的脱壳。在后续主动调用其每一个方法的流程中其实也并不需要联网。

如果其有远程动态下发的热补丁,其实FART也不会对这些插件dex进行主动调用,而只会主动调用壳修正后的Classloader中的dex。面对这种场景应该如何处理呢?1. 可以结合frida来遍历Classloader,主动送入FART的主动调用中;2. 也可以修改源码,在DexClassLoader以及InMemoryClassLoader中添加上主动调用的函数。当然这些得读者手动来实现了。

这时候进入手机的/sdcard/fart文件夹中,就可以找到以脱壳App的包名来命名的文件夹,在文件夹里就放着脱下来的dex,和方法体bin文件(里面放着脱下来的函数体CodeItem)。

该文件夹中的文件种类分以下几种:

  • excute关键字的dex文件是在excute脱壳点脱下来的dex文件,关于该脱壳点的更多信息可以看《拨云见日:安卓APP脱壳的本质以及如何快速发现ART下的脱壳点》,里面介绍了具体的实现思路和流程;

  • excute关键字的classlist_execute.txt文件,该文件是dex中所有类的类列表,类名是签名形式的以L开头,表示它是一个类,$后面是类中的方法。

文件名前面的1349780dex文件的大小,有同样的文件名开头说明这俩文件是一对的。

所以在检索类或者类中的方法时,或者定位哪个类在哪个txt当中时,应该检索txt文件,而不是dex文件,可以使用grep -ril 'MainActivity' ./*.txt这样的命令。因为dex是二进制文件,当grep检索到一些不可见字符或者特殊字符串时就会中断而退出检索,txt就不会有这样的问题。

  • dexfiledex文件就是在主动加载dex文件时脱下来的,关于这个脱壳点的详细介绍可以看《FART正餐前甜点:ART下几个通用简单高效的dump内存中dex方法》,同样,开头的3925368正是dex的文件大小。

  • ins_bin文件,它跟带同样大小的dexfiledex文件是一对的,ins_后面的5208代表线程的ID,这个其实就不用管了。

dexfiledexexecutedex有什么区别呢?其实只是在不同的时间点脱下来的而已,execute要早一些,而dexfile的时机较晚。所以在实际操作中要么两者文件是一模一样的,要么dexfile的会更全一些,可能有些壳已经恢复了一些函数体。

有时候可以观察到不仅是3027,还会有个4794的线程ID的bin文件,其实是因为FART的脱壳线程是加在ActivityThread里的performLaunchActivity函数中的,也就是说每呈现一个Activity,都会新建一个线程来脱壳;也就是说,如果在进入MainActivity之前,如果有一个过渡动画的Activity的话,那么就会创建多个主动调用的线程。这里因为4794启动的较晚,所以也是较晚结束,这两个ins.bin文件其实是没有区别的,因为是对同一个dex进行的主动调用。如果App打开再关闭,再打开,那就会出现多个ins也很正常。

接下来就是等FART运行结束,如果是较老的手机,比如Nexus 5,可能需要等好几个小时甚至一个晚上,Pixel一般几分钟到几十分钟就会跑完。日志中3027线程出现如图所示的fart run over,则说明FART跑完了,3925368_ins_3027.bin对应是个有效的ins函数体文件。

如果点击App进行登录进入另一个Activity的话,则肯定又会创建新的脱壳线程,以此类推,所以脱壳时尽量少操作。而且这也是不要使用FART手机用于日常测试的主要原因,每个Activity都在脱壳,其实是奇卡无比的。

对于只是采用了整体加载保护技术的一代壳来说,这时候其实完成脱壳的操作了,而且FART选择的脱壳时机点是极其刁钻的,execute函数是个内联函数,一代壳是不可能通过hook的技术绕过这里的,目前来看是没有对抗的可能性,当然目前也不存在只采用一代壳这一种技术来进行保护的加固厂商了,一般都会结合多种技术进行加固。

还有就是如果某些抽取型壳,在App运行时或执行时动态恢复函数体,App运行一段时间之后内存中的dex可能就比较完整了,并且没有再将函数体“清场”,这时候如果dump的时机选择的更晚一些,也是可以将其完整的dump下来的。

FART完整修复步骤

在修复文件的选择上,首先其实只需要看有bin函数体文件的,没有bin文件的dex是处于不同Classloader中的dex,那些是些系统库或者壳本身,不是我们关心的对象。

也就是对于我们刚刚脱壳完成的App来说,其他dex都不用看了,只需要最终3925368_dexfile.dex3925368_classlist.txt和出现fart run over的线程脱壳线程函数体文件3925368_ins_3027.bin三个文件,因为它只有这一个bin

可以先打开dex来看下,比如随便看个md5函数,可以看到函数体都被抽空了,全部都是nop,这就是个典型的函数抽取的技术。

如果打开没有bin文件的dex来看的话,那就是基本上都会是系统框架层的dex,这些其实跟App没有关系。FART为了提升效率,并不会对系统框架库里的方法进行主动调用,所以不存在方法体bin文件。

接下来就是使用fart.py脚本来修复。

# python fart.py -d 3925368_dexfile.dex -i 3925368_ins_3027.bin >> repired.txt

注意此python的版本为python2.7

fart.py文件是个单线程文件,比较慢,需要多等待一会儿,修复出来的repaired.txt高达99m

等待修复完成来看repired.txt文件,还是那个md5的类。可以发现虽然md5类的构造函数只有八个字节,却也被抽取了,下面部分就是修复好的。

可以看最重要的md5函数部分,修复前和修复后的对比是非常强烈的,直接就是从无到有的体验。

DirectMethod:java.lang.String com.aipao.hanmoveschool.MD5::md5(java.lang.String)

before repire method+++++++++++++++++++++++++++++++++++

registers_size :0000000d: 13
insns_size :0000005a: 90
debug_info_off :03ffff00: 67108608
ins_size :00000001: 1
outs_size :00000002: 2
tries_size :00000001: 1
insns :00222774: 2238324
tries :00222828: 2238504
handlers :00222830: 2238512
00222774: 1200 |0000: const/4 v0 , 0
00222776: 1100 |0001: return-object v0
00222778: 0000 |0002: nop
0022277a: 0000 |0003: nop
0022277c: 0000 |0004: nop
0022277e: 0000 |0005: nop
00222780: 0000 |0006: nop
00222782: 0000 |0007: nop
00222784: 0000 |0008: nop
00222786: 0000 |0009: nop
00222788: 0000 |000a: nop
0022278a: 0000 |000b: nop
0022278c: 0000 |000c: nop
0022278e: 0000 |000d: nop
00222790: 0000 |000e: nop
00222792: 0000 |000f: nop
00222794: 0000 |0010: nop
00222796: 0000 |0011: nop
00222798: 0000 |0012: nop
0022279a: 0000 |0013: nop
0022279c: 0000 |0014: nop
0022279e: 0000 |0015: nop
002227a0: 0000 |0016: nop
002227a2: 0000 |0017: nop
002227a4: 0000 |0018: nop
002227a6: 0000 |0019: nop
002227a8: 0000 |001a: nop
002227aa: 0000 |001b: nop
002227ac: 0000 |001c: nop
002227ae: 0000 |001d: nop
002227b0: 0000 |001e: nop
002227b2: 0000 |001f: nop
002227b4: 0000 |0020: nop
002227b6: 0000 |0021: nop
002227b8: 0000 |0022: nop
002227ba: 0000 |0023: nop
002227bc: 0000 |0024: nop
002227be: 0000 |0025: nop
002227c0: 0000 |0026: nop
002227c2: 0000 |0027: nop
002227c4: 0000 |0028: nop
002227c6: 0000 |0029: nop
002227c8: 0000 |002a: nop
002227ca: 0000 |002b: nop
002227cc: 0000 |002c: nop
002227ce: 0000 |002d: nop
002227d0: 0000 |002e: nop
002227d2: 0000 |002f: nop
002227d4: 0000 |0030: nop
002227d6: 0000 |0031: nop
002227d8: 0000 |0032: nop
002227da: 0000 |0033: nop
002227dc: 0000 |0034: nop
002227de: 0000 |0035: nop
002227e0: 0000 |0036: nop
002227e2: 0000 |0037: nop
002227e4: 0000 |0038: nop
002227e6: 0000 |0039: nop
002227e8: 0000 |003a: nop
002227ea: 0000 |003b: nop
002227ec: 0000 |003c: nop
002227ee: 0000 |003d: nop
002227f0: 0000 |003e: nop
002227f2: 0000 |003f: nop
002227f4: 0000 |0040: nop
002227f6: 0000 |0041: nop
002227f8: 0000 |0042: nop
002227fa: 0000 |0043: nop
002227fc: 0000 |0044: nop
002227fe: 0000 |0045: nop
00222800: 0000 |0046: nop
00222802: 0000 |0047: nop
00222804: 0000 |0048: nop
00222806: 0000 |0049: nop
00222808: 0000 |004a: nop
0022280a: 0000 |004b: nop
0022280c: 0000 |004c: nop
0022280e: 0000 |004d: nop
00222810: 0000 |004e: nop
00222812: 0000 |004f: nop
00222814: 0000 |0050: nop
00222816: 0000 |0051: nop
00222818: 0000 |0052: nop
0022281a: 0000 |0053: nop
0022281c: 0000 |0054: nop
0022281e: 0000 |0055: nop
00222820: 0000 |0056: nop
00222822: 0000 |0057: nop
00222824: 0000 |0058: nop
00222826: 0000 |0059: nop
after repire method++++++++++++++++++++++++++++++++++++

registers_size :0000000d: 13
insns_size :0000005a: 90
debug_info_off :001a4314: 1721108
ins_size :00000001: 1
outs_size :00000002: 2
tries_size :00000001: 1
try[0] ins start: :00000009: 9
try[0] ins count: :00000022: 34
try[0]:handler[0] exception type::LDecoder/CEStreamExhausted;
try[0]:handler[0] ins start::0000002c: 44
00000010: 130b 1000 |0000: const/16 v11 , 16
00000014: 23b3 4610 |0002: new-array v3 , v11 , type@[C
00000018: 2603 4200 0000 |0004: fill-array-data v3 , string@66
0000001e: 6e10 1268 0c00 |0007: invoke-virtual v12 , meth@getBytes //byte[] java.lang.String::getBytes()
00000024: 0c00 |000a: move-result-object v0
00000026: 1a0b b326 |000b: const-string v11 , "MD5"
0000002a: 7110 4269 0b00 |000d: invoke-static v11 , meth@getInstance //java.security.MessageDigest java.security.MessageDigest::getInstance(java.lang.String)
00000030: 0c09 |0010: move-result-object v9
00000032: 6e20 4569 0900 |0011: invoke-virtual v9 , v0 , meth@update //void java.security.MessageDigest::update(byte[])
00000038: 6e10 4069 0900 |0014: invoke-virtual v9 , meth@digest //byte[] java.security.MessageDigest::digest()
0000003e: 0c08 |0017: move-result-object v8
00000040: 2185 |0018: array-length v5 , v8
00000042: da0b 0502 |0019: mul-int/lit8 v11 , v2 , 5
00000046: 23ba 4610 |001b: new-array v10 , v11 , type@[C
0000004a: 1206 |001d: const/4 v6 , 0
0000004c: 1204 |001e: const/4 v4 , 0
0000004e: 0167 |001f: move v7 , v6
00000050: 3454 0800 |0020: if-lt v4 , v5 , 0028
00000054: 220b 360e |0022: new-instance v11 , type@Ljava/lang/String;
00000058: 7020 0668 ab00 |0024: invoke-direct v11 , v10 , meth@<init> //void java.lang.String::<init>(char[])
0000005e: 110b |0027: return-object v11
00000060: 4801 0804 |0028: aget-byte v1 , v4 , v8
00000064: d806 0701 |002a: add-int/lit8 v6 , v1 , 7
00000068: e20b 0104 |002c: ushr-int/lit8 v11 , v4 , 1
0000006c: dd0b 0b0f |002e: and-int/lit8 v11 , v15 , 11
00000070: 490b 030b |0030: aget-char v11 , v11 , v3
00000074: 500b 0a07 |0032: aput-shar v11 , v7 , v10
00000078: d807 0601 |0034: add-int/lit8 v7 , v1 , 6
0000007c: dd0b 010f |0036: and-int/lit8 v11 , v15 , 1
00000080: 490b 030b |0038: aget-char v11 , v11 , v3
00000084: 500b 0a06 |003a: aput-shar v11 , v6 , v10
00000088: d804 0401 |003c: add-int/lit8 v4 , v1 , 4
0000008c: 28e2 |003e: goto 0020
0000008e: 0d02 |003f: move-exception v2
00000090: 6e10 8367 0200 |0040: invoke-virtual v2 , meth@printStackTrace //void java.lang.Exception::printStackTrace()
00000096: 120b |0043: const/4 v11 , 0
00000098: 28e3 |0044: goto 0027
0000009a: 0000 |0045: nop

阅读Smali源码可以发现,这是一个自己实现的md5,并不是标准的md5,如果没有这份Smali,就根本无法实现静态分析了。

FART脱壳中的常见问题

Q1:app点击运行后,FART脱壳线程成功启动,但运行一段时间后app异常退出,是为什么?

A1:FART自发布到现在也已经接近一年,不排除某些壳已经针对FART的指纹特征进行了检测;

同时,有些壳为了对抗FART的脱壳原理,和对抗DexHunter的脱壳原理类似,在dex中插入一些“垃圾类”,这些“垃圾类”在正常运行的过程中是绝对不会用到的,但是FART却会全部加载并主动调用,通过判断“垃圾类”的加载来确定是否正在被脱壳,如果是那就退出。

这时候就用到前文提到的类列表,单独对自己需要的类,进行主动调用,这样就可以避免通过无效类来对抗FART的方式。

Q2:有些App点击运行后,没有log信息,不知道脱壳线程是否启动,并且何时结束。

A2:这是因为某些壳hook掉了log流程中的关键函数,阻止了log的打印,比如hook liblog.so中的一些函数,可以实现这个效果。

壳这样做的目的,也是为了防止App中的敏感信息泄露,即使再三强调下,开发人员还是将敏感信息通过log打印了出来,壳屏蔽掉liblog.so中的函数,确保log打印不出来。

不过此时就看不到FART在不在脱壳、有没有崩溃、以及什么时候结束了,这时候只有一种方法,那就是去看bin文件。

  • 只要FART主动调用开始了,那么一定会有bin文件生成;

  • 可以不断地ls -alit看下bin文件大小,只要是在不断变大,说明主动调用正在进行,正在不断向文件中写文件;

  • 等到文件不再增大,说明脱壳结束。

小总结

本文首先介绍了编译AOSP源码的流程,然后在源码中添加FART脱壳代码继续编译,实现脱壳机镜像并刷入手机,最后用这个脱壳手机来脱壳并dump和修复函数体,实现通杀一代整体型壳和二代函数抽取型壳的目标,从系统框架层源码的高维、来“降维”打击App用户层的低维技术,对于实际逆向分析工作帮助重大,再次感谢寒冰大佬将技术开源,为他的分享精神点赞。

网址汇总:

- ThomasKing:https://bbs.pediy.com/user-627520.htm

- 《来自高纬的对抗 - 逆向TinyTool自制》:https://bbs.pediy.com/thread-215953.htm)

-https://github.com/hanbinglengyue/FART:https://github.com/hanbinglengyue/FART

- 三篇文章:https://bbs.pediy.com/user-632473.htm)。

- `Github`:https://github.com/r0ysue/AndroidSecurityStudy:https://github.com/r0ysue/AndroidSecurityStudy

- 这篇文章:https://blog.csdn.net/click_idc/article/details/80591686

- `FART`的`github`发布页:https://github.com/hanbinglengyue/FART.git

- 这篇51-android:https://github.com/snowdream/51-android

- 《FART源码解析及编译镜像支持到Pixel2(xl)》:https://www.anquanke.com/post/id/201896

- 《拨云见日:安卓APP脱壳的本质以及如何快速发现ART下的脱壳点》:https://bbs.pediy.com/thread-254555.htm

- 《FART正餐前甜点:ART下几个通用简单高效的dump内存中dex方法》:https://bbs.pediy.com/thread-254028.htm



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存