1 回顾

2 分析 XhsHttpInterceptor

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
# 0 正常我们应该使用unidbg 运行intercept了,但是还得做如下分析

# 1 正常使用拦截器流程
1 实例化得到拦截器对象
2 通过 addInterceptor(interceptor)

# 2 所以我们要分析出 XhsHttpInterceptor,在实例化的时候做了什么操作
public class XhsHttpInterceptor implements Interceptor {
public long cPtr;
public a<Request> predicate;
#0 这个代码比构造方法执行更早
static {
initializeNative();
}
public static native void initializeNative();

#1 构造方法
public XhsHttpInterceptor(String str, a<Request> aVar) {
this.cPtr = initialize(str);
this.predicate = aVar;
}
# 2 执行jni的 initialize 方法
public native long initialize(String str);

# 3 执行 intercept 方法
public native Response intercept(Interceptor.Chain chain, long j2) throws IOException;

}、


# 3 分析完后--》真正的执行顺序是:
-1 public static native void initializeNative();
-2 public native long initialize(String str);
-3 public native Response intercept(Interceptor.Chain chain, long j2) throws IOException;

# 4 以后遇到这种情况,可以直接执行intercept得到结果--》如果得到的结果发送请求可以用,上面两个就不用执行了,如果不能用,要倒回来再处理上面两个



image-20240517191340243

3 unidbg

3.1 分析

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
# 0 so文件:libshield.so
# 1 void initializeNative()
1.1 找到类:DvmClass cls = vm.resolveClass("com/xingin/shield/http/XhsHttpInterceptor");
1.2 获取签名
String method = "initializeNative()V";
1.3 执行
cls.callStaticJniMethodObject(emulator, method);

# 2 long initialize(String str)
2.1 找到类:
DvmClass cls = vm.resolveClass("com/xingin/shield/http/XhsHttpInterceptor");
2.2 获取签名
String method = "initialize(Ljava/lang/String;)J";
2.3 执行
long cPtr = cls.callStaticJniMethodLong(emulator, method, new StringObject(vm, str));

# 3 Response intercept(Interceptor.Chain chain, long j2)
3.1 找到类:
DvmClass cls = vm.resolveClass("com/xingin/shield/http/XhsHttpInterceptor");
2.2 获取签名(两个参数)
String method = "intercept(Lokhttp3/Interceptor$Chain;J)Lokhttp3/Response;";
2.3 执行
cls.callStaticJniMethodLong(
emulator,
method,
vm.resolveClass("okhttp3/Interceptor$Chain").newObject(null),
cPtr
);



3.2 unidbg运行(initializeNative)

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
package com.nb.demo;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.memory.Memory;

import java.io.File;

public class XHS extends AbstractJni {
public static AndroidEmulator emulator; // 静态属性,以后对象和类都可以直接使用
public static Memory memory;
public static VM vm;
public static Module module;

public XHS() {
// 1.创建设备(32位或64位模拟器), 具体看so文件在哪个目录。 在armeabi-v7a就选择32位
// 传进设备时,如果是32位,后面so文件就要用32位,同理需要用64位的
// 这个名字可以随便写,一般写成app的包名 以后可能会动
emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.xhs").build();

// 2 获取内存对象(可以操作内存)
memory = emulator.getMemory();

// 3.设置安卓sdk版本(只支持19、23)
memory.setLibraryResolver(new AndroidResolver(23));

// 4.创建虚拟机(运行安卓代码需要虚拟机,就想运行py代码需要python解释器一样) 以后会动
vm = emulator.createDalvikVM(new File("apks/xhs/v6.73.0.apk"));
vm.setJni(this); // 后期补环境会用,把要补的环境,写在当前这个类中,执行这个代码即可,但是必须继承AbstractJni
//vm.setVerbose(true); //是否展示调用过程的细节

// 5.加载so文件
// 以后会动,只要懂so文件路径即可
DalvikModule dm = vm.loadLibrary(new File("apks/xhs/libshield.so"), false);
dm.callJNI_OnLoad(emulator); // jni开发动态注册,会执行JNI_OnLoad,如果是动态注册,需要执行一下这个,如果静态注册,这个不需要执行,车智赢案例是静态注册

// 6.dm代表so文件,dm.getModule()得到module对象,基于module对象可以访问so中的成员。
module = dm.getModule(); // 把so文件加载到内存后,后期可以获取基地址,偏移量等,该变量代指so文件

}
public void initializeNative(){
DvmClass cls = vm.resolveClass("com/xingin/shield/http/XhsHttpInterceptor");
String method = "initializeNative()V";
cls.callStaticJniMethodObject(emulator, method);

}

public static void main(String[] args) {
//1 new出一个对象,调用构造方法
XHS xhs = new XHS();
xhs.initializeNative();
}
}

3.2.1 补环境

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
# 1 java/nio/charset/Charset->defaultCharset()Ljava/nio/charset/Charset;
if(signature.equals("java/nio/charset/Charset->defaultCharset()Ljava/nio/charset/Charset;")){
// java 层有的,直接补
Charset charset=Charset.defaultCharset();
return ProxyDvmObject.createObject(vm,charset);
}

# 2 com/xingin/shield/http/ContextHolder->sDeviceId:Ljava/lang/String;
if(signature.equals("com/xingin/shield/http/ContextHolder->sDeviceId:Ljava/lang/String;")){
// com/xingin/shield/http/ContextHolder类对象的sDeviceId字段,返回字符串
//1 不能直接拿到,应该是app内部生成,赋值给这个字段,正常我们通过反编译,找到位置,查找加密算法
//2 直接hook得到具体值,放入:c90832a6-bfb9-3e46-a3dd-f5a939a4bdd2
return ProxyDvmObject.createObject(vm,"c90832a6-bfb9-3e46-a3dd-f5a939a4bdd2");

}

# 3 com/xingin/shield/http/ContextHolder->sAppId:I
if(signature.equals("com/xingin/shield/http/ContextHolder->sAppId:I")){
return -319115519;
}

# 4 com/xingin/shield/http/ContextHolder->sExperiment:Z
if (signature.equals("com/xingin/shield/http/ContextHolder->sExperiment:Z")){
return true;
}

image-20240517191356982

image-20240517191403796

3.2.2 hook得到sDeviceId

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Java.perform(function () {
var ContextHolder = Java.use('com.xingin.shield.http.ContextHolder');

console.log('sAppId=',ContextHolder.sAppId.value);
console.log('sDeviceId=',ContextHolder.sDeviceId.value);
console.log('sExperiment=',ContextHolder.sExperiment.value);
})

// frida -UF -l 1-hook得到sDeviceld.js
/*
sAppId= -319115519
sDeviceId= c90832a6-bfb9-3e46-a3dd-f5a939a4bdd2
sExperiment= true

*/

3.3 unidbg运行(initialize)

1
2
3
4
5
6
7
8
9
10
11
12
# 1 long initialize(String str) 有参数,我们需要hook得到这个值
# 2 hook不到,需要延迟hook,晚一点hook
hook到str 是 main,多次hook都一样


