环境准备

安装Frida框架

Frida官网:https://www.frida.re/

Frida源码:https://github.com/frida

Fride是一个轻量的hook框架,有着访问进程内存等能力。

我们需要在windows安装frida客户端和在安卓安装frida服务端。

Windows需要有Python3环境,而安卓必须有Root权限,本机如无Root权限可以在虚拟机上进行。

Windows下的frida CLI

首先需要有Python3的环境,然后直接进行安装

1
pip install frida

然后安装frida-tools

1
pip install frida-tools

查看版本号,即说明安装成功

1
frida --version

Android端的frida-server

可以在Github下载最新的frida-server。上面有着非常多的平台和架构可以选择,选择你的Android手机的架构就可以下载了。

Tips:如果不清楚手机cpu架构是什么,可以进行下面操作:

  1. 连接手机ADB
  2. 进入adb shell
1
adb shell
  1. 查看cpu架构
1
getprop ro.product.cpu.abi

然后选择对应的frida-server进行下载

下载后进行解压,将解压的frida-server通过adb或者其他方式传入手机,这里我们用adb进行传输

1
adb push frida-server-15.1.8-android-arm64 /data/local/tmp

对端口进行转发,默认使用27042端口与frida-server通信

1
adb forward tcp:27042 tcp:27042

对frida-server加权限

1
2
3
4
5
6
adb shell
equuleus:/ $ su
2|equuleus:/ # cd /data/local/tmp
equuleus:/data/local/tmp # ls
frida-server-15.1.8-android-arm64
equuleus:/data/local/tmp # chmod 755 frida-server-15.1.8-android-arm64

运行frida-server

1
./frida-server-15.1.8-android-arm64

新建一个控制台窗口,检查frida-server是否正常运行,查询手机进程

1
frida-ps -R

控制台将手机进程信息进行输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  PID  Name
----- -------------------------------------------------------------------------------------------------
9020 .dataservices
9059 .qtidataservices
21109 ADB WiFi
1276 ATFWD-daemon
20772 Google Play 商店
21191 Magisk
9430 Pharos Pro
9136 USIM卡应用
24100 adbd
1239 adsprpcd
601 android.hardware.atrace@1.0-service
8325 android.hardware.audio@2.0-service
1322 android.hardware.biometrics.fingerprint@2.1-service
834 android.hardware.bluetooth@1.0-service-qti
835 android.hardware.camera.provider@2.4-service
836 android.hardware.cas@1.1-service
837 android.hardware.configstore@1.1-service
838 android.hardware.drm@1.0-service
839 android.hardware.drm@1.2-service.clearkey

证明frida-server是正常运行的

APK去壳

获取目标应用的dex

我们通过“3秒去壳”神器:FRIDA-DEXDump(https://github.com/hluwa/FRIDA-DEXDump)获取应用dex

直接进行安装

1
pip3 install frida-dexdump

显示帮助信息

1
frida-dexdump -h

显示帮助页面信息,则证明安装成功

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
------------------------------------------------------------------------------------------------------------------------
____________ ___________ ___ ______ _______ _______
| ___| ___ \_ _| _ \/ _ \ | _ \ ___\ \ / / _ \
| |_ | |_/ / | | | | | / /_\ \______| | | | |__ \ V /| | | |_ _ _ __ ___ _ __
| _| | / | | | | | | _ |______| | | | __| / \| | | | | | | '_ ` _ \| '_ \
| | | |\ \ _| |_| |/ /| | | | | |/ /| |___/ /^\ \ |/ /| |_| | | | | | | |_) |
\_| \_| \_|\___/|___/ \_| |_/ |___/ \____/\/ \/___/ \__,_|_| |_| |_| .__/
| |
|_|
https://github.com/hluwa/FRIDA-DEXDump
------------------------------------------------------------------------------------------------------------------------

Usage: frida-dexdump -n <process> -p <pid> -f[enable spawn mode] -s <delay seconds> -d[enable deep search] -P <prepend script path> -A <append script path>

