baidu免费壳分析复现

libbaiduprotect.so加载分析

通过Manifest可以看到包名和第一个加载的app类。——“com.example.test”和“com.baidu.protect.StubApplication”。

image-20250508082508430

在原来的lib下,多出了一个libbaiduprotect.so,而在assets下面也多了几个文件。

image-20250508082719470

猜测是libbaiduprotect.so将assets下的文件解密出了dex,然后进行加载。

下面开始分析。

根据app类的名称,找到app类,一般整体壳,app类会对attachBaseContext和onCreate进行覆写。

先看attachBaseContext。

image-20250508083159829

其中,StubApplication.loadLibrary实际上是加载了libbaiduprotect.so,因此,接下来用ida查看libbaiduprotect.so。

image-20250508083312421

搜索函数“init_proc”并没有搜到,接下来查看.init_array节,存在以下在加载so阶段会执行的函数。

image-20250508083540297

先来看sub_88060,一眼混淆,本来靠着NOP,去掉了几个虚假控制流和不透明谓词,但还是太多了,直接使用d810进行处理。

image-20250508083806546

处理完后,算上default的话,有32个case选项,看样子是在做解密操作。

image-20250508084218294

再来看JNI_OnLoad,会发现被加密了,可以猜到是sub_88060对它进行了加密。

image-20250508091104891

下面是一个frida脚本,用于在sub_88060解完密后,第一时间dump整个so。

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
// 要hook的函数offset
var offset = 0x88060;
// so文件的大小
var module_size = 0;
// 要hook的so的名字
var module_name = "libbaiduprotect.so";

function hook_linker_call_constructors() {
// 获得linker64的地址
let linker64_base_addr = Module.getBaseAddress('linker64')
let offset_call = 0x51BA8 // __dl__ZN6soinfo17call_constructorsEv
let call_constructors = linker64_base_addr.add(offset_call)
let listener = Interceptor.attach(call_constructors,{
onEnter:function(args){
// 判断是否可以通过findModuleByName查找到目标so文件
console.log('[linker] Call_Constructors onEnter')
let secmodule = Process.findModuleByName(module_name);
if (secmodule != null){
module_size = secmodule.size;
// 第一时间hook
hook_target_func(secmodule.base);
listener.detach();
}
}
})
}

function hook_target_func(baseaddr){
let listener = Interceptor.attach(baseaddr.add(offset), {
onEnter:function(args){

},
onLeave:function(retval){
// 在执行完某个函数后,立马进行dump
dump(baseaddr, module_size);
listener.detach();
}
});
}

function dump(begin_addr, dump_size){
console.log("[name] ", module_name);
console.log("[base] ", begin_addr);
console.log("[size] ", "0x" + dump_size.toString(16));
var file_path = "/data/data/com.example.test/zzc_" + begin_addr + "_0x" + dump_size.toString(16) + ".so";
var file_handle = new File(file_path, "wb");
if (file_handle && file_handle != null) {
Memory.protect(ptr(begin_addr), dump_size, 'rwx');
var libso_buffer = ptr(begin_addr).readByteArray(dump_size);
file_handle.write(libso_buffer);
file_handle.flush();
file_handle.close();
console.log("[dump] ", file_path);
}
}

setImmediate(hook_linker_call_constructors)

将dump下来的so文件,通过soFixer进行修复。

1
SoFixer-Windows-64.exe -s .\zzc_0x6f7471a000_0xc1000.so -o .\zzc_0x6f7471a000_0xc1000.sofixer.so -m 0x6f7471a000 -d

可以看到,JNI_OnLoad已经恢复正常了。

image-20250508091410337

接下来看其它的在.init_array上的函数(在加密so的中查看)。

查看sub_6FC4。

image-20250508091509137

可以看出来,qword_28c28s是一个函数指针,它在解密后面那一串内容。

image-20250508091601327

点开地址28c28的位置,发现存在加密的内容。

image-20250508091851238

这说明这个函数也需要得到解密,那就只能是在sub_88060里了,去另一个解密的so文件看一眼,发现果然解密了。

image-20250508091913526

通过hook解密后的sub_28c28,可以得到解密字符串,脚本如下。

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
// 要hook的函数offset
var offset = 0x88060;
// so文件的大小
var module_size = 0;
// 要hook的so的名字
var module_name = "libbaiduprotect.so";

function hook_linker_call_constructors() {
// 获得linker64的地址
let linker64_base_addr = Module.getBaseAddress('linker64')
let offset_call = 0x51BA8 // __dl__ZN6soinfo17call_constructorsEv
let call_constructors = linker64_base_addr.add(offset_call)
let listener = Interceptor.attach(call_constructors,{
onEnter:function(args){
// 判断是否可以通过findModuleByName查找到目标so文件
console.log('[linker] Call_Constructors onEnter')
let secmodule = Process.findModuleByName(module_name);
if (secmodule != null){
module_size = secmodule.size;
// 第一时间hook
hook_target_func(secmodule.base);
listener.detach();
}
}
})
}

function hook_target_func(baseaddr){
let listener = Interceptor.attach(baseaddr.add(offset), {
onEnter:function(args){

},
onLeave:function(retval){
// 第一个函数解密完后,才能hook sub_28c28
decrypt_28c28(baseaddr);
listener.detach();
}
});
}

function decrypt_28c28(baseaddr){

Interceptor.attach(baseaddr.add(0x28c28), {
onEnter:function(args){
console.log("[sub_28c28] ", "arg = ", ptr(args[0]).readCString());
}, onLeave:function(retval){
console.log("[sub_28c28] ", "retval = ", ptr(retval).readCString());
}
})

}

setImmediate(hook_linker_call_constructors)

执行的结果如下。

image-20250508092118543

写了一个ida脚本,给这些字符串的地方添加注释。

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
import idaapi
import idautils

# 加密/明文映射
mappings = {
"710E34DFB3D273975E0936FBB4CF62BE541F3CC4A8": "FeatureLibcProtection",
"530A39DDAFCB39A84E1821CEAB8F52BE4F3B34DFAEEC7FA843": "dalvik/system/DexPathList"
}

# 函数列表(地址或函数名)
func_list = [
0x28c28
]

def find_string_addr(search_str):
"""查找字符串的地址"""
for addr in idautils.Strings():
if str(addr) == search_str:
return addr.ea
return None

def resolve_func_addr(func_entry):
"""将函数名或地址转换为地址"""
if isinstance(func_entry, int):
return func_entry
elif isinstance(func_entry, str):
func_addr = idaapi.get_name_ea_simple(func_entry)
if func_addr == idaapi.BADADDR:
print(f"Function not found: {func_entry}")
return None
return func_addr
else:
print(f"Invalid function entry: {func_entry}")
return None

def add_comments_to_strings():
"""为 .rodata 中的字符串添加解密注释和重命名"""
for encrypted, decrypted in mappings.items():
str_addr = find_string_addr(encrypted)
if str_addr:
idaapi.set_cmt(str_addr, f"Decrypted: {decrypted}", 0)
# 重命名字符串,替换特殊字符以符合命名规则
safe_name = f"str_{decrypted.replace('/', '_').replace(' ', '_')}"
idaapi.set_name(str_addr, safe_name, idaapi.SN_FORCE)
print(f"Added comment at 0x{str_addr:x}: {encrypted} -> {decrypted}")
else:
print(f"String not found in .rodata: {encrypted}")

def add_comments_to_function(func_addr):
"""为指定函数添加注释,扫描加载字符串的指令"""
func = idaapi.get_func(func_addr)
if not func:
print(f"Function at 0x{func_addr:x} not found or invalid")
return

# 添加函数注释
func_name = idaapi.get_func_name(func_addr)
idaapi.set_func_cmt(func_addr, f"Decrypts strings (e.g., {', '.join(f'{k}->{v}' for k, v in mappings.items())})", 1)
print(f"Processing function: {func_name} at 0x{func_addr:x}")

# 扫描函数指令,查找加载字符串的指令
for ea in idautils.FuncItems(func.start_ea):
disasm = idaapi.generate_disasm_line(ea, 0)
if "LDR" in disasm: # 检查加载指令
for enc, dec in mappings.items():
if enc in disasm:
idaapi.set_cmt(ea, f"Loads {enc} -> {dec}", 0)
print(f"Added comment at 0x{ea:x}: {enc} -> {dec}")

def main():
"""主函数:处理字符串和函数列表"""
# 处理 .rodata 中的字符串
print("Processing strings in .rodata...")
add_comments_to_strings()

# 处理函数列表
print("\nProcessing functions...")
for func_entry in func_list:
func_addr = resolve_func_addr(func_entry)
if func_addr is not None:
add_comments_to_function(func_addr)
else:
print(f"Skipping invalid function: {func_entry}")

# 执行脚本
if __name__ == "__main__":
main()

ida脚本的执行情况如下,自动添加注释并对字符串重命名。

image-20250508092227454

回到sub_6FC4的位置,710….代表FeatureLibcProtection。

image-20250508092616184

函数sub_3E590也被加密了,通过解密的so文件查看,它似乎是在赋值?a1是一个全局变量,a2是解密后的字符串,a3是1,似乎是一个索引。

image-20250508092711397

BB9B0是一个函数指针,存放着的内容是sub_811B8的地址(也就是811B8)。

image-20250508093513610

这里创建一个结构体,由于函数指针和字符串指针都是指针,这里定义为__int64*。

image-20250508094125599

结构就比较容易懂了。

image-20250508094428716

因此,sub_6FC4可以理解为在做函数注册。

image-20250508100357713

会根据index,在对应的槽位注册。

image-20250508100323592

根据分析,注册了8个函数。

image-20250508095355396

整理一下,注册关系大致如下。

sub_B3B4 -> 索引1 -> global_func_list[1]。

sub_3E29C -> 索引3 -> global_func_list[3]。

sub_40CF8 -> 索引6 -> global_func_list[6]。

sub_3DFC4 -> 索引7 -> global_func_list[7]。

sub_11F5C -> 索引8 -> global_func_list[8]。

sub_45964 -> 索引9 -> global_func_list[9]。

sub_3E96C -> 索引10 -> global_func_list[10]。

sub_42388 -> 索引13 -> global_func_list[13]。

之后开始分析JNI_onLoad,由于加密的so中,JNI_onLoad没有解密,所以这里根据解密的so的JNI_onLoad进行分析。

image-20250508103142823

上来就看到一大堆混淆,用d810处理一下。

可能是由于从内存中dump出来的缘故,反汇编伪代码的效果不是很好,连vm都看不到在哪里使用了。

image-20250508103810153

先看sub_91D8,十分眼熟,+48,基本可以确认a1是JNIEnv*的类型了。

image-20250508103914736 image-20250508104015382

之后再看sub_7BC4,槽位0是经过初始化的,但没存前面的函数(应该被sub_82254初始化了),如果0号元素的内容为空,则调用sub_3E5A8进行清空。

image-20250508104607048 image-20250508104938793

所以,可以把函数sub_7BC4理解为,判断函数是否注册完毕。

接着看函数sub_3E628,没搞明白v11、v12在做什么,但能看得出来,是在调用函数列表里的函数,第一个参数是自己的地址,第二个参数是1(JNI_OnLoad传入的a2)。

image-20250508130255689

先来观察函数列表的index为1的函数sub_B3B4。

image-20250508130512877

如果a2不为1,直接返回,说明sub_B3B4只接受a2==1的情况。

image-20250508130621090

sub_B3B4对很多字符串进行了解密,同时连接了很多字符串。

image-20250508130801265

配合着脚本,实现了对这些字符串的解密。

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
// 要hook的函数offset
var offset = 0x88060;
// so文件的大小
var module_size = 0;
// 要hook的so的名字
var module_name = "libbaiduprotect.so";

// 解密函数的偏移列表(从 sub_28AD0 到 sub_28A24)
var decrypt_offsets = [
0x28AD0, 0x25E78, 0x25F24, 0x25FD0, 0x2607C, 0x26128, 0x261D4, 0x26280,
0x2632C, 0x263D8, 0x26484, 0x26530, 0x265DC, 0x26688, 0x26734, 0x267E0,
0x2688C, 0x26938, 0x269E4, 0x26A90, 0x26B3C, 0x26BE8, 0x26C94, 0x26D40,
0x26DEC, 0x26E98, 0x26F44, 0x26FF0, 0x2709C, 0x27148, 0x271F4, 0x272A0,
0x2734C, 0x273F8, 0x274A4, 0x27550, 0x275FC, 0x276A8, 0x27754, 0x27800,
0x278AC, 0x27958, 0x27A04, 0x27AB0, 0x27B5C, 0x27C08, 0x27CB4, 0x27D60,
0x27E0C, 0x27EB8, 0x27F64, 0x28010, 0x280BC, 0x28168, 0x28214, 0x282C0,
0x2836C, 0x28418, 0x284C4, 0x28570, 0x2861C, 0x286C8, 0x28774, 0x28820,
0x288CC, 0x28A24
];

function hook_linker_call_constructors() {
// 获得linker64的地址
let linker64_base_addr = Module.getBaseAddress('linker64');
let offset_call = 0x51BA8; // __dl__ZN6soinfo17call_constructorsEv
let call_constructors = linker64_base_addr.add(offset_call);
let listener = Interceptor.attach(call_constructors, {
onEnter: function(args) {
// 判断是否可以通过findModuleByName查找到目标so文件
console.log('[linker] Call_Constructors onEnter');
let secmodule = Process.findModuleByName(module_name);
if (secmodule != null) {
module_size = secmodule.size;
// 第一时间hook
hook_target_func(secmodule.base);
listener.detach();
}
}
});
}

function hook_target_func(baseaddr) {
let listener = Interceptor.attach(baseaddr.add(offset), {
onEnter: function(args) {
console.log("[target_func] onEnter, offset:", offset.toString(16));
},
onLeave: function(retval) {
console.log("[target_func] onLeave, retval:", retval);
// 第一个函数解密完后,Hook 所有解密函数
hook_decrypt_funcs(baseaddr);
listener.detach();
}
});
}

function hook_decrypt_funcs(baseaddr) {
// Hook sub_28C28(保持原有逻辑)
Interceptor.attach(baseaddr.add(0x28C28), {
onEnter: function(args) {
console.log("[sub_28C28] arg =", ptr(args[0]).readCString());
},
onLeave: function(retval) {
console.log("[sub_28C28] retval =", ptr(retval).readCString());
}
});

// 循环 Hook 所有解密函数
decrypt_offsets.forEach(function(offset) {
let func_name = "sub_" + offset.toString(16).toUpperCase();
Interceptor.attach(baseaddr.add(offset), {
onEnter: function(args) {
console.log("[" + func_name + "] arg =", ptr(args[0]).readCString());
},
onLeave: function(retval) {
console.log("[" + func_name + "] retval =", ptr(retval).readCString());
}
});
});
}

setImmediate(hook_linker_call_constructors);

用ida进行修改并添加注释。

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
303
304
305
306
307
308
309
310
import idaapi
import idautils

