本系列文章,将全貌的讲解App安全中常见的一些内容,包含了逆向分析,正向防护等的相关工具和处理思路,涉及抓包、脱壳、分析等多个环节。

本系列文章共2篇。第二篇可前往:App逆向安全(二)- 调试与工具

0、工具集简介

Frida官方

​ 适用于全平台,可动态注入js脚本,Hook应用内任何函数、监听api出入参、Trace代码调用链。【适用:全平台】

strongR-frida-android

​ 基于官方Frida修改,跟踪FRIDA最新代码自动打补丁,构建 Android 版 frida-server 的反检测版本【适用:Android】

frida-dexdump

​ 基于frida的工具,用于在内存中查找并转储 dex,以支持安全工程师分析恶意软件。【适用:Android】

手动编译Hluda Frida Server

​ 自行编译用于Android平台的去特征化Frida版本,可加入自定义的去特征化内容。【适用:Android】

Objection

​ 基于Frida开发的手机运行时搜索工具包,其功能强大,命令众多,而且不用写一行代码,便可实现诸如内存搜索、类和模块搜索、方法hook打印参数返回值调用栈等常用功能,是一个非常方便的,逆向必备、内存漫游神器。【适用:Android、iOS】

unidbg

​ 调试工具,支持在Windows/Linux/MacOS上模拟Native包的调用,包括.so、.dylib的动态调用。【适用:Android、iOS】

Charles、Fildder、BurpSute、WireShark

​ HTTP/HTTPS代理工具

Xposed

​ Xposed框架(Xposed Framework)是一套开源的、在Android高权限模式下运行的框架服务,可以在不修改APK文件的情况下影响程序运行(修改系统)的框架服务,基于它可以制作出许多功能强大的模块,且在功能不冲突的情况下同时运作。

反射大师

​ 一款基于Xposed的安卓修改工具。它可以帮助用户针对目标APP的界面进行可视化调整,拥有布局分析功能,获取当前Activity的所有变量等等功能,非常强大的一款工具。目标软件Activity启动后,反射大师会在启上面启动一个悬浮窗,基于当前的Activity对象进行可视化代码调用操作。反射大师是安卓脱壳神器,支持安卓8.0以下系统脱壳。

Apktool

​ Android apk反编译&二次打包工具

jadx

​ 反编译工具,可将dex反编译成java,提供图形化界面

SupersuMagisk

​ Android Root工具,通常需要刷机安装

JEB

​ 模块化逆向工具,可进行Android下的反汇编、反编译,支持动态调试。

ProxyDroidDrony

​ Android全局代理工具,可绕过App的代理检测进行抓包;

StreamHttpCanary

​ iOS抓包工具

VMos Pro

​ 在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

1、抓包

常规的http抓包,利用Charles、Fiddler、Burpsuite等抓包工具即可很便捷的实现。针对Https包,则需要在手机上安装SSL证书,利用中间人攻击的原理,实现数据包的抓取。

当前比较常见的防止被抓包的方法,有采用Https协议、代理屏蔽、证书锁定(即SSL Pinning)。下面,我们分别聊聊这两种方式。

1.1 Https协议

采用Https协议替代Http协议,实际上是在Http之外多了一个SSL加密协议。因此若App上未安装需要的SSL证书的话,抓包是无法看到完整的报文的。当前Https已经成为app网络请求的标准协议,与之相关的抓包方法在网络上已经很多了。以Charles为例,同时在电脑和手机上安装Charles的SSL证书即可进行抓包,这里不赘述了。

1.2 代理屏蔽

一般抓包工具都是基于流量代理的方式,通过中间人攻击的原理的来获取app流量。因此在app在进行流量请求时,禁止使用系统代理,即可防止常规的通过代理来抓包的方式。

// 以okhttp为例,设置代理屏蔽
OkHttpClient client = new OkHttpClient().newBuilder().proxy(Proxy.NO_PROXY).build();

