本系列文章,将全貌的讲解App安全中常见的一些内容,包含了逆向分析,正向防护等的相关工具和处理思路,涉及抓包、脱壳、分析等多个环节。
本系列文章共2篇。第二篇可前往(点击文章上方的“App安全”合集):App逆向安全(二)- 调试与工具[1]
Frida官方[2]
适用于全平台,可动态注入js脚本,Hook应用内任何函数、监听api出入参、Trace代码调用链。【适用:全平台】
strongR-frida-android[3]
基于官方Frida修改,跟踪FRIDA最新代码自动打补丁,构建 Android 版 frida-server 的反检测版本【适用:Android】
frida-dexdump[4]
基于frida的工具,用于在内存中查找并转储 dex,以支持安全工程师分析恶意软件。【适用:Android】
手动编译Hluda Frida Server[5]
自行编译用于Android平台的去特征化Frida版本,可加入自定义的去特征化内容。【适用:Android】
Objection[6]
基于Frida开发的手机运行时搜索工具包,其功能强大,命令众多,而且不用写一行代码,便可实现诸如内存搜索、类和模块搜索、方法hook
打印参数返回值调用栈等常用功能,是一个非常方便的,逆向必备、内存漫游神器。【适用:Android、iOS】
unidbg[7]
调试工具,支持在Windows/Linux/MacOS上模拟Native包的调用,包括.so、.dylib的动态调用。【适用:Android、iOS】
Charles[8]、Fildder、BurpSute、WireShark
HTTP/HTTPS代理工具
Xposed[9]
Xposed框架(Xposed Framework)是一套开源的、在Android高权限模式下运行的框架服务,可以在不修改APK文件的情况下影响程序运行(修改系统)的框架服务,基于它可以制作出许多功能强大的模块,且在功能不冲突的情况下同时运作。
反射大师
一款基于Xposed的安卓修改工具。它可以帮助用户针对目标APP的界面进行可视化调整,拥有布局分析功能,获取当前Activity的所有变量等等功能,非常强大的一款工具。目标软件Activity启动后,反射大师会在启上面启动一个悬浮窗,基于当前的Activity对象进行可视化代码调用操作。反射大师是安卓脱壳神器,支持安卓8.0以下系统脱壳。
Apktool[10]
Android apk反编译&二次打包工具
jadx[11]
反编译工具,可将dex反编译成java,提供图形化界面
Supersu、Magisk
Android Root工具,通常需要刷机安装
JEB[12]
模块化逆向工具,可进行Android下的反汇编、反编译,支持动态调试。
ProxyDroid[13]、Drony[14]
Android全局代理工具,可绕过App的代理检测进行抓包;
Stream[15]、HttpCanary[16]
iOS抓包工具
VMos Pro[17]
在Android手机上运行的Android模拟器
注:本文中实操环节使用的部分相关软件及版本号如下:
frida-server v15.2.2、MuMu模拟器Mac版(Android 6.0.1)、VMos Pro v2.9.4 + Android5.1极客版;
采用了SSL Pinning的App样本:sample-v5.4.0.apk[18]
常规的http抓包,利用Charles、Fiddler、Burpsuite等抓包工具即可很便捷的实现。针对Https包,则需要在手机上安装SSL证书,利用中间人攻击的原理,实现数据包的抓取。
当前比较常见的防止被抓包的方法,有采用Https协议、代理屏蔽、证书锁定(即SSL Pinning)。下面,我们分别聊聊这两种方式。
采用Https协议替代Http协议,实际上是在Http之外多了一个SSL加密协议。因此若App上未安装需要的SSL证书的话,抓包是无法看到完整的报文的。当前Https已经成为app网络请求的标准协议,与之相关的抓包方法在网络上已经很多了。以Charles为例,同时在电脑和手机上安装Charles的SSL证书即可进行抓包,这里不赘述了。
一般抓包工具都是基于流量代理的方式,通过中间人攻击的原理的来获取app流量。因此在app在进行流量请求时,禁止使用系统代理,即可防止常规的通过代理来抓包的方式。
// 以okhttp为例,设置代理屏蔽
OkHttpClient client = new OkHttpClient().newBuilder().proxy(Proxy.NO_PROXY).build();
针对该方式的抓包应对方案有以下几种:
try{
var URL = Java.use("java.net.URL");
URL.openConnection.overload('java.net.Proxy').implementation = function(arg1){
return this.openConnection();
}
}catch(e){
console.log(""+e);
}
try{
var Builder = Java.use("okhttp3.OkHttpClient$Builder");
var myBuilder = Builder.$new();
Builder.proxy.overload('java.net.Proxy').implementation = function(arg1){
return myBuilder;
}
}catch(e){
console.log(""+e);
}
在手机上安装全局代理软件,接管手机全局网络流量,能做到对app透明以绕过app上的代理检测。ProxyDroid、Drony、HttpCanary均在使用时均会自动创建vpn,流量将通过vpn进出。其中,ProxyDroid和Drony需要设置将流量指向代理服务器(如:Charles),HttpCanary则直接在自身app上记录了流量。
SSL Pinning是通过在App中内置证书或公钥,对每次请求做证书/公钥比对,达到防止被中间人攻击的目的。大致分为两种:
• 证书锁定(Certificate Pinning) 在客户端代码内置仅接受指定域名的证书,而不接受操作系统或浏览器内置的CA根证书对应的任何证书。(缺陷:证书有效期问题)
• 公钥锁定(Public Key Pinning) 提取证书中的公钥并内置到客户端中,通过与服务器对比公钥值来验证连接的正确性。
相关资料可参考:证书锁定SSL/TLS Pinning [19]。下面,我们将重点聊聊如何绕过SSL Pinning限制
手机需要root,安装Xposed框架后,安装JustTrustMe
插件,或JustMePlush(升级版Just)
插件。JustTrustMe
是一个用来禁用、绕过 SSL 证书检查的Xposed模块。它将 APK 中所有用于校验 SSL 证书的 API 都进行了 Hook,从而绕过证书检查。JustMePlush
则是在JustTrustMe
的基础上,优化了对自定义SSL校验的支持。
Objection是基于Frida开发的客户端工具集,因此在使用方法上基本遵循Frida的使用方式。需要先在手机上安装frida服务端并启动(Android需Root,iOS需越狱 -_-!! ),然后在连接了手机的电脑上执行Objection指令。
objection -g <ios/apk package name> explore
# 注意:
# 若上述执行执行成果,会进入一个objection下的shell,以下指令均在objection的shell下执行。
# Android 平台
android sslpinning disable
# iOS平台
ios sslpinning disable
先在手机上安装frida服务端并启动frida-server,然后在电脑上执行如下命令(电脑上需安装frida-tools
):
frida -U -l ./frida-android-unpinning.js -f <app-package-name> --no-pause
其中,./frida-android-unpinning.js
来自开源项目frida-android-unpinning[20]。app-package-name为目标app的包名,如com.android.settings。脚本在启动时,会加载hook js文件,达到动态修改/关闭app证书校验的机制。该js文件不一定对解决所有app的ssl pinning都能生效,app本身可能通过一些手段,绕开js的hook逻辑。
通常Android加固分为dex加壳和so加固,因此我们讨论脱壳时也是从这两个方面来进行。相关的加壳器和加壳原理,大家可以参考看雪上的这篇文章:Android加壳与脱壳——各类加壳器和原理分析推荐[21]
Android apk通常情况下,打包流程为:代码编译(含混淆) - 打包 - 签名。打包流程可参考掘金上的这篇文章:Android Apk 编译打包流程[22]。而采用第三方平台(如360加固,梆梆加固)后,会在上述流程之后,进行加固并重新签名。加固的原理可以简单理解为,加固程序首先对apk进行解压获取到原dex, 接着对原dex 进行加密,制作并生成壳dex(加载时用来解密原dex),并重新打包成apk,运行时利用壳dex对加密的dex进行解密并加载到内存中。
针对未加固的app,通常通过Apktool
即可进行解压,并反编译成smali/Java代码
$ apktool d testapp.apk
I: Using Apktool 2.0.0 on testapp.apk
I: Loading resource table...
I: Decoding AndroidManifest.xml with resources...
I: Loading resource table from file: 1.apk
I: Regular manifest package...
I: Decoding file-resources...
I: Decoding values */* XMLs...
I: Baksmaling classes.dex...
I: Copying assets and libs...
针对加固过的app,通过apktool
反编译(或unzip解压包)后,发现核心的Java文件和包都不见了,只有一个硕大的classes.dex文件。因为核心文件都被打包藏到dex文件了,该dex文件是经过加密的也无法正常解压。这时通常的应对方案有如下几种:
因为app加固后必须保证能正常运行,因此app在实际运行时,原加密过的dex文件已经被解密,并且正确加载到内存中。次时若根据当下内存中的关键编码信息和字段,便壳找到解密后的dex文件在内存中的实际位置,通过内存转储到本地,即可得到原始的未经过加密的dex文件。以上即是frida-dexdump
的基本原理。改工具需要安装名为frida-dexdump的python包,搭配frida-server一起使用。
原理与frida-dexdump
大体相同,都是动态获取内存中的dex信息并转储。区别是操作更简单,而且实测成功率更高。需要安装Xposed
,反射大师
app,比在xposed
中激活反射大师
模块。指定需要脱壳的app后,在目标app打开的页面中点开芒星,长按导出dex文件即可。
通过上述方案一般会得到多个dex文件,可以将单个文件直接用jadx
打开, 便可以得到反编译后的Java代码,此时基本已经具备可读性了。也可以将得到的多个dex文件合并成一个dex文件,方便阅读。多dex的合并可以利用以下脚本,运行完成后会得到的全部反编译的代码。
import os, sys
# 合并dex
# file: merge.dex.py
# e.g: python3 merge_dex.py ./source_dir/ output_dir
if __name__ == "__main__":
if len(sys.argv) < 3:
print("start error")
sys.exit()
source_dir = sys.argv[1]
output_dir = sys.argv[2]
print(source_dir, output_dir)
files = os.listdir(source_dir) # 得到文件夹下的所有文件名称
s = []
for file in files: # 遍历文件夹
if file.find(".dex") > 0: ## 查找dex 文件
sh = './bin/jadx -j 1 -r -d ' + output_dir + " " + source_dir + file
print(sh)
os.system(sh)
为了防止so包被反编译,或者被调试,开发者可以在原始so的基础上进行加密或者重新套壳。加壳思路与dex文件的加壳逻辑类似,因此其脱壳思路也与dex基本一致。即在app运行过程中,动态获取内存中的指定信息,并转储为原始的so文件。这个过程中,需要了解Android Jni机制、汇编
与反汇编
。常用的分析工具是IDA
和 unidgb
,这种重点介绍unidgb。
开源地址:https://github.com/zhkl0228/unidbg 。当前社区非常活跃,unidgb能模拟so的运行环境,并且可以进行单步调试so。unidgb可以自动脱掉一些处理相对常规一些的壳,在调试中,导入so文件,即可根据相关的jni函数名,直接在本地电脑的JVM环境下发起调用。一些复杂的so文件,则需要通过ida、unidgb分析,并手动打patch去壳后,才能导入unidgb正常发起jni调用。
public class SignUtil {
...
public SignUtil() {
emulator = AndroidEmulatorBuilder.for32Bit()
.setProcessName("com.anjuke.android.app")
.build();
Memory memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));
vm = emulator.createDalvikVM();
vm.setDvmClassFactory(new ProxyClassFactory());
vm.setVerbose(false);
DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/resources/example_binaries/armeabi-v7a/libsignutil.so"), false);
cSignUtil = vm.resolveClass("com/anjuke/mobile/sign/SignUtil");
dm.callJNI_OnLoad(emulator);
}
public String getSign0(String p1, String p2, Map<String, byte[]> map, String p3, int i) {
String methodSign = "getSign0(Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/lang/String;I)Ljava/lang/String;";
StringObject obj = cSignUtil.callStaticJniMethodObject(emulator, methodSign, p1, p2, ProxyDvmObject.createObject(vm, map), p3, i);
return obj.getValue();
}
private synchronized String sign(String p1, String p2, Map<String, String> paramMap, String p3, int i) {
Map<String, byte[]> map = new HashMap<>();
for (String key : paramMap.keySet()) {
map.put(key, paramMap.get(key).getBytes(StandardCharsets.UTF_8));
}
return getSign0(p1, p2, map, p3, i);
}
public static void main(String[] args) throws Exception {
Map<String, String> paramMap = new HashMap<String, String>() {{
put("a", "b");
put("b", "b");
}};
SignUtil signUtil = new SignUtil();
String sign = signUtil.sign("aa", "bb", paramMap, "cc", 10);
System.out.println("sign=" + sign);
}
}
比较典型的so脱壳案例,可以参考:https://blog.csdn.net/Y_morph/article/details/130361942
Android-HTTPS认证的N种方式和对抗方法总结[23]
Https防抓包机制[24]
[1]
App逆向安全(二)- 调试与工具: https://blog.fh6766.com/2023/10/16/app-security-02/[2]
Frida官方: https://frida.re/[3]
strongR-frida-android: https://github.com/hzzheyang/strongR-frida-android[4]
frida-dexdump: https://github.com/hluwa/frida-dexdump[5]
手动编译Hluda Frida Server: https://bbs.kanxue.com/thread-269889.htm[6]
Objection: https://github.com/sensepost/objection[7]
unidbg: https://github.com/zhkl0228/unidbg[8]
Charles: https://www.charlesproxy.com/[9]
Xposed: https://github.com/rovo89/Xposed[10]
Apktool: https://apktool.org[11]
jadx: https://github.com/skylot/jadx[12]
JEB: https://www.pnfsoftware.com/[13]
ProxyDroid: https://apkpure.com/proxydroid/org.proxydroid[14]
Drony: https://www.apkshub.com/app/org.sandroproxy.drony[15]
Stream: https://apps.apple.com/cn/app/stream/id1312141691[16]
HttpCanary: https://m.apkpure.com/httpcanary-%E2%80%94-http-sniffer-capture-analysis/com.guoshi.httpcanary[17]
VMos Pro: https://www.vmos.cn/[18]
sample-v5.4.0.apk: https://www.wandoujia.com/apps/366355/history_v109[19]
证书锁定SSL/TLS Pinning : https://www.cnblogs.com/amyzhu/p/11838665.html[20]
frida-android-unpinning: https://github.com/httptoolkit/frida-android-unpinning[21]
Android加壳与脱壳——各类加壳器和原理分析推荐: https://bbs.kanxue.com/thread-275089.htm[22]
Android Apk 编译打包流程: https://juejin.cn/post/7113713363900694565[23]
Android-HTTPS认证的N种方式和对抗方法总结: https://ch3nye.top/Android-HTTPS%E8%AE%A4%E8%AF%81%E7%9A%84N%E7%A7%8D%E6%96%B9%E5%BC%8F%E5%92%8C%E5%AF%B9%E6%8A%97%E6%96%B9%E6%B3%95%E6%80%BB%E7%BB%93/[24]
Https防抓包机制: https://www.jianshu.com/p/a004f081d8aa