# 3 unidbg运行,返回的long 是给下一个方式使用的(通过hook知道的),我们直接返回
public long initialize(String str){
DvmClass cls = vm.resolveClass("com/xingin/shield/http/XhsHttpInterceptor");
String method = "initialize(Ljava/lang/String;)J";
long cPtr = cls.callStaticJniMethodLong(emulator, method, new StringObject(vm, str));
return cPtr;
}

3.3.1 hook得到initialize的参数

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
function do_hook() {
setTimeout(function () {
Java.perform(function () {
var XhsHttpInterceptor = Java.use('com.xingin.shield.http.XhsHttpInterceptor');
XhsHttpInterceptor.initialize.implementation = function (str) {
console.log("str=", str);
return this.initialize(str);
};
})
}, 10);
}

function load_so_and_hook() {
// 加载某个so文件 dlopen("xxxx/xxxx.so") -> 函数执行完毕 -> Hook
var dlopen = Module.findExportByName(null, "dlopen");
var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");

//1 只要加载so文件,就会执行下面代码
Interceptor.attach(dlopen, {
onEnter: function (args) {
var path_ptr = args[0];
var path = ptr(path_ptr).readCString();
// console.log("[dlopen:]", path);
this.path = path;
},
onLeave: function (retval) {
// 2 通过判断是否是加载的libshield,如果是,就执行hook
if (this.path.indexOf("libshield.so") !== -1) {
console.log("[dlopen:]", this.path);
do_hook();
}
}
});

Interceptor.attach(android_dlopen_ext, {
onEnter: function (args) {
var path_ptr = args[0];
var path = ptr(path_ptr).readCString();

this.path = path;
}, onLeave: function (retval) {
if (this.path.indexOf("libshield.so") !== -1) {
console.log("\nandroid_dlopen_ext加载:", this.path);
do_hook();

}
}
});
}

load_so_and_hook();

// frida -U -f com.xingin.xhs -l 2-hook得到initValue入参.js


// frida -U -f com.xingin.xhs -l 9.initValue.js
/*
android_dlopen_ext加载: /data/app/~~r7msU7NcBJL3NkJ57MaXUg==/com.xingin.xhs-mzAo1WQQwA8YM9pU8iKXXw==/lib/arm/libshield.so

str= main

*/

3.3.2 补环境

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
# 1 android/content/Context->getSharedPreferences(Ljava/lang/String;I)Landroid/content/SharedPreferences;

if (signature.equals("android/content/Context->getSharedPreferences(Ljava/lang/String;I)Landroid/content/SharedPreferences;")) {
// android的,day10学的
// 第一个参数是要读取的 xml文件名
String xmlName = (String) vaList.getObjectArg(0).getValue();
System.out.println("XML文件名:" + xmlName); // 读s.xml
//return vm.resolveClass("android/content/SharedPreferences").newObject(vaList.getObjectArg(0));
return vm.resolveClass("android/content/SharedPreferences").newObject(null);
}

'''day10
SharedPreferences sp = getSharedPreferences("sp_token", MODE_PRIVATE);
String token = sp.getString("token","");
'''


# 2 android/content/SharedPreferences->getString(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
if (signature.equals("android/content/SharedPreferences->getString(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;")) {
// 去XML文件中读取内容并返回 String token = sp.getString("token","");
String key = (String) vaList.getObjectArg(0).getValue();
String defaultValue = (String) vaList.getObjectArg(1).getValue();
System.out.println("键:" + key + " 默认值:" + defaultValue);
// 我们去对应的目录下找到 s.xml-->拿出main 和 main_hmac 对应的值,直接补上
if (key.equals("main")) { // 读取main返回默认值
return new StringObject(vm, defaultValue);
}
if (key.equals("main_hmac")) { // 读取main_hmac 返回拿到的值
return new StringObject(vm, "aUIfjHpYObXfXBYfXvjCQwwUbuORShG8BR/LCeecL8fwui6ZXoqVZFLMNx9nO2x/fn8Izev0xOINhYGMMMAvCVw5bkgPk7sqavtlPJYDW9O3IEmqHktC4JUzOjRgnjSs");
}
}

cd /data/data/com.xingin.xhs/shared_prefs

# 3 com/xingin/shield/http/Base64Helper->decode(Ljava/lang/String;)[B

if (signature.equals("com/xingin/shield/http/Base64Helper->decode(Ljava/lang/String;)[B")) {
// 小红书自己的--》可能是 Base64 魔改的--》我们需要反编译找到地方读代码,或者通过hook比较 看看是否魔改
// 最终验证,就是Base64
String input = (String) vaList.getObjectArg(0).getValue();
byte[] result = Base64.decodeBase64(input);
return new ByteArray(vm, result);
}

image-20240517191417875

3.4 unidbg运行(intercept)

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
# ### Response intercept(Interceptor.Chain chain, long j2)
#找到类:
DvmClass cls = vm.resolveClass("com/xingin/shield/http/XhsHttpInterceptor");
#获取签名(两个参数)
String method = "intercept(Lokhttp3/Interceptor$Chain;J)Lokhttp3/Response;";
# 执行
cls.callStaticJniMethodLong(
emulator,
method,
vm.resolveClass("okhttp3/Interceptor$Chain").newObject(null),
cPtr
);


### 有两个参数,第一个我们传空,第二个是 上个方法执行返回的值



## unidbg运行
public void intercept(long cPtr) {
DvmClass cls = vm.resolveClass("com/xingin/shield/http/XhsHttpInterceptor");
String method = "intercept(Lokhttp3/Interceptor$Chain;J)Lokhttp3/Response;";
cls.callStaticJniMethodLong(
emulator,
method,
vm.resolveClass("okhttp3/Interceptor$Chain").newObject(null),
cPtr
);
}

3.4.1 补环境

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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# 1 okhttp3/Interceptor$Chain->request()Lokhttp3/Request;
# 我们直接定义出一个request对象返回,这个对象是okhttp3的,我们通过maven引入,就可以new出来
# 定义成全局,在main中new出来
private static Request request;
request = new Request.Builder()
.url("https://www.xiaohongshu.com/api/sns/v1/system_service/check_code?zone=86&phone=18630099999&code=112233")
.addHeader("xy-common-params", "fid=166893364910401a044595fd44d95587c504e09275d9&device_fingerprint=2022112007045266bb5eba5505d24d7b1d35dec1975913018dc5b30ffa5ab5&device_fingerprint1=2022112007045266bb5eba5505d24d7b1d35dec1975913018dc5b30ffa5ab5&launch_id=1673279778&tz=Asia%2FHong_Kong&channel=YingYongBao&versionName=6.73.0&deviceId=d7a8fa4f-98a2-398a-a7a5-417bf5e0b971&platform=android&sid=session.1668933583120053844884&identifier_flag=0&t=1673279684&project_id=ECFAAF&build=6730157&x_trace_page_current=login_full_screen_sms_page&lang=zh-Hans&app_id=ECFAAF01&uis=light")
.build();

if (signature.equals("okhttp3/Interceptor$Chain->request()Lokhttp3/Request;")) {
// 执行 chain.request() 获取request请求相关的对象
return vm.resolveClass("okhttp3/Request").newObject(request);
}


