让安卓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测试环境。
d. 雷电模拟器、夜神模拟器和逍遥模拟器等作为补充,国内的一系列模拟器大部分停留在安卓5和安卓7,目前仅夜神模拟器的官方内测版支持安卓9,但使用的效果还不太理想,实际工作中发现有一些app已经开始仅适配安卓9以上了。
2、frida
frida是一套hook框架。多用于移动app平台上。通过插入一些JavaScript或者python代码到app中,以进程注入的方式来实现劫持app中的函数,从而检测或者修改app的行为和结果。
frida的官方网站、frida的安装和使用
genymotion是一个x86架构的模拟器,所以我下载了x86的frida,实际根据情况选择。
安装服务端:
1 | adb devices |
安装客户端:
1 | pip3 install frida |
注意客户端和服务端的版本要一致。
运行测试:
1 | frida-ps -U |
3、apkx和apktool
我在kali里部署了这两个工具。
a. apkx是dex文件转换和Java反编译工具,可直接从 apk中提取 Java源代码。
b. apktool是谷歌提供的APK编译工具,能够反编译及回编译apk。
安装:
1 | apt install apktool -y |
使用方法:
1 | 反编译: |
4、Ghidra
Ghidra是NSA发布的一款功能强大、免费的开源逆向分析工具。其基于JAVA开发,是一款适用于Windows、Mac和Linux的跨平台反汇编工具,用户可以使用Java或Python开发自己的Ghidra插件或者脚本。Ghidra具有反编译功能,查看、定位反编译后的代码相较于IDA有优势。
0x03 绕过GPU检测
可能是版本的问题,我发现在最新的genymotion中触发不了这个检测场景,最终用逍遥重现了问题。
真是个美妙的界面,想起来之前遇到的好几个项目,在一个个的模拟器和真机的尝试中,最终才解决了运行问题。
现在,感谢“Damn-Vulnerable-Bank”…
1、apktool反编译
1 | apktool d dvba_v1.1.0.apk -o dvba_v1.1.0 |
2、检查AndroidManifest.xml文件
AndroidManifest.xml是一个二进制 XML 格式的清单文件。每个 APK 文件都包含一个 AndroidManifest.xml 文件,该文件声明了应用程序的包名称、版本组件和其他元数据。
在AndroidManifest.xml文件中检查发现hardwareAccelerated属性为true,这意思是app配置了硬件加速,需要GPU支持,逍遥模拟器没有GPU,导致了导致app的崩溃。
接下来将hardwareAccelerated属性改为false,然后编译签名。
3、编译app
编译apk
1 | apktool b dvba_v1.1.0 -o dvba_v1.1.0-no-gpu.apk |
在编译过程出了一些问题,导致资源没有加载到安装包内。
后来在Ubuntu重新安装了一下,成功避开…
创建和使用签名
1 | keytool -genkey -v -keystore my-release-key.keystore -alias dvba-no-gpu -keyalg RSA -keysize 2048 -validity 10000 |
4、测试运行
重新在逍遥安装运行
0x04 绕过root检测
继续在逍遥里面观察,发现app运行后出现了另一个检测现象:“Phone is Rooted”,不过显然逍遥重现不了“Damn-Vulnerable-Bank”描述的场景。
切换回genymotion,运行app,进行观察
1 | adb.exe shell pm list packages |
“Emulator Detected”、“Phone is Rooted”、“Frida is NOT running”
1、反编译
1 | apkx dvba_v1.1.0_no_gpu.apk |
“dvba_v1.1.0_no_gpu/src/com/app/damnvulnerablebank/MainActivity.java”中存在特征。
2、代码审计
审计“MainActivity.java”
可以看到,这段代码进行了模拟器检测、debug检测、root检测和frida检测。不过在检测过程中,只有root检测和frida检测才会调用finish()方法退出app,其他都是调用show()方法进行打印输出。最后检测通过后实现登录。
其中,root检测的a.R() 的返回值为true才进入打印输出并结束。
跟踪a.R() 的函数实现
1 | 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; |
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 | setTimeout(function() { |
10秒执行一次,加载a.a.a.a.a
类中的R
方法,获取函数返回值,并设置与返回值相反的值进行返回。
4、运行结果
1 | frida -U -f com.app.damnvulnerablebank -l Damn-Vulnerable-Bank/rooted.js |
0x05 绕过frida检测
上一关,在最后出现了“Frida is running”,显然,app做了frida的检测。先尝试重新审计上一关的Java代码。
1、代码审计
MainActivity.java中的关键代码:
当条件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 | grep -r -i "fridaCheck" |
经过搜索发现,在FridaCheckJNI类中存在一段代码为System.loadLibrary("frida-check");
,其意思是通过调用这个方法在系统的路径下去搜索so库。
在安卓app中,lib文件夹包含编译代码的可选文件夹 - 即本机代码库。
在lib目录下存在多个so库,命名为libfrida-check.so
。
显然,lib/x86/libfrida-check.so
中的代码就包含了如何检测frida的内容。
接下来,通过Ghidra分析该libfrida-check.so文件。创建项目,导入so文件后,通过symbol tree 可以看到在导入表里面存在与网络连接相关的connect和socket。
在导出表和函数表里面存在更为明显的字符串“Java_com_app_damnvulnerablebank_FridaCheckJNI_fridaCheck”。
在“Java_com_app_damnvulnerablebank_FridaCheckJNI_fridaCheck”函数的伪代码中,存在“127.0.0.1”和“0xa2690002”。
由此我们确定了frida的检测逻辑就是检查本地是否存在某个“0xa2690002”端口号,如果这个端口号存在,则判断存在frida,从而结束退出app。
结合genymotion和官方文档印证如下:
至于“0xa2690002”与27042之间的转换关系,涉及到了在网络传输过程中的大端序和小端序问题。
在大多数情况下,二进制文件包含大端格式的代码。由于网络协议规定接收到得第一个字节是高字节,存放到低地址,所以发送时会首先去低地址取数据的高字节。并且由于genymotion为x86架构,所以8位的“0xa2690002”需要分为4位的“0xa269”和“0x0002”。最后“0xa269”为实际有用值。
将大端序的“0xa269”转换为十进制需要将前后2位对调,结果得“0x69a2”,其十进制为27042。
2、绕过检测
确定了frida的检测逻辑就是检查本地是否存在某个“27042”默认端口,那么将frida的服务端运行在另一个端口,即绕过frida检测了。
1 | netstat -anptl | grep frida |
3、运行结果
1 | adb devices |