猿人学爬虫练习

闲着没事找了个2022年的猿人学的安卓逆向对抗比赛apk。

参考自:猿人学安卓逆向对抗比赛(1-5题) - 吾爱破解 - 52pojie.cn

时间冲突

因为是2025年复现的,但比赛当时的服务器环境可能是2022年的,而app运行起来首先会比较时间是否在20s偏差以内,若时间不对会直接退出。

pVZ0LOe.png

我在修改了本地时间为北京时间后,仍然提示,所以根据提示字符串搜索到校验代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
public void onSuccess(OooOO0O oooOO0O) throws Throwable {
if (oooOO0O == null || Math.abs(oooOO0O.OooO00o().longValue() - (System.currentTimeMillis() / 1000)) <= 20) {
return;
}
OooO OooO0o02 = OooO.OooO0o0();
final Context context = this.f5593OooO0O0;
OooO0o02.OooO0O0(context, R.color.xui_config_color_red, "提示", "当前时间和北京时间相差超过20s, 请检查手机时间, 否则某些题目会失败", "退出", new DialogInterface.OnClickListener() { // from class: o00O0O0O.OooO00o
@Override // android.content.DialogInterface.OnClickListener
public final void onClick(DialogInterface dialogInterface, int i) {
GuideTipsDialog.OooO00o.OooO0O0(context, dialogInterface, i);
}
});
}

其中oooOO0O.OooO00o().longValue()应该就是服务器返回的时间,而System.currentTimeMillis()则是本地获取的时间,可能是过了两年的原因,猜测服务器返回时间不一致,所以我们直接把onSuccess函数给hook掉(通过启动时注入的方式),之后就能正常做题了。

1
2
3
4
5
6
7
8
9
function main() {
Java.perform(function () {
let OooO00o = Java.use("com.yuanrenxue.match2022.widget.GuideTipsDialog$OooO00o");
OooO00o["onSuccess"].overload('java.lang.Object').implementation = function () {
console.log('onSuccess is replaced!');
};
});
}
setImmediate(main,2000);

进入主界面:

pVZ0bQO.png

第一关 java层加密

打开界面如下:

pVZ0TW6.png

用httpcanary抓包,发现打开第一关会自动发送post请求获取数据(根据page页数回显),然后下滑会显示第2、3页,同时发送对应的page页。

请求包:

pVZ0qyD.png

响应包:

pVZ0HSK.png

分析加密逻辑,尝试几次后发现token串是固定的,唯一不变的是sign字段。

jadx打开搜索"sign 字符串,定位到接口处。

1
2
3
4
5
6
7
8
9
10
11
12
public interface OooO0O0 {
@FormUrlEncoded
@Headers({"Content-Type:application/x-www-form-urlencoded; charset=utf-8"})
@POST("/list")
Oooo0<o00O000.OooO0O0> OooO(@Field("token") String str);

@FormUrlEncoded
@POST("/app1")
Oooo0<o00O000.OooO00o> OooO00o(@Field("page") Integer num, @Field("sign") String str, @Field("t") Long l);

.................
}

OooO00o进行交叉引用,定位到封装请求包的地方。

1
2
3
4
5
6
7
8
public /* synthetic */ o00Ooo lambda$initListeners$0(o00O000.OooOO0O oooOO0O) throws Exception {
StringBuilder sb = new StringBuilder();
sb.append("page=");
sb.append(this.page);
long longValue = oooOO0O.OooO00o().longValue();
sb.append(longValue);
return ((o0O0ooO.OooO0O0) this.mRequest.OooOOO0(o0O0ooO.OooO0O0.class)).OooO00o(Integer.valueOf(this.page), new Sign().sign(sb.toString().getBytes(StandardCharsets.UTF_8)), Long.valueOf(longValue));
}

所以我们直接hook Sign().sign

1
2
3
4
5
6
7
let Sign = Java.use("com.yuanrenxue.match2022.security.Sign");
Sign.sign.implementation = function (bArr) {
console.log('sign is called' + ', ' + 'bArr: ' + JSON.stringify(bArr));
let ret = this.sign(bArr);
console.log('sign ret value is ' + ret);
return ret;
};

