让安卓APP正常运行起来

0x01 前言

最近在工作上经常遇到一些关于安卓app的项目,特别关注了一些安卓的攻击资料。

Damn-Vulnerable-Bank,就是一个非常好的关于安卓app的攻击项目,它有比较完善的攻击指南

所以,这是一个系列的“Damn-Vulnerable-Bank”复现和翻译的第一篇文章。

在此,感谢前辈的分享!

0x02 环境搭建

1、攻击环境

我手上真实设备有iPhone 6s和一加5,另外之前也使用过雷电模拟器、夜神模拟器、逍遥模拟器和Memu模拟器,现在在尝试用genymotion模拟器。

我对自己的“全家桶”是这样设想的:

a. 一台未越狱的iPhone 满足我对IOS app的测试需求。

b. 一台未root的安卓9的一加5,目前苦恼在我没root,怎么把burpsuite的证书放入系统目录。

c. 一个genymotion模拟器可以满足我对root的需求,genymotion模拟器可以选择各种手机类型,同时默认root环境,还可以选择安卓版本,目前支持安卓10,这使得我决定可能不对一加5进行root。

特别注意genymotion模拟器用于ARM架构的app,还需要安装指令翻译包。

同时阿里云和亚马逊云开始提供genymotion的服务,这感觉可能以后可以拥有一个云端的app测试环境。

genymotion模拟器的安装参考官方文档

ARM架构指令翻译包参考

d. 雷电模拟器、夜神模拟器和逍遥模拟器等作为补充,国内的一系列模拟器大部分停留在安卓5和安卓7,目前仅夜神模拟器的官方内测版支持安卓9,但使用的效果还不太理想,实际工作中发现有一些app已经开始仅适配安卓9以上了。

2、frida

frida是一套hook框架。多用于移动app平台上。通过插入一些JavaScript或者python代码到app中,以进程注入的方式来实现劫持app中的函数,从而检测或者修改app的行为和结果。

frida的官方网站、frida的安装使用

genymotion是一个x86架构的模拟器,所以我下载了x86的frida,实际根据情况选择。

下载仓库

安装服务端:

1
2
3
4
5
6
adb devices
adb pull frida-server-15.0.13-android-x86 /tmp
adb shell
cd tmp
chmod +x frida-server-15.0.13-android-x86
./frida-server-15.0.13-android-x86 &

image-20210801044806233

安装客户端:

1
2
3
4
5
pip3 install frida
pip3 install frida-tools
frida --version
pip3 install -U frida
pip3 install -U frida-tools

注意客户端和服务端的版本要一致。

运行测试:

1
frida-ps -U

image-20210731034613507

3、apkx和apktool

我在kali里部署了这两个工具。

a. apkx是dex文件转换和Java反编译工具,可直接从 apk中提取 Java源代码。

apkx

b. apktool是谷歌提供的APK编译工具,能够反编译及回编译apk。

apktool

安装:

1
2
3
4
5
apt install apktool -y
git clone https://github.com/b-mueller/apkx
cd apkx
chmod +x install.sh
./install.sh

image-20210731035730730

image-20210731035903199

使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
反编译:
apktool d test.apk
编译:
apktool b test

反编译 HelloWord.apk 到 HelloWord
apkx HelloWorld.apk
Converting: classes.dex -> classes.jar (dex2jar)
dex2jar HelloWord/classes.dex -> HelloWord/classes.jar
Decompiling to HelloWord/src (cfr)

转换器和反编译器的默认组合是dex2jar和cfr。使用-c-d标志来改变这一点。例如:
apkx -c enjarify -d procyon HelloWorld.apk

4、Ghidra

Ghidra是NSA发布的一款功能强大、免费的开源逆向分析工具。其基于JAVA开发,是一款适用于Windows、Mac和Linux的跨平台反汇编工具,用户可以使用Java或Python开发自己的Ghidra插件或者脚本。Ghidra具有反编译功能,查看、定位反编译后的代码相较于IDA有优势。

官方网站

使用教程

image-20210731042455746

0x03 绕过GPU检测