# 加密/明文映射(从 Frida 输出中提取)
mappings = {
"710E34DFB3D273975E0936FBB4CF62BE541F3CC4A8": "FeatureLibcProtection",
"530A39DDAFCB39A84E1821CEAB8F52BE4F3B34DFAEEC7FA843": "dalvik/system/DexPathList",
"3D7CDAABED5340D60976CFBAFB5560E80D70C9B0F64C40E80F": "FeatureProtectEnvironment",
"5227DBB22926DA554467C6EF2433D6524567F7": "com/baidu/protect/A",
"14D64606364E3C6D": "unknown",
"5A483597E76FC0D170423682FE54CBF073": "FeatureGlobalInfo",
"47986CBE0A46F6EE6F8968AD0D5DE7DE429568A914": "FeatureIntegrityCheck",
"5227DBB22926DA554467C6EF2433D6524567F5EF2A34DB795026D2F12E35": "com/baidu/protect/CrashHandler",
"02D740473B583B0914975D1A364D370E15976F": "com/baidu/protect/B",
"0406D0C873B03D581246CD957EA5315F1346FC9761983A5A08": "com/baidu/protect/AppInfo",
"C7D432A325E854F696D43CFF27BF03F7": "c07954f5209e7c14",
"67C538FE4857A6C4309F39AB4B06A5C5": "f8547c5c1b4a426b",
"260714BFA582481D715D15EAA6D34B1C785B43B8F385444A705E15EAF3D54C4B": "f8547c5c1b4a426b8db3ad940a4aa415",
"57585C810030ABC6530C06D65363FF9C545A5CD60367A9CD545400D45430A8C9": "359b71797ac5dbcc07954f5209e7c146",
"7E6EA364435927F7": ".suuid",
"3148B69D4B47B331": "",
"61B82D685939526D": "",
"065A40FFE793182D255C54F9FB95042D0474": "FeatureSecuritySDK",
"1901FAF72A31D21E5D29D8FA6408D15B542BC2A6100BD950472999F12A29D41E7E2ADCF82833881867": "(ILjava/lang/Object;[Ljava/lang/Object;)V",
"49F16102384F33420DD9430F7676300704DB59530275380C17D90204385735422EDA470D3A4D69443B": "(ILjava/lang/Object;[Ljava/lang/Object;)Z",
"4F20F18D70A735130B08D3803E9E3656020AC9DC4A9D3E5D1108928B70BF3313280BD78272A56F1525": "(ILjava/lang/Object;[Ljava/lang/Object;)B",
"8CAD49F071AA53ECC8856BFD3F9350A9C18771A14B9058A2D2852AF671B255ECEB866FFF73A809EAE7": "(ILjava/lang/Object;[Ljava/lang/Object;)C",
"29B441A01E42F2886D9C63AD507BF1CD649E79F12478F9C6779C22A61E5AF4884E9F67AF1C40A88E52": "(ILjava/lang/Object;[Ljava/lang/Object;)S",
"68766DE1F3971C512C5E4FECBDAE1F14255C55B0C9AD171F365E0EE7F38F1A510F5D4BEEF195465709": "(ILjava/lang/Object;[Ljava/lang/Object;)I",
"4C2429895677FDD0080C0B84184EFE95010E11D86C4DF69E120C4A8F566FFBD02B0F0F865475A7D62E": "(ILjava/lang/Object;[Ljava/lang/Object;)J",
"78549A7B4B4B46D83C7CB8760572459D357EA22A71714D96267CF97D4B5340D81F7FBC7449491CDE16": "(ILjava/lang/Object;[Ljava/lang/Object;)F",
"8F4502F0DDCDC5C2CB6D20FD93F4C687C26F3AA1E7F7CE8CD16D61F6DDD5C3C2E86E24FFDFCF9FC4E3": "(ILjava/lang/Object;[Ljava/lang/Object;)D",
"A34B5090CFED8214E763729D81D48151EE6168C1F5D7895AFD633396CFF58414C460769FCDEFD812C7687D8CCFB48F5AE56533B5CCF18658FF39": "(ILjava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;",
"2E68C02CC53A063D255DD337C42D001F0162CF": "FeatureVMProtection",
"F48398ED31542B0E": "libc.so",
"6E2DCEF43F47B331": "_exit",
"04C0441C5939526D": "exit",
"171DD59574B03063041BD88665B4": "pthread_create",
"D4906DE875BD569CCE8B6CF4": "pthread_join",
"6C9860A90F4D93A7": "memcpy",
"2D5E4DE7FD827D7E": "malloc",
"070C098F58629CFF": "calloc",
"3D78BB624F4927F7": "memset",
"C1633EFFD2BBA4ED": "fopen",
"ED617095DDFEE33B": "fclose",
"E24E68C72ED3BB4E": "fgets",
"4EFE6FC425A325B6": "strtoul",
"30640DB4A5A47CCE": "strtoull",
"59CA0B1C240EE666": "strstr",
"B3783A25593967C1": "ptrace",
"715D268CE678C6E2": "mprotect",
"678568CC1272D9F4": "strlen",
"4333E40A59E92B6B": "sscanf",
"20C396763EB607CE": "free",
"59EFF51F63070DB8": "strdup",
"1B79D33BDD38636B": "strcmp",
"11EDA0D862CA71BA0FE9": "strcasecmp",
"AD1C8CA1863C3CF3": "utime",
"7F7567B6D496D9AC": "mkdir",
"3F4E6764B2499D86": "open",
"7838B71CCAAF75C6": "close",
"04B2CD050F9C47EE": "unlink",
"086DDAAB98212586": "stat",
"E2292D97D01141B6": "time",
"6E4AC4CB291788D4": "snprintf",
"302FD3054ECECD91": "strchr",
"C65AB9D5625867A8": "strncmp",
"0C8A9ABA3559D876189B86A93350": "pthread_detach",
"B4D5455E4F6B8C3BB7C4414A": "pthread_self",
"A832C433D41105B4": "opendir",
"63C7D8A1855BE1D1": "readdir",
"DD596595BC74408E": "closedir",
"711BD780E966683E": "mmap",
"3B70EDB05642EDB6": "munmap",
"31F4B35BA15DF8D9": "lseek",
"3A7B1DDB8EAEEB3C": "fstat",
"2DC5B93C51DE4E8C": "read",
"CD3E5AC4CE4EB523": "select",
"9C6E379605B933C39F71": "bsd_signal",
"C489B09443666A3D": "fork",
"266DF31562D26748": "prctl",
"176E4794FD670E8110": "setrlimit",
"50660A65A953F202": "getppid",
"9B333E341D4B67D8": "getpid",
"68D77BE990FA40DB": "waitpid",
"03B093571B66B5A9": "kill",
"75F6DC484BF43E44": "flock",
"795BD9475A856CF3": "write",
"BE66196673ACF758": "execve",
"296C25084100FFA8": "execv",
"164C148245E50A35": "execl",
"FEF19D484641B9EC": "sysconf",
"E215844F2E808FC7E23A85592D9198DEC415905329": "__system_property_get",
"C033AAA8B3EE1BBFC3": "ftruncate",
"D818528B9AD01DD0": "gettid",
"2E1331FA88B61B58": "pread64",
"8F6EB5FBB011CE0A": "pwrite64",
"3DFBFF6E535AF038": "pread",
"B2D06CEC09B428DF": "pwrite",
"326FA0A56FCADBAB": "statvfs",
"09598DD611D1543C": "n001",
"8CA86FFB66BD1DAFC58A62B543A840AACA833ED67ABD44A28B8864F477F361B7D68D6BFD2B9058A2D2852AF671B255ECF79077F37EBB098FCE8573FB3FB053ADC3CB56EE62B55CA49FAD5FB346": "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IZ)V",
"6FCD3DF87F3493A7": "n002",
"687340E5F6931217241042E4FC951810341062E4FC951806340408DD": "(Landroid/content/Context;)V",
"0A5D55D037019CFF": "n003",
"783480112A3D27F7": "()V",
"226212F6FEFC669A22": "arm64-v8a",
"4819CF8872FE27590B0F928A70A127": "/proc/self/maps",
"D6E4059A10DC32C3": "r",
"6D946FAE0959BDD46E": "libdvm.so",
"2C5643EAE095530D2F": "libart.so",
"080407955A6AF59B3B01008E4273B28C0B": "libvmkid_lemur.so",
"3C74B470455E09843F": "libaoc.so",
"54AFA16C61F747EE": "%s",
"44CB02463B5D3E0202D3": "%s/.bdlock",
"050301915868F8D007020B97526FE8D014004AB05E66F29E10181786": "android/content/pm/Signature",
"3173B263455443D83372B8654F5353D82070F9414B5E4C9637789F7F4C52": "android/content/pm/PackageInfo",
"C6622AE8D3D2C0C2C46320EED9D5D0C2D76161CADDD8CF8CC06903FBD2DAC388D5": "android/content/pm/PackageManager",
"EA6C7888C1F28714EA726CD5EFF89752FD6B6883FAF3915EEA66": "android/app/ActivityThread",
"E54769C132BADF61E5597D9C1EBCD53AE15179FA30A3D7": "android/app/ContextImpl",
"5AEF69E333A53DD350C972DE3EB331C2": "getSystemContext",
"6B3933A1A4B562CD2A7450A1BAA13FE12C7E0BA5B2A559CF337C44": "()Landroid/app/ContextImpl;",
"49CB0B1D3512922749CA101939089F3242CC1C0E34": "currentActivityThread",
"EB250425543815AEAA6867254A2C4880A078213253281E95AB7E2D255E67": "()Landroid/app/ActivityThread;",
"7B4820B3F37ECEF77B481982FC7CC2F36E": "getPackageManager",
"3CD856C11978AB9B7D9535C31872AD917A8535D01A338995779A7BC71251B89A75967FD24C": "()Landroid/content/pm/PackageManager;",
"5725F33B56EC400A5725CE0551E0": "getPackageInfo",
"6EFD997248D728A227DF943C6DC275A728D6C85A17FA66A022C39C7A5A9964A128C5967D4A9977A369E1927055D760AB0FDF957C05": "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;",
"59F2E015770378CA4FE8": "signatures",
"3341C036D43A0C020C22C237DE3C06051C22D1359F1B0A0C066CD52DC22D58": "[Landroid/content/pm/Signature;",
"16F690C277DC55AB10F8AB": "toByteArray",
"F041BE8EE33C3CF3": "()[B",
"376D2CF19796D9AC": "%s/.1",
"754D2D248366B8F5": "%s/.1/%s",
"EA6C7888C1F28714E47133B8DBF28F5F": "android/os/Build",
"C96649F611D3BB4E": "MODEL",
"71E07CC62BF925D753ED32E33EA420D85AB1": "Ljava/lang/String;",
"E67F6728533E67C1": "%s/lib",
"395E7BCDB779A596": "%s/.%d",
"758269C5036FF69675987ED5076EB68071926E851332B39566": "assets/baiduprotect%d.jar",
"1F71A90156FD2B6B": "/1.jar",
"69D29F724DC562BD68DB9261": "/classes.jar",
"05F8EB1A650468CB04FFE203": "/classes.dex",
"4769C02CD167070A1C6C8E7DC3674D4E0C22C234D13B100E1B23CB39C2": "/data/data/%s/.%d/classes.jar",
"4DFDB3CF629670B816F8FD9E70963AFC06B6B1D762CA67BC11B7B6DE7B": "/data/data/%s/.%d/classes.dex",
"0DD14F092B4D300C12DD031B36": "libartbase.so",
"1E38C4F22868C0545D2E99F02A37C0": "/proc/self/maps",
"13B82D685939526D": "r",
"1544909711D1543C": "r--p",
"819725BF63FC17E9D7C420B063FC17E9D7C420E9": "%s %s %*s %*s %*s %s",
"73D075BA7F3493A7": "r-xp",
"FBBB64F474AE5DAAC0BB69F5778342B1CD8A71": "__android_log_print",
"2D5240FB92E17D7E": "mmap",
"503BC5F83F349C535021D2E83B35DC45542BC2B82F69D95043": "assets/baiduprotect%d.jar",
"0900049337019CFF": "mmap",
"D2290DB35DD3BB4E": "V",
"678A1DB04AD649B6": "Z",
"01107FC0CAD110A2": "B",
"69BE796F507CE666": "C",
"900C48443A5C67C1": "S",
"552D54E3921DA596": "I",
"5EF11AA0771CD9F4": "J",
"7640876B378F2B6B": "F",
"02B1F3133EB607CE": "D",
"669B877B16770DB8": "L",
"6A0EB69D4B47B331": "[F",
"3AFC2D685939526D": "[D",
"0D08CB863EBD35520046FF887EBD315D09": "java/lang/Boolean",
"CE8573FB3FB053ADC3CB47E364B9": "java/lang/Byte",
"6B9C7BAB5058F2C966D24EA21E46F2C475987F": "java/lang/Character",
"2A5E57EABD8D1C10271072E3FD9309": "java/lang/Short",
"0E0C1382186DFD9103422C8D4364FB9A16": "java/lang/Integer",
"3A7CA0700551469937329A7E445A": "java/lang/Long",
"CD6D38FB93D7C583C02308F6D3DAD0": "java/lang/Float",
"E1636A9B81F78255EC2D5895DBF98F5E": "java/lang/Double",
"EE487BD272BFDA20E30642D137B6D83A": "java/lang/Object",
"01E373D93EE849B6": "<init>",
"6B4A5696CAD110A2": "(Z)V",
"16D717062442E666": "<init>",
"EB4E61123A5C67C1": "(B)V",
"20443A8AE623A596": "<init>",
"3CB233F6771CD9F4": "(C)V",
"0C29E90243B12B6B": "<init>",
"6EE2DA453EB607CE": "(S)V",
"16F2E91262490DB8": "<init>",
"4044880EB048636B": "(I)V",
"5EF0BCD2778714D9": "<init>",
"F022CC9AE33C3CF3": "(J)V",
"2E776DB6D2A8D9AC": "<init>",
"78782B5CB2499D86": "(F)V",
"273DB606DB9175C6": "<init>",
"5998883A61F747EE": "(D)V",
"1976D4B3FD404BD01A75CEBA": "booleanValue",
"BE691AF2D01141B6": "()Z",
"7F5DC0DC161890C778": "byteValue",
"6B72E36626BCCD91": "()B",
"D646AAC957547BDDD0": "charValue",
"54D7B1C85038BC29": "()C",
"B7C9425E5E5C8908B1C4": "shortValue",
"EF6BF25DB07877B4": "()S",
"78CCCD93805EE6B4": "intValue",
"961C43E6D91029FC": "()I",
"7019D897BF07044B79": "longValue",
"7E2CC9DD3732EDB6": "()J",
"3BEBB95FBE0B99B528E2": "floatValue",
"74212FBAFAAEEB3C": "()F",
"3BCFAD3A3DBB18ED33D5BD": "doubleValue",
"967272A1AD3AB523": "()D",
"947C25A859BC35C3993200BD04B93ACA": "java/lang/String",
"CB88B69A31086A3D": "intern",
"7E36DC0B6FA406673A7EFE062181133A3F71F75A": "()Ljava/lang/String;",
"0406D0C873B03D581246C59479B831500346DC843E891C": "com/baidu/xshield/ac/XH",
"8CA864F474AE5DAAC0CB66F57EA857ADD0CB46F57EA857BBD0DF49F071AA53ECC8856BFD3F8F46B1CD8A62A15CB653B5C5CB69FB7EBB1D90D0966CF477E7698A8DB2": "(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;[I)V",
"689364BE7F3493A7": "init",
"503BC5F83F349C535021D2E83B35DC45542BC2B326": "assets/baiduprotect.m",
"44CB2D685939526D": "%s",
"3E27F603C0CC1EC6": "%s.lock"
}

# 函数列表(从 Frida 输出中提取涉及的函数地址)
func_list = [
0x28C28, # sub_28C28
0x2709C, # sub_2709C
0x25E78, # sub_25E78
0x25F24, # sub_25F24
0x25FD0, # sub_25FD0
0x2607C, # sub_2607C
0x26128, # sub_26128
0x261D4, # sub_261D4
0x26280, # sub_26280
0x2632C, # sub_2632C
0x263D8, # sub_263D8
0x26484, # sub_26484
0x26BE8, # sub_26BE8
0x28AD0 # sub_28AD0
]

def find_string_addr(search_str):
"""查找字符串的地址"""
for addr in idautils.Strings():
if str(addr) == search_str:
return addr.ea
return None

def resolve_func_addr(func_entry):
"""将函数名或地址转换为地址"""
if isinstance(func_entry, int):
return func_entry
elif isinstance(func_entry, str):
func_addr = idaapi.get_name_ea_simple(func_entry)
if func_addr == idaapi.BADADDR:
print(f"Function not found: {func_entry}")
return None
return func_addr
else:
print(f"Invalid function entry: {func_entry}")
return None

def add_comments_to_strings():
"""为 .rodata 中的字符串添加解密注释和重命名"""
for encrypted, decrypted in mappings.items():
str_addr = find_string_addr(encrypted)
if str_addr:
idaapi.set_cmt(str_addr, f"Decrypted: {decrypted}", 0)
# 重命名字符串,替换特殊字符以符合命名规则
safe_name = f"str_{decrypted.replace('/', '_').replace(' ', '_').replace('(', '_').replace(')', '_').replace(';', '_').replace('[', '_').replace(']', '_')}"
idaapi.set_name(str_addr, safe_name, idaapi.SN_FORCE)
print(f"Added comment at 0x{str_addr:x}: {encrypted} -> {decrypted}")
else:
print(f"String not found in .rodata: {encrypted}")

def add_comments_to_function(func_addr):
"""为指定函数添加注释,扫描加载字符串的指令"""
func = idaapi.get_func(func_addr)
if not func:
print(f"Function at 0x{func_addr:x} not found or invalid")
return

# 添加函数注释
func_name = idaapi.get_func_name(func_addr)
idaapi.set_func_cmt(func_addr, f"Decrypts strings (e.g., {', '.join(f'{k}->{v}' for k, v in list(mappings.items())[:5])}...)", 1)
print(f"Processing function: {func_name} at 0x{func_addr:x}")

# 扫描函数指令,查找加载字符串的指令
for ea in idautils.FuncItems(func.start_ea):
disasm = idaapi.generate_disasm_line(ea, 0)
if "LDR" in disasm: # 检查加载指令
for enc, dec in mappings.items():
if enc in disasm:
idaapi.set_cmt(ea, f"Loads {enc} -> {dec}", 0)
print(f"Added comment at 0x{ea:x}: {enc} -> {dec}")

def main():
"""主函数:处理字符串和函数列表"""
# 处理 .rodata 中的字符串
print("Processing strings in .rodata...")
add_comments_to_strings()

# 处理函数列表
print("\nProcessing functions...")
for func_entry in func_list:
func_addr = resolve_func_addr(func_entry)
if func_addr is not None:
add_comments_to_function(func_addr)
else:
print(f"Skipping invalid function: {func_entry}")

# 执行脚本
if __name__ == "__main__":
main()

效果如下。

image-20250508133149140

由于解密我在hook的脚本里,设置第一个参数是字符串,而在sub_b3b4中,有些解密函数的参数是字节码,因此还需要补充5个解密函数的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
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
// 要 hook 的 so 文件名字
var module_name = "libbaiduprotect.so";
// so 文件的大小
var module_size = 0;
// 默认的目标函数偏移
var target_offset = 0x88060;

// 需要 hook 的字节码解密函数偏移列表
var bytecode_decrypt_offsets = [
0x267e0, 0x26A90, 0x27550, 0x27800, 0x28418
];

// Hook linker64 的 call_constructors,检测目标 so 文件加载
function hook_linker_call_constructors() {
let linker64_base_addr = Module.getBaseAddress('linker64');
let offset_call = 0x51BA8; // __dl__ZN6soinfo17call_constructorsEv
let call_constructors = linker64_base_addr.add(offset_call);

let listener = Interceptor.attach(call_constructors, {
onEnter: function(args) {
console.log('[linker] Call_Constructors onEnter');
let secmodule = Process.findModuleByName(module_name);
if (secmodule != null) {
module_size = secmodule.size;
// 第一时间 hook 目标函数
hook_target_func(secmodule.base);
listener.detach();
}
}
});
}

// Hook 目标函数(触发后续字节码解密函数的 hook)
function hook_target_func(baseaddr) {
let listener = Interceptor.attach(baseaddr.add(target_offset), {
onEnter: function(args) {
console.log("[target_func] onEnter, offset: 0x" + target_offset.toString(16));
},
onLeave: function(retval) {
console.log("[target_func] onLeave, retval: " + retval);
// 在目标函数退出时,hook 所有字节码解密函数
hook_bytecode_decrypt_funcs(baseaddr);
listener.detach();
}
});
}

// Hook 指定的字节码解密函数
function hook_bytecode_decrypt_funcs(baseaddr) {
bytecode_decrypt_offsets.forEach(function(offset) {
let func_name = "sub_" + offset.toString(16).toUpperCase();
Interceptor.attach(baseaddr.add(offset), {
onEnter: function(args) {
let input_ptr = args[0]; // 捕获输入指针(指向字节数组)
console.log("[" + func_name + "] Input pointer: " + input_ptr);

// 读取以 0x00 结尾的字节数组
let bytes = [];
let i = 0;
while (true) {
let byte = Memory.readU8(input_ptr.add(i));
if (byte === 0x00) break; // 遇到 0x00 停止
bytes.push(byte.toString(16));
i++;
}
console.log("[" + func_name + "] Input bytes: " + bytes.join(", "));
},
onLeave: function(retval) {
// 捕获返回值(解密后的字符串)
let output_str = retval.readCString();
console.log("[" + func_name + "] Output string: " + output_str);
}
});
});
}

// 主逻辑:启动 hook
setImmediate(hook_linker_call_constructors);

看得出来,sub_b3b4负责解密字符串,因此改名成这个。

image-20250508133416118

接下来看其它的函数,根据分析,当a2==1时,只执行sub_B3B4。

image-20250508130512877

回到JNI_OnLoad,继续分析sub_91E4。

image-20250508134058128

可以看出来,在进行JNI动态注册,所以给它改了些函数名及变量名。

image-20250508134858811

回到JNI_OnLoad,我一直很好奇剩下的fread是什么东西?下图是别人对同样的so文件的分析,它这里伪代码是gettimeofday,那就很好理解了,在计算时间差,一般计算时间差是为了反调试,博主说这里只是收集信息。

745332_UJ2HKKKE2VPQ7PW

接下来可以分析第a2==2的时候,会调用什么内容。

image-20250508140130462

已知B3B4只当a2==1时才调用,这里从3E29C开始分析。

image-20250508130512877

直接分析3E36C。

image-20250508140313511

再进入3E3F0。

image-20250508142124937

3E3F0这两个函数很奇怪,似乎是我dump出现了点bug?

image-20250508142155835

和博主dump下来的so不太一样呢。

image-20250508142103062

用博主的图来讲函数3E3F0,大概是通过/proc/self/maps获取进程虚拟机类型。

image-20250508142607362

这样子分析下去有点难受,我重新dump了一个,还是这样子,难道是壳做了什么手脚?——保留疑惑。看了一下修复的soFixer,它github写着。——阿这。

image-20250508144748157

之后换了一个so修复的程序,需要自己编译。

链接:https://github.com/maiyao1988/elf-dump-fix

再次查看3e3f0,这回终于没问题了。

image-20250508171016443

继续看函数列表,发现当a2==2时,只执行了3E29C。

image-20250508130512877

总结

至此,libbaiduprotect.so加载完了,总结一下:

  1. 在.init_array的sub_88060进行解密,对so里的一些关键函数进行恢复。
  2. 下述这些函数,统统在全局函数列表处进行注册添加,等待调用。
image-20250508130512877
  1. 在JNI_OnLoad处。

    sub_B3B4(a2==1)解密了一些libc常用的函数地址,存在全局变量中。

    image-20250508180536833

    sub_91E4处,进行JNI动态注册,注册了n001、n002、n003共3个JNI函数。

    image-20250508180647854

    sub_3E29C(a2==2)执行了下面的内容,获取最大IO向量数量并获得虚拟机类型。

    image-20250508191242049

JNI函数分析(attachBaseContext/onCreate)

n001

Java层的n001,对应于sub_91E4注册的3个函数的第1个函数。

通过分析,得知n001属于类com.baidu.protect.A。

image-20250508192514683

然后在com.baidu.protect.StubApplication类的attachBaseContext中进行调用。

image-20250508192627062

接下来进入native层进行分析。

首先简单的改了一下名字和类型。

image-20250508194738777

下图中,还原了一些函数名。

image-20250509141717335

先来分析sub_95B4。

下面两个图中,可以看到函数sub_968C的参数列表不一样。

image-20250509142032751 image-20250509142046768

在sub_95B4调用sub_968C的地方,进入汇编层分析。

可以看到,前sub_968C的前两个参数是sub_9584的前两个参数,而X2 = X19 + X1 = strlen(X1) + X1,因此,sub_968C的参数列表应该是3个参数,第3个参数是a2字符串的结束地址。

image-20250509142257558

修改后是这样。

image-20250509142546267

进入sub_968C进行分析,修改了一些变量名后,如下图所示。

它大致是一个管理缓冲区的函数,将字符串复制到目标缓冲区,并防止缓冲区溢出,重命名为buffer_manager。

image-20250509142702869

回到n001的JNI函数,继续往下分析,尝试hook decrypt_str2,观察返回值。