发现参数就是page=i+时间戳,如page=31750577329 (3是第三页)

但是验证的时候发现不是标准的md5加密。

算法还原

所以我们把java层的sign加密提取出来。

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
import java.util.ArrayList;

/* compiled from: proguard-dict.txt */
/* loaded from: classes2.dex */
public class Sign {
private static final int A = 1732584193;
private static final int B = -271733879;
private static final int C = -1732584194;
private static final int D = 271733878;

private static int f(int i, int i2, int i3) {
return ((~i) & i3) | (i2 & i);
}

private static int ff(int i, int i2, int i3, int i4, int i5, int i6) {
return rotateLeft(i + f(i2, i3, i4) + i5, i6);
}

private static int g(int i, int i2, int i3) {
return (i & i3) | (i & i2) | (i2 & i3);
}

private static int gg(int i, int i2, int i3, int i4, int i5, int i6) {
return rotateLeft(i + g(i2, i3, i4) + i5 + 1518565785, i6);
}

private static int h(int i, int i2, int i3) {
return (i ^ i2) ^ i3;
}

private static int hh(int i, int i2, int i3, int i4, int i5, int i6) {
return rotateLeft(i + h(i2, i3, i4) + i5 + 1859775393, i6);
}

private static ArrayList<Integer> padding(byte[] bArr) {
int 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 static String sign(byte[] bArr) {
ArrayList<Integer> padding = padding(bArr);
int lastIndex = -1;
for (int i = padding.size() - 1; i >= 0; i--) {
if (padding.get(i) == 128) {
lastIndex = i;
break;
}
}
if (lastIndex != -1) {
padding.set(lastIndex, 0);
}

int i = A;
int i2 = B;
int i3 = C;
int i4 = D;
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 = ff(i8, i9, i10, i11, iArr[i13], 3);
int ff = ff(i11, i8, i9, i10, iArr[i13 + 1], 7);
i10 = ff(i10, ff, i8, i9, iArr[i13 + 2], 11);
i9 = ff(i9, i10, ff, i8, iArr[i13 + 3], 19);
i12++;
i11 = ff;
}
int[] iArr3 = {0, 1, 2, 3};
int i14 = i8;
int i15 = i11;
for (int i16 = 0; i16 < 4; i16++) {
int i17 = iArr3[i16];
i14 = gg(i14, i9, i10, i15, iArr[i17], 3);
i15 = gg(i15, i14, i9, i10, iArr[i17 + 4], 5);
i10 = gg(i10, i15, i14, i9, iArr[i17 + 8], 9);
i9 = gg(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 hh = hh(i18, i9, i10, i15, iArr[i20], 3);
i15 = hh(i15, hh, i9, i10, iArr[i20 + 8], 9);
i10 = hh(i10, i15, hh, i9, iArr[i20 + 4], 11);
i9 = hh(i9, i10, i15, hh, iArr[i20 + 12], 15);
i19++;
i18 = hh;
}
i += i18;
i2 += i9;
i3 += i10;
i4 += i15;
}
return String.format("%08x%08x%08x%08x" ,Integer.valueOf(i), Integer.valueOf(i2), Integer.valueOf(i3), Integer.valueOf(i4));
}
}

sign加密的第一步先进行了md5填充,我们hook一下padding函数,发现返回值如下:

1
[112, 97, 103, 101, 61, 49, 49, 55, 53, 48, 53, 56, 52, 57, 51, 50, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0]

但是在java里运行打印一下padding,发现结果不一致:

1
[112, 97, 103, 101, 61, 49, 49, 55, 53, 48, 53, 55, 56, 49, 48, 57, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 128, 0, 0, 0]

代码里的多了个128,但实际运行的没有,猜测可能是jadx反编译错误的原因。

所以我们进行修改,将最后一个128置为0:

1
2
3
4
5
6
7
8
9
10
11
ArrayList<Integer> padding = padding(bArr);
int lastIndex = -1;
for (int i = padding.size() - 1; i >= 0; i--) {
if (padding.get(i) == 128) {
lastIndex = i;
break;
}
}
if (lastIndex != -1) {
padding.set(lastIndex, 0);
}

修改后运行sign结果与hook的一致。

协议复现

