抖音 37.8.0 逆向抓取明文数据包

仅作网络安全技术分享,最新版本已经放出来了,做了一定的限制,所以把思路公开,此前这篇文章是加密的,解锁时间为 2026 年 3 月 23 日 22 时 56 分 10 秒

image-20260323225548290

关于frida可以看我之前写的 从 0 到 1 精通 Frida Hook,在抖音的 37.8.0 版本之前,可以通过修改 SSL_CTX_set_custom_verify 来突破 SSL 证书校验,从而去实现抓包,但是抖音最新版本做了限制

image-20260318095800934

在最新的版本,使用抓包工具会直接显示网络错误,然后没有相应请求,并且修改 SSL_CTX_set_custom_verify,能建立抓包连接,但是请求参数呈现乱码状态,无法解析信息,这里解释一下这个原理

在安卓渗透或逆向分析中,当 APP 使用了底层的 openssl 或 boringssl 进行证书校验(即 Native 层 SSL Pinning)时 SSL_CTX_set_custom_verify 是一个非常关键的函数。

1
void SSL_CTX_set_custom_verify(SSL_CTX *ctx, int mode, int (*callback)(SSL *ssl, uint8_t *out_alert));

核心逻辑在于第三个参数:callback 回调函数。如果这个回调函数返回 ssl_verify_ok(通常是 0),则验证通过,使用 Frida Hook 回调函数,我们不需要修改 SSL_CTX_set_custom_verify 本身,而是要拦截这个函数,并篡改它设置的那个 callback 指针,让它指向一个永远返回“验证成功”的伪造函数

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
/// <reference types="frida-gum" />
Java.Perform(function() {
// 寻找函数地址(通常在 libssl.so h欧着 libcrypto.so)
var targetSymbol = "SSL_CTX_set_custom_verify";
var addr = Module.findExportByName("libssl.so", targetSymbol);
if (addr == null) {
// 部分 APP 可能会把库改名,或者在 libcrypto 中
addr = Module.findExportByName(null, targetSymbol);
}
if (addr) {
console.log("Found " + targetSymbol + " at: " + addr);
Interceptor.attach(addr, {
onEnter: function(args) {
// args[0] 是 SSL_CTX*
// args[1] 是 mode
// args[2] 是 原始 callback 函数地址
this.originalCallback = args[2];
console.log("[*] 原始 Callback 地址: " + this.originalCallback);
// 2. 替换第三个参数 (args[2])
// 我们创建一个 NativeCallback,它接受两个参数并直接返回 0 (验证通过)
var fakeCallback = new NativeCallback(function (ssl, out_alert) {
console.log("[!] 触发伪造的验证回调,强制返回 0 (PASS)");
return 0;
}, 'int', ['pointer', 'pointer']);
// 将参数替换为我们的伪造函数
args[2] = fakeCallback;

// 保持 fakeCallback 的引用,防止被 GC 回收
this.fakeCallback = fakeCallback;
}
})
} else {
console.log("[-] 未找到 " + targetSymbol + ",请确认库名是否正确。");
}
});
// frida-ps -Ua
// frida -U -f com.ss.android.ugc.aweme -l bypass-ssl.js

首先我们要先想办法修改 SSL_CTX_set_custom_verify 来实现抓包通联,抖音的叫做 libsscronet.so ,然后再 sub_3dC3A8 这里看到了相关函数

image-20260318095914410

把这个方法修改后,就可以正常通联了,但是抓包工具抓到的请求参数还是乱码,所以接下来只能继续找找,定位一下乱码的根源,然后修改对应的函数,反向跟踪这个函数 sub_49A568,这是用来 zstd 压缩的

image-20260318095928651

这里我们发现有一个判断 (result &1) != 0,只要这个条件为假,程序就不会进入该分支,所以我们请求的参数就不会被加密和压缩,抓包就能抓到明文的数据,所以开始编写 hook 脚本,版本为高版本 Frida 17,和 16 的相关 API 会有些出入

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
function hook_dlopen(soName = '') {
var dlopen_ext = Module.getGlobalExportByName("android_dlopen_ext");
if (!dlopen_ext || dlopen_ext.isNull()) {
console.error("[-] android_dlopen_ext not found");
return;
}
console.log('[+] android_dlopen_ext at: ' + dlopen_ext);
Interceptor.attach(dlopen_ext, {
onEnter: function (args) {
this.is_can_hook = false;
var pathptr = args[0];
if (pathptr && !pathptr.isNull()) {
try {
var path = pathptr.readCString();
if (path && path.indexOf(soName) !== -1) {
console.log('[+] Loading target: ' + path);
this.is_can_hook = true;
}
} catch (e) {}
}
},
onLeave: function (retval) {
if (this.is_can_hook) {
console.log('[+] Target SO loaded, applying patches...');
setTimeout(function() {
fix_plain();
ssl_bypass();
}, 100);
}
}
});
}
function ssl_bypass() {
try {
var module = Process.findModuleByName('libsscronet.so');
if (!module) {
console.error("[-] libsscronet.so not found");
return;
}
let SSL_CTX_set_custom_verify = null;
try {
SSL_CTX_set_custom_verify = module.getExportByName('SSL_CTX_set_custom_verify');
} catch(e) {
SSL_CTX_set_custom_verify = Module.findExportByName('libsscronet.so', 'SSL_CTX_set_custom_verify');
}
console.log('[+] SSL_CTX_set_custom_verify found at: ' + SSL_CTX_set_custom_verify);
if (SSL_CTX_set_custom_verify && !SSL_CTX_set_custom_verify.isNull()) {
Interceptor.attach(SSL_CTX_set_custom_verify, {
onEnter: function (args) {
console.log('[*] SSL_CTX_set_custom_verify called, mode: ' + args[1]);
args[1] = ptr(0);
console.log('[+] Patched mode to 0 (SSL bypass)');
}
});
} else {
console.error("[-] SSL_CTX_set_custom_verify not found");
}
} catch (e) {
console.error("[-] ssl_bypass error: " + e);
}
}
function fix_plain() {
try {
let module = Process.findModuleByName("libsscronet.so");
if (!module) {
console.error("[-] libsscronet.so not found");
return;
}
let moduleBase = module.base;
var targetAddr = moduleBase.add(0x49A73C);
console.log('[+] Patching at: ' + targetAddr);
try {
Memory.patchCode(targetAddr, 4, function (code) {
code.writeByteArray([0x80, 0x3F, 0x08, 0x36]);
});
console.log('[+] Memory patch applied via patchCode');
} catch(e) {
try {
Memory.writeByteArray(targetAddr, [0x80, 0x3F, 0x08, 0x36]);
console.log('[+] Memory patch applied via writeByteArray');
} catch(e2) {
console.error("[-] All patch methods failed: " + e2);
}
}
} catch (e) {
console.error("[-] fix_plain error: " + e);
}
}

setImmediate(function() {
console.log('[*] Starting Frida 17 script...');
hook_dlopen("libsscronet.so");
});

运行后,安卓启动

1
./frida-server -l 127.0.0.1:1234

电脑启动

1
frida -H 127.0.0.1:1234 -l .\frida_scripts\douyin.js -f com.ss.android.ugc.aweme

image-20260318095956705

运行之后,抖音打开正常

image-20260318100004642

抓包也正常

image-20260318100016907

dbefcacb36ff8a6512c75f0dc1baec22

Support via Solana

Solana

Solana

Solana Pay

Solana Pay

WeChat

WeChat