# 2 okhttp3/Request->url()Lokhttp3/HttpUrl;
if (signature.equals("okhttp3/Request->url()Lokhttp3/HttpUrl;")) {
Request request = (Request) dvmObject.getValue();
return vm.resolveClass("okhttp3/HttpUrl").newObject(request.url());
}

# 3 okhttp3/HttpUrl->encodedPath()Ljava/lang/String;
if (signature.equals("okhttp3/HttpUrl->encodedPath()Ljava/lang/String;")) {
HttpUrl httpUrl = (HttpUrl) dvmObject.getValue();
return new StringObject(vm, httpUrl.encodedPath());
}

# 4 okhttp3/HttpUrl->encodedQuery()Ljava/lang/String;
if (signature.equals("okhttp3/HttpUrl->encodedQuery()Ljava/lang/String;")) {
HttpUrl httpUrl = (HttpUrl) dvmObject.getValue();
if (httpUrl.encodedQuery() != null) {
return new StringObject(vm, httpUrl.encodedQuery());
}
return new StringObject(vm, "");
}

# 5 okhttp3/Request->body()Lokhttp3/RequestBody;
if (signature.equals("okhttp3/Request->body()Lokhttp3/RequestBody;")) {
Request request = (Request) dvmObject.getValue();
return vm.resolveClass("okhttp3/RequestBody").newObject(request.body());
}

# 6 okhttp3/Request->headers()Lokhttp3/Headers;
if (signature.equals("okhttp3/Request->headers()Lokhttp3/Headers;")) {
Request request = (Request) dvmObject.getValue();
return vm.resolveClass("okhttp3/Headers").newObject(request.headers());
}

# 7okio/Buffer-><init>()V
if (signature.equals("okio/Buffer-><init>()V")) {
return dvmClass.newObject(new Buffer());
}

# 8 okio/Buffer->writeString(Ljava/lang/String;Ljava/nio/charset/Charset;)Lokio/Buffer;
if (signature.equals("okio/Buffer->writeString(Ljava/lang/String;Ljava/nio/charset/Charset;)Lokio/Buffer;")) {
Buffer buffer = (Buffer) dvmObject.getValue();
buffer.writeString(vaList.getObjectArg(0).getValue().toString(), (Charset) vaList.getObjectArg(1).getValue());
return dvmObject;
}

# 9 okhttp3/Headers->size()I
@Override
public int callIntMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
if (signature.equals("okhttp3/Headers->size()I")) {
Headers headers = (Headers) dvmObject.getValue();
return headers.size();
}
return super.callIntMethodV(vm, dvmObject, signature, vaList);
}

# 10 okhttp3/Headers->name(I)Ljava/lang/String;
if (signature.equals("okhttp3/Headers->name(I)Ljava/lang/String;")) {
Headers headers = (Headers) dvmObject.getValue();
return new StringObject(vm, headers.name(vaList.getIntArg(0)));
}

# 11 okhttp3/Headers->value(I)Ljava/lang/String;
if (signature.equals("okhttp3/Headers->value(I)Ljava/lang/String;")) {
Headers headers = (Headers) dvmObject.getValue();
return new StringObject(vm, headers.value(vaList.getIntArg(0)));
}

# 12 okhttp3/RequestBody->writeTo(Lokio/BufferedSink;)V
@Override
public void callVoidMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
if (signature.equals("okhttp3/RequestBody->writeTo(Lokio/BufferedSink;)V")) {
BufferedSink bufferedSink = (BufferedSink) vaList.getObjectArg(0).getValue();
RequestBody requestBody = (RequestBody) dvmObject.getValue();
if (requestBody != null) {
try {
requestBody.writeTo(bufferedSink);
} catch (IOException e) {
System.out.println("错误了" + e);
}
}
return;
}

super.callVoidMethodV(vm, dvmObject, signature, vaList);
}

# 13 okio/Buffer->clone()Lokio/Buffer;
if (signature.equals("okio/Buffer->clone()Lokio/Buffer;")) {
Buffer buffer = (Buffer) dvmObject.getValue();
return vm.resolveClass("okio/Buffer").newObject(buffer.clone());
}

#14 okio/Buffer->read([B)I
if (signature.equals("okio/Buffer->read([B)I")) {
Buffer buffer = (Buffer) dvmObject.getValue();
return buffer.read((byte[]) vaList.getObjectArg(0).getValue());
}

# 15 okhttp3/Request->newBuilder()Lokhttp3/Request$Builder;
if (signature.equals("okhttp3/Request->newBuilder()Lokhttp3/Request$Builder;")) {
Request request = (Request) dvmObject.getValue();
return vm.resolveClass("okhttp3/Request$Builder").newObject(request.newBuilder());
}

# 16 okhttp3/Request$Builder->header(Ljava/lang/String;Ljava/lang/String;)Lokhttp3/Request$Builder;(有shield)
if (signature.equals("okhttp3/Request$Builder->header(Ljava/lang/String;Ljava/lang/String;)Lokhttp3/Request$Builder;")) {
Request.Builder builder = (Request.Builder) dvmObject.getValue();
builder.header(vaList.getObjectArg(0).getValue().toString(), vaList.getObjectArg(1).getValue().toString());
if ("shield".equals(vaList.getObjectArg(0).getValue().toString())) {
String shield = vaList.getObjectArg(1).getValue().toString();
System.out.println("shield=" + shield);
}
return dvmObject;
}

# 17 okhttp3/Request$Builder->build()Lokhttp3/Request;
if (signature.equals("okhttp3/Request$Builder->build()Lokhttp3/Request;")) {
Request.Builder builder = (Request.Builder) dvmObject.getValue();
Request request = builder.build();
return vm.resolveClass("okhttp3/Request").newObject(request);
}

# 18 okhttp3/Interceptor$Chain->proceed(Lokhttp3/Request;)Lokhttp3/Response;
if (signature.equals("okhttp3/Interceptor$Chain->proceed(Lokhttp3/Request;)Lokhttp3/Response;")) {
return vm.resolveClass("okhttp3/Response").newObject(null);
}

# 19 okhttp3/Response->code()I
if (signature.equals("okhttp3/Response->code()I")) {
return 200;
}

3.4.2 okhttp3

由于我们要主动执行拦截器的方法,所以需要构造请求先关等对象,依赖okhttp3框架。

image-20240517191431471

1
2
3
4
5
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.4.2</version>
</dependency>

3.5 总体代码

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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
package com.nb.demo;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.linux.android.dvm.array.ByteArray;
import com.github.unidbg.linux.android.dvm.jni.ProxyDvmObject;
import com.github.unidbg.memory.Memory;
import okhttp3.Headers;
import okhttp3.HttpUrl;
import okhttp3.Request;
import okhttp3.RequestBody;
import okio.Buffer;
import okio.BufferedSink;
import org.apache.commons.codec.binary.Base64;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;