image-20250509163101547

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
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
// 要 hook 的 so 文件名字
var module_name = "libbaiduprotect.so";
// so 文件的大小
var module_size = 0;
// 默认的目标函数偏移
var target_offset = 0x88060;

// 需要 hook 的字节码解密函数偏移列表
var bytecode_decrypt_offsets = [
0x95F4
];

// Hook linker64 的 call_constructors,检测目标 so 文件加载
function hook_linker_call_constructors() {
let linker64_base_addr = Module.getBaseAddress('linker64');
let offset_call = 0x51BA8; // __dl__ZN6soinfo17call_constructorsEv
let call_constructors = linker64_base_addr.add(offset_call);

let listener = Interceptor.attach(call_constructors, {
onEnter: function(args) {
console.log('[linker] Call_Constructors onEnter');
let secmodule = Process.findModuleByName(module_name);
if (secmodule != null) {
module_size = secmodule.size;
// 第一时间 hook 目标函数
hook_target_func(secmodule.base);
listener.detach();
}
}
});
}

// Hook 目标函数(触发后续字节码解密函数的 hook)
function hook_target_func(baseaddr) {
let listener = Interceptor.attach(baseaddr.add(target_offset), {
onEnter: function(args) {
console.log("[target_func] onEnter, offset: 0x" + target_offset.toString(16));
},
onLeave: function(retval) {
console.log("[target_func] onLeave, retval: " + retval);
// 在目标函数退出时,hook 所有字节码解密函数
hook_bytecode_decrypt_funcs(baseaddr);
listener.detach();
}
});
}

// Hook 指定的字节码解密函数
function hook_bytecode_decrypt_funcs(baseaddr) {
bytecode_decrypt_offsets.forEach(function(offset) {
let func_name = "sub_" + offset.toString(16).toUpperCase();
Interceptor.attach(baseaddr.add(offset), {
onEnter: function(args) {
let input_ptr = args[0]; // 捕获输入指针(指向字节数组)
console.log("[" + func_name + "] Input pointer: " + input_ptr);

// 读取以 0x00 结尾的字节数组
let bytes = [];
let i = 0;
while (true) {
let byte = Memory.readU8(input_ptr.add(i));
if (byte === 0x00) break; // 遇到 0x00 停止
bytes.push(byte.toString(16));
i++;
}
console.log("[" + func_name + "] Input bytes: " + bytes.join(", "));
},
onLeave: function(retval) {
// 捕获返回值(解密后的字符串)
let output_str = retval.readCString();
console.log("[" + func_name + "] Output string: " + output_str);
}
});
});
}

// 主逻辑:启动 hook
setImmediate(hook_linker_call_constructors);

打印的结果如下。

image-20250509163338561

打印了几个字符串,BCDB0[0]的值是BE660,因此可以确定,/data/user/0/com.example.test是我们要找的解密字符串。

image-20250509163951244

之后又调用函数sub_95FC。

image-20250509170223847

在这个函数中,创建了文件/data/user/0/com.example.test/.bdlock,然后通过flock函数上锁(文件锁)。更名为open_and_lock。

image-20250509170400314

之后再分析sub_781C,又有混淆,通过d810去一下混淆,又调用函数列表,这回调用a2==3的情况。

image-20250509183928072

观察函数列表的函数,先整理出有哪些函数当a2==3的时候执行。

image-20250508130512877

经过整理,会执行3E29C、40CF8(与java层传入的arg5有关)、3DFC4、11F5C、45964。

sub_3E29C(a2==3)

先分析3E29C。

查看call_funcs_list,传入的参数为:a1是函数列表,a2是3,a3是env。

image-20250509191538468

进入sub_13880进行查看。

image-20250509194725564

解密了很多系统库中的函数名,通过JNI进行调用。

这一块代码有点怪,v29应该传入getPackageInfo的函数参数里的,可以从汇编看看。

image-20250509194808323

如下图所示,应该有5个参数。(arm64支持X0-X7传递参数)

image-20250509195624108

但这里不能修改一下callxxxxx的函数签名,因为他是一个不定长的函数调用。但我们知道,a2是一个字符串(包名,稍微追踪一下,发现是在JNI_OnLoad赋值的,当时没改名字,现在苦苦分析)。

image-20250509200504526

flags的值是0x40,代表返回PackageInfo的PackageInfo对象,包含签名信息。(如果不填0x40,返回的PackageInfo对象中的signatures字段将会是null)

继续分析,为他们修改注释和变量名。

image-20250509204903121

进入sub_3AE58,直接返回16字节,有点像md5的初始化常量。

image-20250509205051133

借助大模型的力量,我成功识别了md5的相关函数,不过也说明了算法是我的薄弱点,得找时间好好看看。

image-20250509211428955

分析完了,sub_13880的行为:根据签名数组的第0个签名生成md5,赋值给a3并返回。

image-20250509212723680

接着看sub_66064。

在sub_65E94中,会根据md5的值,在result地址+8的为止,根据md5,累积异或产生了16个新字节(假如记作buf1)。

在sub_64A50中,result将指向ptr_result+1的位置,也就是result = buf1,然后在sub_64A50中,根据buf1的最后4个字节,又生成了160个字节,也就是说,result指向的字节数组大小为176。

因此,将sub_66064命名为md5_extend_176。

image-20250510094601966

整理一下,sub_3E29C得到了一个md5和一个基于md5的176字节。

sub_40cf8(a2==3)

继续看,下一个看40CF8——3E29C、40CF8(与java层传入的arg5有关)、3DFC4、11F5C、45964。

image-20250510095619450

这里的BCE28,和n001传入的arg5有关。

image-20250510095704606

image-20250510095818583

为了确定这个值,尝试hook一下Java层的n001,脚本如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function hook_n001(){
Java.perform(function(){
var clazz_A = Java.use("com.baidu.protect.A");
clazz_A.n001.implementation = function(arg0, arg1, arg2, arg3, arg4, arg5){
console.log("[arg0] ", arg0);
console.log("[arg1] ", arg1);
console.log("[arg2] ", arg2);
console.log("[arg3] ", arg3);
console.log("[arg4] ", arg4);
console.log("[arg5] ", arg5);
var result = this.n001(arg0,arg1,arg2,arg3,arg4,arg5);
return result;
}
})
}

setImmediate(hook_n001);

结果如下。

image-20250510101348916

从调用的地方可以猜到含义,分别代表着:包名、app名、apk路径、数据路径、sdk版本、是否报崩溃的错误。

image-20250510101848906

回到40CF8,这回知道了off_BCE28的含义了,用来判断是否上报crash信息的,那40CF8的这段代码,应该是用来处理崩溃信息上传的?

在调用call_funcs_list这个函数中,调用函数列表每个函数的传参方式是:函数地址、a2、env、a4、a5、a6。(a3就是env)

image-20250510103253286

因此,在40CF8中,sub_409E0和sub_40F38的传参就好懂了。

image-20250510102611433

先进入409E0,修改a2的类型,看样子是注册了两个函数。

image-20250510103527181

交叉引用qword_BE808,找到它赋值的位置,发现它是一个之前的解密字符串,“com/baidu/protect/CrashHandler”。

image-20250510105100608

鉴于sub_7398、sub_409E0等函数还存在字符串未解密,这里先把它们的解密字符串收集起来,就不提供脚本了,脚本都大差不差。

回到409e0。

有些搞不明白这里的v10与v11的关系,v11按理来说应该是异常的回调函数,但这里的写法我确实没看懂。

总之,sub_409E0动态注册了com.baidu.protect.CrashHandler.a()和com.baidu.protect.CrashHandler.b(Ljava/lang/String)。

同时,注册了一个统一的自定义处理函数sub_40bd0,负责处理异常。

综上所述,直接将这个函数改名成处理异常。

image-20250510120359482

回到40cf8,这个函数在a2==3时,做了下面的操作,接着看下一个函数列表的函数。

image-20250510140228861

sub_3DFC4(a2==3)

该看3DFC4了——3E29C、40CF8(与java层传入的arg5有关)、3DFC4、11F5C、45964。

先将a3的类型改为JNIEnv*,然后尝试阅读一下代码。

image-20250510143523604

这一个函数的难度,比我想象中难很多啊,博客说,3D6AC是3DFC4的关键函数…点进去一看,不知道3D6AC在做什么,ptr_buf的大小是0x13E80,不妨hook sub_3d6ac,观察结束后,ptr_buf的内容是多少。

image-20250510143621535

脚本如下。

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
// Frida脚本:hexdump sub_3D6AC的第一个参数ptr_buf

// 1. 请将 "libyourtarget.so" 替换为目标SO文件的实际名称
const TARGET_MODULE_NAME = "libbaiduprotect.so";

// 2. 这是 sub_3D6AC 函数在SO文件中的偏移地址
// 如果 "sub_3D6AC" 是一个导出的函数名,你可以尝试使用 Module.findExportByName()
// 但通常这种 "sub_XXXXX" 格式的名称是IDA反编译后基于地址命名的,所以使用偏移量更可靠。
const FUNCTION_OFFSET = 0x3D6AC; // 函数 sub_3D6AC 的偏移地址

// 3. ptr_buf 指向的内存区域大小
const BUFFER_SIZE = 0x13e80;

// (可选) 你想在hexdump中实际打印的最大字节数,以防BUFFER_SIZE过大导致控制台输出混乱
// 如果你想打印全部内容,可以将MAX_DUMP_SIZE设置为BUFFER_SIZE
const MAX_DUMP_SIZE = Math.min(BUFFER_SIZE); // 例如,最多打印1024字节

function hook_linker_call_constructors() {
// 获得linker64的地址
let linker64_base_addr = Module.getBaseAddress('linker64');
let offset_call = 0x51BA8; // __dl__ZN6soinfo17call_constructorsEv
let call_constructors = linker64_base_addr.add(offset_call);
let listener = Interceptor.attach(call_constructors, {
onEnter: function(args) {
// 判断是否可以通过findModuleByName查找到目标so文件
console.log('[linker] Call_Constructors onEnter');
let secmodule = Process.findModuleByName(TARGET_MODULE_NAME);
if (secmodule != null) {
// 第一时间hook
hook_target_func(secmodule.base);
listener.detach();
}
}
});
}

function hook_target_func(baseaddr){
let listener = Interceptor.attach(baseaddr.add(0x88060), {
onEnter:function(args){

},
onLeave:function(retval){
// 第一个函数解密完后,才能hook sub_28c28
main(baseaddr);
listener.detach();
}
});
}

function main(base) {
const moduleBase = base;
if (!moduleBase) {
console.error("[-] 模块 " + TARGET_MODULE_NAME + " 未找到。请确保模块名正确且已加载。");
return;
}

const targetFunctionAddr = moduleBase.add(FUNCTION_OFFSET);
console.log("[+] 目标函数 sub_3D6AC 地址: " + targetFunctionAddr);
console.log("[+] ptr_buf 大小: 0x" + BUFFER_SIZE.toString(16) + " (" + BUFFER_SIZE + " bytes)");
console.log("[+] hexdump 将显示最多: 0x" + MAX_DUMP_SIZE.toString(16) + " (" + MAX_DUMP_SIZE + " bytes)");

Interceptor.attach(targetFunctionAddr, {
onEnter: function(args) {
// args[0] 是第一个参数 (__int64 a1, 即 ptr_buf)
// args[1] 是第二个参数 (JNIEnv *env)
this.ptr_buf = args[0]; // 保存 ptr_buf 的指针以在 onLeave 中使用
this.jniEnv = args[1]; // 保存 JNIEnv 指针

console.log("\n[*] 进入 sub_3D6AC:");
console.log(" ptr_buf (a1): " + this.ptr_buf);
console.log(" JNIEnv* (a2): " + this.jniEnv);
},
onLeave: function(retval) {
console.log("[*] 离开 sub_3D6AC. 返回值: " + retval);

if (this.ptr_buf && !this.ptr_buf.isNull()) {
console.log("[+] ptr_buf (" + this.ptr_buf + ") 内容 (前 " + MAX_DUMP_SIZE + " 字节):");
try {
// Memory.readByteArray(pointer, length) 返回一个 ArrayBuffer
const bufferContent = Memory.readByteArray(this.ptr_buf, Math.min(BUFFER_SIZE, MAX_DUMP_SIZE));
console.log(hexdump(bufferContent, {
offset: 0,
length: Math.min(BUFFER_SIZE, MAX_DUMP_SIZE), // 确保hexdump长度与读取长度一致
header: true,
ansi: true // 如果你的控制台支持,可以开启颜色高亮
}));

// 如果你想保存整个缓冲区到文件(这部分比较复杂,通常建议在PC端Python脚本中处理)
// 你可以在这里发送 bufferContent (或者 this.ptr_buf 和 BUFFER_SIZE) 到你的Python脚本
// 例如: send({ ptr: this.ptr_buf.toString(), size: BUFFER_SIZE });
// 然后在Python端接收并保存:
// def on_message(message, data):
// if message['type'] == 'send':
// payload = message['payload']
// ptr = int(payload['ptr'], 16)
// size = payload['size']
// print(f"Receiving buffer from {hex(ptr)} with size {size}")
// buffer_data = process.read_bytes(ptr, size)
// with open(f"ptr_buf_dump_{hex(ptr)}.bin", "wb") as f_out:
// f_out.write(buffer_data)
// print(f"Buffer dumped to ptr_buf_dump_{hex(ptr)}.bin")
// script.on('message', on_message)


} catch (e) {
console.error("[-] 读取或hexdump ptr_buf 时发生错误: " + e);
console.error(" 错误详情: " + e.stack);
}
} else {
console.warn("[-] ptr_buf 在 onLeave 时为 null 或无效。");
}
console.log("--- 结束 sub_3D6AC 打印 ---");
}
});
}

// 确保在Java环境准备好后执行main函数,尤其是在attach模式下早期hook时
// 对于 Android SO 文件中的函数,通常直接调用 main() 即可,
// 或者使用 setImmediate(main) 来确保主循环已准备好
setImmediate(hook_linker_call_constructors);
// 或者对于某些情况,特别是应用启动非常早期的hook,可能需要等待Java VM加载完毕
// Java.perform(function() {
// console.log("[+] Java VM 已加载,开始执行 main()");
// main();
// });

打印的结果如下图所示,文件还挺大的。

image-20250510174628687

回到3DFC4,验证一下我们之前的分析,第0x721个元素存放了目录路径的个数。0x721 x 4 = 0x1C84,因此到0x1C84去读取个数。

image-20250510174732047

读4个字节,02 00 00 00,即为2。因此会创建2个文件夹,再来到第0x744个元素(也就是0x744 x 4 = 0x1D10),去读取目录字符串。

image-20250510174841862

也就是说,第一个目录字符串的地址存放再0x7969adfcc0处,下一个地址则是0x7969adfd20。

image-20250510175058363

要获取这2个字符串,即去读取ptr_buf + 0x1D10和ptr_buf + 0x1D40,修改一下脚本。

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
const TARGET_MODULE_NAME = "libbaiduprotect.so";
const FUNCTION_OFFSET = 0x3D6AC;
const BUFFER_SIZE = 0x13e80;
const MAX_DUMP_SIZE = Math.min(BUFFER_SIZE);

function hook_linker_call_constructors() {
let linker64_base_addr = Module.getBaseAddress('linker64');
let offset_call = 0x51BA8; // __dl__ZN6soinfo17call_constructorsEv
let call_constructors = linker64_base_addr.add(offset_call);
let listener = Interceptor.attach(call_constructors, {
onEnter: function(args) {
console.log('[linker] Call_Constructors onEnter');
let secmodule = Process.findModuleByName(TARGET_MODULE_NAME);
if (secmodule != null) {
hook_target_func(secmodule.base);
listener.detach();
}
}
});
}

function hook_target_func(baseaddr) {
let listener = Interceptor.attach(baseaddr.add(0x88060), {
onEnter: function(args) {},
onLeave: function(retval) {
main(baseaddr);
listener.detach();
}
});
}

function main(base) {
const moduleBase = base;
if (!moduleBase) {
console.error("[-] 模块 " + TARGET_MODULE_NAME + " 未找到。请确保模块名正确且已加载。");
return;
}

const targetFunctionAddr = moduleBase.add(FUNCTION_OFFSET);
console.log("[+] 目标函数 sub_3D6AC 地址: " + targetFunctionAddr);
console.log("[+] ptr_buf 大小: 0x" + BUFFER_SIZE.toString(16) + " (" + BUFFER_SIZE + " bytes)");
console.log("[+] hexdump 将显示最多: 0x" + MAX_DUMP_SIZE.toString(16) + " (" + MAX_DUMP_SIZE + " bytes)");

Interceptor.attach(targetFunctionAddr, {
onEnter: function(args) {
this.ptr_buf = args[0]; // 保存 ptr_buf 的指针
this.jniEnv = args[1]; // 保存 JNIEnv 指针

console.log("\n[*] 进入 sub_3D6AC:");
console.log(" ptr_buf (a1): " + this.ptr_buf);
console.log(" JNIEnv* (a2): " + this.jniEnv);
},
onLeave: function(retval) {
console.log("[*] 离开 sub_3D6AC. 返回值: " + retval);

if (this.ptr_buf && !this.ptr_buf.isNull()) {
console.log("[+] ptr_buf (" + this.ptr_buf + ") 内容:");

try {
// 读取目录个数 (ptr_buf[0x721])
const dirCount = this.ptr_buf.add(0x721 * 4).readU32();
console.log("[+] 目录个数 (ptr_buf[0x721]): " + dirCount);

// 从 0x744 开始遍历目录结构
let dirOffset = 0x744 * 4; // 字节偏移
for (let i = 0; i < dirCount; i++) {
const dirEntry = this.ptr_buf.add(dirOffset);
const dirPathPtr = dirEntry.readPointer(); // 读取目录路径指针
try {
const dirPath = dirPathPtr.readCString();
console.log(`[Dir ${i}] ${dirPathPtr}: ${dirPath}`);
} catch (e) {
console.log(`[Dir ${i}] ${dirPathPtr}: (无法读取字符串: ${e})`);
}
dirOffset += 48; // 每个目录结构 48 字节
}

// 可选:打印整个缓冲区 (注释掉,避免输出过多)
/*
const bufferContent = Memory.readByteArray(this.ptr_buf, Math.min(BUFFER_SIZE, MAX_DUMP_SIZE));
console.log(hexdump(bufferContent, Math.min(BUFFER_SIZE, MAX_DUMP_SIZE)));
*/

} catch (e) {
console.error("[-] 读取 ptr_buf 时发生错误: " + e);
console.error(" 错误详情: " + e.stack);
}
} else {
console.warn("[-] ptr_buf 在 onLeave 时为 null 或无效。");
}
console.log("--- 结束 sub_3D6AC 打印 ---");
}
});
}

setImmediate(hook_linker_call_constructors);

打印的结果如下图所示,创建了2个目录。

image-20250511140930719

至于sub_3DFC4d剩下的内容就不分析了,因为sdk版本之前打印好像是32。

image-20250511141315957

sub_11F5C(a2==3)

接着看11F5C。——3E29C、40CF8(与java层传入的arg5有关)、3DFC4、11F5C、45964。

直接调用sub_BC60。

image-20250511141842306

在sub_BC60中,根据sdk版本,执行不同的代码块,我记得之前打印过,sdk_version是32,因此这里只关注需要执行的函数。

image-20250511143526110

来看看sub_188AC,用d810去一下混淆。

image-20250511143722434

hook这些字符串,打印看看什么含义,似乎没打印出来,说明没执行到。v0是一个指针,存储着BCEB8。我猜测,这里是在根据sdk,选择对应的so。

image-20250511145520335

我知道libart.so,但libartbase.so是什么呢?

image-20250511150550133

接着看sub_4029c,进行一定分析,修改变量名,写注释,如下图所示。

看上去,sub_4029C是一个处理内存映射的函数。

image-20250511152105188

整理一下:

  1. 读取 /proc/self/maps
  • 打开 /proc/self/maps 文件,获取当前进程的内存映射信息。
  1. 查找目标模块(str)
  • 在内存映射中查找包含 str(例如 “libartbase.so”)的条目。
  1. 修改内存权限
  • 对于目标模块的内存段,修改其权限(mprotect):
    • 如果段权限是 r–p(只读),改为 r-x(可读可执行)。
    • 如果段权限是 r-xp(可读可执行),改为 rwx(可读可写可执行)。
  1. 存储基地址
  • 将目标模块的基地址存储到 a1 中。