这里需要用到jpype库,即在python里导入java的jar包,调用java函数。

使用步骤:

1
2
3
4
javac Sign.java
jar cvf Sign.jar Sign.class
pip install JPype1
import jpype

最后的爬虫脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import requests
import json
import time
import jpype

url = "https://appmatch.yuanrenxue.cn/app1"
headers = {
"content-type": "application/x-www-form-urlencoded",
"User-Agent": "Mozilla/5.0 (Linux; U; Android 10; zh-cn; Pixel XL Build/QP1A.191005.007.A3) AppleWebKit/533.1 (KHTML, like Gecko) Version/5.0 Mobile Safari/533.1"
}

jpype.startJVM(jpype.getDefaultJVMPath(), "-ea", "-Djava.class.path=Sign.jar") # 启动java虚拟机
jclass = jpype.JClass("Sign") # 获取java类
Sign = jclass() # 实例化java对象

#循环发送获取100页的数据
for i in range(1,101):
timetmp = int(time.time())
param = 'page=' + str(i) + str(timetmp)
sign = Sign.sign(bytearray(param,'utf-8'))
data = "page=" + str(i) + "&sign=" + str(sign) + "&t=" + str(timetmp) + "&token=b3QoNNTCnzjjPkAYS83yls6gkuJW6s6I0rlX12GLmw0H%20Ar8Unf53c%20FDnGn%2FoPK"
r = requests.post(url=url, data=data, headers=headers)
recv = r.text
print(recv)

但是发现有的时候会爬不下来,并且只能爬取前9页数据,猜测应该也是服务器时间不一致的原因。。

pVZ0zFI.png

第二关 so层加密

同样的界面,首先先抓包。

请求包:

pVZTcp8.png

响应包:

pVZTyff.png

同样搜索"sign字符串,定位到接口处。

1
2
3
4
@FormUrlEncoded
@Headers({"Content-Type:application/x-www-form-urlencoded; charset=utf-8"})
@POST("/app2")
Oooo0<o00O000.OooO00o> OooOO0(@Field("page") Integer num, @Field("ts") Long l, @Field("sign") String str);

交叉引用找到发包地方。

1
2
3
public /* synthetic */ o00Ooo lambda$initListeners$0(o00O000.OooOO0O oooOO0O) throws Exception {
return ((o0O0ooO.OooO0O0) this.f5548OooO0Oo.OooOOO0(o0O0ooO.OooO0O0.class)).OooOO0(Integer.valueOf(this.f5546OooO0O0), oooOO0O.OooO00o(), sign(String.format(getResources().getString(R.string.format_match_02_sign), Integer.valueOf(this.f5546OooO0O0), oooOO0O.OooO00o())));
}

查看sign方法。

1
public native String sign(String str);

ida打开查看是md5+base64,参数是page:time的形式,如1:1750648187

本来打算逆向分析md5魔改了哪些地方的,但是inline hook 寄存器的时候老是崩掉,索性放弃分析了,直接改用unidbg模拟执行拿到加密结果sign

Unidbg代码

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
package com.yuanrenxue.match2022.fragment.challenge;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.Unicorn2Factory;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.DvmClass;
import com.github.unidbg.linux.android.dvm.StringObject;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.memory.Memory;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;