public class XHS extends AbstractJni {
public static AndroidEmulator emulator; // 静态属性,以后对象和类都可以直接使用
public static Memory memory;
public static VM vm;
public static Module module;
private static Request request;

public XHS() {
// 1.创建设备(32位或64位模拟器), 具体看so文件在哪个目录。 在armeabi-v7a就选择32位
// 传进设备时,如果是32位,后面so文件就要用32位,同理需要用64位的
// 这个名字可以随便写,一般写成app的包名 以后可能会动
emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.xhs").build();

// 2 获取内存对象(可以操作内存)
memory = emulator.getMemory();

// 3.设置安卓sdk版本(只支持19、23)
memory.setLibraryResolver(new AndroidResolver(23));

// 4.创建虚拟机(运行安卓代码需要虚拟机,就想运行py代码需要python解释器一样) 以后会动
vm = emulator.createDalvikVM(new File("apks/xhs/v6.73.0.apk"));
vm.setJni(this); // 后期补环境会用,把要补的环境,写在当前这个类中,执行这个代码即可,但是必须继承AbstractJni
//vm.setVerbose(true); //是否展示调用过程的细节

// 5.加载so文件
// 以后会动,只要懂so文件路径即可
DalvikModule dm = vm.loadLibrary(new File("apks/xhs/libshield.so"), false);
dm.callJNI_OnLoad(emulator); // jni开发动态注册,会执行JNI_OnLoad,如果是动态注册,需要执行一下这个,如果静态注册,这个不需要执行,车智赢案例是静态注册

// 6.dm代表so文件,dm.getModule()得到module对象,基于module对象可以访问so中的成员。
module = dm.getModule(); // 把so文件加载到内存后,后期可以获取基地址,偏移量等,该变量代指so文件

}

public void initializeNative() {
DvmClass cls = vm.resolveClass("com/xingin/shield/http/XhsHttpInterceptor");
String method = "initializeNative()V";
cls.callStaticJniMethodObject(emulator, method);
}

public long initialize(String str) {
DvmClass cls = vm.resolveClass("com/xingin/shield/http/XhsHttpInterceptor");
String method = "initialize(Ljava/lang/String;)J";
long cPtr = cls.callStaticJniMethodLong(emulator, method, new StringObject(vm, str));
System.out.println(cPtr);
return cPtr;
}

public void intercept(long cPtr) {
DvmClass cls = vm.resolveClass("com/xingin/shield/http/XhsHttpInterceptor");
String method = "intercept(Lokhttp3/Interceptor$Chain;J)Lokhttp3/Response;";
cls.callStaticJniMethodLong(
emulator,
method,
vm.resolveClass("okhttp3/Interceptor$Chain").newObject(null),
cPtr
);
}

public static void main(String[] args) {
//1 new出一个对象,调用构造方法
XHS xhs = new XHS();
xhs.initializeNative();
request = new Request.Builder()
.url("https://www.xiaohongshu.com/api/sns/v1/system_service/check_code?zone=86&phone=18630099999&code=112233")
.addHeader("xy-common-params", "fid=1709625890105b664aedccbcd10c2ad2eb632c9e2dca&device_fingerprint=2024030515543183adcbaf57cfa94ffd875e79c8f077a4011d87121ee36228&device_fingerprint1=2024030515543183adcbaf57cfa94ffd875e79c8f077a4011d87121ee36228&launch_id=1709633113&tz=Asia%2FShanghai&channel=YingYongBao&versionName=6.73.0&deviceId=c90832a6-bfb9-3e46-a3dd-f5a939a4bdd2&platform=android&sid=session.1709632456270565876089&identifier_flag=0&t=1709632497&project_id=ECFAAF&build=6730157&x_trace_page_current=welcome_page&lang=zh-Hans&app_id=ECFAAF01&uis=light")
.build();
long l = xhs.initialize("main");
xhs.intercept(l);
}


// 补环境

@Override
public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
if (signature.equals("java/nio/charset/Charset->defaultCharset()Ljava/nio/charset/Charset;")) {
// java 层有的,直接补
Charset charset = Charset.defaultCharset();
return ProxyDvmObject.createObject(vm, charset);
}

if (signature.equals("com/xingin/shield/http/Base64Helper->decode(Ljava/lang/String;)[B")) {
// 小红书自己的--》可能是 Base64 魔改的--》我们需要反编译找到地方读代码,或者通过hook比较 看看是否魔改
// 最终验证,就是Base64
String input = (String) vaList.getObjectArg(0).getValue();
byte[] result = Base64.decodeBase64(input);
return new ByteArray(vm, result);
}
return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
}

@Override
public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {
if (signature.equals("com/xingin/shield/http/ContextHolder->sDeviceId:Ljava/lang/String;")) {
// com/xingin/shield/http/ContextHolder类对象的sDeviceId字段,返回字符串
//1 不能直接拿到,应该是app内部生成,赋值给这个字段,正常我们通过反编译,找到位置,查找加密算法
//2 直接hook得到具体值,放入:c90832a6-bfb9-3e46-a3dd-f5a939a4bdd2
return ProxyDvmObject.createObject(vm, "c90832a6-bfb9-3e46-a3dd-f5a939a4bdd2");

}


return super.getStaticObjectField(vm, dvmClass, signature);
}

@Override
public int getStaticIntField(BaseVM vm, DvmClass dvmClass, String signature) {
if (signature.equals("com/xingin/shield/http/ContextHolder->sAppId:I")) {
return -319115519;
}
return super.getStaticIntField(vm, dvmClass, signature);
}

@Override
public boolean getStaticBooleanField(BaseVM vm, DvmClass dvmClass, String signature) {
if (signature.equals("com/xingin/shield/http/ContextHolder->sExperiment:Z")) {
return true;
}
return super.getStaticBooleanField(vm, dvmClass, signature);
}

@Override
public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
if (signature.equals("android/content/Context->getSharedPreferences(Ljava/lang/String;I)Landroid/content/SharedPreferences;")) {
// android的,day10学的
// 第一个参数是要读取的 xml文件名
String xmlName = (String) vaList.getObjectArg(0).getValue();
System.out.println("XML文件名:" + xmlName); // 读s.xml
//return vm.resolveClass("android/content/SharedPreferences").newObject(vaList.getObjectArg(0));
return vm.resolveClass("android/content/SharedPreferences").newObject(null);
}

if (signature.equals("android/content/SharedPreferences->getString(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;")) {
// 去XML文件中读取内容并返回 String token = sp.getString("token","");
String key = (String) vaList.getObjectArg(0).getValue();
String defaultValue = (String) vaList.getObjectArg(1).getValue();
System.out.println("键:" + key + " 默认值:" + defaultValue);
// 我们去对应的目录下找到 s.xml-->拿出main 和 main_hmac 对应的值,直接补上
if (key.equals("main")) { // 读取main返回默认值
return new StringObject(vm, defaultValue);
}
if (key.equals("main_hmac")) { // 读取main_hmac 返回拿到的值
return new StringObject(vm, "aUIfjHpYObXfXBYfXvjCQwwUbuORShG8BR/LCeecL8fwui6ZXoqVZFLMNx9nO2x/fn8Izev0xOINhYGMMMAvCVw5bkgPk7sqavtlPJYDW9O3IEmqHktC4JUzOjRgnjSs");
}
}