针对该方式的抓包应对方案有以下几种:

1.2.1 通过frida hook js 绕过代理屏蔽

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);
}

1.2.2 通过ProxyDroid、Drony、HttpCanary等工具,实现全局流量代理

在手机上安装全局代理软件,接管手机全局网络流量,能做到对app透明以绕过app上的代理检测。ProxyDroid、Drony、HttpCanary均在使用时均会自动创建vpn,流量将通过vpn进出。其中,ProxyDroid和Drony需要设置将流量指向代理服务器(如:Charles),HttpCanary则直接在自身app上记录了流量。

1.3 SSL Pinning

SSL Pinning是通过在App中内置证书或公钥,对每次请求做证书/公钥比对,达到防止被中间人攻击的目的。大致分为两种:

  • 证书锁定(Certificate Pinning)
    在客户端代码内置仅接受指定域名的证书,而不接受操作系统或浏览器内置的CA根证书对应的任何证书。(缺陷:证书有效期问题)

  • 公钥锁定(Public Key Pinning)
    提取证书中的公钥并内置到客户端中,通过与服务器对比公钥值来验证连接的正确性。

相关资料可参考:证书锁定SSL/TLS Pinning 下面,我们将重点聊聊如何绕过SSL Pinning限制

1.3.1 Xposed + JustTrustMe插件 【推荐】(适用平台:Android)

手机需要root,安装Xposed框架后,安装JustTrustMe插件,或JustMePlush(升级版Just)插件。JustTrustMe是一个用来禁用、绕过 SSL 证书检查的Xposed模块。它将 APK 中所有用于校验 SSL 证书的 API 都进行了 Hook,从而绕过证书检查。JustMePlush则是在JustTrustMe的基础上,优化了对自定义SSL校验的支持。

1.3.2 Frida + Objection(适用平台:Android、iOS)

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

1.3.3 Frida + Hook JS (适用平台:Android、iOS)

先在手机上安装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。app-package-name为目标app的包名,如com.android.settings。脚本在启动时,会加载hook js文件,达到动态修改/关闭app证书校验的机制。该js文件不一定对解决所有app的ssl pinning都能生效,app本身可能通过一些手段,绕开js的hook逻辑。

2、脱壳

通常Android加固分为dex加壳和so加固,因此我们讨论脱壳时也是从这两个方面来进行。相关的加壳器和加壳原理,大家可以参考看雪上的这篇文章:Android加壳与脱壳——各类加壳器和原理分析推荐

2.1 apk文件脱壳

Android apk通常情况下,打包流程为:代码编译(含混淆) - 打包 - 签名。打包流程可参考掘金上的这篇文章:Android Apk 编译打包流程。而采用第三方平台(如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文件是经过加密的也无法正常解压。这时通常的应对方案有如下几种:

2.1.1 frida-dexdump

因为app加固后必须保证能正常运行,因此app在实际运行时,原加密过的dex文件已经被解密,并且正确加载到内存中。次时若根据当下内存中的关键编码信息和字段,便壳找到解密后的dex文件在内存中的实际位置,通过内存转储到本地,即可得到原始的未经过加密的dex文件。以上即是frida-dexdump的基本原理。改工具需要安装名为frida-dexdump的python包,搭配frida-server一起使用。

2.1.2 反射大师

原理与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)

2.2 so文件脱壳

为了防止so包被反编译,或者被调试,开发者可以在原始so的基础上进行加密或者重新套壳。加壳思路与dex文件的加壳逻辑类似,因此其脱壳思路也与dex基本一致。即在app运行过程中,动态获取内存中的指定信息,并转储为原始的so文件。这个过程中,需要了解Android Jni机制、汇编反汇编。常用的分析工具是IDAunidgb,这种重点介绍unidgb。

2.2.1 unidgb(适用平台:Android、iOS)

开源地址: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

3、推荐阅读

Android-HTTPS认证的N种方式和对抗方法总结

Https防抓包机制