-n: [Optional] Specify target process name, when spawn mode, it requires an application package name. If not specified, use frontmost application.
-p: [Optional] Specify pid when multiprocess. If not specified, dump all.
-f: [Optional] Use spawn mode, default is disable.
-s: [Optional] When spawn mode, start dump work after sleep few seconds. default is 10s.
-d: [Optional] Enable deep search maybe detected more dex, but speed will be slower.
-P: [Optional] Prepend a Frida script to run before dexdump does.
-A: [Optional] Append a Frida script to run after dexdump done.
-E: [Optional] Changes 'su -c cmd' to 'su 0 cmd' for emulators.
-h: show help.

我们在手机上运行目标应用,然后使用上面的frida-ps -R命令获取进程的PID(即下面代码演示中的18912),然后尝试获取dex,因为普通搜索可能搜索到的dex不全,这里我们使用深度搜索:

1
frida-dexdump -p 18912 -d

在搜索过程中,会有一些dex被发现,并进行保存,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
[DEXDump]: DexSize=0x34c, DexMd5=3adc8638108be839ded93b461116144c, SavePath=D:\Person_Program\Frida\dex/0xe62d5000.dex
[DEXDump]: DexSize=0x1000, DexMd5=c0d7b0658d67591a8426ffec23e7aac0, SavePath=D:\Person_Program\Frida\dex/0xe62d5000.dex
[DEXDump]: DexSize=0x2ac, DexMd5=c44309f71fdb137742dcea736be5f92a, SavePath=D:\Person_Program\Frida\dex/0xe62d7000.dex
[DEXDump]: DexSize=0x1000, DexMd5=0205ddcc1829e68e7191c363d8ba4df3, SavePath=D:\Person_Program\Frida\dex/0xe62d7000.dex
[DEXDump]: DexSize=0x2b0, DexMd5=9c25cb9a5adb25ed0673f594cf87571f, SavePath=D:\Person_Program\Frida\dex/0xe62d9000.dex
[DEXDump]: DexSize=0x1000, DexMd5=d915d67068efbd38c54f7c653d4518cf, SavePath=D:\Person_Program\Frida\dex/0xe62d9000.dex
[DEXDump]: DexSize=0xecfc, DexMd5=2dd14f384bfe4741e5a9463e12c79c89, SavePath=D:\Person_Program\Frida\dex/0xe661c02c.dex
[DEXDump]: DexSize=0xefd4, DexMd5=18445cc5ff022b10460403ea87d232d4, SavePath=D:\Person_Program\Frida\dex/0xe661c02c.dex
[DEXDump]: DexSize=0x1d0, DexMd5=8c261b848588b1a4345c570ef6fe63d8, SavePath=D:\Person_Program\Frida\dex/0xe74ef000.dex
[DEXDump]: DexSize=0x1000, DexMd5=0434eb92bd66c427682c7423c9934336, SavePath=D:\Person_Program\Frida\dex/0xe74ef000.dex
[DEXDump]: DexSize=0x6391c, DexMd5=82d43d81513d858f3e6e89cdf9f27a15, SavePath=D:\Person_Program\Frida\dex/0xe750f02c.dex
[DEXDump]: DexSize=0x63fd4, DexMd5=b96219f7a71a763d089d4120fc065cfd, SavePath=D:\Person_Program\Frida\dex/0xe750f02c.dex

结束之后,所有找到的dex会保存在命令行所在的目录中。

查看应用代码