if (signature.equals("okhttp3/Interceptor$Chain->request()Lokhttp3/Request;")) {
// 执行 chain.request() 获取request请求相关的对象
return vm.resolveClass("okhttp3/Request").newObject(request);
}
if (signature.equals("okhttp3/Request->url()Lokhttp3/HttpUrl;")) {
Request request = (Request) dvmObject.getValue();
return vm.resolveClass("okhttp3/HttpUrl").newObject(request.url());
}
if (signature.equals("okhttp3/HttpUrl->encodedPath()Ljava/lang/String;")) {
HttpUrl httpUrl = (HttpUrl) dvmObject.getValue();
return new StringObject(vm, httpUrl.encodedPath());
}

if (signature.equals("okhttp3/HttpUrl->encodedQuery()Ljava/lang/String;")) {
HttpUrl httpUrl = (HttpUrl) dvmObject.getValue();
if (httpUrl.encodedQuery() != null) {
return new StringObject(vm, httpUrl.encodedQuery());
}
return new StringObject(vm, "");
}

if (signature.equals("okhttp3/Request->body()Lokhttp3/RequestBody;")) {
Request request = (Request) dvmObject.getValue();
return vm.resolveClass("okhttp3/RequestBody").newObject(request.body());
}
if (signature.equals("okhttp3/Request->headers()Lokhttp3/Headers;")) {
Request request = (Request) dvmObject.getValue();
return vm.resolveClass("okhttp3/Headers").newObject(request.headers());
}
if (signature.equals("okio/Buffer->writeString(Ljava/lang/String;Ljava/nio/charset/Charset;)Lokio/Buffer;")) {
Buffer buffer = (Buffer) dvmObject.getValue();
buffer.writeString(vaList.getObjectArg(0).getValue().toString(), (Charset) vaList.getObjectArg(1).getValue());
return dvmObject;
}

if (signature.equals("okhttp3/Headers->name(I)Ljava/lang/String;")) {
Headers headers = (Headers) dvmObject.getValue();
return new StringObject(vm, headers.name(vaList.getIntArg(0)));
}
if (signature.equals("okhttp3/Headers->value(I)Ljava/lang/String;")) {
Headers headers = (Headers) dvmObject.getValue();
return new StringObject(vm, headers.value(vaList.getIntArg(0)));
}

if (signature.equals("okio/Buffer->clone()Lokio/Buffer;")) {
Buffer buffer = (Buffer) dvmObject.getValue();
return vm.resolveClass("okio/Buffer").newObject(buffer.clone());
}

if (signature.equals("okhttp3/Request->newBuilder()Lokhttp3/Request$Builder;")) {
Request request = (Request) dvmObject.getValue();
return vm.resolveClass("okhttp3/Request$Builder").newObject(request.newBuilder());
}
if (signature.equals("okhttp3/Request$Builder->header(Ljava/lang/String;Ljava/lang/String;)Lokhttp3/Request$Builder;")) {
Request.Builder builder = (Request.Builder) dvmObject.getValue();
builder.header(vaList.getObjectArg(0).getValue().toString(), vaList.getObjectArg(1).getValue().toString());
if ("shield".equals(vaList.getObjectArg(0).getValue().toString())) {
String shield = vaList.getObjectArg(1).getValue().toString();
System.out.println("shield=" + shield);
}
return dvmObject;
}
if (signature.equals("okhttp3/Request$Builder->build()Lokhttp3/Request;")) {
Request.Builder builder = (Request.Builder) dvmObject.getValue();
Request request = builder.build();
return vm.resolveClass("okhttp3/Request").newObject(request);
}
if (signature.equals("okhttp3/Interceptor$Chain->proceed(Lokhttp3/Request;)Lokhttp3/Response;")) {
return vm.resolveClass("okhttp3/Response").newObject(null);
}
return super.callObjectMethodV(vm, dvmObject, signature, vaList);
}

@Override
public DvmObject<?> newObjectV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
if (signature.equals("okio/Buffer-><init>()V")) {
return dvmClass.newObject(new Buffer());
}
return super.newObjectV(vm, dvmClass, signature, vaList);
}


@Override
public int callIntMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
if (signature.equals("okhttp3/Headers->size()I")) {
Headers headers = (Headers) dvmObject.getValue();
return headers.size();
}
if (signature.equals("okio/Buffer->read([B)I")) {
Buffer buffer = (Buffer) dvmObject.getValue();
return buffer.read((byte[]) vaList.getObjectArg(0).getValue());
}
if (signature.equals("okhttp3/Response->code()I")) {
return 200;
}
return super.callIntMethodV(vm, dvmObject, signature, vaList);
}
@Override
public void callVoidMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
if (signature.equals("okhttp3/RequestBody->writeTo(Lokio/BufferedSink;)V")) {
BufferedSink bufferedSink = (BufferedSink) vaList.getObjectArg(0).getValue();
RequestBody requestBody = (RequestBody) dvmObject.getValue();
if (requestBody != null) {
try {
requestBody.writeTo(bufferedSink);
} catch (IOException e) {
System.out.println("错误了" + e);
}
}
return;
}

super.callVoidMethodV(vm, dvmObject, signature, vaList);
}
}

4 参数化和打包

4.1 参数处理

image-20240517191449261

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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
package com.nb.demo;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.linux.android.dvm.array.ByteArray;
import com.github.unidbg.linux.android.dvm.jni.ProxyDvmObject;
import com.github.unidbg.memory.Memory;
import okhttp3.*;
import okio.Buffer;
import okio.BufferedSink;
import org.apache.commons.codec.binary.Base64;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;

