日期:2026-11-15 状态:native 组件完成并经零账号端到端验证;运行时集成暂缓(见 §〇) 目标:MoeHoo 式 packet 收发监听,为诊断与闭源 addon 失效场景提供独立通道
运行时不存在 “send_packet 缺口”。 深入 snowluma fork 桥接层后确认:
PipeOp.recvPacket = 6——闭源 hook 模块本来就通过 QHP1 recv 管道流式推送全部已解析包
(QqHookPacket {seq, cmd, uin, body} → HookSession → BridgeManagerSink.onPacket),
与 MoeHoo 回调字段一一对应send_packet action 早已实现(packages/onebot/src/actions/extended.ts:4810,
ctx.bridge.sendRawPacket(cmd, body) → QHP1 sendRequest);NAS 生产环境的戳一戳撤回
即走此通道因此本组件的定位修正为:诊断工具 + 保险——当未来 QQ 版本上闭源 addon(不可修)失效时, 这套开源的独立捕获通道是现成的降级路径。日常不必接入运行时(闭源管道已覆盖,多一路注入 只增加攻击面与复杂度)。
| 选项 | 结论 |
|---|---|
复用闭源 loader(loadModuleManualAsync) |
❌ 实测拒绝非白名单模块(COMPONENT_LOAD_FAILED)——loader 按导出符号识别模块(snowluma_linux_hook_* 6 个符号);模仿符号会与真 hook 模块冲突 |
| 自研 ptrace 注入器(远程 dlopen) | ✅ 采用。PTRACE_ATTACH → 单步执行 libc 内 syscall;ret gadget 完成远程 mmap → 远程调用 dlopen(path, RTLD_NOW) → int3 返回桩捕获 → 恢复寄存器 DETACH。glibc 原生完成重定位/TLS/init_array,无需手写 manual-map |
| LD_PRELOAD(部署层) | 备选(需控制 QQ 启动),当前不需要 |
组件(全部原创代码,PolyForm NC):
| 文件 | 角色 |
|---|---|
native/packet-hook/injector.c |
注入器(~300 行 C,独立二进制) |
native/packet-hook/packet-hook.c |
hook 模块(detour + 无锁环形缓冲 + UNIX socket JSON 行协议 + 文件 dump) |
jmp [rip+0] + 8 字节绝对地址;跳板 = 原 17 字节前缀 + 跳回 entry+17(前缀均为 push/sub,无 RIP 相对引用,30 秒观察窗内 QQ 稳定收发)。{"kind":"send|recv","t","ns","tid","args":[6 个十六进制],"l1":[各参数一级内存],"l2":[二级字段 hex]}。来自 uprobe 寄存器快照 + 进程内 dump(登录页流量):
| 观测 | 事实 |
|---|---|
| 线程 | MSFNG-*(MSF NextGen 工作线程) |
| a0(rdi) | 会话/引擎对象,恒定;L1 内 +0x48 递增计数器、+0x58 方向字段(send=1/recv=2) |
| a1(rsi) | 调用方栈上结构体;+0x38 处指针指向命令字符串(实测 22 "wtlogin.trans_emp" 00) |
| recv a3(rcx) | 递增序列号(0x27c498 起每包 +1)——即 MoeHoo 回调的 seq |
| send a4(r8) | 每次调用新分配 0x1e0 字节请求对象 |
| 收发对称 | uprobe 三次窗口 39:39 / 64:64 / 18:18,进程内 13:12 —— 全部近 1:1 |
| 坑 | 症状 | 修法 |
|---|---|---|
| 闭源 loader 白名单 | COMPONENT_LOAD_FAILED,构造函数不执行 |
弃用闭源 loader,自研注入器 |
| maps 解析截断 | “未找到 libc” | 上限 512→4096,堆分配 |
| 符号名 | glibc 2.36 无 __libc_dlopen_mode 命中 |
优先公开符号 dlopen(2.34 起在 libc 本体) |
| gadget 地址换算 | SINGLESTEP 即杀进程 | 运行时地址 = 映射起点 + (文件命中 − 段文件偏移);执行前 /proc/pid/mem 校验 0f 05 c3 |
| PEEK 未附加 | 校验永远失败(EPERM 当作字节错) | 校验改用 /proc/pid/mem 直读 |
| 远程栈 2KB | dlopen 内部 SIGSEGV | 1MB 栈;mmap RW + 页内 int3 桩 + 页转 RX(无 RWX 瞬态) |
| 栈对齐 | movaps SIGSEGV(dump 无 hook 模块 = 崩在 dlopen 内) | 入口 rsp%16==8:stack_top 再减 8 |
| 致命信号回注 | 崩溃被放大成进程死亡 | 致命信号不回注,恢复原始寄存器 DETACH(QQ 可存活),打印 rip 所属模块 |
| GOT 双重解引用缺失 | jmp *%r11 跳进自己 .data 段当代码执行——三模式全崩、RIP-RSP 差跨次一致 |
mov x@GOTPCREL(%rip), %r11 取到的是变量地址,须再 mov (%r11), %r11 |
| 热路径 stdio | fopen/fprintf 在外部线程调用链里 SIGSEGV | 桩内零 libc:无锁环形缓冲入环 + L1/L2 就地拷贝,worker 离线格式化落盘 |
gcc -O2 -o nekolux-packet-inject injector.c
gcc -shared -fPIC -O1 -o nekolux-packet-hook.so packet-hook.c -lpthread
# conf(可选):mode=2 sock=/tmp/nekolux-packet.sock dump=/tmp/nekolux-packet-dump.log
./nekolux-packet-inject <映射 wrapper.node 的 pid> ./nekolux-packet-hook.so
# 客户端连 /tmp/nekolux-packet.sock(SOCK_SEQPACKET)逐行收 JSON
测试脚本:tools/reversing/m3-cycle.sh(注入周期)、m3-socket-test.sh(socket 验证)、
uprobe-abi-r1.sh/r2(ABI 快照)、m2-loader-probe.sh(闭源 loader 行为实证)。
packages/bridge):PacketHookService —— spawn 注入器 → 连 socket → 解析 JSON → 事件分发;dist 打包加 nekolux-packet-hook.so + 注入器;注入时机排在主 hook 会话建立之后(ptrace 槽位串行)g_head++ 改原子操作(当前 send/recv 同线程,暂无竞争);v2 增加卸载恢复(摘除 detour、还原入口)