#1038·ecapture

bpf_probe_read_user 的返回值被丢弃,被标记的指针会静默产出全零数据

Author: YuYuKunKunCreated Sep 30, 2026Updated Sep 30, 2026

bpf_probe_read_user 的返回值被丢弃,被标记的指针会静默产出全零数据

现象

process_SSL_data() 忽略 bpf_probe_read_user() 的返回值,无论成功与否都发送事件。读失败时, 事件里带的是缓冲区原有的内容 —— 也就是零。在 Android 上,堆指针的高字节带有标记 (TBI, Top-Byte-Ignore),于是每一次读取都失败,工具看起来在工作,而每条载荷都是零。

证据

kern/openssl.h 的 process_SSL_data():

c
event->data_len = (len < MAX_DATA_SIZE_OPENSSL ? ... : MAX_DATA_SIZE_OPENSSL);
bpf_probe_read_user(event->data, event->data_len, buf);   /* 返回值被丢弃 */
bpf_perf_event_output(ctx, &tls_events, BPF_F_CURRENT_CPU, event, ...);

用 DEBUG=1 构建(打开 debug_bpf_printk)后,读 /sys/kernel/tracing/trace_pipe(需先 echo 1 > /sys/kernel/tracing/tracing_on):

bpf_trace_printk: (OPENSSL) bpf_probe_read ssl_ver_ptr failed, ret: -14
bpf_trace_printk: (OPENSSL) bpf_probe_read ssl_bio_ptr failed, ret: -14
bpf_trace_printk: (OPENSSL) len=42  ptr=b400006e8853ec8a ret=-14
bpf_trace_printk: (OPENSSL) len=90  ptr=b400006f21e3d000 ret=-14
bpf_trace_printk: (OPENSSL) len=166 ptr=b400006f21e3d000 ret=-14

-14 是 -EFAULT。注意指针:每一个都以 b40000 开头,那是 Android 的指针标记, 真实地址是低 56 位。bpf_probe_read_user() 不会去掉它。

长度本身是对的(42、90、166),所以失败在地址,不在长度。

影响

同一台设备、同一目标、各跑一次:

版本 解出的载荷 零字节占比 可打印字符
2.6.0 发布版 557,875 B 100.0% 0.0%
去掉指针标记后 126,667 B 0.1% 88.1%

发布版写出了 2.3 MB、223 条「载荷」记录,没有任何报错,全是零。输出里没有任何东西 表明读取失败了。

建议的修法

读用户内存前掩掉高字节:

c
#define UNTAG(p) ((void *)((unsigned long long)(p) & 0x00FFFFFFFFFFFFFFULL))

用在 kern/openssl.h 中指针的来源处(probe_entry_SSL 的 PT_REGS_PARM1 / PT_REGS_PARM2, 以及 probe_SSL_set_fd 里的 SSL 指针)。对未标记的地址这是空操作,因为其高字节本就是零。

另外,与掩码无关的一点:读失败至少应当上报或跳过。ssl_ver_ptr 与 ssl_bio_ptr 的调试 输出本来就有,只有载荷这一次读没有检查。把静默的错误答案变成可见的错误,本身就值得做。

说明

这也解释了为什么在此类库上 keylog 模式拿不到东西:它通过同一个被标记的指针去读 SSL 结构体。

复现环境

DEBUG=1 CROSS_ARCH=arm64 ANDROID=1 make nocore
ecapture tls -m text --libssl=<库路径>

可用改动与构建工作流(基于 master,保留完整历史): https://github.com/YuYuKunKun/ecapture-openssl-android