public class XHS extends AbstractJni {
public static AndroidEmulator emulator; // 静态属性,以后对象和类都可以直接使用
public static Memory memory;
public static VM vm;
public static Module module;
private static Request request;

public XHS() {
// 1.创建设备(32位或64位模拟器), 具体看so文件在哪个目录。 在armeabi-v7a就选择32位
// 传进设备时,如果是32位,后面so文件就要用32位,同理需要用64位的
// 这个名字可以随便写,一般写成app的包名 以后可能会动
emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.xhs").build();

// 2 获取内存对象(可以操作内存)
memory = emulator.getMemory();

// 3.设置安卓sdk版本(只支持19、23)
memory.setLibraryResolver(new AndroidResolver(23));

// 4.创建虚拟机(运行安卓代码需要虚拟机,就想运行py代码需要python解释器一样) 以后会动
vm = emulator.createDalvikVM(new File("apks/xhs/v6.73.0.apk"));
vm.setJni(this); // 后期补环境会用,把要补的环境,写在当前这个类中,执行这个代码即可,但是必须继承AbstractJni
//vm.setVerbose(true); //是否展示调用过程的细节

// 5.加载so文件
// 以后会动,只要懂so文件路径即可
DalvikModule dm = vm.loadLibrary(new File("apks/xhs/libshield.so"), false);
dm.callJNI_OnLoad(emulator); // jni开发动态注册,会执行JNI_OnLoad,如果是动态注册,需要执行一下这个,如果静态注册,这个不需要执行,车智赢案例是静态注册

// 6.dm代表so文件,dm.getModule()得到module对象,基于module对象可以访问so中的成员。
module = dm.getModule(); // 把so文件加载到内存后,后期可以获取基地址,偏移量等,该变量代指so文件

}

public void initializeNative() {
DvmClass cls = vm.resolveClass("com/xingin/shield/http/XhsHttpInterceptor");
String method = "initializeNative()V";
cls.callStaticJniMethodObject(emulator, method);
}

public long initialize(String str) {
DvmClass cls = vm.resolveClass("com/xingin/shield/http/XhsHttpInterceptor");
String method = "initialize(Ljava/lang/String;)J";
long cPtr = cls.callStaticJniMethodLong(emulator, method, new StringObject(vm, str));
// System.out.println(cPtr);
return cPtr;
}

public void intercept(long cPtr) {
DvmClass cls = vm.resolveClass("com/xingin/shield/http/XhsHttpInterceptor");
String method = "intercept(Lokhttp3/Interceptor$Chain;J)Lokhttp3/Response;";
cls.callStaticJniMethodLong(
emulator,
method,
vm.resolveClass("okhttp3/Interceptor$Chain").newObject(null),
cPtr
);
}

public static void main(String[] args) {
String method = args[0];
String url = args[1];
String commonParams = args[2];
String content = args[3]; // 请求体

//1 new出一个对象,调用构造方法
XHS xhs = new XHS();
xhs.initializeNative();
long l = xhs.initialize("main");
if (method.equalsIgnoreCase("post")) {
MediaType TEXT = MediaType.parse("text/plan;charset=utf-8");
RequestBody body = RequestBody.create(TEXT, content);
request = new Request.Builder()
.url(url)
.addHeader("xy-common-params", commonParams)
.addHeader("content-type", "application/x-www-form-urlencoded")
.method("post", body)
.build();
} else {
request = new Request.Builder()
.url(url)
.addHeader("xy-common-params", commonParams)
.build();
}


xhs.intercept(l);
}


// 补环境

@Override
public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
if (signature.equals("java/nio/charset/Charset->defaultCharset()Ljava/nio/charset/Charset;")) {
// java 层有的,直接补
Charset charset = Charset.defaultCharset();
return ProxyDvmObject.createObject(vm, charset);
}

if (signature.equals("com/xingin/shield/http/Base64Helper->decode(Ljava/lang/String;)[B")) {
// 小红书自己的--》可能是 Base64 魔改的--》我们需要反编译找到地方读代码,或者通过hook比较 看看是否魔改
// 最终验证,就是Base64
String input = (String) vaList.getObjectArg(0).getValue();
byte[] result = Base64.decodeBase64(input);
return new ByteArray(vm, result);
}
return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
}

@Override
public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {
if (signature.equals("com/xingin/shield/http/ContextHolder->sDeviceId:Ljava/lang/String;")) {
// com/xingin/shield/http/ContextHolder类对象的sDeviceId字段,返回字符串
//1 不能直接拿到,应该是app内部生成,赋值给这个字段,正常我们通过反编译,找到位置,查找加密算法
//2 直接hook得到具体值,放入:c90832a6-bfb9-3e46-a3dd-f5a939a4bdd2
return ProxyDvmObject.createObject(vm, "c90832a6-bfb9-3e46-a3dd-f5a939a4bdd2");

}


return super.getStaticObjectField(vm, dvmClass, signature);
}

@Override
public int getStaticIntField(BaseVM vm, DvmClass dvmClass, String signature) {
if (signature.equals("com/xingin/shield/http/ContextHolder->sAppId:I")) {
return -319115519;
}
return super.getStaticIntField(vm, dvmClass, signature);
}

@Override
public boolean getStaticBooleanField(BaseVM vm, DvmClass dvmClass, String signature) {
if (signature.equals("com/xingin/shield/http/ContextHolder->sExperiment:Z")) {
return true;
}
return super.getStaticBooleanField(vm, dvmClass, signature);
}

@Override
public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
if (signature.equals("android/content/Context->getSharedPreferences(Ljava/lang/String;I)Landroid/content/SharedPreferences;")) {
// android的,day10学的
// 第一个参数是要读取的 xml文件名
String xmlName = (String) vaList.getObjectArg(0).getValue();
// System.out.println("XML:" + xmlName); // 读s.xml
//return vm.resolveClass("android/content/SharedPreferences").newObject(vaList.getObjectArg(0));
return vm.resolveClass("android/content/SharedPreferences").newObject(null);
}

if (signature.equals("android/content/SharedPreferences->getString(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;")) {
// 去XML文件中读取内容并返回 String token = sp.getString("token","");
String key = (String) vaList.getObjectArg(0).getValue();
String defaultValue = (String) vaList.getObjectArg(1).getValue();
// System.out.println("key:" + key + " value:" + defaultValue);
// 我们去对应的目录下找到 s.xml-->拿出main 和 main_hmac 对应的值,直接补上
if (key.equals("main")) { // 读取main返回默认值
return new StringObject(vm, defaultValue);
}
if (key.equals("main_hmac")) { // 读取main_hmac 返回拿到的值
return new StringObject(vm, "aUIfjHpYObXfXBYfXvjCQwwUbuORShG8BR/LCeecL8fwui6ZXoqVZFLMNx9nO2x/fn8Izev0xOINhYGMMMAvCVw5bkgPk7sqavtlPJYDW9O3IEmqHktC4JUzOjRgnjSs");
}
}


if (signature.equals("okhttp3/Interceptor$Chain->request()Lokhttp3/Request;")) {
// 执行 chain.request() 获取request请求相关的对象
return vm.resolveClass("okhttp3/Request").newObject(request);
}
if (signature.equals("okhttp3/Request->url()Lokhttp3/HttpUrl;")) {
Request request = (Request) dvmObject.getValue();
return vm.resolveClass("okhttp3/HttpUrl").newObject(request.url());
}
if (signature.equals("okhttp3/HttpUrl->encodedPath()Ljava/lang/String;")) {
HttpUrl httpUrl = (HttpUrl) dvmObject.getValue();
return new StringObject(vm, httpUrl.encodedPath());
}

if (signature.equals("okhttp3/HttpUrl->encodedQuery()Ljava/lang/String;")) {
HttpUrl httpUrl = (HttpUrl) dvmObject.getValue();
if (httpUrl.encodedQuery() != null) {
return new StringObject(vm, httpUrl.encodedQuery());
}
return new StringObject(vm, "");
}

if (signature.equals("okhttp3/Request->body()Lokhttp3/RequestBody;")) {
Request request = (Request) dvmObject.getValue();
return vm.resolveClass("okhttp3/RequestBody").newObject(request.body());
}
if (signature.equals("okhttp3/Request->headers()Lokhttp3/Headers;")) {
Request request = (Request) dvmObject.getValue();
return vm.resolveClass("okhttp3/Headers").newObject(request.headers());
}
if (signature.equals("okio/Buffer->writeString(Ljava/lang/String;Ljava/nio/charset/Charset;)Lokio/Buffer;")) {
Buffer buffer = (Buffer) dvmObject.getValue();
buffer.writeString(vaList.getObjectArg(0).getValue().toString(), (Charset) vaList.getObjectArg(1).getValue());
return dvmObject;
}