public class ChallengeTwoFragment {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
private final DvmClass ChallengeTwoFragment;
private final boolean logging;

public ChallengeTwoFragment(boolean logging) throws IOException {
this.logging = logging;

emulator = AndroidEmulatorBuilder.for64Bit()
.setProcessName("com.yuanrenxue.match2022")
.addBackendFactory(new Unicorn2Factory(true))
.build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析

vm = emulator.createDalvikVM(); // 创建Android虚拟机
vm.setVerbose(logging); // 设置是否打印Jni调用细节

InputStream soStream = getClass().getResourceAsStream("/example_binaries/libmatch02.so");
File soFile = File.createTempFile("libmatch02", ".so");
Files.copy(soStream, soFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
soFile.deleteOnExit(); // JVM退出时删除临时文件

// 加载SO文件
DalvikModule dm = vm.loadLibrary(soFile, false);
//DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/resources/example_binaries/libmatch02.so"), false);

dm.callJNI_OnLoad(emulator); // 手动执行JNI_OnLoad函数
module = dm.getModule(); // 加载好的libttEncrypt.so对应为一个模块

ChallengeTwoFragment = vm.resolveClass("com/yuanrenxue/match2022/fragment/challenge/ChallengeTwoFragment");
}

public void destroy() throws IOException {
emulator.close();
}

public static void main(String[] args) throws Exception {
String currentDir = System.getProperty("user.dir");
ChallengeTwoFragment challengeTwoFragment = new ChallengeTwoFragment(false);
System.out.println(challengeTwoFragment.sign("1:1652965963"));
challengeTwoFragment.destroy();
}

public String sign(String str) {
StringObject sign = ChallengeTwoFragment.newObject(null).callJniMethodObject(
emulator,
"sign(Ljava/lang/String;)Ljava/lang/String;",
vm.addLocalObject(new StringObject(vm, str))
);
return sign.getValue();
}
}

因为需要对Unidbg项目打jar包供python使用jpype调用,所以默认的加载so的方法行不通:

1
DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/resources/example_binaries/libmatch02.so"), false);

这种方法用File加载so文件,但是我们打包成jar后,JAR 中的资源不是文件系统中的普通文件,而是压缩包内的条目,无法直接用 File API 访问。

所以需要创建一个临时文件,将资源流复制到临时文件

1
2
3
4
5
6
7
InputStream soStream = getClass().getResourceAsStream("/example_binaries/libmatch02.so");
File soFile = File.createTempFile("libmatch02", ".so");
Files.copy(soStream, soFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
soFile.deleteOnExit(); // JVM退出时删除临时文件

// 加载SO文件
DalvikModule dm = vm.loadLibrary(soFile, false);

Unidbg打包

打包步骤参考:【JAVA】使用intellij IDEA将项目打包为jar包_idea打包jar文件-CSDN博客

注意:打包的时候需要勾选Include tests,因为我们的代码是写在test目录下的。

pVZTstP.png

打包完成后把jar包放到py同目录下。

协议复现

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
import requests
import json
import time
import jpype
import base64

url = "https://appmatch.yuanrenxue.cn/app2?token=b3QoNNTCnzjjPkAYS83yls6gkuJW6s6I0rlX12GLmw0H%2BAr8Unf53c%2BFDnGn%2FoPK"
headers = {
"content-type": "application/x-www-form-urlencoded",
"User-Agent": "Mozilla/5.0 (Linux; U; Android 10; zh-cn; Pixel XL Build/QP1A.191005.007.A3) AppleWebKit/533.1 (KHTML, like Gecko) Version/5.0 Mobile Safari/533.1"
}

jpype.startJVM(jpype.getDefaultJVMPath(), "-ea", "-Djava.class.path=unidbg-android.jar") # 启动java虚拟机
jclass = jpype.JPackage("com.yuanrenxue.match2022.fragment.challenge").ChallengeTwoFragment
ChallengeTwoFragment = jclass(False)

#循环发送获取100页的数据
for i in range(1,101):
timetmp = int(time.time())
param = str(i) + ":" + str(timetmp)
sign = ChallengeTwoFragment.sign(param)
data = "page=" + str(i) + "&ts=" + str(timetmp) + "&sign=" + str(sign)
r = requests.post(url=url, data=data, headers=headers)
recv = r.text
print(recv)

应该也是服务器时间不一致的原因,也是有的可能爬取失败。

pVZTg1S.png

第三关 so层加密带混淆

同第二关,变成了crypto函数。

1
public native String crypto(String str, long j);

ida打开,发现加载特别缓慢,而且导出函数里找不到目标函数,由于混淆的特别严重,直接放弃分析,改用unidbg。

首先需要hook crypto,查看一下参数内容:

1
2
3
4
5
6
7
let ChallengeThreeFragment = Java.use("com.yuanrenxue.match2022.fragment.challenge.ChallengeThreeFragment");
ChallengeThreeFragment.crypto.implementation = function (str,i){
console.log("ChallengeThreeFragment param1: " + str)
var res = this.crypto(str,i);
console.log("ChallengeThreeFragment param1: " + res);
return res;
}

输出如下:

1
2
3
ChallengeThreeFragment param1: 0011750663330000
ChallengeThreeFragment param2: 1750663330000
ChallengeThreeFragment res: 206a08845849001b2a99f5727bd9a79d30fb0b2d0265390e3175c48450d3d750

hook结果与请求包m字段一致:

pVZqf2V.png

响应包:

pVZqW80.png

所以直接修改unidbg代码。

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
package com.yuanrenxue.match2022.fragment.challenge;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.Unicorn2Factory;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.DvmClass;
import com.github.unidbg.linux.android.dvm.StringObject;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.memory.Memory;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;

public class ChallengeThreeFragment {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
private final DvmClass ChallengeThreeFragment;
private final boolean logging;

public ChallengeThreeFragment(boolean logging) throws IOException {
this.logging = logging;

emulator = AndroidEmulatorBuilder.for64Bit()
.setProcessName("com.yuanrenxue.match2022")
.addBackendFactory(new Unicorn2Factory(true))
.build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析

vm = emulator.createDalvikVM(); // 创建Android虚拟机
vm.setVerbose(logging); // 设置是否打印Jni调用细节

InputStream soStream = getClass().getResourceAsStream("/example_binaries/libmatch03.so");
File soFile = File.createTempFile("libmatch03", ".so");
Files.copy(soStream, soFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
soFile.deleteOnExit(); // JVM退出时删除临时文件

// 加载SO文件
DalvikModule dm = vm.loadLibrary(soFile, false);
//DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/resources/example_binaries/libmatch02.so"), false); // 加载libttEncrypt.so到unicorn虚拟内存,加载成功以后会默认调用init_array等函数

dm.callJNI_OnLoad(emulator); // 手动执行JNI_OnLoad函数
module = dm.getModule(); // 加载好的libttEncrypt.so对应为一个模块

ChallengeThreeFragment = vm.resolveClass("com/yuanrenxue/match2022/fragment/challenge/ChallengeThreeFragment");
}

public void destroy() throws IOException {
emulator.close();
}

public static void main(String[] args) throws Exception {
ChallengeThreeFragment ChallengeThreeFragment = new ChallengeThreeFragment(false);
System.out.println(ChallengeThreeFragment.sign("0011750662897000","1750662897000"));
ChallengeThreeFragment.destroy();
}

public String sign(String str,String i) {
StringObject sign = ChallengeThreeFragment.newObject(null).callJniMethodObject(
emulator,
"crypto(Ljava/lang/String;J)Ljava/lang/String;",
vm.addLocalObject(new StringObject(vm, str)), Long.parseLong(i)
);
return sign.getValue();
}
}

运行,发现每次的结果不一致,猜测可能是so层加了随机数作盐值。

尝试python发包请求:

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
import requests
import json
import time
import jpype
import base64

url = "https://appmatch.yuanrenxue.cn/app3"
headers = {
"content-type": "application/x-www-form-urlencoded",
"User-Agent": "Mozilla/5.0 (Linux; U; Android 10; zh-cn; Pixel XL Build/QP1A.191005.007.A3) AppleWebKit/533.1 (KHTML, like Gecko) Version/5.0 Mobile Safari/533.1"
}

jpype.startJVM(jpype.getDefaultJVMPath(), "-ea", "-Djava.class.path=unidbg-android.jar") # 启动java虚拟机
jclass = jpype.JPackage("com.yuanrenxue.match2022.fragment.challenge").ChallengeThreeFragment
ChallengeThreeFragment = jclass(False)

#循环发送获取100页的数据
for i in range(1,101):
timetmp = int(time.time())
param2 = str(timetmp) + "000"
param1 = "00" + str(i) + param2
sign = ChallengeThreeFragment.sign(param1,param2)
data = "m=" + str(sign) + "&page=" + str(i) + "&token=b3QoNNTCnzjjPkAYS83yls6gkuJW6s6I0rlX12GLmw0H%20Ar8Unf53c%20FDnGn%2FoPK"
r = requests.post(url=url, data=data, headers=headers)
recv = r.text
print(recv)

结果如下:

pVZqRCq.png

依旧是只有前9条数据。。。

第四关 grpc