如何用fiddler抓包参考:https://www.cnblogs.com/JKding233/p/16649489.html

先介绍一些前置知识点和环境配置

Frida 安装和运行

pip3 install frida frida-tools

查看模拟器版本:

1
2
3
4
adb shell
getprop ro.product.cpu.abi

>>> x86_64

下载对应 frida-server压缩包:(小于或等于本地frida版本

image.png

解压缩,拿到里面的可执行文件

1
frida-server-16.2.1-android-x86_64

把可执行文件推送到手机里面:

1
adb push frida-server-15.1.10-android-x86_64 /data/local/tmp/frida-server

启动 frida:

1
2
3
4
5
adb shell
su
cd /data/local/tmp
chmod 755 frida-server
./frida-server

配置接口转发,注意每次启动都需要配置:

1
2
adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043

开启另一个cmd,运行:frida-ps -R,连接成功:

image.png

Frida Python 脚本

hook 脚本示例
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
import frida
import sys

device = frida.get_remote_device() # adb devices 查看
process = device.attach("猿人学2022") # 进程名

src = """
function main(){
console.log("脚本加载成功");
Java.perform(function() {
var clazz = Java.use('com.yuanrenxue.match2022.security.Sign');
clazz.sign.implementation = function() {
console.log('进入函数内部');
console.log("参数为:", arguments)
console.log("结果为:",clazz.sign.apply(this, arguments));
return clazz.sign.apply(this, arguments);
}
});
}
setImmediate(main)
"""


def on_message(message, data):
if message["type"] == "send":
print(f"send >>> {message['payload']}")
else:
print(message)


script = process.create_script(src)
script.on("message", on_message)
script.load()
sys.stdin.read()

  • 一直没有输出的话,先退出重新启动app,再运行脚本
  • 一直进入不了函数内部的话,可以使用 rpc 的方式
  • 不需要 on_message 也可以打印
rpc 脚本示例
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
import frida


device = frida.get_remote_device() # adb devices 查看
process = device.attach("猿人学2022")

rpc_code = """
var result
function main() {
console.log("Script loded successfully")
console.log()
Java.perform(function () {
// 主要是修改这里面
var Sign = Java.use('o00OO.OooO00o')
console.log("调用函数成功")
// var sign = Sign.$new()
// console.log("创建实例成功")
result = Sign.OooO00o(-274893829204)
console.log(result)
})
return result
}
rpc.exports = {
main: main
}
"""


def on_message(message, data):
if message["type"] == "send":
print(f"send >>> {message['payload']}")
# bytes2str(message['payload'])
else:
print(message)


script = process.create_script(rpc_code)
script.on("message", on_message)
script.load()
result = script.exports.main()
print(result)

  • 看情况是否要创建实例public static 开头的静态函数直接调用就行

题目分析:

登录APP后,显示要强制更新,

http://download.python-spider.com/yuanrenxuem107.apk

那就先把 apk 文件下载到本地,方便后面分析,抓包看一下分析请求

image.png

只有 sign 是变动的,page、t、token 都是一些普通的参数,直接整个文件拖进 jadx 分析,搜索关键词 第一题

很快定位到 com.yuanrenxue.match2022.fragment.challenge.ChallengeOneFragment 这个类,直接搜索 sign

image.png

可以看出,参数主要是 sbstring 再转 bytes 传入的,先用 frida hook 一下sign函数的参数和结果:

image.png

把参数 send 出来,转成字符串,python bytes 列表转字符串方法如下

1
2
3
def bytes2str(ascii_lst):
string = ''.join(map(chr, ascii_lst))
print(string)
1
2
3
4
5
6
7
8
var clazz = Java.use('com.yuanrenxue.match2022.security.Sign');
clazz.sign.implementation = function() {
console.log('进入函数内部');
console.log("参数为:", arguments[0]);
send(arguments[0]);
console.log("结果为:",clazz.sign.apply(this, arguments));
return clazz.sign.apply(this, arguments);
}

on_message 函数改一下:

1
2
3
4
5
6
def on_message(message, data):
if message["type"] == "send":
print(f"send >>> {message['payload']}")
bytes2str(message['payload'])
else:
print(f"log >>> {message}")

打印结果:

image.png

可见参数为:page=x 加 时间戳

进入 sign 函数内部,只引入了一个依赖,可以考虑扣 java代码,觉得麻烦的话就直接用rpc,这里两个方案的实现都写一下

image.png

方案一 调用 java 代码

既然要扣代码,就得先解决第三方依赖的问题,看一下调用点:

image.png

传入的是一个长整型数字,结合 String.format() 函数的使用 ,这里很可能是一个固定值,用于字符转换

本来想用 hook,结果死活进不去函数,无奈改用rpc调用的方式,脚本我已经放在上面了,计算结果为:%02x%02x%02x%02x

简单介绍一下,在 java中,String.format("%02X", 11) 就是 11 转 十六进制的意思,不足的部分补0,所以输出:0B

同理 String.format("%03X", 11) 输出 00B

抠出来的java代码如下:

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import java.util.ArrayList;

/* compiled from: proguard-dict.txt */
/* loaded from: classes2.dex */
public class Sign {

/* renamed from: A */
private static final int f276A = 1732584193;

/* renamed from: B */
private static final int f277B = -271733879;

/* renamed from: C */
private static final int f278C = -1732584194;

/* renamed from: D */
private static final int f279D = 271733878;

/* renamed from: f */
private static int m21f(int i, int i2, int i3) {
return ((~i) & i3) | (i2 & i);
}

/* renamed from: ff */
private static int m20ff(int i, int i2, int i3, int i4, int i5, int i6) {
return rotateLeft(i + m21f(i2, i3, i4) + i5, i6);
}

/* renamed from: g */
private static int m19g(int i, int i2, int i3) {
return (i & i3) | (i & i2) | (i2 & i3);
}

/* renamed from: gg */
private static int m18gg(int i, int i2, int i3, int i4, int i5, int i6) {
return rotateLeft(i + m19g(i2, i3, i4) + i5 + 1518565785, i6);
}

/* renamed from: h */
private static int m17h(int i, int i2, int i3) {
return (i ^ i2) ^ i3;
}

/* renamed from: hh */
private static int m16hh(int i, int i2, int i3, int i4, int i5, int i6) {
return rotateLeft(i + m17h(i2, i3, i4) + i5 + 1859775393, i6);
}

private ArrayList<Integer> padding(byte[] bArr) {
long length = bArr.length * 8;
ArrayList<Integer> arrayList = new ArrayList<>();
for (byte b : bArr) {
arrayList.add(Integer.valueOf(b));
}
arrayList.add(128);
while (((arrayList.size() * 8) + 64) % 512 != 0) {
arrayList.add(0);
}
for (int i = 0; i < 8; i++) {
arrayList.add(Integer.valueOf((int) ((length >>> (i * 8)) & 255)));
}
return arrayList;
}

private static int rotateLeft(int i, int i2) {
return (i >>> (32 - i2)) | (i << i2);
}

public String sign(byte[] bArr) {
ArrayList<Integer> padding = padding(bArr);
int i = f276A;
int i2 = f277B;
int i3 = f278C;
int i4 = f279D;
for (int i5 = 0; i5 < padding.size() / 64; i5++) {
int[] iArr = new int[16];
for (int i6 = 0; i6 < 16; i6++) {
int i7 = (i5 * 64) + (i6 * 4);
iArr[i6] = (padding.get(i7 + 3).intValue() << 24) | padding.get(i7).intValue() | (padding.get(i7 + 1).intValue() << 8) | (padding.get(i7 + 2).intValue() << 16);
}
int[] iArr2 = {0, 4, 8, 12};
int i8 = i;
int i9 = i2;
int i10 = i3;
int i11 = i4;
int i12 = 0;
while (i12 < 4) {
int i13 = iArr2[i12];
i8 = m20ff(i8, i9, i10, i11, iArr[i13], 3);
int m20ff = m20ff(i11, i8, i9, i10, iArr[i13 + 1], 7);
i10 = m20ff(i10, m20ff, i8, i9, iArr[i13 + 2], 11);
i9 = m20ff(i9, i10, m20ff, i8, iArr[i13 + 3], 19);
i12++;
i11 = m20ff;
}
int[] iArr3 = {0, 1, 2, 3};
int i14 = i8;
int i15 = i11;
for (int i16 = 0; i16 < 4; i16++) {
int i17 = iArr3[i16];
i14 = m18gg(i14, i9, i10, i15, iArr[i17], 3);
i15 = m18gg(i15, i14, i9, i10, iArr[i17 + 4], 5);
i10 = m18gg(i10, i15, i14, i9, iArr[i17 + 8], 9);
i9 = m18gg(i9, i10, i15, i14, iArr[i17 + 12], 13);
}
int[] iArr4 = {0, 2, 1, 3};
int i18 = i14;
int i19 = 0;
while (i19 < 4) {
int i20 = iArr4[i19];
int m16hh = m16hh(i18, i9, i10, i15, iArr[i20], 3);
i15 = m16hh(i15, m16hh, i9, i10, iArr[i20 + 8], 9);
i10 = m16hh(i10, i15, m16hh, i9, iArr[i20 + 4], 11);
i9 = m16hh(i9, i10, i15, m16hh, iArr[i20 + 12], 15);
i19++;
i18 = m16hh;
}
i += i18;
i2 += i9;
i3 += i10;
i4 += i15;
}
return String.format("%02x%02x%02x%02x", Integer.valueOf(i), Integer.valueOf(i2), Integer.valueOf(i3), Integer.valueOf(i4));
}
}

有一个地方需要注意,int length = bArr.length * 8 替换为 long length = bArr.length * 8

jpype的使用,上面有介绍,可以比对 hook结果进行验证

我这里就直接上爬虫脚本了

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
# -*- coding: utf-8 -*-
# time: 2022/10/1 13:44
# author: Chen

import requests
import jpype

jvm_path = jpype.getDefaultJVMPath()
jpype.startJVM(jvm_path, "-ea", "-Dfile.encoding=utf-8", convertStrings=True)
JDClass = jpype.JClass("Sign")
javaInstance = JDClass()

# jpype.shutdownJVM()
results = []
for page in range(1, 101):
token_url = "https://appmatch.yuanrenxue.com/time?token=MuuqC7kHE79hZhN3L3Ip3u2e1jp%252BgLY1V2S1%252FKWTbpYZMqchIHNxll%252BE06%252BEuHQ5"
resp_json = requests.get(token_url).json()
ts = resp_json["time"]
raw_sign = f"page={page}{ts}"
url = "https://appmatch.yuanrenxue.com/app1"
data = {
"page": page,
"sign": javaInstance.sign(raw_sign.encode()),
"t": ts,
"token": "MuuqC7kHE79hZhN3L3Ip3u2e1jp gLY1V2S1/KWTbpYZMqchIHNxll E06 EuHQ5"
}
resp_json = requests.post(url, data=data).json()
num_lst = [int(d['value'].strip()) for d in resp_json["data"]]
print(f"第 {page} 页:", num_lst)
results.extend(num_lst)

print("100页面总和为:", sum(results))

运行结果:

image.png

方案二 Frida rpc

参考:https://www.cnblogs.com/JKding233/p/16649489.html

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
# -*- coding: utf-8 -*-
# time: 2022/10/1 14:29
# author: Chen

import frida
import requests


rpc_code2 = """
var bArr
var result
function main(bArr) {
console.log("Script loded successfully")
console.log(bArr)
Java.perform(function () {
var Sign = Java.use('com.yuanrenxue.match2022.security.Sign')
console.log("调用函数成功")
var instance = Sign.$new()
console.log("创建实例成功")
result = instance.sign(bArr)
console.log(result)
})
return result
}
rpc.exports = {
main: main
}
"""

device = frida.get_remote_device() # adb devices 查看
process = device.attach("猿人学2022")
script = process.create_script(rpc_code2)
script.load()
results = []
for page in range(1, 101):
token_url = "https://appmatch.yuanrenxue.com/time?token=MuuqC7kHE79hZhN3L3Ip3u2e1jp%252BgLY1V2S1%252FKWTbpYZMqchIHNxll%252BE06%252BEuHQ5"
resp_json = requests.get(token_url).json()
ts = resp_json["time"]
raw_sign = f"page={page}{ts}"
bArr = [ord(c) for c in raw_sign]
url = "https://appmatch.yuanrenxue.com/app1"
data = {
"page": page,
"sign": script.exports.main(bArr),
"t": ts,
"token": "MuuqC7kHE79hZhN3L3Ip3u2e1jp gLY1V2S1/KWTbpYZMqchIHNxll E06 EuHQ5"
}
resp_json = requests.post(url, data=data).json()
num_lst = [int(d['value'].strip()) for d in resp_json["data"]]
print(f"第 {page} 页:", num_lst)
results.extend(num_lst)

print("100页面总和为:", sum(results))

挑战成功

image.png

整个逆向过程花费的时间挺多的,不过总算搞懂了 frida的使用,以后要逆向其他的app就方便多了 😁✨👌