if (signature.equals("okhttp3/Headers->name(I)Ljava/lang/String;")) {
Headers headers = (Headers) dvmObject.getValue();
return new StringObject(vm, headers.name(vaList.getIntArg(0)));
}
if (signature.equals("okhttp3/Headers->value(I)Ljava/lang/String;")) {
Headers headers = (Headers) dvmObject.getValue();
return new StringObject(vm, headers.value(vaList.getIntArg(0)));
}

if (signature.equals("okio/Buffer->clone()Lokio/Buffer;")) {
Buffer buffer = (Buffer) dvmObject.getValue();
return vm.resolveClass("okio/Buffer").newObject(buffer.clone());
}

if (signature.equals("okhttp3/Request->newBuilder()Lokhttp3/Request$Builder;")) {
Request request = (Request) dvmObject.getValue();
return vm.resolveClass("okhttp3/Request$Builder").newObject(request.newBuilder());
}
if (signature.equals("okhttp3/Request$Builder->header(Ljava/lang/String;Ljava/lang/String;)Lokhttp3/Request$Builder;")) {
Request.Builder builder = (Request.Builder) dvmObject.getValue();
builder.header(vaList.getObjectArg(0).getValue().toString(), vaList.getObjectArg(1).getValue().toString());
if ("shield".equals(vaList.getObjectArg(0).getValue().toString())) {
String shield = vaList.getObjectArg(1).getValue().toString();
System.out.println("shield=" + shield);
}
return dvmObject;
}
if (signature.equals("okhttp3/Request$Builder->build()Lokhttp3/Request;")) {
Request.Builder builder = (Request.Builder) dvmObject.getValue();
Request request = builder.build();
return vm.resolveClass("okhttp3/Request").newObject(request);
}
if (signature.equals("okhttp3/Interceptor$Chain->proceed(Lokhttp3/Request;)Lokhttp3/Response;")) {
return vm.resolveClass("okhttp3/Response").newObject(null);
}
return super.callObjectMethodV(vm, dvmObject, signature, vaList);
}

@Override
public DvmObject<?> newObjectV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
if (signature.equals("okio/Buffer-><init>()V")) {
return dvmClass.newObject(new Buffer());
}
return super.newObjectV(vm, dvmClass, signature, vaList);
}


@Override
public int callIntMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
if (signature.equals("okhttp3/Headers->size()I")) {
Headers headers = (Headers) dvmObject.getValue();
return headers.size();
}
if (signature.equals("okio/Buffer->read([B)I")) {
Buffer buffer = (Buffer) dvmObject.getValue();
return buffer.read((byte[]) vaList.getObjectArg(0).getValue());
}
if (signature.equals("okhttp3/Response->code()I")) {
return 200;
}
return super.callIntMethodV(vm, dvmObject, signature, vaList);
}

@Override
public void callVoidMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
if (signature.equals("okhttp3/RequestBody->writeTo(Lokio/BufferedSink;)V")) {
BufferedSink bufferedSink = (BufferedSink) vaList.getObjectArg(0).getValue();
RequestBody requestBody = (RequestBody) dvmObject.getValue();
if (requestBody != null) {
try {
requestBody.writeTo(bufferedSink);
} catch (IOException e) {
// System.out.println("错误了" + e);
}
}
return;
}

super.callVoidMethodV(vm, dvmObject, signature, vaList);
}
}

4.2 打包调用

image-20240517191504407

1
java -jar unidbg-0.9.6.jar get "https://www.xiaohongshu.com/api/sns/v1/system_service/check_code?zone=86&phone=18630099999&code=112233" "fid=166893364910401a044595fd44d95587c504e09275d9&device_fingerprint=2022112007045266bb5eba5505d24d7b1d35dec1975913018dc5b30ffa5ab5&device_fingerprint1=2022112007045266bb5eba5505d24d7b1d35dec1975913018dc5b30ffa5ab5&launch_id=1673279778&tz=Asia%2FHong_Kong&channel=YingYongBao&versionName=6.73.0&deviceId=d7a8fa4f-98a2-398a-a7a5-417bf5e0b971&platform=android&sid=session.1668933583120053844884&identifier_flag=0&t=1673279684&project_id=ECFAAF&build=6730157&x_trace_page_current=login_full_screen_sms_page&lang=zh-Hans&app_id=ECFAAF01&uis=light"  null

4.3 Python调用

4.3.1 GET请求

image-20240517191512599

1
2
3
4
5
6
7
8
9
10
11
import subprocess

method = "get"
url = "https://www.xiaohongshu.com/api/sns/v1/system_service/check_code?zone=86&phone=18630099999&code=112233"
common_params = "fid=166893364910401a044595fd44d95587c504e09275d9&device_fingerprint=2022112007045266bb5eba5505d24d7b1d35dec1975913018dc5b30ffa5ab5&device_fingerprint1=2022112007045266bb5eba5505d24d7b1d35dec1975913018dc5b30ffa5ab5&launch_id=1673279778&tz=Asia%2FHong_Kong&channel=YingYongBao&versionName=6.73.0&deviceId=d7a8fa4f-98a2-398a-a7a5-417bf5e0b971&platform=android&sid=session.1668933583120053844884&identifier_flag=0&t=1673279684&project_id=ECFAAF&build=6730157&x_trace_page_current=login_full_screen_sms_page&lang=zh-Hans&app_id=ECFAAF01&uis=light"
body = "null"

cmd = f'java -jar unidbg-0.9.7.jar {method} "{url}" "{common_params}" "{body}"'
signature = subprocess.check_output(cmd, shell=True, cwd="unidbg_0_9_7_jar")
data_string = signature.strip().decode('utf-8').split("\n")[-1]
print(data_string)

4.3.2 POST请求

image-20240517191522539

1
2
3
4
5
6
7
8
9
10
11
12
import subprocess

method = "post"
url = "https://www.xiaohongshu.com/api/sns/v4/user/login/password"
common_params = "fid=166893364910401a044595fd44d95587c504e09275d9&device_fingerprint=2022112007045266bb5eba5505d24d7b1d35dec1975913018dc5b30ffa5ab5&device_fingerprint1=2022112007045266bb5eba5505d24d7b1d35dec1975913018dc5b30ffa5ab5&launch_id=1673282424&tz=Asia%2FHong_Kong&channel=YingYongBao&versionName=6.73.0&deviceId=d7a8fa4f-98a2-398a-a7a5-417bf5e0b971&platform=android&sid=session.1668933583120053844884&identifier_flag=0&t=1673282339&project_id=ECFAAF&build=6730157&x_trace_page_current=login_full_screen_pwd_page&lang=zh-Hans&app_id=ECFAAF01&uis=light"
body = "vaid=efd721ebebc492d5&password=3ea9d0b0ee7cd39d4de9f5d01c422d85&zone=86&phone=18631115555&aaid=2373c739-b380-469e-bea5-fcc86cae658f&imsi=unknow&android_version=29&type=phone&android_id=145d7fb8645488c2&mac=bc%3A6a%3Ad1%3A17%3A61%3A9a"

cmd = f'java -jar unidbg-parent.jar {method} "{url}" "{common_params}" "{body}"'
signature = subprocess.check_output(cmd, shell=True, cwd="unidbg_parent_jar")
data_string = signature.strip().decode('utf-8').split("\n")[-1]
data_string = signature.strip().decode('gbk').split("\n")[-1]
print(data_string)