可能是版本的问题,我发现在最新的genymotion中触发不了这个检测场景,最终用逍遥重现了问题。

image-20210731043048973

真是个美妙的界面,想起来之前遇到的好几个项目,在一个个的模拟器和真机的尝试中,最终才解决了运行问题。

现在,感谢“Damn-Vulnerable-Bank”…

1、apktool反编译

1
apktool d dvba_v1.1.0.apk -o dvba_v1.1.0

image-20210731163556148

2、检查AndroidManifest.xml文件

AndroidManifest.xml是一个二进制 XML 格式的清单文件。每个 APK 文件都包含一个 AndroidManifest.xml 文件,该文件声明了应用程序的包名称、版本组件和其他元数据。

image-20210731163751713

在AndroidManifest.xml文件中检查发现hardwareAccelerated属性为true,这意思是app配置了硬件加速,需要GPU支持,逍遥模拟器没有GPU,导致了导致app的崩溃。

image-20210731172716313

安卓硬件加速方式的开启和关闭

AndroidManifest.xml文件详解

接下来将hardwareAccelerated属性改为false,然后编译签名。

image-20210731173140897

3、编译app

编译apk

1
apktool b dvba_v1.1.0 -o dvba_v1.1.0-no-gpu.apk

在编译过程出了一些问题,导致资源没有加载到安装包内。

image-20210731205802261

后来在Ubuntu重新安装了一下,成功避开…

image-20210731205923295

apktool的安装参考

创建和使用签名

1
2
3
keytool -genkey -v -keystore my-release-key.keystore -alias dvba-no-gpu -keyalg RSA -keysize 2048 -validity 10000

jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore my-release-key.keystore dvba_v1.1.0_no_gpu.apk dvba-no-gpu

image-20210731210425026

image-20210731211152773

4、测试运行

重新在逍遥安装运行

image-20210731211513545

0x04 绕过root检测

继续在逍遥里面观察,发现app运行后出现了另一个检测现象:“Phone is Rooted”,不过显然逍遥重现不了“Damn-Vulnerable-Bank”描述的场景。

image-20210731212118906

切换回genymotion,运行app,进行观察

1
2
adb.exe shell pm list packages
adb.exe uninstall com.app.damnvulnerablebank

image-20210731213517436

image-20210731213517436

“Emulator Detected”、“Phone is Rooted”、“Frida is NOT running”

1、反编译

1
2
apkx dvba_v1.1.0_no_gpu.apk
grep -r -i "Emulator Detected"

image-20210801004539248

“dvba_v1.1.0_no_gpu/src/com/app/damnvulnerablebank/MainActivity.java”中存在特征。

2、代码审计

审计“MainActivity.java”

image-20210801014945900

可以看到,这段代码进行了模拟器检测、debug检测、root检测和frida检测。不过在检测过程中,只有root检测和frida检测才会调用finish()方法退出app,其他都是调用show()方法进行打印输出。最后检测通过后实现登录。

其中,root检测的a.R() 的返回值为true才进入打印输出并结束。

跟踪a.R() 的函数实现

image-20210801031433735

image-20210801032259310

1
2
3
if (!new File(new String[]{"/system/app/Superuser.apk", "/sbin/su", "/system/bin/su", "/system/xbin/su", "/data/local/xbin/su", "/data/local/bin/su", "/system/sd/xbin/su", "/system/bin/failsafe/su", "/data/local/su", "/su/bin/su"}[n2]).exists()) continue;

java.lang.Process process = Runtime.getRuntime().exec(new String[]{"/system/xbin/which", "su"});

root权限是安卓最高的操作权限,俗称superuser,简称su,一般来说root过的手机,系统目录会有su目录和系统app目录中有Superuser.apk,或者kingroot、360Root、Root精灵、等apk。在安卓8系列中会使用magisk。

3、绕过检测

整个检测过程明确后,很容易地得到root检测的绕过方式:

a. 直接在Java上直接修改,无论进入哪个判断都设置返回值为false。

b. hook,利用frida控制a.R()函数的返回值为false。