回到sub_188AC,继续分析,将sub_4029C改名成get_so_baseaddr_AND_change_flags。

我之前hook过sub_25E78,但这里似乎并没有对6A6DC5A……进行解密,大概率是没执行,这里分析else分支,继续分析sub_3FF9C。

image-20250511153622384

baseaddr是libartbase.so的基址,这里看上去是在解析ELF文件。

image-20250511154637350

随便打开一个普通的so文件看看,寻找第0x38个字节和0x20个字节的含义。——先取指针,后进行运算,所以这里用0x38*8。

第0x38字节的含义:e_phnum_NUMBER_OF_PROGRAM_HEADER_ENTRIES,代表PHT的个数。

image-20250511160146292

第0x20字节的含义:e_phoff_PROGRAM_HEADER_OFFSET_IN_FILE,代表PHT在文件中的偏移量。

image-20250511160307043

除此之外,还有访问pht + 16位置的代码段…这里就不贴图了,直接修改变量名。

这个函数手动解析了ELF动态信息,这种方式可以绕过一些针对标准dlsym的Hook,或者在某些特殊环境中加载和解析符号。

那么,sub_3FF9C看似是在解析动态链接库,并找到对应的符号。——当然不是这样,继续往下看。

image-20250511164204307

可以注意到,函数3FAE0和3F924出现在了switch-case结构中,这里简单介绍这俩函数的作用。

函数sub_3FAE0负责处理标准的ELF重定位表(如 .rela.dyn 用于数据重定位,.rela.plt 用于过程链接表PLT的函数调用重定位)。它查找对目标符号的引用,并将这些引用重定向到a3(调用时提供的“空函数”地址,伏笔)。

函数sub_3FAE0负责处理一个自定义的、以”APS2”开头的特殊数据结构。这个结构中也包含了对某些符号的引用信息(可能是为了处理更复杂的场景,如与符号版本控制相关的重定位,或者是一些内部模块间的链接)。它同样查找对目标符号的引用,并将这些引用重定向到提供的“空函数”地址。

这里的空函数,指的是sub_1B044,这是一个空函数,点进去只有一个return。(这里的redirect_func_to_a3是sub_3FF9C,被我改名了)。

image-20250511191508810

image-20250511191528201

在sub_188AC中,除了将__android_log_print进行hook,重定位到一个空函数,还将mmap进行了hook,重定位到了sub_1B070,博主说,这里在加载Dex的时候有用。那就待会再来看sub_1B070吧。

image-20250511191747476

回到sub_BC60,总结一下sub_188AC,它将__android_log_print和mmap进行了hook。

因为我们的sdk是32,再继续分析sub_11AB0。

image-20250511193723418

还有好多未解密的字符串,写个脚本打印一下。

image-20250511193921095

打印的结果如下,涉及动态加载。

image-20250511201719617

有个很奇怪的现象,可能是我没分析到?当把未解密的so文件放入ida中,跳转到11ab0,是可以看到循环的(和博客一致)。而解密后的so文件在查看时,没有for循环,不知道是什么导致的。麻了,因为这个so文件我没添加注释,没改变量名,有些东西乍一看看不出来。

image-20250511203419140

直接拿博客的图来用一下吧,好糊。

简单说一下思路,将jar文件转换成dex文件,然后通过InMemoryDexClassLoader进行加载,然后将两个加载器的Element数组合并,重新设置成原加载器的Element数组(为了之后加载的类,可以被原始类加载器加载,热更新),随后将Dex文件的信息,在baidu设置的0x13E80个字节的缓冲区里进行更新——也不知道更新什么东西?费这么大信息,hook上mmap函数,最后只是为了拿到mmap分配的地址,给到函数update_dex_info_to_baidu_struct。

image-20250511211536106

回到sub_bc60,再次总结一下,在hook mmap之后,会加载jar文件(目标dex文件),然后去除mmap的hook。

image-20250511213147216

至此,sub_bc60分析完了——我没特意分析具体是如何解密出dex文件的,感觉很复杂——之前的签名扩展的176字节,会被用来当密钥,也就是说,如果这个apk被修改后重新签名,是无法打开的,因为Dex文件解不开。

要想得到这个Dex文件,可以通过hook sub_3BA90,在onLeave时dump第3个参数,而dump的大小是第4个参数。

image-20250511213343307

sub_45964(a2==3)

最后,分析sub_45964。——3E29C、40CF8(与java层传入的arg5有关)、3DFC4、11F5C、45964。

做了一下简单的分析。

在RegisterNative_10item的函数中,注册了10个JNI函数,10个JNI函数全部注册到了1个Native地址上。

image-20250511215230452

10个函数全部注册到1个地址,说明是vmp。

image-20250511215317797

下图来自未解密的so文件,又有do-while没被检测出来。在函数sub_42D8C中,会对每个Dex文件去解析附加数据3。

image-20250511222646400

下图是每个Jar文件的内容。sub_42D8C负责处理其中的附加数据3。

image-20250511222818162

这里直接引用博主的分析,如下图所示。

image-20250511223003506

如何分析出来的呢?过几天来看看。

总结

至此,n001分析完了,总结一下。

sub_3E29C取出了签名数组,取了数组的第0个元素,通过md5得到了16个字节,再把这16字节扩展成了176字节。

sub_40cf8注册的函数异常处理有关,大致是负责异常处理。

sub_3DFC4创建了两个目录,分别是/data/user/0/com.example.test/.1和/data/user/0/com.example.test/.2,这两个目录应该是与dex、jar相关,为sub_11f5c服务的。——我发现了一些字符串,/data/data/包名/ == /data/user/0/包名。

image-20250511221807574

sub_11f5C hook了两个函数,一个是__android_log_print,一个是mmap,然后解密加载了业务dex文件(需要用到sub_3E29C签名扩展的176字节进行解密),加载完后,删除了mmap的hook。

sub_4596c注册了10个JNI函数,解析了vmp的方法数组和指令替换表。

n002

n002函数是在onCreate函数中调用的,也是启动app的流程必经的函数之一。

在n002中,又是调用函数列表上的函数,这回的a2==4。

通过分析,会有sub_ 40CF8、sub_ 3E96C、sub_ 42388这3个函数执行。

sub_ 40CF8调用CrashHandler.asynRun方法,向https://apkprotect.baidu.com/apklog发送统计信息。

sub_ 3E96C assets/baiduprotect.m检查dex的完整性,该文件中存有加密的dex MD5,如果修改dex进行重新签名,会导致app打不开。——应该还有对调试器的检测,因为使用frida和IDA的时候会异常退出。

sub_ 42388注册com.baidu.xshield.jni.Asc和com.baidu.xshield.utility.KeyUtil的本地函数,调用com.baidu.xshield.ac.XH.init方法。

至此,app启动流程涉及到的n001和n002分析完了。

vmp分析

先把dex dump下来,下面是脚本,hook sub_3BA90,在onLeave时进行dump。

脚本如下。

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
const TARGET_MODULE_NAME = "libbaiduprotect.so";

function hook_linker_call_constructors() {
let linker64_base_addr = Module.getBaseAddress('linker64');
let offset_call = 0x51BA8; // __dl__ZN6soinfo17call_constructorsEv
let call_constructors = linker64_base_addr.add(offset_call);
let listener = Interceptor.attach(call_constructors, {
onEnter: function(args) {
console.log('[linker] Call_Constructors onEnter');
let secmodule = Process.findModuleByName(TARGET_MODULE_NAME);
if (secmodule != null) {
hook_target_func(secmodule.base);
listener.detach();
}
}
});
}

function hook_target_func(baseaddr) {
let listener = Interceptor.attach(baseaddr.add(0x88060), {
onEnter: function(args) {},
onLeave: function(retval) {
// hook sub_3BA90
hook_3BA90(baseaddr);
listener.detach();
}
});
}

function hook_3BA90(baseaddr) {
Interceptor.attach(baseaddr.add(0x3BA90), {
onEnter: function(args) {
// 保存指针地址
this.dexPtr = args[2]; // &dex_ 的地址
this.sizePtr = args[3]; // &v6 的地址
console.log("[+] sub_3BA90 - dexPtr:", this.dexPtr, "sizePtr:", this.sizePtr);
console.log("[+] Call stack:\n", Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join("\n"));
},
onLeave: function(retval) {

// 读取 dex_ 和 v6 的值
this.ptr = this.dexPtr.readPointer(); // *dexPtr, 即 dex_ 的值
this.size = this.sizePtr.readU64().toNumber(); // *sizePtr, 即 v6 的值

console.log("[+] DEX dump - ptr:", this.ptr, "size:", this.size);

if (this.ptr && !this.ptr.isNull() && this.size > 0 && this.size < 0x10000000) { // 限制最大 256MB
// 修改内存权限
Memory.protect(this.ptr, this.size, 'rwx');
console.log("[+] 内存权限已修改为 rwx");

// 读取 DEX 文件内容
const dexData = Memory.readByteArray(this.ptr, this.size);
console.log("[+] 成功读取 DEX 数据");

// 生成唯一的文件名
const filename = `/data/data/com.example.test/dex_dump_${this.ptr.toString(16)}_${Date.now()}.dex`;

// 写入文件
const file = new File(filename, "wb");
if (file) {
file.write(dexData);
file.flush();
file.close();
console.log("[+] DEX 文件成功保存到:", filename);

} else {
console.error("[-] 无法打开文件:", filename);
}
} else {
console.warn("[-] 无效的 ptr 或 size:", this.ptr, this.size);
}

}
});
}

setImmediate(hook_linker_call_constructors);

打印的结果如下。

image-20250512102740207

这里图方便,分析onCreate函数(vmp化),需要判断它在哪个dex文件里。

通过这个指令:grep -r “MainActivity” ./dex_dump 可以找到。

image-20250512103001280

算了,直接将2个dex都pull出来,丢进行jadx里看看。

成功找到了类MainActivity,onCreate明显被vmp动过。

image-20250512103150789

-1426063360即为0xAB00 0000。

image-20250512103506171

来到之前注册V的函数的地方,函数列表很怪,按理来说,应该有5个参数,前2个是JNIEnv和jclass,然后是A.V传的3个参数。

image-20250512105550561

把签名改成我期待的样子。

image-20250512105617334

再看这个sub_4A458,很不正常,连传了3个BYTE(a3),还是看汇编吧。

image-20250512124624278

通过汇编的还原,调用的参数列表应该是这样子的。

image-20250512124116293

接下来,进入sub_4a458。看一眼就累了,很多函数的参数都是瞎传递的,ida没正确识别。

image-20250512131633429

试试用ida的trace,由于vmp这一块代码是加密的,要等到解密后才方便下断点。因此,还是使用frida进行trace吧。

但是frida trace受挫了,似乎有反调?我之前尝试使用jnitrace也是执行到特定时候就会退出。

之后尝试绕过。——hook pthread_create的线程函数,置为空,然后重新使用hook_libart.js来查看调用的JNI函数,看看会不会退出。

好消息,成功了,脚本如下。

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
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
var offset = 0x88060;
var module_size = 0;
var module_name = "libbaiduprotect.so";
var base_addr = null; // To store the base address of libbaiduprotect.so

// Create an empty function
var empty_func = new NativeCallback(function() {
return 0;
}, 'int', ['pointer']);

function hook_linker_call_constructors() {
let linker64_base_addr = Module.getBaseAddress('linker64');
let offset_call = 0x51BA8; // __dl__ZN6soinfo17call_constructorsEv
let call_constructors = linker64_base_addr.add(offset_call);
let listener = Interceptor.attach(call_constructors, {
onEnter: function(args) {
console.log('[linker] Call_Constructors onEnter');
let secmodule = Process.findModuleByName(module_name);
if (secmodule != null) {
module_size = secmodule.size;
base_addr = secmodule.base; // Save the base address
console.log("libbaiduprotect.so base: " + base_addr + ", size: " + module_size);
hook_target_func(base_addr);
listener.detach();
}
}
});
}

function hook_target_func(baseaddr) {
let target_addr = baseaddr.add(offset);
let listener = Interceptor.attach(target_addr, {
onEnter: function(args) {
console.log("Target function at " + target_addr + " entered");
},
onLeave: function(retval) {
console.log("libbaiduprotect.so base address: " + baseaddr);
hook_pthread_create();
listener.detach();
}
});
}

function hook_pthread_create() {
let pthread_create_addr = Module.getExportByName("libc.so", "pthread_create");
Interceptor.attach(pthread_create_addr, {
onEnter: function(args) {
let func_addr = args[2];
let target_func_addr = base_addr.add(0x3E9F0); // Calculate the target function address
if (func_addr.equals(target_func_addr)) {
console.log("Replacing func_addr " + func_addr + " with empty function");
args[2] = empty_func; // Replace with the empty function
hook_libart(); // Execute hook_libart
} else {
let module = Process.findModuleByAddress(func_addr);
if (module && module.name === module_name) {
console.log("pthread_create called in libbaiduprotect.so, function address: " + func_addr);
}
}
},
onLeave: function(retval) {
// Optionally log return value
}
});
}

const STD_STRING_SIZE = 3 * Process.pointerSize;
class StdString {
constructor() {
this.handle = Memory.alloc(STD_STRING_SIZE);
}

dispose() {
const [data, isTiny] = this._getData();
if (!isTiny) {
Java.api.$delete(data);
}
}

disposeToString() {
const result = this.toString();
this.dispose();
return result;
}

toString() {
const [data] = this._getData();
return data.readUtf8String();
}

_getData() {
const str = this.handle;
const isTiny = (str.readU8() & 1) === 0;
const data = isTiny ? str.add(1) : str.add(2 * Process.pointerSize).readPointer();
return [data, isTiny];
}
}

function prettyMethod(method_id, withSignature) {
const result = new StdString();
Java.api['art::ArtMethod::PrettyMethod'](result, method_id, withSignature ? 1 : 0);
return result.disposeToString();
}

function hook_libart() {
var symbols = Module.enumerateSymbolsSync("libart.so");
var addrGetStringUTFChars = null;
var addrNewStringUTF = null;
var addrFindClass = null;
var addrGetMethodID = null;
var addrGetStaticMethodID = null;
var addrGetFieldID = null;
var addrGetStaticFieldID = null;
var addrRegisterNatives = null;
var so_name = "lib"; // TODO: Specify the SO to filter

for (var i = 0; i < symbols.length; i++) {
var symbol = symbols[i];
if (symbol.name.indexOf("art") >= 0 &&
symbol.name.indexOf("JNI") >= 0 &&
symbol.name.indexOf("CheckJNI") < 0 &&
symbol.name.indexOf("_ZN3art3JNIILb0") >= 0
) {
if (symbol.name.indexOf("GetStringUTFChars") >= 0) {
addrGetStringUTFChars = symbol.address;
console.log("GetStringUTFChars is at ", symbol.address, symbol.name);
} else if (symbol.name.indexOf("NewStringUTF") >= 0) {
addrNewStringUTF = symbol.address;
console.log("NewStringUTF is at ", symbol.address, symbol.name);
} else if (symbol.name.indexOf("FindClass") >= 0) {
addrFindClass = symbol.address;
console.log("FindClass is at ", symbol.address, symbol.name);
} else if (symbol.name.indexOf("GetMethodID") >= 0) {
addrGetMethodID = symbol.address;
console.log("GetMethodID is at ", symbol.address, symbol.name);
} else if (symbol.name.indexOf("GetStaticMethodID") >= 0) {
addrGetStaticMethodID = symbol.address;
console.log("GetStaticMethodID is at ", symbol.address, symbol.name);
} else if (symbol.name.indexOf("GetFieldID") >= 0) {
addrGetFieldID = symbol.address;
console.log("GetFieldID is at ", symbol.address, symbol.name);
} else if (symbol.name.indexOf("GetStaticFieldID") >= 0) {
addrGetStaticFieldID = symbol.address;
console.log("GetStaticFieldID is at ", symbol.address, symbol.name);
} else if (symbol.name.indexOf("RegisterNatives") >= 0) {
addrRegisterNatives = symbol.address;
console.log("RegisterNatives is at ", symbol.address, symbol.name);
} else if (symbol.name.indexOf("CallStatic") >= 0) {
console.log("CallStatic is at ", symbol.address, symbol.name);
Interceptor.attach(symbol.address, {
onEnter: function (args) {
var module = Process.findModuleByAddress(this.returnAddress);
if (module != null && module.name.indexOf(so_name) == 0) {
var java_class = args[1];
var mid = args[2];
var class_name = Java.vm.tryGetEnv().getClassName(java_class);
if (class_name.indexOf("java.") == -1 && class_name.indexOf("android.") == -1) {
var method_name = prettyMethod(mid, 1);
console.log("<>CallStatic:", DebugSymbol.fromAddress(this.returnAddress), class_name, method_name);
}
}
},
onLeave: function (retval) { }
});
} else if (symbol.name.indexOf("CallNonvirtual") >= 0) {
console.log("CallNonvirtual is at ", symbol.address, symbol.name);
Interceptor.attach(symbol.address, {
onEnter: function (args) {
var module = Process.findModuleByAddress(this.returnAddress);
if (module != null && module.name.indexOf(so_name) == 0) {
var jobject = args[1];
var jclass = args[2];
var jmethodID = args[3];
var obj_class_name = Java.vm.tryGetEnv().getObjectClassName(jobject);
var class_name = Java.vm.tryGetEnv().getClassName(jclass);
if (class_name.indexOf("java.") == -1 && class_name.indexOf("android.") == -1) {
var method_name = prettyMethod(jmethodID, 1);
console.log("<>CallNonvirtual:", DebugSymbol.fromAddress(this.returnAddress), class_name, obj_class_name, method_name);
}
}
},
onLeave: function (retval) { }
});
} else if (symbol.name.indexOf("Call") >= 0 && symbol.name.indexOf("Method") >= 0) {
console.log("Call<>Method is at ", symbol.address, symbol.name);
Interceptor.attach(symbol.address, {
onEnter: function (args) {
var module = Process.findModuleByAddress(this.returnAddress);
if (module != null && module.name.indexOf(so_name) == 0) {
var java_class = args[1];
var mid = args[2];
var class_name = Java.vm.tryGetEnv().getObjectClassName(java_class);
if (class_name.indexOf("java.") == -1 && class_name.indexOf("android.") == -1) {
var method_name = prettyMethod(mid, 1);
console.log("<>Call<>Method:", DebugSymbol.fromAddress(this.returnAddress), class_name, method_name);
}
}
},
onLeave: function (retval) { }
});
}
}
}

if (addrGetStringUTFChars != null) {
Interceptor.attach(addrGetStringUTFChars, {
onEnter: function (args) {
},
onLeave: function (retval) {
if (retval != null) {
var module = Process.findModuleByAddress(this.returnAddress);
if (module != null && module.name.indexOf(so_name) == 0) {
var bytes = Memory.readCString(retval);
console.log("[GetStringUTFChars] result:" + bytes, DebugSymbol.fromAddress(this.returnAddress));
}
}
}
});
}
if (addrNewStringUTF != null) {
Interceptor.attach(addrNewStringUTF, {
onEnter: function (args) {
if (args[1] != null) {
var module = Process.findModuleByAddress(this.returnAddress);
if (module != null && module.name.indexOf(so_name) == 0) {
var string = Memory.readCString(args[1]);
console.log("[NewStringUTF] bytes:" + string, DebugSymbol.fromAddress(this.returnAddress));
}
}
},
onLeave: function (retval) { }
});
}

if (addrFindClass != null) {
Interceptor.attach(addrFindClass, {
onEnter: function (args) {
if (args[1] != null) {
var module = Process.findModuleByAddress(this.returnAddress);
if (module != null && module.name.indexOf(so_name) == 0) {
var name = Memory.readCString(args[1]);
console.log("[FindClass] name:" + name, DebugSymbol.fromAddress(this.returnAddress));
}
}
},
onLeave: function (retval) { }
});
}
if (addrGetMethodID != null) {
Interceptor.attach(addrGetMethodID, {
onEnter: function (args) {
if (args[2] != null) {
var clazz = args[1];
var class_name = Java.vm.tryGetEnv().getClassName(clazz);
var module = Process.findModuleByAddress(this.returnAddress);
if (module != null && module.name.indexOf(so_name) == 0) {
var name = Memory.readCString(args[2]);
if (args[3] != null) {
var sig = Memory.readCString(args[3]);
console.log("[GetMethodID] class_name:" + class_name + " name:" + name + ", sig:" + sig, DebugSymbol.fromAddress(this.returnAddress));
} else {
console.log("[GetMethodID] class_name:" + class_name + " name:" + name, DebugSymbol.fromAddress(this.returnAddress));
}
}
}
},
onLeave: function (retval) { }
});
}
if (addrGetStaticMethodID != null) {
Interceptor.attach(addrGetStaticMethodID, {
onEnter: function (args) {
if (args[2] != null) {
var clazz = args[1];
var class_name = Java.vm.tryGetEnv().getClassName(clazz);
var module = Process.findModuleByAddress(this.returnAddress);
if (module != null && module.name.indexOf(so_name) == 0) {
var name = Memory.readCString(args[2]);
if (args[3] != null) {
var sig = Memory.readCString(args[3]);
console.log("[GetStaticMethodID] class_name:" + class_name + " name:" + name + ", sig:" + sig, DebugSymbol.fromAddress(this.returnAddress));
} else {
console.log("[GetStaticMethodID] class_name:" + class_name + " name:" + name, DebugSymbol.fromAddress(this.returnAddress));
}
}
}
},
onLeave: function (retval) { }
});
}
if (addrGetFieldID != null) {
Interceptor.attach(addrGetFieldID, {
onEnter: function (args) {
if (args[2] != null) {
var module = Process.findModuleByAddress(this.returnAddress);
if (module != null && module.name.indexOf(so_name) == 0) {
var name = Memory.readCString(args[2]);
if (args[3] != null) {
var sig = Memory.readCString(args[3]);
console.log("[GetFieldID] name:" + name + ", sig:" + sig, DebugSymbol.fromAddress(this.returnAddress));
} else {
console.log("[GetFieldID] name:" + name, DebugSymbol.fromAddress(this.returnAddress));
}
}
}
},
onLeave: function (retval) { }
});
}
if (addrGetStaticFieldID != null) {
Interceptor.attach(addrGetStaticFieldID, {
onEnter: function (args) {
if (args[2] != null) {
var module = Process.findModuleByAddress(this.returnAddress);
if (module != null && module.name.indexOf(so_name) == 0) {
var name = Memory.readCString(args[2]);
if (args[3] != null) {
var sig = Memory.readCString(args[3]);
console.log("[GetStaticFieldID] name:" + name + ", sig:" + sig, DebugSymbol.fromAddress(this.returnAddress));
} else {
console.log("[GetStaticFieldID] name:" + name, DebugSymbol.fromAddress(this.returnAddress));
}
}
}
},
onLeave: function (retval) { }
});
}

if (addrRegisterNatives != null) {
Interceptor.attach(addrRegisterNatives, {
onEnter: function (args) {
console.log("[RegisterNatives] method_count:", args[3], DebugSymbol.fromAddress(this.returnAddress));
var env = args[0];
var java_class = args[1];
var class_name = Java.vm.tryGetEnv().getClassName(java_class);

var methods_ptr = ptr(args[2]);
var method_count = parseInt(args[3]);
for (var i = 0; i < method_count; i++) {
var name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));
var sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize));
var fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));