5 测试

5.1 发送短信

image-20240517191532532

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 subprocess

method = "get"
url = "https://www.xiaohongshu.com/api/sns/v1/system_service/vfc_code?zone=86&phone=自己的手机号&type=login"
common_params = "fid=166893364910401a044595fd44d95587c504e09275d9&device_fingerprint=2022112007045266bb5eba5505d24d7b1d35dec1975913018dc5b30ffa5ab5&device_fingerprint1=2022112007045266bb5eba5505d24d7b1d35dec1975913018dc5b30ffa5ab5&launch_id=1673279778&tz=Asia%2FHong_Kong&channel=YingYongBao&versionName=6.73.0&deviceId=d7a8fa4f-98a2-398a-a7a5-417bf5e0b971&platform=android&sid=session.1668933583120053844884&identifier_flag=0&t=1673279684&project_id=ECFAAF&build=6730157&x_trace_page_current=login_full_screen_sms_page&lang=zh-Hans&app_id=ECFAAF01&uis=light"
body = "null"

cmd = f'java -jar unidbg-parent.jar {method} "{url}" "{common_params}" "{body}"'
signature = subprocess.check_output(cmd, shell=True, cwd="unidbg_parent_jar")
data_string = signature.strip().decode('utf-8').split("\n")[-1]
shield_string = data_string.lstrip("shield=")

print(shield_string)

res = requests.get(
url=url,
headers={
"xy-common-params": common_params,
"xy-platform-info": "platform=android&build=6730157&deviceId=d7a8fa4f-98a2-398a-a7a5-417bf5e0b971",
"shield": shield_string
}
)

print(res.text)

5.2 短信校验

image-20240517191543497

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 subprocess

method = "get"
url = "https://www.xiaohongshu.com/api/sns/v1/system_service/check_code?zone=86&phone=18953675223&code=889988"
common_params = "fid=166893364910401a044595fd44d95587c504e09275d9&device_fingerprint=2022112007045266bb5eba5505d24d7b1d35dec1975913018dc5b30ffa5ab5&device_fingerprint1=2022112007045266bb5eba5505d24d7b1d35dec1975913018dc5b30ffa5ab5&launch_id=1673279778&tz=Asia%2FHong_Kong&channel=YingYongBao&versionName=6.73.0&deviceId=d7a8fa4f-98a2-398a-a7a5-417bf5e0b971&platform=android&sid=session.1668933583120053844884&identifier_flag=0&t=1673279684&project_id=ECFAAF&build=6730157&x_trace_page_current=login_full_screen_sms_page&lang=zh-Hans&app_id=ECFAAF01&uis=light"
body = "null"

cmd = f'java -jar unidbg-0.9.7.jar {method} "{url}" "{common_params}" "{body}"'
signature = subprocess.check_output(cmd, shell=True, cwd="unidbg_0_9_7_jar")
data_string = signature.strip().decode('utf-8').split("\n")[-1]
shield_string = data_string.lstrip("shield=")

print(shield_string)

res = requests.get(
url=url,
headers={
"xy-common-params": common_params,
"xy-platform-info": "platform=android&build=6730157&deviceId=d7a8fa4f-98a2-398a-a7a5-417bf5e0b971",
"shield": shield_string
}
)

print(res.text)

5.3 账号密码登录(风控)

注意:小红书有滑动验证来进行风控监测,请手动过风控后,再发送请求测试。

image-20240517191555049

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 subprocess

method = "post"
url = "https://www.xiaohongshu.com/api/sns/v4/user/login/password"
common_params = "fid=167337148910401a044595fd44d95587c504e09275d9&device_fingerprint=2022112007045266bb5eba5505d24d7b1d35dec1975913018dc5b30ffa5ab5&device_fingerprint1=2022112007045266bb5eba5505d24d7b1d35dec1975913018dc5b30ffa5ab5&launch_id=1673371489&tz=Asia%2FHong_Kong&channel=YingYongBao&versionName=6.73.0&deviceId=d7a8fa4f-98a2-398a-a7a5-417bf5e0b971&platform=android&sid=session.1673371377666975539727&identifier_flag=0&t=1673371416&project_id=ECFAAF&build=6730157&x_trace_page_current=login_full_screen_pwd_page&lang=zh-Hans&app_id=ECFAAF01&uis=light"

body = "vaid=efd721ebebc492d5&password=f813d8c91a894a68439058572dfd4750&zone=86&phone=15131555553&aaid=2373c739-b380-469e-bea5-fcc86cae658f&imsi=unknow&android_version=29&type=phone&android_id=145d7fb8645488c2&mac=bc%3A6a%3Ad1%3A17%3A61%3A9a"

cmd = f'java -jar unidbg-parent.jar {method} "{url}" "{common_params}" "{body}"'
signature = subprocess.check_output(cmd, shell=True, cwd="unidbg_parent_jar")
data_string = signature.strip().decode('utf-8').split("\n")[-1]
shield_string = data_string.lstrip("shield=")

res = requests.post(
url=url,
data=body,
headers={
"xy-common-params": common_params,
"xy-platform-info": "platform=android&build=6730157&deviceId=d7a8fa4f-98a2-398a-a7a5-417bf5e0b971",
"shield": shield_string,
"content-type": "application/x-www-form-urlencoded"
}
)

print(res.status_code, res.text)

5.4 获取评论(风控)

风控监测到,强制账号下线。。。

image-20240517191604610

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 subprocess

method = "get"
url = "https://edith.xiaohongshu.com/api/sns/v1/note/feed?note_id=63b01b4d0000000022034d77&page=1&num=5&fetch_mode=1&source=explore&ads_track_id="
common_params = "fid=167337148910401a044595fd44d95587c504e09275d9&device_fingerprint=2022112007045266bb5eba5505d24d7b1d35dec1975913018dc5b30ffa5ab5&device_fingerprint1=2022112007045266bb5eba5505d24d7b1d35dec1975913018dc5b30ffa5ab5&launch_id=1673372503&tz=Asia%2FHong_Kong&channel=YingYongBao&versionName=6.73.0&deviceId=d7a8fa4f-98a2-398a-a7a5-417bf5e0b971&platform=android&sid=session.1673372424638448802568&identifier_flag=0&t=1673372498&project_id=ECFAAF&build=6730157&x_trace_page_current=explore_feed&lang=zh-Hans&app_id=ECFAAF01&uis=light"

body = "null"

cmd = f'java -jar unidbg-parent.jar {method} "{url}" "{common_params}" "{body}"'
signature = subprocess.check_output(cmd, shell=True, cwd="unidbg_parent_jar")
data_string = signature.strip().decode('utf-8').split("\n")[-1]
shield_string = data_string.lstrip("shield=")

res = requests.get(
url=url,
headers={
"xy-common-params": common_params,
"xy-platform-info": "platform=android&build=6730157&deviceId=d7a8fa4f-98a2-398a-a7a5-417bf5e0b971",
"shield": shield_string
}
)

print(res.status_code, res.text)

__END__