← Back to blog

某音直播伴侣的违规检测逻辑:一篇客户端逆向学习笔记

版本:13.1.1.502547155(2026-10-09) 方法:纯静态分析,Electron 渲染层/主进程 bundle 逆向 结论先行:直播伴侣客户端不判定违规,它是一台“接收服务端判决并渲染出来”的终端。


一、先说清楚我分析的是什么,以及不是什么

某音直播伴侣是 Electron 应用,主程序 直播伴侣.exe 之外还带了一整套自研音视频栈(MediaSDK、Lynx 跨端引擎、Byte-Guard 安全 SDK)。本文只覆盖 resources/app/app.content/ 这一层——也就是 JS bundle。

有几个看起来像但其实不是的东西 worth先排除,否则很容易在错误的分支上浪费时间:

疑似物 真相
@bytegecko(原生 addon + gecko.config.json) Argus / Byte-Gurd 反篡改 SDK,给 Lynx 动态模板做签名校验。与内容审核无关。全库 534 处 “gecko” 命中全部误报
model.zip(59.6MB,algo_*.model) 美颜算法模型(磨皮、高光)
loki.zip(46MB,AmgBeautyPrefab / LUT / warpX) 美颜引擎 + 人脸形状模型
“敏感词”功能 实际是屏蔽词——主播自己配的弹幕过滤,服务端执行
manifest.json 里的 strict_min_version 仅校验 JS 资源版本完整性

一个 900MB 的安装目录里,能被误判为“审核组件”的东西至少有 5 个。逆向的第一课是排除法。


二、核心发现:违规字段的来源

客户端渲染处罚弹窗的函数(resource/js/9138.0011714a.js @542483)解构了服务端下发的完整判决:

const {
  ban_type,                    // 处罚类型
  ban_reason,                  // 违规原因
  ban_adjust_advice,           // 整改建议
  is_banned_forever,           // 是否永久封禁
  ban_duration, ban_time,      // 处罚时长 / 起始时间
  appeal_deadline_time,        // 申诉截止
  webcast_appeal_status,       // 申诉状态
  left_un_block_times,         // 剩余解封次数
  is_during_live,              // 是否直播中
  ban_time_left,               // 剩余封禁秒数
  early_warning_text,          // 预警文案
  ban_detail_schema,           // 违规详情页 schema
  log_id,                      // 申诉凭证
} = e;

这十四个字段里没有一个是本地计算出来的。 全部来自 HTTP 接口返回,或 WebSocket 推送。

这决定了整个架构的性质:伴侣是判决的执行终端,不是判决的产生者。


三、开播前的三道闸

客户端调 create_room,服务端返回 status_code,客户端按码分支。真正的审核相关分支:

封禁类:10018 / 20006 / 4003035

ban_type = resp.ban_type
├─ ban_type ∈ [2,19,20,21] → POST /webcast/review/get_live_assistant_window/  (直播助理引导)
└─ 否则:
   ├─ GET  /webcast/review/get_latest_ban_record/     → 取 log_id_str
   └─ GET  /webcast/review/get_ban_record/            → 取完整判决
      附带 early_warning_text / ban_time_left / ban_detail_schema

注意 ban_type 的语义分支:1 是账号封禁(“此账号已被封禁,封禁账号无法直播和登录”),21 对应电商 APP_ID(“直播伴侣”而非“直播”)。封禁文案里还有一条路径:

“可前往手机端「主播中心-违规记录」入口完成直播行为规范学习,可获得自动解封机会”

——即学习抵扣违规次数。

人脸核身:4003028 / 4003173

ipcRenderer.send("TOGGLE_FACE_DETECT", {
  url: resp.web_auth_address, ticket, scene, faceType, from: "status_code"
});
return { code: Error, error: FaceRecognitionError };

这是身份核验,不是内容审核。别把它算进“违规检测”。

健康分:4003163

block_dialog_info 里带 scene / type:

  • HealthScoreCheck + 普通警告 → 弹“礼物收入功能被关闭”,勾选“我已知悉”后可继续开播
  • LivingEducationCheck + StrongWarning → 强制观看学习视频,/webcast/review/education/get_task_record_status 轮询 is_pass

这是“以罚代教”的机制设计。


四、直播中的四条控制指令

直播中走 WebSocket WebcastControlMessage,control_type 分派(@866200 起):

control_type 常量 客户端行为
2 LIVING_PAUSE 弹”直播流已屏蔽“,10 秒倒计时,按钮文案”调整完毕,进入人工审核” → POST /webcast/room/auditing/apply/
3 LIVING_RESUME 恢复直播,manualReviewResultEvent({result:"pass"})
4 MESSAGE_TIPS 警告 / 违规提醒,punishPopupShowEvent({type:"warn"})
5 EMPTY_COVER 空封面
11 DARK_VEST 马甲 / 隐身
14 OVER_LIVING 关播