var name = Memory.readCString(name_ptr);
var sig = Memory.readCString(sig_ptr);
var find_module = Process.findModuleByAddress(fnPtr_ptr);
console.log("[RegisterNatives] java_class:", class_name, "name:", name, "sig:", sig, "fnPtr:", fnPtr_ptr, "module_name:", find_module.name, "module_base:", find_module.base, "offset:", ptr(fnPtr_ptr).sub(find_module.base));
}
},
onLeave: function (retval) { }
});
}
}

hook_linker_call_constructors();

成功打印出jni调用的函数,也就是说,离frida trace所有vmp的指令又进一步。

image-20250512194612007

暂时先到这吧,明天面携程,先复习一下加解密算法,之后继续复现vmp。

ok,继续分析,也不知道携程能不能有个善终——20250514 09:28。

我将jni调用的日志输出到文件中,慢慢看。

我们要分析的被vmp的函数是onCreate,不妨猜测它会调用super.onCreate,而要实现super.onCreate,不能只靠vmp的解释器进行解释执行,还需要用到jni调用,因此可以在日志中搜索onCreate,于是得到下面的日志。

于是,我们能确定了解释器是在哪里获取的onCreate的jmethod_id(0x5df18)和在哪里真正调用onCreate函数的(0x54370)。

1
2
3
[GetMethodID] class_name:androidx.appcompat.app.AppCompatActivity name:onCreate, sig:(Landroid/os/Bundle;)V 0x743fd08f18 libbaiduprotect.so!0x5df18
[GetMethodID] class_name:android.system.StructStatVfs name:<init>, sig:(JJJJJJJJJJJ)V 0x744a5310b4 libjavacore.so!0x310b4
<>CallNonvirtual: 0x743fcff370 libbaiduprotect.so!0x54370 androidx.appcompat.app.AppCompatActivity com.example.test.MainActivity void androidx.appcompat.app.AppCompatActivity.onCreate(android.os.Bundle)

不妨想想如何倒推——如果没有被vmp化,原onCreate中,大概率要执行super.onCreate,对应的smali指令,大概率是invoke-super,通过invoke-super调用父类的onCreate。

先尝试将指令trace下来吧,试了一下,即便先绕过反调,仍然无法trace下来,那是什么原因呢?

追踪指令流,发现执行到sub_45EBC后,程序便不会返回到BL sub_45EBC的下一条指令了,而sub_45EBC的最后一条指令是,将栈指针寄存器变成了0x1,难怪会退出——为什么使用frida-stalker之后,会出现这种问题呢?

1
mov wsp, #1

整理一下现有的信息,我知道:

  1. 哪里调用了onCreate。

  2. 执行sub_45EBC就会崩溃。

那我是否可以根据onCreate的交叉引用,一步一步回推是哪个跳转出现了问题,最后进入了sub_45EBC退出的。

已知0x5df18处会获取onCreate的method_id,而0x54370会调用onCreate,交叉引用一下这两个地址,发现两个函数都追踪到了.data节,除此之外,没有其它函数引用了它们。

把函数地址存放在.data节中,而且不只一个函数地址,大概率是通过BLR或BR指令跳转的。

BL和BLR的区别在,前者是通过PC相对偏移量跳转到目标函数在内存中的地址,而后者是跳转到寄存器中存储的地址,也就是说,BL的跳转地址基本是硬编码在指令里的,是编译时就确定的,而BLR可以根据寄存器的内容进行跳转,在运行时决定。这里这么多个地址放在一块,大概率在执行时要跳转很多个函数,所以使用有R的跳转指令。

这里继续交叉引用off_BD040。

image-20250514154101276

BR X8,符合上面的分析。

image-20250514155831323

回到函数列表中,突然发现它有256个函数地址,smali的操作码是1个字节,对应256种操作,这里会不会就是vmp解释器模拟smali指令的地方?读取解密后的vmp_code_item,然后根据vmp_code_item里的每一条指令,模拟执行对应的函数。

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
.data:00000000000BD040 58 CD 04 00 00 00 00 00 off_BD040       DCQ sub_4CD58           ; DATA XREF: sub_4CC20+C4↑o
.data:00000000000BD048 8C CD 04 00 00 00 00 00 DCQ sub_4CD8C
.data:00000000000BD050 E0 CD 04 00 00 00 00 00 DCQ sub_4CDE0
.data:00000000000BD058 34 CE 04 00 00 00 00 00 DCQ sub_4CE34
.data:00000000000BD060 84 CE 04 00 00 00 00 00 DCQ sub_4CE84
.data:00000000000BD068 D4 CE 04 00 00 00 00 00 DCQ sub_4CED4
.data:00000000000BD070 24 CF 04 00 00 00 00 00 DCQ sub_4CF24
.data:00000000000BD078 70 CF 04 00 00 00 00 00 DCQ sub_4CF70
.data:00000000000BD080 C0 CF 04 00 00 00 00 00 DCQ sub_4CFC0
.data:00000000000BD088 10 D0 04 00 00 00 00 00 DCQ sub_4D010
.data:00000000000BD090 5C D0 04 00 00 00 00 00 DCQ sub_4D05C
.data:00000000000BD098 C0 D0 04 00 00 00 00 00 DCQ sub_4D0C0
.data:00000000000BD0A0 2C D1 04 00 00 00 00 00 DCQ sub_4D12C
.data:00000000000BD0A8 98 D1 04 00 00 00 00 00 DCQ sub_4D198
.data:00000000000BD0B0 DC 46 05 00 00 00 00 00 DCQ loc_546DC ; jumptable 0000000000053D00 cases 69,71,72,75,77-82,84,85,87-89
.data:00000000000BD0B0 ; jumptable 0000000000053D50 cases 69,71,72,75,77-82,84,85,87-89
.data:00000000000BD0B0 ; jumptable 0000000000053DAC cases 69,71,72,75,77-82,84,85,87-89
.data:00000000000BD0B8 BC 46 05 00 00 00 00 00 DCQ loc_546BC
.data:00000000000BD0C0 14 D2 04 00 00 00 00 00 DCQ sub_4D214
.data:00000000000BD0C8 14 D2 04 00 00 00 00 00 DCQ sub_4D214
.data:00000000000BD0D0 34 D2 04 00 00 00 00 00 DCQ loc_4D234
.data:00000000000BD0D8 80 D2 04 00 00 00 00 00 DCQ loc_4D280
.data:00000000000BD0E0 D0 D2 04 00 00 00 00 00 DCQ loc_4D2D0
.data:00000000000BD0E8 24 D3 04 00 00 00 00 00 DCQ loc_4D324
.data:00000000000BD0F0 74 D3 04 00 00 00 00 00 DCQ loc_4D374
.data:00000000000BD0F8 C0 D3 04 00 00 00 00 00 DCQ loc_4D3C0
.data:00000000000BD100 18 D4 04 00 00 00 00 00 DCQ loc_4D418
.data:00000000000BD108 7C D4 04 00 00 00 00 00 DCQ loc_4D47C
.data:00000000000BD110 CC D4 04 00 00 00 00 00 DCQ loc_4D4CC
.data:00000000000BD118 44 D5 04 00 00 00 00 00 DCQ loc_4D544
.data:00000000000BD120 C4 D5 04 00 00 00 00 00 DCQ loc_4D5C4
.data:00000000000BD128 40 D6 04 00 00 00 00 00 DCQ loc_4D640
.data:00000000000BD130 A8 D6 04 00 00 00 00 00 DCQ loc_4D6A8
.data:00000000000BD138 24 D7 04 00 00 00 00 00 DCQ loc_4D724
.data:00000000000BD140 C4 D7 04 00 00 00 00 00 DCQ loc_4D7C4
.data:00000000000BD148 68 D8 04 00 00 00 00 00 DCQ loc_4D868
.data:00000000000BD150 E0 D8 04 00 00 00 00 00 DCQ loc_4D8E0
.data:00000000000BD158 C0 DA 04 00 00 00 00 00 DCQ loc_4DAC0
.data:00000000000BD160 90 33 05 00 00 00 00 00 DCQ loc_53390
.data:00000000000BD168 28 DC 04 00 00 00 00 00 DCQ loc_4DC28
.data:00000000000BD170 30 DC 04 00 00 00 00 00 DCQ loc_4DC30
.data:00000000000BD178 AC DC 04 00 00 00 00 00 DCQ loc_4DCAC
.data:00000000000BD180 E8 DC 04 00 00 00 00 00 DCQ loc_4DCE8
.data:00000000000BD188 24 DD 04 00 00 00 00 00 DCQ loc_4DD24
.data:00000000000BD190 60 DD 04 00 00 00 00 00 DCQ loc_4DD60
.data:00000000000BD198 A4 DD 04 00 00 00 00 00 DCQ loc_4DDA4
.data:00000000000BD1A0 24 DE 04 00 00 00 00 00 DCQ loc_4DE24
.data:00000000000BD1A8 A4 DE 04 00 00 00 00 00 DCQ loc_4DEA4
.data:00000000000BD1B0 30 DF 04 00 00 00 00 00 DCQ loc_4DF30
.data:00000000000BD1B8 B0 DF 04 00 00 00 00 00 DCQ loc_4DFB0
.data:00000000000BD1C0 34 E0 04 00 00 00 00 00 DCQ loc_4E034
.data:00000000000BD1C8 AC E0 04 00 00 00 00 00 DCQ loc_4E0AC
.data:00000000000BD1D0 28 E1 04 00 00 00 00 00 DCQ loc_4E128
.data:00000000000BD1D8 14 E2 04 00 00 00 00 00 DCQ loc_4E214
.data:00000000000BD1E0 00 E3 04 00 00 00 00 00 DCQ loc_4E300
.data:00000000000BD1E8 94 E3 04 00 00 00 00 00 DCQ loc_4E394
.data:00000000000BD1F0 28 E4 04 00 00 00 00 00 DCQ loc_4E428
.data:00000000000BD1F8 BC E4 04 00 00 00 00 00 DCQ loc_4E4BC
.data:00000000000BD200 50 E5 04 00 00 00 00 00 DCQ loc_4E550
.data:00000000000BD208 D4 E5 04 00 00 00 00 00 DCQ loc_4E5D4
.data:00000000000BD210 58 E6 04 00 00 00 00 00 DCQ loc_4E658
.data:00000000000BD218 E0 E6 04 00 00 00 00 00 DCQ loc_4E6E0
.data:00000000000BD220 68 E7 04 00 00 00 00 00 DCQ loc_4E768
.data:00000000000BD228 F0 E7 04 00 00 00 00 00 DCQ loc_4E7F0
.data:00000000000BD230 78 E8 04 00 00 00 00 00 DCQ loc_4E878
.data:00000000000BD238 78 E8 04 00 00 00 00 00 DCQ loc_4E878
.data:00000000000BD240 78 E8 04 00 00 00 00 00 DCQ loc_4E878
.data:00000000000BD248 78 E8 04 00 00 00 00 00 DCQ loc_4E878
.data:00000000000BD250 78 E8 04 00 00 00 00 00 DCQ loc_4E878
.data:00000000000BD258 78 E8 04 00 00 00 00 00 DCQ loc_4E878
.data:00000000000BD260 78 E8 04 00 00 00 00 00 DCQ loc_4E878
.data:00000000000BD268 A4 E9 04 00 00 00 00 00 DCQ loc_4E9A4
.data:00000000000BD270 A4 EA 04 00 00 00 00 00 DCQ loc_4EAA4
.data:00000000000BD278 30 EC 04 00 00 00 00 00 DCQ loc_4EC30
.data:00000000000BD280 1C ED 04 00 00 00 00 00 DCQ loc_4ED1C
.data:00000000000BD288 E0 ED 04 00 00 00 00 00 DCQ loc_4EDE0
.data:00000000000BD290 A0 EE 04 00 00 00 00 00 DCQ loc_4EEA0
.data:00000000000BD298 64 EF 04 00 00 00 00 00 DCQ loc_4EF64
.data:00000000000BD2A0 60 F0 04 00 00 00 00 00 DCQ loc_4F060
.data:00000000000BD2A8 50 F1 04 00 00 00 00 00 DCQ loc_4F150
.data:00000000000BD2B0 F4 F1 04 00 00 00 00 00 DCQ loc_4F1F4
.data:00000000000BD2B8 9C F2 04 00 00 00 00 00 DCQ loc_4F29C
.data:00000000000BD2C0 44 F3 04 00 00 00 00 00 DCQ loc_4F344
.data:00000000000BD2C8 EC F3 04 00 00 00 00 00 DCQ loc_4F3EC
.data:00000000000BD2D0 94 F4 04 00 00 00 00 00 DCQ loc_4F494
.data:00000000000BD2D8 6C F5 04 00 00 00 00 00 DCQ loc_4F56C
.data:00000000000BD2E0 30 F6 04 00 00 00 00 00 DCQ loc_4F630
.data:00000000000BD2E8 A4 F7 04 00 00 00 00 00 DCQ loc_4F7A4
.data:00000000000BD2F0 4C F8 04 00 00 00 00 00 DCQ loc_4F84C
.data:00000000000BD2F8 F8 F8 04 00 00 00 00 00 DCQ loc_4F8F8
.data:00000000000BD300 A0 F9 04 00 00 00 00 00 DCQ loc_4F9A0
.data:00000000000BD308 4C FA 04 00 00 00 00 00 DCQ loc_4FA4C
.data:00000000000BD310 0C FB 04 00 00 00 00 00 DCQ loc_4FB0C
.data:00000000000BD318 C8 FB 04 00 00 00 00 00 DCQ loc_4FBC8
.data:00000000000BD320 68 FC 04 00 00 00 00 00 DCQ loc_4FC68
.data:00000000000BD328 0C FD 04 00 00 00 00 00 DCQ loc_4FD0C
.data:00000000000BD330 B0 FD 04 00 00 00 00 00 DCQ loc_4FDB0
.data:00000000000BD338 54 FE 04 00 00 00 00 00 DCQ loc_4FE54
.data:00000000000BD340 F8 FE 04 00 00 00 00 00 DCQ loc_4FEF8
.data:00000000000BD348 C0 FF 04 00 00 00 00 00 DCQ loc_4FFC0
.data:00000000000BD350 78 00 05 00 00 00 00 00 DCQ loc_50078
.data:00000000000BD358 E8 01 05 00 00 00 00 00 DCQ loc_501E8
.data:00000000000BD360 88 02 05 00 00 00 00 00 DCQ loc_50288
.data:00000000000BD368 2C 03 05 00 00 00 00 00 DCQ loc_5032C
.data:00000000000BD370 CC 03 05 00 00 00 00 00 DCQ loc_503CC
.data:00000000000BD378 70 04 05 00 00 00 00 00 DCQ loc_50470
.data:00000000000BD380 24 05 05 00 00 00 00 00 DCQ loc_50524
.data:00000000000BD388 D4 05 05 00 00 00 00 00 DCQ loc_505D4
.data:00000000000BD390 6C 06 05 00 00 00 00 00 DCQ loc_5066C
.data:00000000000BD398 08 07 05 00 00 00 00 00 DCQ loc_50708
.data:00000000000BD3A0 A4 07 05 00 00 00 00 00 DCQ loc_507A4
.data:00000000000BD3A8 40 08 05 00 00 00 00 00 DCQ loc_50840
.data:00000000000BD3B0 74 37 05 00 00 00 00 00 DCQ loc_53774
.data:00000000000BD3B8 C4 37 05 00 00 00 00 00 DCQ loc_537C4
.data:00000000000BD3C0 9C 38 05 00 00 00 00 00 DCQ loc_5389C
.data:00000000000BD3C8 CC 39 05 00 00 00 00 00 DCQ loc_539CC
.data:00000000000BD3D0 44 38 05 00 00 00 00 00 DCQ loc_53844
.data:00000000000BD3D8 70 37 05 00 00 00 00 00 DCQ loc_53770
.data:00000000000BD3E0 70 37 05 00 00 00 00 00 DCQ loc_53770
.data:00000000000BD3E8 C0 37 05 00 00 00 00 00 DCQ loc_537C0
.data:00000000000BD3F0 DC 08 05 00 00 00 00 00 DCQ loc_508DC
.data:00000000000BD3F8 C8 39 05 00 00 00 00 00 DCQ loc_539C8
.data:00000000000BD400 40 38 05 00 00 00 00 00 DCQ loc_53840
.data:00000000000BD408 F4 08 05 00 00 00 00 00 DCQ loc_508F4
.data:00000000000BD410 F4 08 05 00 00 00 00 00 DCQ loc_508F4
.data:00000000000BD418 F4 08 05 00 00 00 00 00 DCQ loc_508F4
.data:00000000000BD420 4C 09 05 00 00 00 00 00 DCQ loc_5094C
.data:00000000000BD428 A4 09 05 00 00 00 00 00 DCQ loc_509A4
.data:00000000000BD430 F8 09 05 00 00 00 00 00 DCQ loc_509F8
.data:00000000000BD438 4C 0A 05 00 00 00 00 00 DCQ loc_50A4C
.data:00000000000BD440 AC 0A 05 00 00 00 00 00 DCQ loc_50AAC
.data:00000000000BD448 00 0B 05 00 00 00 00 00 DCQ loc_50B00
.data:00000000000BD450 54 0B 05 00 00 00 00 00 DCQ loc_50B54
.data:00000000000BD458 B4 0B 05 00 00 00 00 00 DCQ loc_50BB4
.data:00000000000BD460 10 0C 05 00 00 00 00 00 DCQ loc_50C10
.data:00000000000BD468 68 0C 05 00 00 00 00 00 DCQ loc_50C68
.data:00000000000BD470 C4 0C 05 00 00 00 00 00 DCQ loc_50CC4
.data:00000000000BD478 18 0D 05 00 00 00 00 00 DCQ loc_50D18
.data:00000000000BD480 B8 0D 05 00 00 00 00 00 DCQ loc_50DB8
.data:00000000000BD488 50 0E 05 00 00 00 00 00 DCQ loc_50E50
.data:00000000000BD490 A8 0E 05 00 00 00 00 00 DCQ loc_50EA8
.data:00000000000BD498 44 0F 05 00 00 00 00 00 DCQ loc_50F44
.data:00000000000BD4A0 D8 0F 05 00 00 00 00 00 DCQ loc_50FD8
.data:00000000000BD4A8 34 10 05 00 00 00 00 00 DCQ loc_51034
.data:00000000000BD4B0 88 10 05 00 00 00 00 00 DCQ loc_51088
.data:00000000000BD4B8 DC 10 05 00 00 00 00 00 DCQ loc_510DC
.data:00000000000BD4C0 30 11 05 00 00 00 00 00 DCQ loc_51130
.data:00000000000BD4C8 98 11 05 00 00 00 00 00 DCQ loc_51198
.data:00000000000BD4D0 00 12 05 00 00 00 00 00 DCQ loc_51200
.data:00000000000BD4D8 68 12 05 00 00 00 00 00 DCQ loc_51268
.data:00000000000BD4E0 0C 13 05 00 00 00 00 00 DCQ loc_5130C
.data:00000000000BD4E8 98 13 05 00 00 00 00 00 DCQ loc_51398
.data:00000000000BD4F0 00 14 05 00 00 00 00 00 DCQ loc_51400
.data:00000000000BD4F8 68 14 05 00 00 00 00 00 DCQ loc_51468
.data:00000000000BD500 D0 14 05 00 00 00 00 00 DCQ loc_514D0
.data:00000000000BD508 3C 15 05 00 00 00 00 00 DCQ loc_5153C
.data:00000000000BD510 A8 15 05 00 00 00 00 00 DCQ loc_515A8
.data:00000000000BD518 14 16 05 00 00 00 00 00 DCQ loc_51614
.data:00000000000BD520 74 16 05 00 00 00 00 00 DCQ loc_51674
.data:00000000000BD528 D4 16 05 00 00 00 00 00 DCQ loc_516D4
.data:00000000000BD530 34 17 05 00 00 00 00 00 DCQ loc_51734
.data:00000000000BD538 C4 17 05 00 00 00 00 00 DCQ loc_517C4
.data:00000000000BD540 48 18 05 00 00 00 00 00 DCQ loc_51848
.data:00000000000BD548 A8 18 05 00 00 00 00 00 DCQ loc_518A8
.data:00000000000BD550 08 19 05 00 00 00 00 00 DCQ loc_51908
.data:00000000000BD558 68 19 05 00 00 00 00 00 DCQ loc_51968
.data:00000000000BD560 D0 19 05 00 00 00 00 00 DCQ loc_519D0
.data:00000000000BD568 38 1A 05 00 00 00 00 00 DCQ loc_51A38
.data:00000000000BD570 A0 1A 05 00 00 00 00 00 DCQ loc_51AA0
.data:00000000000BD578 10 1B 05 00 00 00 00 00 DCQ loc_51B10
.data:00000000000BD580 80 1B 05 00 00 00 00 00 DCQ loc_51B80
.data:00000000000BD588 F0 1B 05 00 00 00 00 00 DCQ loc_51BF0
.data:00000000000BD590 60 1C 05 00 00 00 00 00 DCQ loc_51C60
.data:00000000000BD598 F0 1C 05 00 00 00 00 00 DCQ loc_51CF0
.data:00000000000BD5A0 50 1D 05 00 00 00 00 00 DCQ loc_51D50
.data:00000000000BD5A8 B0 1D 05 00 00 00 00 00 DCQ loc_51DB0
.data:00000000000BD5B0 10 1E 05 00 00 00 00 00 DCQ loc_51E10
.data:00000000000BD5B8 70 1E 05 00 00 00 00 00 DCQ loc_51E70
.data:00000000000BD5C0 F0 1E 05 00 00 00 00 00 DCQ loc_51EF0
.data:00000000000BD5C8 50 1F 05 00 00 00 00 00 DCQ loc_51F50
.data:00000000000BD5D0 B0 1F 05 00 00 00 00 00 DCQ loc_51FB0
.data:00000000000BD5D8 10 20 05 00 00 00 00 00 DCQ loc_52010
.data:00000000000BD5E0 A0 20 05 00 00 00 00 00 DCQ loc_520A0
.data:00000000000BD5E8 24 21 05 00 00 00 00 00 DCQ loc_52124
.data:00000000000BD5F0 84 21 05 00 00 00 00 00 DCQ loc_52184
.data:00000000000BD5F8 E4 21 05 00 00 00 00 00 DCQ loc_521E4
.data:00000000000BD600 44 22 05 00 00 00 00 00 DCQ loc_52244
.data:00000000000BD608 A8 22 05 00 00 00 00 00 DCQ loc_522A8
.data:00000000000BD610 0C 23 05 00 00 00 00 00 DCQ loc_5230C
.data:00000000000BD618 70 23 05 00 00 00 00 00 DCQ loc_52370
.data:00000000000BD620 CC 23 05 00 00 00 00 00 DCQ loc_523CC
.data:00000000000BD628 28 24 05 00 00 00 00 00 DCQ loc_52428
.data:00000000000BD630 84 24 05 00 00 00 00 00 DCQ loc_52484
.data:00000000000BD638 0C 25 05 00 00 00 00 00 DCQ loc_5250C
.data:00000000000BD640 88 25 05 00 00 00 00 00 DCQ loc_52588
.data:00000000000BD648 E4 25 05 00 00 00 00 00 DCQ loc_525E4
.data:00000000000BD650 40 26 05 00 00 00 00 00 DCQ loc_52640
.data:00000000000BD658 9C 26 05 00 00 00 00 00 DCQ loc_5269C
.data:00000000000BD660 00 27 05 00 00 00 00 00 DCQ loc_52700
.data:00000000000BD668 64 27 05 00 00 00 00 00 DCQ loc_52764
.data:00000000000BD670 C8 27 05 00 00 00 00 00 DCQ loc_527C8
.data:00000000000BD678 2C 28 05 00 00 00 00 00 DCQ loc_5282C
.data:00000000000BD680 90 28 05 00 00 00 00 00 DCQ loc_52890
.data:00000000000BD688 F4 28 05 00 00 00 00 00 DCQ loc_528F4
.data:00000000000BD690 58 29 05 00 00 00 00 00 DCQ loc_52958
.data:00000000000BD698 DC 29 05 00 00 00 00 00 DCQ loc_529DC
.data:00000000000BD6A0 38 2A 05 00 00 00 00 00 DCQ loc_52A38
.data:00000000000BD6A8 94 2A 05 00 00 00 00 00 DCQ loc_52A94
.data:00000000000BD6B0 F0 2A 05 00 00 00 00 00 DCQ loc_52AF0
.data:00000000000BD6B8 4C 2B 05 00 00 00 00 00 DCQ loc_52B4C
.data:00000000000BD6C0 C8 2B 05 00 00 00 00 00 DCQ loc_52BC8
.data:00000000000BD6C8 24 2C 05 00 00 00 00 00 DCQ loc_52C24
.data:00000000000BD6D0 80 2C 05 00 00 00 00 00 DCQ loc_52C80
.data:00000000000BD6D8 DC 2C 05 00 00 00 00 00 DCQ loc_52CDC
.data:00000000000BD6E0 70 2D 05 00 00 00 00 00 DCQ loc_52D70
.data:00000000000BD6E8 F8 2D 05 00 00 00 00 00 DCQ loc_52DF8
.data:00000000000BD6F0 54 2E 05 00 00 00 00 00 DCQ loc_52E54
.data:00000000000BD6F8 B0 2E 05 00 00 00 00 00 DCQ loc_52EB0
.data:00000000000BD700 0C 2F 05 00 00 00 00 00 DCQ loc_52F0C
.data:00000000000BD708 6C 2F 05 00 00 00 00 00 DCQ loc_52F6C
.data:00000000000BD710 D0 2F 05 00 00 00 00 00 DCQ loc_52FD0
.data:00000000000BD718 34 30 05 00 00 00 00 00 DCQ loc_53034
.data:00000000000BD720 C8 30 05 00 00 00 00 00 DCQ loc_530C8
.data:00000000000BD728 50 31 05 00 00 00 00 00 DCQ loc_53150
.data:00000000000BD730 B0 31 05 00 00 00 00 00 DCQ loc_531B0
.data:00000000000BD738 10 32 05 00 00 00 00 00 DCQ loc_53210
.data:00000000000BD740 70 32 05 00 00 00 00 00 DCQ loc_53270
.data:00000000000BD748 D0 32 05 00 00 00 00 00 DCQ loc_532D0
.data:00000000000BD750 30 33 05 00 00 00 00 00 DCQ loc_53330
.data:00000000000BD758 98 33 05 00 00 00 00 00 DCQ loc_53398
.data:00000000000BD760 98 33 05 00 00 00 00 00 DCQ loc_53398
.data:00000000000BD768 98 33 05 00 00 00 00 00 DCQ loc_53398
.data:00000000000BD770 98 33 05 00 00 00 00 00 DCQ loc_53398
.data:00000000000BD778 98 33 05 00 00 00 00 00 DCQ loc_53398
.data:00000000000BD780 98 33 05 00 00 00 00 00 DCQ loc_53398
.data:00000000000BD788 98 33 05 00 00 00 00 00 DCQ loc_53398
.data:00000000000BD790 98 33 05 00 00 00 00 00 DCQ loc_53398
.data:00000000000BD798 98 33 05 00 00 00 00 00 DCQ loc_53398
.data:00000000000BD7A0 98 33 05 00 00 00 00 00 DCQ loc_53398
.data:00000000000BD7A8 98 33 05 00 00 00 00 00 DCQ loc_53398
.data:00000000000BD7B0 98 33 05 00 00 00 00 00 DCQ loc_53398
.data:00000000000BD7B8 98 33 05 00 00 00 00 00 DCQ loc_53398
.data:00000000000BD7C0 98 33 05 00 00 00 00 00 DCQ loc_53398
.data:00000000000BD7C8 98 33 05 00 00 00 00 00 DCQ loc_53398
.data:00000000000BD7D0 98 33 05 00 00 00 00 00 DCQ loc_53398
.data:00000000000BD7D8 98 33 05 00 00 00 00 00 DCQ loc_53398
.data:00000000000BD7E0 98 33 05 00 00 00 00 00 DCQ loc_53398
.data:00000000000BD7E8 98 33 05 00 00 00 00 00 DCQ loc_53398
.data:00000000000BD7F0 98 33 05 00 00 00 00 00 DCQ loc_53398
.data:00000000000BD7F8 98 33 05 00 00 00 00 00 DCQ loc_53398
.data:00000000000BD800 98 33 05 00 00 00 00 00 DCQ loc_53398
.data:00000000000BD808 98 33 05 00 00 00 00 00 DCQ loc_53398
.data:00000000000BD810 98 33 05 00 00 00 00 00 DCQ loc_53398
.data:00000000000BD818 98 33 05 00 00 00 00 00 DCQ loc_53398
.data:00000000000BD820 98 33 05 00 00 00 00 00 DCQ loc_53398
.data:00000000000BD828 98 33 05 00 00 00 00 00 DCQ loc_53398
.data:00000000000BD830 98 33 05 00 00 00 00 00 DCQ loc_53398
.data:00000000000BD838 98 33 05 00 00 00 00 00 DCQ loc_53398