我们使用jadx(https://github.com/skylot/jadx )对dex文件进行反编译,1.2.0版本支持对多个dex文件的查看,正好符合我们的需求。

将所有的dex文件添加至jadx中,如果正常的话,会展现出所有的源代码。但大部分情况,由于部分dex文件损坏,jadx会提示文件无法打开,此时我们需要逐一将有问题的dex文件删除掉(暂时没有找到更好的办法,如果有好的办法可以踢我一下:)

将损坏的dex文件剔除后,我们可以看到所有的源代码

以某APP为例

需求

APP更新了某签到功能的提交签到接口,参数部分做了加密,我们的目标就是找出相应的加密逻辑

定位

APP接口更新了加密的参数

1
{"appVersion":"xxx","systemName":"android","bodyString":"8tbnsTxxxxxxxx==","sign":"41b42bddffe257599f7xxxxxxx","model":"M2006J10C","lon":120.2,"calVersion":"firstv","systemVersion":"11","deviceId":"e6adf99332c3ecebf9933","userId":"123456","version":"first_v2","lat":30.1}

通过jadx搜索字符串去快速定位到相应代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
private void doHttpAction(IUIViewPlugin.HttpAction httpAction, JSONObject jSONObject, Map<String, String> map, CallbackContext callbackContext, Map<String, Object> map2) {
try {
String optString = jSONObject.optString("url");
String str = "utf-8";
String str2 = null;
Map<String, String> map3 = null;
if (httpAction == IUIViewPlugin.HttpAction.sendGetRequest) {
String optString2 = jSONObject.optString(a.p);
if (!TextUtils.isEmpty(optString2)) {
map3 = (Map) JSON.parseObject(optString2, Map.class);
}
if (map.containsKey("Except-Encoding")) {
str = map.get("Except-Encoding");
}
map.put("Except-Encoding", str);
callbackContext.success(new JSONObject(JSON.toJSONString(new OkHTTPHelper().get(optString, map, map3))));
} else if (httpAction == IUIViewPlugin.HttpAction.sendPostRequest) {
String optString3 = jSONObject.optString("bodyString");
String c = tq.c(optString3, CommonUtils.q(), CpUiUtils.f());
if (map2 != null) {
map2.put("bodyString", c);
map2.put("sign", CommonUtils.A(map2));
map2.put("version", tq.h(CpUiUtils.f()));
map2.put("calVersion", "firstv");
optString3 = new JSONObject(map2).toString();
}
if (map != null && (str2 = map.get("Content-Type")) == null) {
str2 = map.get("content-type");
}
if (str2 == null) {
str2 = "application/json; charset=utf-8";
}
if (map != null && map.containsKey("Except-Encoding")) {
str = map.get("Except-Encoding");
}
OkHttpClient build = OkHTTPHelper.getOkHttpBuilder().build();
Headers.Builder builder = new Headers.Builder();
builder.add("tenantId", com.wisedu.campushoy.common.utils.b.h("tenant_id")).add("User-Agent", NetWorkUtil.getDefaultConfigUserAgent()).add("CpdailyStandAlone", ModuleCommImpl.getInstance().getBuildPackageType());
if (map != null) {
for (String str3 : map.keySet()) {
builder.add(str3, map.get(str3));
}
}
Response execute = build.newCall(new Request.Builder().url(optString).headers(builder.build()).post(RequestBody.create(MediaType.parse(str2), optString3)).build()).execute();
HttpResult httpResult = new HttpResult();
httpResult.code = execute.code();
String contentType = getContentType(execute.header("Content-Type"));
if (TextUtils.isEmpty(contentType) || !contentType.startsWith("image/")) {
contentType = getContentType(execute.header("content-type"));
}
if (TextUtils.isEmpty(contentType) || !contentType.startsWith("image/")) {
httpResult.data = new String(execute.body().bytes(), str);
} else {
httpResult.data = "data:" + contentType + ";base64," + Base64.encodeToString(execute.body().bytes(), 2);
}
httpResult.url = execute.request().url().toString();
httpResult.headers = new HashMap<String, String>(httpResult) {
public final /* synthetic */ HttpResult val$result;

{
this.val$result = r2;
put("url", r2.url);
}
};
callbackContext.success(new JSONObject(JSON.toJSONString(httpResult)));
}
} catch (Exception e) {
e.printStackTrace();
}
}

通过IDA

x查看引用

F5 显示代码

未完待续。。