LIVING_PAUSE → LIVING_RESUME 这对指令构成了完整的实时熔断 + 申诉窗口:

服务端检测到风险
   ↓ WebcastControlMessage(LIVING_PAUSE)
客户端遮蔽直播流,弹违规通知
   ↓ 主播点击"调整完毕" / 10秒超时
POST /webcast/room/auditing/apply/  → 进入人工审核队列
   ↓ 审核结果
   ├─ 通过 → LIVING_RESUME,"屏蔽结束,已恢复直播"
   └─ 不通过 → manualReviewResultEvent({result:"notpass"}) + 断播

“直播流已屏蔽”而非“直接断播”是个精妙的产品设计——它给主播 10 秒自我修正窗口,把大部分误伤消化在客户端,不产生处罚记录。


五、所以违规到底谁在判?

把上面所有链路合起来看:

             ┌─────────────────────────────────────┐
             │  抖音服务端流处理集群                 │
             │  画面 / 音频 / 话术 / 商品 识别       │
             └──────────────┬──────────────────────┘
                            │ 判决(HTTP / WebSocket)
             ┌──────────────▼──────────────────────┐
             │  直播伴侣客户端                       │
             │  · 人脸核身(身份)                   │
             │  · 协议/实名/学习确认(合规)          │
             │  · 结果呈现(弹窗、倒计时、申诉)      │
             └─────────────────────────────────────┘

客户端做的只有三件事:身份核验、合规确认、结果呈现。没有任何一个字节的视频/音频数据在本机被用于内容判定。


六、支线一:第三方工具检测(反作弊)

这是全库最有技术含量的一段。它的判据不是进程名单,而是**“这个房间的比特到底是谁发的”**。

主进程 HackPerformanceHandler(module 87262):

async getKeys(e, {sdk}) {
  if (++this.freCounter % 6 !== 0) return [];      // 1/6 采样

  const { pushUrl, roomId, isLiving, liveMode } = await this.getRoomInfo(e);
  if (!pushUrl || this.hackUrlsObj[pushUrl] || !isLiving) return [];

  const tcpTable = await this.getTcpTable();       // system/network/tcp_table/ipv4
  const cdnIPs   = await getDnsAddress(parse(pushUrl).hostname);
  const { bitrate, sendFps } = getPerformanceActiveStatistics();

  const hits = tcpTable.filter(c =>
    cdnIPs.includes(c.remoteIp) && c.state === "ESTABLISHED");

  for (const conn of hits) {
    const { appName, appPath } = await this.getPidInfo(conn.processId);

    if (appName && appPath
        && appName !== "MediaSDK_Server.exe"   // ① 不是我
        && bitrate <= 2                        // ② 我几乎没在推
        && sendFps  <= 2) {                    //    我几乎没在发
      this.hackUrlsObj[pushUrl] = 1;           //    同一流只报一次
      this.reportHack({roomId, appName, appPath, remotePort: conn.remotePort, ...});
      if (!this.isWhitelistPort(conn.remotePort) || liveMode === OBS) {
        global.is_injected = 2;                // ③ 判定第三方推流
      }
    }
  }
  sdk.mediasdk.protosdk.call.msapi.SetHackPushStream({ hackEnabled });
}

算法精髓

  1. 取自己房间的 rtmp_push_url,DNS 解析出推流 CDN 的 IP 集合
  2. 枚举全机 IPv4 TCP 连接表,筛出“连到同一批 CDN 且 ESTABLISHED”的
  3. 关键判据:伴侣自己 bitrate ≤ 2 kbps && sendFps ≤ 2(几乎静默),但别的进程连着同一批 CDN → 那个人才是真正在推流
  4. process/{pid}/path 反查进程,剔除自己的 MediaSDK_Server.exe

这是纯行为特征检测,伪装进程名、改端口都无效——只要比特在别人机器上往外走,就会暴露。

渲染层还有一道端口闸

if (liveMode !== OBS && r.room_id && r.hack_exe && 1935 === Number(r.hack_remote_port))

1935 是 RTMP 端口。 弹窗只认这个端口。主进程上报所有端口,渲染层筛出 1935 才提示。

两个分支

条件 行为
首次命中 count===1 弹“直播风险提示 / 检测到您当前正在使用第三方工具或插件篡改直播流开播”,附官方开播指南链接
小玩法场景 读服务端配置 minigameHack.moreConfig,若 open 且 hack_exe ∉ bls(黑名单)→ 5 秒倒计时后自动关播

上报

明确字段(CONNECT_START 埋点):

{ device_id, stream_name, is_third_push: global.is_injected === 2 ? 1 : 0 }