继续交叉引用sub_4CC20,来到了关键的地方,基本可以确认sub_4CC20就是解释器了。

image-20250514160856292

hook了一些libc的open、strstr、read、strcmp之类的函数,并没有发现对frida的检测,好奇怪,那为什么会执行到sub_45EBC就崩溃呢?

对着sub_45EBC进行分析,发现一个比较关键的地方。

image-20250514210759485

这个时候,可以打开我们之前trace的汇编指令,究竟是谁让w8变成了0。从0x4ad0c往上找,关于x8/w8的赋值。

image-20250514211854347

很容易就找到,x11存放了一个地址,而x10的值为0,因此w8变成了0,继续追踪x10/x11的含义。

image-20250514212027583

可以根据减去基址,看看x8和x0的含义,基址是0x743fc99000。

x0对应0x2F38BFB0,x8对应0x2D40FEC78,看来调用的不是libbaiduprotect.so的函数,应该是别的库,所以减错了基址。

我在ida中还原过这个地方,x8是malloc的运行地址,所以这里的x0是分配到的空间地址,和我想得不太一样啊?

image-20250514212503520

这说明,进入sub_45EBC函数后,应该是能正常出来的。

其实仔细观察,为什么会发生崩溃呢?MOV WSP, #1虽然将栈顶寄存器指向了一个会奇怪的地方,但下一条指令很快的覆盖了SP的内容,因此应该不会造成崩溃,那问题只能处在frida-stalker了,它是不是在插桩的时候用到了SP?

image-20250514222207967

我们来看一下frida-stalker的原理。

image-20250514222454544

离我们的猜想很近,如果插桩的黄色代码中,使用到了sp寄存器,就会把程序弄崩溃,我们试试能不能在识别到MOV WSP, #1时候将它NOP掉,不执行,避免产生这样的问题。

代码如下,下面这个代码可以绕过这一条指令,继续打印追踪的流程。

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
303
304
var offset = 0x88060;
var module_size = 0;
var module_name = "libbaiduprotect.so";
var base_addr = null; // To store the base address of libbaiduprotect.so

// Create an empty function
var empty_func = new NativeCallback(function() {
// console.log("[Anti-Debug] Empty function executed."); // 可选日志
return 0;
}, 'int', ['pointer']);

function hook_linker_call_constructors() {
// 为提高健壮性,建议使用更完善的 linker 及 call_constructors 查找逻辑
// 但为保持与你原始脚本的结构,暂时保留这种直接方式
let linker_module_name = Process.arch === 'arm64' ? 'linker64' : 'linker';
let linker64_base_addr = Module.getBaseAddress(linker_module_name);
if (!linker64_base_addr) {
console.warn("[-] Failed to get " + linker_module_name + " base address. Trying to find module directly later.");
// Fallback if linker not found
setTimeout(function() {
if (!base_addr) { // Check if already found
let secmodule = Process.findModuleByName(module_name);
if (secmodule) {
base_addr = secmodule.base;
module_size = secmodule.size;
console.log(module_name + " base (fallback): " + base_addr + ", size: " + module_size);
hook_target_func(base_addr);
} else {
console.error("[-] " + module_name + " not found via fallback. Cannot proceed.");
}
}
}, 1500); // Delay slightly
return;
}

let offset_call = 0x51BA8; // __dl__ZN6soinfo17call_constructorsEv - 这个偏移非常依赖系统
let call_constructors = linker64_base_addr.add(offset_call);
console.log("[i] Attempting to attach to linker's call_constructors at " + call_constructors);

try {
let listener = Interceptor.attach(call_constructors, {
onEnter: function(args) {
// console.log('[linker] Call_Constructors onEnter');
let secmodule = Process.findModuleByName(module_name);
if (secmodule != null) {
if (base_addr === null) { // 确保只初始化一次
module_size = secmodule.size;
base_addr = secmodule.base; // Save the base address
console.log(module_name + " base: " + base_addr + ", size: " + module_size);

// *** 在这里也可以考虑直接打补丁已知崩溃点, 但Stalker内处理更灵活些 ***
// patchInstructionToNop(base_addr, 0x48AC0, 4);
// patchInstructionToNop(base_addr, 0x4ABB8, 4);

hook_target_func(base_addr);
listener.detach();
console.log("[i] Detached from linker hook.");
}
}
}
});
} catch (e) {
console.error("[-] Failed to attach to linker's call_constructors: " + e.message);
console.log(" Will rely on fallback to find module " + module_name);
setTimeout(function() { // 与上面的 fallback 逻辑类似
if (!base_addr) {
let secmodule = Process.findModuleByName(module_name);
if (secmodule) {
base_addr = secmodule.base;
module_size = secmodule.size;
console.log(module_name + " base (fallback after attach error): " + base_addr + ", size: " + module_size);
hook_target_func(base_addr);
} else {
console.error("[-] Fallback after attach error: " + module_name + " not found.");
}
}
}, 1500);
}
}

function hook_target_func(current_base_addr) { // 使用 current_base_addr 区分全局 base_addr
if (!current_base_addr || current_base_addr.isNull()){
console.error("[-] hook_target_func: current_base_addr is invalid.");
return;
}
let target_addr = current_base_addr.add(offset); // offset = 0x88060
console.log("[+] Hooking target function at " + target_addr + " (offset 0x" + offset.toString(16) + ")");
try {
let listener = Interceptor.attach(target_addr, {
onEnter: function(args) {
console.log("Target function " + module_name + "!0x" + offset.toString(16) + " entered. Called from: " + this.returnAddress);
},
onLeave: function(retval) {
console.log(module_name + "!0x" + offset.toString(16) + " onLeave. Base address: " + current_base_addr);
if (base_addr && !base_addr.isNull()) { // 确保全局base_addr有效
hook_pthread_create();
StalkerTrace(base_addr); // 传递正确的模块基址给StalkerTrace
} else {
console.error("[-] Global base_addr not set in hook_target_func onLeave. Cannot start Stalker / pthread hook.");
}
listener.detach();
console.log("[i] Detached from target_func hook at " + target_addr);
}
});
} catch (e) {
console.error("[-] Failed to attach to target_func at " + target_addr + ": " + e.message);
}
}

function hook_pthread_create() {
if (!base_addr || base_addr.isNull()) {
console.error("[-] hook_pthread_create: Global base_addr is null.");
return;
}
let pthread_create_addr = Module.findExportByName("libc.so", "pthread_create");
if (pthread_create_addr) {
console.log("[+] Hooking pthread_create at " + pthread_create_addr);
try {
Interceptor.attach(pthread_create_addr, {
onEnter: function(args) {
let func_addr = args[2];
let target_func_addr = base_addr.add(0x3E9F0);
if (func_addr.equals(target_func_addr)) {
console.log("[Anti-Debug] Replacing thread start_routine " + func_addr + " with empty function.");
args[2] = empty_func;
}
}
});
} catch (e) {
console.error("[-] Failed to attach to pthread_create: " + e.message);
}
} else {
console.warn("[-] pthread_create not found in libc.so. Hook skipped.");
}
}

const TARGET_MODULE_NAME = "libbaiduprotect.so"; // 等同于 module_name

function get_diff_regs(context, pre_regs) {
var diff_regs = {};
const current_regs_snapshot = JSON.parse(JSON.stringify(context));
for (const reg_name_orig in current_regs_snapshot) {
if (current_regs_snapshot.hasOwnProperty(reg_name_orig)) {
const reg_name = reg_name_orig.toLowerCase();
if (reg_name !== "pc" && !reg_name.startsWith("q") && reg_name !== "sp") { // 也可以过滤掉sp
if (pre_regs[reg_name_orig] !== current_regs_snapshot[reg_name_orig]) {
pre_regs[reg_name_orig] = current_regs_snapshot[reg_name_orig];
diff_regs[reg_name_orig] = current_regs_snapshot[reg_name_orig];
}
}
}
}
return diff_regs;
}

function get_involved_regs(instruction) { /* ... (保持不变) ... */
const involved_regs = new Set();
instruction.operands.forEach(op => {
if (op.type === 'reg') { involved_regs.add(op.value); }
else if (op.type === 'mem') {
if (op.value.base) { involved_regs.add(op.value.base); }
if (op.value.index) { involved_regs.add(op.value.index); }
}
});
return Array.from(involved_regs);
}