利用frida提供的JavaScript API,实现控制函数的返回值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
setTimeout(function() {
Java.perform(function() {
// We are loading the class here
var Rfunc = Java.use("a.a.a.a.a");
// Whenever a.a.a.a.a.R() is called, the below code is executed
Rfunc.R.implementation = function() {
// Capture the return value of R function
let retval = this.R();
// Printing the value we got from this function
console.log("Original return value = ", retval);
// Toggle the return value to make sure it doesn't enter the if loop
return !retval
}
})
}, 10);

10秒执行一次,加载a.a.a.a.a类中的R方法,获取函数返回值,并设置与返回值相反的值进行返回。

frida的官方JavaScript API

frida的参考案例

4、运行结果

1
frida -U -f com.app.damnvulnerablebank -l Damn-Vulnerable-Bank/rooted.js 

image-20210801041417263

0x05 绕过frida检测

上一关,在最后出现了“Frida is running”,显然,app做了frida的检测。先尝试重新审计上一关的Java代码。

1、代码审计

MainActivity.java中的关键代码:

image-20210801054017775

当条件var1_1.fridaCheck() == 1达成时候,输出Frida is running,同时记录"FRIDA CHECK""FRIDA Server DETECTED"到日志中,最后调用finish()方法退出。关注有两点:

a. fridaCheck()方法的具体实现细节,如何检测到frida的。

b. 从记录"FRIDA CHECK""FRIDA Server DETECTED"到日志的操作,可以猜测,检测的对象是frida的服务端特征。

继续全局搜索与frida相关的特殊字符。

1
2
grep -r -i "fridaCheck"
grep -r -i "frida"

经过搜索发现,在FridaCheckJNI类中存在一段代码为System.loadLibrary("frida-check");,其意思是通过调用这个方法在系统的路径下去搜索so库。

在安卓app中,lib文件夹包含编译代码的可选文件夹 - 即本机代码库。

在lib目录下存在多个so库,命名为libfrida-check.so

显然,lib/x86/libfrida-check.so中的代码就包含了如何检测frida的内容。

image-20210801055200015

接下来,通过Ghidra分析该libfrida-check.so文件。创建项目,导入so文件后,通过symbol tree 可以看到在导入表里面存在与网络连接相关的connect和socket。

在导出表和函数表里面存在更为明显的字符串“Java_com_app_damnvulnerablebank_FridaCheckJNI_fridaCheck”。

image-20210801061330541

在“Java_com_app_damnvulnerablebank_FridaCheckJNI_fridaCheck”函数的伪代码中,存在“127.0.0.1”和“0xa2690002”。

由此我们确定了frida的检测逻辑就是检查本地是否存在某个“0xa2690002”端口号,如果这个端口号存在,则判断存在frida,从而结束退出app。

image-20210801061922537

结合genymotion和官方文档印证如下:

image-20210801062425351

image-20210801062602326

至于“0xa2690002”与27042之间的转换关系,涉及到了在网络传输过程中的大端序和小端序问题。

在大多数情况下,二进制文件包含大端格式的代码。由于网络协议规定接收到得第一个字节是高字节,存放到低地址,所以发送时会首先去低地址取数据的高字节。并且由于genymotion为x86架构,所以8位的“0xa2690002”需要分为4位的“0xa269”和“0x0002”。最后“0xa269”为实际有用值。

将大端序的“0xa269”转换为十进制需要将前后2位对调,结果得“0x69a2”,其十进制为27042。

2、绕过检测

确定了frida的检测逻辑就是检查本地是否存在某个“27042”默认端口,那么将frida的服务端运行在另一个端口,即绕过frida检测了。

1
2
3
netstat -anptl | grep frida
ps -ef | grep frida | awk '{print $2}' | xargs kill -9
./frida-server-15.0.13-android-x86 -l 0.0.0.0:27041 &

image-20210801064452138

3、运行结果

1
2
adb devices
frida -H 192.168.197.106:27041 -f com.app.damnvulnerablebank -l Damn-Vulnerable-Bank/rooted.js

image-20210801064452138