global.is_injected 初值 -1(未知)/ 0(否)/ 2(是)。另有本地遥测 __TEA__.collect("livesdk_hack_push_detect", {hack_exe, hack_exe_path, hack_remote_port, code_rate, send_fps, live_mode})。


七、支线二:OBS 模式与一个反直觉的细节

直播伴侣内置了完整的 OBS 接入:起本地 nginx.exe 做 RTMP 中继,给用户 rtmp://127.0.0.1:1935/live,并轮询 nginx 的 /stat 端点读取 meta.video.codec,强制要求 H264。

入口由服务端白名单控制:

const res = await callAPI({ api: "/webcast/room/get_pc_obs_status/" });
if (res.data.pc_obs_status !== 1) throw new Error("权限校验失败");
setObsVisible(true);      // 只有 pc_obs_status===1 才渲染 OBS 选项

但反作弊代码里:

if (!isWhitelistPort(remotePort) || liveMode === LIVE_MODE.OBS) {
  global.is_injected = 2;
}

即便官方放行 OBS 模式,is_third_push=1 照样上报。官方 OBS 通路 = 弹窗豁免,不豁免上报。

这个细节比任何规则条文都更能说明平台的真实意图。


八、本地采集面(顺带扒到的)

客户端自研了一套结构化 collector 系统,21 个命名采集组、70 条采集路径:

类别 代表路径
网络 system/network/tcp_table/ipv4、ping/*、mtu/*
进程 process/{pid}/{name,path,command_line,module_list,subprocess}、system/process_list、system/top/N/*
设备指纹 system/device/fingerprint、.../original_wmi、system/cpu/smbios/max_clock_speed
硬件 system/gpu/*、system/display_config/info、system/monitor/info、system/device/drivers
摄像头归属 system/camera/using_process/{devId}\n{devName}
完整性 v_live_get_files_hash、v_live_signature_info

设备指纹抓 baseboardSerialNumber / systemUuid / systemDiskSerialNumber / networkAdapterMac / memoryDevices[],并且同时取一份 original_wmi 做交叉比对(旧 WMI 值 vs 新值,防篡改)。

摄像头归属是第二个第三方工具检测向量——返回码:

CAMERA_CREATE_ERROR_CODE = { IS_OCCUPIED:4, EMPTY_EXE:-5, UNKNOWN_EXE_NULL:-9,
                             PHYSICAL_NAME:-3, UNINIT:-1, TIMEOUT:-4 }

用户可见文案 "摄像头被【$processName】占用",会点名具体进程。

另有 Metasec(字节安全 SDK)双模式运行:spawn metasec.exe 或加载 electron-metasec.node + metasecml.dll,JSB 通道名 METASEC,每 5 分钟 report({scene_name:"live"})。


九、局限与没做出来的事

必须诚实标注:

  • 纯静态分析,无运行时验证。所有结论来自 bundle 字面量与控制流,不是抓包。
  • 原生层零覆盖。 metasecml.dll(7.1MB)、MediaSDK_Server.exe(18.7MB)、gecko.node(2.5MB) 一个字节没看——分析环境缺 Ghidra/Hopper/IDA。所以 SetHackPushStream 的真实实现、metasec 的采集项、Argus 的校验细节全部是未知。
  • 对服务端一无所知。 本文说“判定在服务端”,依据是客户端字段来源,不是对服务端的观测。
  • 没有复现。 没有实际开播触发过 LIVING_PAUSE 或 hack 检测弹窗。
  • 未分析 Electron 主进程与渲染进程间的信任边界(sender_validation_observations: 1)。

十、几点方法论感想

  1. 排除法比正向搜索省时间。 900MB 目录里 5 个“疑似审核组件”,先确认它们不是,比直接追真身快得多。
  2. 压缩 bundle 里按字节偏移抠上下文,比正则全局搜索有效。 单行 2.7MB 的文件,正则会命中几千个噪声点;定位到偏移量后 300 字符窗口就够看清意图。
  3. 判别“谁在决策”要看字段来源。 找到一个函数在处理 ban_reason,第一反应应该是“这个字段从哪来”,而不是“这个函数怎么判的”。
  4. “屏蔽 10 秒再进人工审核”和“多次同类违规加重处罚”这类产品设计,比技术细节更能解释平台意图。

责任使用声明 本文仅为对本地已安装软件的技术分析记录,用于理解客户端架构与学习逆向方法。文中接口路径、字段名与检测机制不应被用于规避平台风控或绕过第三方工具检测。 平台规则以官方公示为准;如有争议,请通过官方申诉渠道处理(客户端封禁弹窗即提供申诉入口)。 本次逆向仅出于学习目的,不用于任何商业项目,如有侵权请联系删除。630110598@qq.com