// =======================================================================================
// 修改 StalkerTrace 函数
// =======================================================================================
function StalkerTrace(current_module_baseaddr) { // 确保传入的是正确的模块基地址
var stalker_target_func_offset = 0x4a458; // 这是你希望Stalker开始追踪的函数偏移
var sub_42598_addr = current_module_baseaddr.add(stalker_target_func_offset);
console.log("[+] StalkerTrace will target " + TARGET_MODULE_NAME + "!" + ptr(stalker_target_func_offset) + " at actual address: " + sub_42598_addr);

var module_stalk_target = Process.findModuleByAddress(sub_42598_addr);
if (!module_stalk_target || module_stalk_target.name !== TARGET_MODULE_NAME) {
console.error("[-] StalkerTrace: Module for target function (" + sub_42598_addr + ") is not " + TARGET_MODULE_NAME +
". Found: " + (module_stalk_target ? module_stalk_target.name : "none") +
". Ensure base address and offset are correct. Stalker might not work as expected.");
// 如果模块名不匹配,我们可能不应该继续,或者用传入的 baseaddr 作为module_start/end的依据
if (!current_module_baseaddr || current_module_baseaddr.isNull()) {
console.error("[-] StalkerTrace: No valid base address to proceed with module bounds.");
return;
}
module_stalk_target = { base: current_module_baseaddr, size: module_size, name: TARGET_MODULE_NAME }; // module_size 是全局的
console.warn("[-] StalkerTrace: Using passed base_addr for module bounds for Stalker.");
}

const module_start = module_stalk_target.base;
const module_end = module_stalk_target.base.add(module_stalk_target.size); // Stalker只记录此模块内的指令
console.log("[i] Stalker module bounds: Start=" + module_start + ", End=" + module_end);


Interceptor.attach(sub_42598_addr, {
onEnter: function (args) {
this.tid = Process.getCurrentThreadId();
console.log("[+] Stalker: Entered " + TARGET_MODULE_NAME + "!" + ptr(stalker_target_func_offset) + " (TID: " + this.tid + ")");

const local_pre_regs = {};
const initial_context_snapshot = JSON.parse(JSON.stringify(this.context));
for (const reg_name_orig in initial_context_snapshot) {
if (initial_context_snapshot.hasOwnProperty(reg_name_orig)) {
const reg_name = reg_name_orig.toLowerCase();
if (reg_name !== "pc" && !reg_name.startsWith("q")) {
local_pre_regs[reg_name_orig] = initial_context_snapshot[reg_name_orig];
}
}
}

// === 新增:用于直接patch的已知崩溃点 ===
// 这些地址相对于 current_module_baseaddr (即 libbaiduprotect.so 的基地址)
const knownCrashOffsets = [0x48AC0, 0x4ABB8]; // 把已知的都列出来
knownCrashOffsets.forEach(offset => {
try {
const patchAddr = current_module_baseaddr.add(offset);
// 读取指令,确认是否是 MOV WSP, #1
const instrBytes = patchAddr.readByteArray(4);
// MOV WSP, #1 (MOVZ W31, #1, LSL #0) -> FF 03 00 32 (Little Endian: 320003FF)
if (instrBytes && instrBytes[0] === 0x32 && instrBytes[1] === 0x00 && instrBytes[2] === 0x03 && instrBytes[3] === 0xFF) {
Memory.patchCode(patchAddr, 4, code => {
const writer = new Arm64Writer(code, { pc: patchAddr });
writer.putNop();
writer.flush();
});
console.log("[MemoryPatch] Stalker's onEnter: NOPped known crash (MOV WSP, #1) at " + module_name + "!" + ptr(offset) + " ("+ patchAddr + ")");
} else {
// console.log("[MemoryPatch] Stalker's onEnter: Instruction at " + module_name + "!" + ptr(offset) + " is not MOV WSP, #1. Bytes: " + Array.from(new Uint8Array(instrBytes)).map(b => b.toString(16).padStart(2,'0')).join(''));
}
} catch (e) {
console.warn("[MemoryPatch] Stalker's onEnter: Error patching " + module_name + "!" + ptr(offset) + ": " + e.message);
}
});
// =====================================

Stalker.follow(this.tid, {
events: { call: false, ret: false, exec: true, block: false, compile: false },
transform(iterator) {
let instruction = iterator.next();
do {
const currentAddress = instruction.address;
let patchedThisInstruction = false;

// 通用的 MOV WSP/SP, #1 检测和NOP逻辑 (作为补充)
const mnemonic = instruction.mnemonic.toLowerCase();
if ((mnemonic === 'mov' || mnemonic === 'movz') && instruction.operands.length >= 2) {
const destOperand = instruction.operands[0];
const srcOperand = instruction.operands[1];

if (destOperand.type === 'reg' &&
(destOperand.value === 'wsp' || destOperand.value === 'sp' ||
destOperand.value === 'w31' || destOperand.value === 'x31') &&
srcOperand.type === 'imm' &&
parseInt(srcOperand.value.toString()) === 1) {

console.warn("[StalkerTransform] Identified '" + instruction.toString() + "' at " + currentAddress + ". Replacing with NOP.");
iterator.putNop();
patchedThisInstruction = true;
}
}

if (!patchedThisInstruction) {
// 仅在模块代码范围内执行putCallout,减少日志量并避免干扰其他模块
const is_module_code = currentAddress.compare(module_start) >= 0 &&
currentAddress.compare(module_end) < 0;
if (is_module_code) {
iterator.putCallout(function (context) {
var pc = context.pc;
// module_start, module_end, TARGET_MODULE_NAME, get_diff_regs, local_pre_regs
// 这些变量需要能被这个闭包访问到。local_pre_regs 是 onEnter 作用域的。
// module_start 等可以考虑从外部作用域传入或再次获取。
// 为简化,假设它们可访问。
var current_callout_module = Process.findModuleByAddress(pc);
if (current_callout_module && current_callout_module.name === TARGET_MODULE_NAME) {
const instrToLog = Instruction.parse(pc);
const diff_regs = get_diff_regs(context, local_pre_regs);
console.log(
current_callout_module.name + "!" + pc.sub(current_callout_module.base),
instrToLog.toString(),
"\t\t", JSON.stringify(diff_regs)
);
}
});
}
iterator.keep();
}
} while ((instruction = iterator.next()) !== null);
}
});
},
onLeave: function (retval) {
console.log("[+] " + TARGET_MODULE_NAME + "!" + ptr(stalker_target_func_offset) + " onLeave, TID:", this.tid);
Stalker.unfollow(this.tid); // 在函数退出时明确停止Stalker是个好习惯
Stalker.garbageCollect(); // 回收Stalker资源
}
});
}

setImmediate(function() {
console.log("[i] Script starting for " + module_name + "...");
hook_linker_call_constructors();
});

console.log("[i] Script loaded. Waiting for " + module_name + "...");

听说frida-sktrace实现了字符串(连续有4个可显示的字符时,打印字符串)的打印,而且更美观一些,这里试试使用frida-sktrace,但似乎不支持spawn模式附加进程,而且hook的时机还得修改,我决定在我原有的代码上添加这个功能。——我改成了,存在连续3个可视字符时,把指针内容当成字符串地址。

下面是让大模型帮我写的代码,不过也太长了吧。

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
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
// =======================================================================================
// Modified StalkerTrace Function (FIXED local_pre_regs access)
// =======================================================================================
function StalkerTrace(current_module_baseaddr) { // Ensure the correct module base address is passed
// Use the global module_size which should be set by now
if (!current_module_baseaddr || current_module_baseaddr.isNull() || module_size === 0) {
console.error("[-] StalkerTrace: Invalid module base address or size. Cannot proceed.");
return;
}

var stalker_target_func_offset = 0x4a458; // The offset of the function you want Stalker to trace
var stalker_start_addr = current_module_baseaddr.add(stalker_target_func_offset);
console.log("[+] StalkerTrace will target " + TARGET_MODULE_NAME + "!" + ptr(stalker_target_func_offset) + " at actual address: " + stalker_start_addr);

// Define the module bounds for Stalker logging
const module_start = current_module_baseaddr;
const module_end = current_module_baseaddr.add(module_size);
console.log("[i] Stalker module bounds: Start=" + module_start + ", End=" + module_end);

Interceptor.attach(stalker_start_addr, {
onEnter: function (args) {
// Stalker operates per thread. Get the current thread ID.
this.tid = Process.getCurrentThreadId();
console.log("[+] Stalker: Entered " + TARGET_MODULE_NAME + "!" + ptr(stalker_target_func_offset) + " (TID: " + this.tid + ")");

// Initialize previous register state *for this specific thread*.
// This variable is local to this onEnter call but will be captured by the closure.
const local_pre_regs = {};
// Populate initial state, excluding filtered registers
const initial_context_snapshot = JSON.parse(JSON.stringify(this.context));
for (const reg_name_orig in initial_context_snapshot) {
if (initial_context_snapshot.hasOwnProperty(reg_name_orig)) {
const reg_name_lower = reg_name_orig.toLowerCase();
if (reg_name_lower !== "pc" && reg_name_lower !== "sp" && !reg_name_lower.startsWith("q") && !reg_name_lower.startsWith("v")) {
local_pre_regs[reg_name_orig] = initial_context_snapshot[reg_name_orig];
}
}
}
// *** REMOVE THIS LINE *** this.local_pre_regs = local_pre_regs;
// The variable 'local_pre_regs' is already in scope for the transform/callout closures.


// === Patch known crashing instructions on entering the traced function ===
// These addresses are relative to the module base address (current_module_baseaddr)
const knownCrashOffsets = [0x48AC0, 0x4ABB8]; // List of known problematic instruction offsets
knownCrashOffsets.forEach(offset => {
try {
const patchAddr = current_module_baseaddr.add(offset);
// Read instruction bytes to verify it's the expected 'MOV WSP, #1'
// ARM64: MOV WSP, #1 is 320003FF (little endian)
const expectedBytes = new Uint8Array([0x32, 0x00, 0x03, 0xFF]);
const instrBytes = patchAddr.readByteArray(4);
const actualBytes = new Uint8Array(instrBytes);

let matchesExpected = true;
if (actualBytes.length === 4) {
for(let i=0; i<4; i++) {
if (actualBytes[i] !== expectedBytes[i]) {
matchesExpected = false;
break;
}
}
} else {
matchesExpected = false;
}

if (matchesExpected) {
// If it matches, NOP it
Memory.patchCode(patchAddr, 4, code => {
const writer = new Arm64Writer(code, { pc: patchAddr });
writer.putNop(); // Replace the instruction with NOP
writer.flush();
});
console.log("[MemoryPatch] Stalker's onEnter: NOPped known crash (MOV WSP, #1) at " + module_name + "!" + ptr(offset) + " ("+ patchAddr + ")");
} else {
// console.log("[MemoryPatch] Stalker's onEnter: Instruction at " + module_name + "!" + ptr(offset) + " is not MOV WSP, #1. Bytes: " + Array.from(actualBytes).map(b => b.toString(16).padStart(2,'0')).join('')); // Optional log
}
} catch (e) {
console.warn("[MemoryPatch] Stalker's onEnter: Error patching " + module_name + "!" + ptr(offset) + ": " + e.message);
}
});
// ==================================================================

Stalker.follow(this.tid, {
// Configure events to trace. exec=true traces every executed instruction.
events: { call: false, ret: false, exec: true, block: false, compile: false },
// The transform callback allows inspecting/rewriting instruction blocks
transform(iterator) {
let instruction = iterator.next();
do {
const currentAddress = instruction.address;
let patchedThisInstruction = false;

// Check if the instruction is within the target module's bounds
const is_module_code = currentAddress.compare(module_start) >= 0 &&
currentAddress.compare(module_end) < 0;

// === Universal MOV WSP/SP, #1 detection and NOP logic ===
// This is a fallback/alternative to the onEnter patch
// It happens per block compiled by Stalker
if (is_module_code) { // Only patch instructions within the target module
const mnemonic = instruction.mnemonic.toLowerCase();
// Check for MOV or MOVZ with SP/WSP as destination and immediate 1
if ((mnemonic === 'mov' || mnemonic === 'movz') && instruction.operands.length >= 2) {
const destOperand = instruction.operands[0];
const srcOperand = instruction.operands[1];

if (destOperand.type === 'reg' &&
(destOperand.value === 'wsp' || destOperand.value === 'sp' ||
destOperand.value === 'w31' || destOperand.value === 'x31') && // w31/x31 are aliases for wsp/sp
srcOperand.type === 'imm' &&
parseInt(srcOperand.value.toString()) === 1) {

console.warn("[StalkerTransform] Identified potential crash instruction '" + instruction.toString() + "' at " + currentAddress + ". Replacing with NOP.");
iterator.putNop(); // Replace the instruction with NOP
patchedThisInstruction = true;
}
}
}
// =========================================================

// If the instruction wasn't NOPped by the above check, add a callout for logging
if (!patchedThisInstruction) {
// Only add callout for instructions within the target module
if (is_module_code) {
// 'local_pre_regs' is accessible here via closure
iterator.putCallout(function (context) {
// Access local_pre_regs directly from the outer scope (captured by closure)
const thread_pre_regs = local_pre_regs;
const pc = context.pc;

// Get the register differences after this instruction executes
// get_diff_regs modifies thread_pre_regs in place
const diff_regs = get_diff_regs(context, thread_pre_regs);

// Find the module info again (can be optimized by caching, but this is reliable)
var current_callout_module = Process.findModuleByAddress(pc);

// Ensure we are logging code from the target module
if (current_callout_module && current_callout_module.name === TARGET_MODULE_NAME) {
const instrToLog = Instruction.parse(pc);
let logLine = `${current_callout_module.name}!${pc.sub(current_callout_module.base)} ${instrToLog.toString()}`;

// Append changed registers and potential strings
const diffRegKeys = Object.keys(diff_regs);
if (diffRegKeys.length > 0) {
logLine += `\t\t| Changed Regs: `;
let firstReg = true;
for (const regName of diffRegKeys) {
if (!firstReg) logLine += ", ";
firstReg = false;
const regValue = diff_regs[regName]; // The new value of the register
logLine += `${regName}=${regValue}`;

// --- Check if the register value points to a string ---
const potentialString = tryReadString(regValue);
if (potentialString !== null) {
// Truncate long strings for readability
const displayString = potentialString.length > 60 ? potentialString.substring(0, 57) + "..." : potentialString;
// Escape potential quotes in the string for cleaner output
const escapedString = displayString.replace(/"/g, '\\"').replace(/\n/g, '\\n').replace(/\r/g, '\\r');
logLine += ` ("${escapedString}")`;
}
// ----------------------------------------------------------
}
}

console.log(logLine);
}
});
}
// Keep the original instruction
iterator.keep();
}
} while ((instruction = iterator.next()) !== null); // Process next instruction in the block
}
});
},
onLeave: function (retval) {
console.log("[+] " + TARGET_MODULE_NAME + "!" + ptr(stalker_target_func_offset) + " onLeave, TID:", this.tid);
// Stop stalking this thread when the function exits
Stalker.unfollow(this.tid);
// Clean up Stalker's compiled code cache
Stalker.garbageCollect();
}
});
}

// ... (rest of the script remains the same)
// hook_linker_call_constructors();
// hook_target_func(base_addr); // Called from linker hook or fallback
// hook_pthread_create(); // Called from hook_target_func onLeave
// tryReadString() // Helper function defined above
// get_diff_regs() // Helper function defined above
// get_involved_regs() // Helper function defined above

var offset = 0x88060;
var module_size = 0; // Will be populated when module is found
var module_name = "libbaiduprotect.so";
var base_addr = null; // To store the base address of libbaiduprotect.so

// Create an empty function
var empty_func = new NativeCallback(function() {
// console.log("[Anti-Debug] Empty function executed."); // Optional log
return 0;
}, 'int', ['pointer']);


// --- Helper function to attempt reading a string ---
function tryReadString(address, minPrintableBytes = 3, maxReadLength = 256) {
if (!address) {
return null; // Cannot read from null address
}

try {
// Step 1: Read the first few bytes to see if they look like printable ASCII
const preliminaryBytes = address.readByteArray(minPrintableBytes);
if (!preliminaryBytes) {
// Can't read the initial bytes (e.g., invalid address)
return null;
}

const uint8Array = new Uint8Array(preliminaryBytes);
let allPrintable = true;
for (let i = 0; i < uint8Array.length; i++) {
const byte = uint8Array[i];
// Basic check for printable ASCII (0x20 to 0x7E)
if (byte < 0x20 || byte > 0x7E) {
allPrintable = false;
break;
}
}

if (allPrintable) {
// Step 2: If they look printable, attempt to read a C-string
// readCString reads until null terminator or max length
const str = address.readCString(maxReadLength);
// Filter out empty strings or strings that only contained non-null printable chars but no null terminator early on
if (str && str.length > 0) {
return str;
}
}

} catch (e) {
// Reading failed (e.g., invalid memory access)
// console.warn("[-] Failed to read memory at " + address + " for string check: " + e.message); // Optional: Log errors
return null;
}

return null; // Did not pass the checks or failed to read
}
// ---------------------------------------------------


function hook_linker_call_constructors() {
// 为提高健壮性,建议使用更完善的 linker 及 call_constructors 查找逻辑
// 但为保持与你原始脚本的结构,暂时保留这种直接方式
let linker_module_name = Process.arch === 'arm64' ? 'linker64' : 'linker';
let linker64_base_addr = Module.getBaseAddress(linker_module_name);
if (!linker64_base_addr) {
console.warn("[-] Failed to get " + linker_module_name + " base address. Trying to find module directly later.");
// Fallback if linker not found
setTimeout(function() {
if (!base_addr) { // Check if already found
let secmodule = Process.findModuleByName(module_name);
if (secmodule) {
base_addr = secmodule.base;
module_size = secmodule.size;
console.log(module_name + " base (fallback): " + base_addr + ", size: " + module_size);
hook_target_func(base_addr);
} else {
console.error("[-] " + module_name + " not found via fallback. Cannot proceed.");
}
}
}, 1500); // Delay slightly
return;
}

// NOTE: This offset is highly dependent on the system/Android version and linker variant!
// A more robust approach would involve scanning for the function signature.
let offset_call = 0x51BA8; // Example: __dl__ZN6soinfo17call_constructorsEv on some systems
let call_constructors = linker64_base_addr.add(offset_call);
console.log("[i] Attempting to attach to linker's call_constructors at " + call_constructors);

try {
let listener = Interceptor.attach(call_constructors, {
onEnter: function(args) {
// console.log('[linker] Call_Constructors onEnter');
let secmodule = Process.findModuleByName(module_name);
if (secmodule != null) {
if (base_addr === null) { // Ensure initialized only once
module_size = secmodule.size;
base_addr = secmodule.base; // Save the base address
console.log(module_name + " base: " + base_addr + ", size: " + module_size);

// *** Consider patching known points here or within Stalker ***
// Patching here happens earlier, might be safer for some anti-debug
// patchInstructionToNop(base_addr, 0x48AC0, 4); // Example offset
// patchInstructionToNop(base_addr, 0x4ABB8, 4); // Example offset

hook_target_func(base_addr);
listener.detach(); // Detach once the module is found
console.log("[i] Detached from linker hook.");
}
}
}
// No onLeave needed for just finding the module base
});
} catch (e) {
console.error("[-] Failed to attach to linker's call_constructors: " + e.message);
console.log("    Will rely on fallback to find module " + module_name);
setTimeout(function() { // Similar fallback logic as above
if (!base_addr) {
let secmodule = Process.findModuleByName(module_name);
if (secmodule) {
base_addr = secmodule.base;
module_size = secmodule.size;
console.log(module_name + " base (fallback after attach error): " + base_addr + ", size: " + module_size);
hook_target_func(base_addr);
} else {
console.error("[-] Fallback after attach error: " + module_name + " not found.");
}
}
}, 1500);
}
}

function hook_target_func(current_base_addr) { // Use current_base_addr to distinguish from global base_addr
if (!current_base_addr || current_base_addr.isNull()){
console.error("[-] hook_target_func: current_base_addr is invalid.");
return;
}
let target_addr = current_base_addr.add(offset); // offset = 0x88060
console.log("[+] Hooking target function at " + target_addr + " (offset 0x" + offset.toString(16) + ")");
try {
// Use attachOnce if you only need it to trigger once
Interceptor.attach(target_addr, {
onEnter: function(args) {
console.log("Target function " + module_name + "!0x" + offset.toString(16) + " entered. Called from: " + this.returnAddress);
},
onLeave: function(retval) {
console.log(module_name + "!0x" + offset.toString(16) + " onLeave. Base address: " + current_base_addr);
// IMPORTANT: Use the global base_addr here as it should be set by now and is needed by StalkerTrace/pthread hook
if (base_addr && !base_addr.isNull()) {
hook_pthread_create();
StalkerTrace(base_addr); // Pass the correct module base address to StalkerTrace
} else {
console.error("[-] Global base_addr not set in hook_target_func onLeave. Cannot start Stalker / pthread hook.");
}
// No need to detach here if using attachOnce, but if using attach, you might detach
// listener.detach();
// console.log("[i] Detached from target_func hook at " + target_addr);
}
});
// Note: Interceptor.attachOnce is often better if you only need the onLeave logic once
// However, let's stick to attach and detach in onLeave for now based on your original code structure.
} catch (e) {
console.error("[-] Failed to attach to target_func at " + target_addr + ": " + e.message);
}
}

function hook_pthread_create() {
// Ensure base_addr is set before hooking pthread_create as it's needed for comparison
if (!base_addr || base_addr.isNull()) {
console.error("[-] hook_pthread_create: Global base_addr is null. Skipping hook.");
return;
}
let pthread_create_addr = Module.findExportByName("libc.so", "pthread_create");
if (pthread_create_addr) {
console.log("[+] Hooking pthread_create at " + pthread_create_addr);
try {
Interceptor.attach(pthread_create_addr, {
onEnter: function(args) {
// pthread_create signature:
// int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
// start_routine is the 3rd argument (index 2)
let func_addr = args[2];

// Target function address within libbaiduprotect.so
// This offset (0x3E9F0) might also need dynamic finding
let target_func_offset_pthread = 0x3E9F0;
let target_func_addr = base_addr.add(target_func_offset_pthread);

// Check if the thread being created is the one we want to intercept
if (func_addr.equals(target_func_addr)) {
console.log("[Anti-Debug] Replacing thread start_routine " + func_addr + " with empty function.");
// Replace the actual start routine pointer with our empty function's pointer
args[2] = empty_func;
}
}
});
} catch (e) {
console.error("[-] Failed to attach to pthread_create: " + e.message);
}
} else {
console.warn("[-] pthread_create not found in libc.so. Hook skipped.");
}
}

const TARGET_MODULE_NAME = "libbaiduprotect.so"; // Same as module_name for clarity with Stalker

function get_diff_regs(context, pre_regs) {
var diff_regs = {};
// Create a snapshot of the current context's general-purpose registers
const current_regs_snapshot = JSON.parse(JSON.stringify(context));

// Iterate through the original register names from the snapshot
for (const reg_name_orig in current_regs_snapshot) {
// Ensure it's a direct property, not inherited
if (current_regs_snapshot.hasOwnProperty(reg_name_orig)) {
const reg_name_lower = reg_name_orig.toLowerCase();
// Filter out PC, SP, and floating-point/vector registers (q* or v*)
if (reg_name_lower !== "pc" && reg_name_lower !== "sp" && !reg_name_lower.startsWith("q") && !reg_name_lower.startsWith("v")) {
// Compare with the previous snapshot
if (pre_regs[reg_name_orig] !== current_regs_snapshot[reg_name_orig]) {
// If the value has changed, update the previous snapshot and record the difference
pre_regs[reg_name_orig] = current_regs_snapshot[reg_name_orig];
diff_regs[reg_name_orig] = current_regs_snapshot[reg_name_orig]; // Store the NEW value
}
}
}
}
return diff_regs;
}

// This function is defined but not currently used in the logging. Keeping it as it was in the original code.
function get_involved_regs(instruction) {
const involved_regs = new Set();
instruction.operands.forEach(op => {
if (op.type === 'reg') { involved_regs.add(op.value); }
else if (op.type === 'mem') {
if (op.value.base) { involved_regs.add(op.value.base); }
if (op.value.index) { involved_regs.add(op.value.index); }
}
});
return Array.from(involved_regs);
}

// =======================================================================================
// Modified StalkerTrace Function (FIXED local_pre_regs access)
// =======================================================================================
function StalkerTrace(current_module_baseaddr) { // Ensure the correct module base address is passed
// Use the global module_size which should be set by now
if (!current_module_baseaddr || current_module_baseaddr.isNull() || module_size === 0) {
console.error("[-] StalkerTrace: Invalid module base address or size. Cannot proceed.");
return;
}

var stalker_target_func_offset = 0x4a458; // The offset of the function you want Stalker to trace
var stalker_start_addr = current_module_baseaddr.add(stalker_target_func_offset);
console.log("[+] StalkerTrace will target " + TARGET_MODULE_NAME + "!" + ptr(stalker_target_func_offset) + " at actual address: " + stalker_start_addr);

// Define the module bounds for Stalker logging
const module_start = current_module_baseaddr;
const module_end = current_module_baseaddr.add(module_size);
console.log("[i] Stalker module bounds: Start=" + module_start + ", End=" + module_end);

Interceptor.attach(stalker_start_addr, {
onEnter: function (args) {
// Stalker operates per thread. Get the current thread ID.
this.tid = Process.getCurrentThreadId();
console.log("[+] Stalker: Entered " + TARGET_MODULE_NAME + "!" + ptr(stalker_target_func_offset) + " (TID: " + this.tid + ")");

// Initialize previous register state *for this specific thread*.
// This variable is local to this onEnter call but will be captured by the closure.
const local_pre_regs = {};
// Populate initial state, excluding filtered registers
const initial_context_snapshot = JSON.parse(JSON.stringify(this.context));
for (const reg_name_orig in initial_context_snapshot) {
if (initial_context_snapshot.hasOwnProperty(reg_name_orig)) {
const reg_name_lower = reg_name_orig.toLowerCase();
if (reg_name_lower !== "pc" && reg_name_lower !== "sp" && !reg_name_lower.startsWith("q") && !reg_name_lower.startsWith("v")) {
local_pre_regs[reg_name_orig] = initial_context_snapshot[reg_name_orig];
}
}
}
// *** REMOVE THIS LINE *** this.local_pre_regs = local_pre_regs;
// The variable 'local_pre_regs' is already in scope for the transform/callout closures.


// === Patch known crashing instructions on entering the traced function ===
// These addresses are relative to the module base address (current_module_baseaddr)
const knownCrashOffsets = [0x48AC0, 0x4ABB8]; // List of known problematic instruction offsets
knownCrashOffsets.forEach(offset => {
try {
const patchAddr = current_module_baseaddr.add(offset);
// Read instruction bytes to verify it's the expected 'MOV WSP, #1'
// ARM64: MOV WSP, #1 is 320003FF (little endian)
const expectedBytes = new Uint8Array([0x32, 0x00, 0x03, 0xFF]);
const instrBytes = patchAddr.readByteArray(4);
const actualBytes = new Uint8Array(instrBytes);

let matchesExpected = true;
if (actualBytes.length === 4) {
for(let i=0; i<4; i++) {
if (actualBytes[i] !== expectedBytes[i]) {
matchesExpected = false;
break;
}
}
} else {
matchesExpected = false;
}

if (matchesExpected) {
// If it matches, NOP it
Memory.patchCode(patchAddr, 4, code => {
const writer = new Arm64Writer(code, { pc: patchAddr });
writer.putNop(); // Replace the instruction with NOP
writer.flush();
});
console.log("[MemoryPatch] Stalker's onEnter: NOPped known crash (MOV WSP, #1) at " + module_name + "!" + ptr(offset) + " ("+ patchAddr + ")");
} else {
// console.log("[MemoryPatch] Stalker's onEnter: Instruction at " + module_name + "!" + ptr(offset) + " is not MOV WSP, #1. Bytes: " + Array.from(actualBytes).map(b => b.toString(16).padStart(2,'0')).join('')); // Optional log
}
} catch (e) {
console.warn("[MemoryPatch] Stalker's onEnter: Error patching " + module_name + "!" + ptr(offset) + ": " + e.message);
}
});
// ==================================================================

Stalker.follow(this.tid, {
// Configure events to trace. exec=true traces every executed instruction.
events: { call: false, ret: false, exec: true, block: false, compile: false },
// The transform callback allows inspecting/rewriting instruction blocks
transform(iterator) {
let instruction = iterator.next();
do {
const currentAddress = instruction.address;
let patchedThisInstruction = false;

// Check if the instruction is within the target module's bounds
const is_module_code = currentAddress.compare(module_start) >= 0 &&
currentAddress.compare(module_end) < 0;

// === Universal MOV WSP/SP, #1 detection and NOP logic ===
// This is a fallback/alternative to the onEnter patch
// It happens per block compiled by Stalker
if (is_module_code) { // Only patch instructions within the target module
const mnemonic = instruction.mnemonic.toLowerCase();
// Check for MOV or MOVZ with SP/WSP as destination and immediate 1
if ((mnemonic === 'mov' || mnemonic === 'movz') && instruction.operands.length >= 2) {
const destOperand = instruction.operands[0];
const srcOperand = instruction.operands[1];

if (destOperand.type === 'reg' &&
(destOperand.value === 'wsp' || destOperand.value === 'sp' ||
destOperand.value === 'w31' || destOperand.value === 'x31') && // w31/x31 are aliases for wsp/sp
srcOperand.type === 'imm' &&
parseInt(srcOperand.value.toString()) === 1) {

console.warn("[StalkerTransform] Identified potential crash instruction '" + instruction.toString() + "' at " + currentAddress + ". Replacing with NOP.");
iterator.putNop(); // Replace the instruction with NOP
patchedThisInstruction = true;
}
}
}
// =========================================================

// If the instruction wasn't NOPped by the above check, add a callout for logging
if (!patchedThisInstruction) {
// Only add callout for instructions within the target module
if (is_module_code) {
// 'local_pre_regs' is accessible here via closure
iterator.putCallout(function (context) {
// Access local_pre_regs directly from the outer scope (captured by closure)
const thread_pre_regs = local_pre_regs;
const pc = context.pc;

// Get the register differences after this instruction executes
// get_diff_regs modifies thread_pre_regs in place
const diff_regs = get_diff_regs(context, thread_pre_regs);

// Find the module info again (can be optimized by caching, but this is reliable)
var current_callout_module = Process.findModuleByAddress(pc);

// Ensure we are logging code from the target module
if (current_callout_module && current_callout_module.name === TARGET_MODULE_NAME) {
const instrToLog = Instruction.parse(pc);
let logLine = `${current_callout_module.name}!${pc.sub(current_callout_module.base)} ${instrToLog.toString()}`;

// Append changed registers and potential strings
const diffRegKeys = Object.keys(diff_regs);
if (diffRegKeys.length > 0) {
logLine += `\t\t| Changed Regs: `;
let firstReg = true;
for (const regName of diffRegKeys) {
if (!firstReg) logLine += ", ";
firstReg = false;
const regValue = diff_regs[regName]; // The new value of the register
logLine += `${regName}=${regValue}`;

// --- Check if the register value points to a string ---
const potentialString = tryReadString(regValue);
if (potentialString !== null) {
// Truncate long strings for readability
const displayString = potentialString.length > 60 ? potentialString.substring(0, 57) + "..." : potentialString;
// Escape potential quotes in the string for cleaner output
const escapedString = displayString.replace(/"/g, '\\"').replace(/\n/g, '\\n').replace(/\r/g, '\\r');
logLine += ` ("${escapedString}")`;
}
// ----------------------------------------------------------
}
}

console.log(logLine);
}
});
}
// Keep the original instruction
iterator.keep();
}
} while ((instruction = iterator.next()) !== null); // Process next instruction in the block
}
});
},
onLeave: function (retval) {
console.log("[+] " + TARGET_MODULE_NAME + "!" + ptr(stalker_target_func_offset) + " onLeave, TID:", this.tid);
// Stop stalking this thread when the function exits
Stalker.unfollow(this.tid);
// Clean up Stalker's compiled code cache
Stalker.garbageCollect();
}
});
}

// Start the script execution by hooking the linker
setImmediate(function() {
console.log("[i] Script starting for " + module_name + "...");
hook_linker_call_constructors();
});

console.log("[i] Script loaded. Waiting for " + module_name + " to be loaded...");

接下来又遇到一个问题,在trace到sub_5AE5C内部的时候,会在随机某处地址断开trace。由于是随机断开的,没搞清楚到底是为什么?头疼。

image-20250516124234164

先通过下面这个脚本,hook上了sub_5AE5C,跑起来没问题。

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
var offset = 0x88060;
var module_size = 0;
var module_name = "libbaiduprotect.so";
var base_addr = null; // To store the base address of libbaiduprotect.so

// Create an empty function
var empty_func = new NativeCallback(function() {
return 0;
}, 'int', ['pointer']);

function hook_linker_call_constructors() {
let linker64_base_addr = Module.getBaseAddress('linker64');
let offset_call = 0x51BA8; // __dl__ZN6soinfo17call_constructorsEv
let call_constructors = linker64_base_addr.add(offset_call);
let listener = Interceptor.attach(call_constructors, {
onEnter: function(args) {
console.log('[linker] Call_Constructors onEnter');
let secmodule = Process.findModuleByName(module_name);
if (secmodule != null) {
module_size = secmodule.size;
base_addr = secmodule.base; // Save the base address
console.log("libbaiduprotect.so base: " + base_addr + ", size: " + module_size);
hook_target_func(base_addr);
listener.detach();
}
}
});
}

function hook_target_func(baseaddr) {
let target_addr = baseaddr.add(offset);
let listener = Interceptor.attach(target_addr, {
onEnter: function(args) {
console.log("Target function at " + target_addr + " entered");
},
onLeave: function(retval) {
console.log("libbaiduprotect.so base address: " + baseaddr);
hook_pthread_create();
hook_sub_5ae5c(baseaddr); // Hook sub_5AE5C after target function
listener.detach();
}
});
}

function hook_pthread_create() {
let pthread_create_addr = Module.findExportByName("libc.so", "pthread_create");
Interceptor.attach(pthread_create_addr, {
onEnter: function(args) {
let func_addr = args[2];
let target_func_addr = base_addr.add(0x3E9F0); // Calculate the target function address
if (func_addr.equals(target_func_addr)) {
console.log("Replacing func_addr " + func_addr + " with empty function");
args[2] = empty_func; // Replace with the empty function
// hook_libart(); // Commented out as it was not provided
} else {
let module = Process.findModuleByAddress(func_addr);
if (module && module.name === module_name) {
console.log("pthread_create called in libbaiduprotect.so, function address: " + func_addr);
}
}
},
onLeave: function(retval) {
// Optionally log return value
}
});
}

function hook_sub_5ae5c(baseaddr) {
if (!baseaddr || baseaddr.isNull()) {
console.error("[-] hook_sub_5ae5c: Invalid base address. Cannot proceed.");
return;
}
let target_addr = baseaddr.add(0x5AE5C);
console.log("[+] Hooking sub_5AE5C at " + target_addr);
Interceptor.attach(target_addr, {
onEnter: function(args) {
console.log("[sub_5AE5C] onEnter");
// Dump args[0] (pointer to _QWORD)
try {
if (!args[0].isNull()) {
console.log("args[0] (*_QWORD at " + args[0] + "):");
console.log(hexdump(args[0], { length: 64, ansi: true }));
} else {
console.warn("args[0] is null");
}
} catch (e) {
console.warn("Failed to hexdump args[0]: " + e.message);
}
// Dump args[2] (data + 8)
try {
if (!args[2].isNull()) {
console.log("args[2] (data + 8 at " + args[2] + "):");
console.log(hexdump(args[2], { length: 64, ansi: true }));
} else {
console.warn("args[2] is null");
}
} catch (e) {
console.warn("Failed to hexdump args[2]: " + e.message);
}
// Store pointers for comparison in onLeave
this.arg0 = args[0];
this.arg2 = args[2];
},
onLeave: function(retval) {
console.log("[sub_5AE5C] onLeave, Return value: " + retval);
// Dump args[0] again to check for changes
try {
if (!this.arg0.isNull()) {
console.log("args[0] (*_QWORD at " + this.arg0 + ") after execution:");
console.log(hexdump(this.arg0, { length: 64, ansi: true }));
} else {
console.warn("args[0] is null in onLeave");
}
} catch (e) {
console.warn("Failed to hexdump args[0] in onLeave: " + e.message);
}
// Dump args[2] again to check for changes
try {
if (!this.arg2.isNull()) {
console.log("args[2] (data + 8 at " + this.arg2 + ") after execution:");
console.log(hexdump(this.arg2, { length: 64, ansi: true }));
} else {
console.warn("args[2] is null in onLeave");
}
} catch (e) {
console.warn("Failed to hexdump args[2] in onLeave: " + e.message);
}
}
});
}

setImmediate(function() {
console.log("[i] Script starting for " + module_name + "...");
hook_linker_call_constructors();
});

console.log("[i] Script loaded. Waiting for " + module_name + " to be loaded...");

可以看到,after execution的args[2]中,存放的内容是一大片地址,将这些地址减去libbaiduprotect.so的基地址,可以发现这些地址都是vmp__funcs_256这个表上的地址。也就是说,经过函数sub_5ae5c,会从第一个参数中,获得到实现JNI函数所需要的的vmp指令集合列表。

image-20250516152914009

既然trace不了函数5ae5c,那就从sub_5ae5c的汇编代码分析吧。

基本块1。

image-20250516164415682

基本块2。

image-20250516164434265

基本块3。

image-20250516164454468

基本块4。

image-20250516164512194

基本块5。

image-20250516164532311

基本块6。

image-20250516191356570

基本块7。

image-20250516172701443

基本块7的下一个块又是基本块4,也就是说,将上述流程写成伪代码的话,如下所示。

1
2
3
for(int index = 0; index < 256; index++){
a2 + (*(((byte*)a0) + 64 + index*4)) * 8 = *(a1 + index*8)
}

和ida做一个对比,会发现ida的伪代码看不懂。

image-20250516192214983

通过这个伪代码,我们可以得知,a0是一个已知的数据源,它的64字节大小的offset偏移处,存放着解密后的vmp_code映射表,比方说,读取一个 a0 + 64 + index*4,命名为v1,然后将vmp_funcs_list的从0到255,把每一个元素,依次赋给 v1*8 + a2 的内存地址上,注意,这里的v1并不是逐增的。

↑我把我的表述扔给gemini,哈哈哈,得到认可了,看来没分析错。

image-20250516193401944

我将函数sub_5ae5c命名为:from_DecodeVmpCode_map_to_SimulateSmaliCode。

分析完它之后,不妨再分析sub_4A458,我想分析vmp_explain传入的参数的含义是什么。

IDA的伪代码问题太大了,传入2个v19很明显没意义。

image-20250516221417532

基本块1。

image-20250516221732841

基本块2。

image-20250517102406624

基本块3,调用了sub_5cb98([[qword_BED18地址 + (vmp_method_code & 0xFF0000)>>16]], vmp_method_code & 0xFFFF)

image-20250517114642368

基本块4,sub_5cb98的操作如下。

image-20250517114339893

基本块5。

image-20250517120326446

基本块6。

image-20250517124953190

基本块7。

image-20250517125335092

基本块8。

image-20250517130438972

基本块9,sub_45EBC比较复杂,暂时先不分析,待会hook一下这个函数,可以不费吹灰之力获得——

  1. [[[qword_BED18地址 + (vmp_method_code & 0xFF0000)>>16] + 0x38] + (vmp_method_code & 0xFFFF)*8]

  2. [原SP-0x40]

  3. bundle数组的内容

  4. malloc1的内容

  5. malloc2的内容

image-20250517132004904

基本块10。

image-20250517132244136

基本块11。

image-20250517152311463

后面还要分析好多函数,暂时先分析到这。——2025/05/17。

参考文章

https://bbs.kanxue.com/thread-257926.htm#msg_header_h1_3

https://bbs.kanxue.com/thread-257926.htm#msg_header_h1_2