From f20cc672393bd40c6eebf31b3a42f185254094b4 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sat, 29 Aug 2026 05:57:32 +0800 Subject: [PATCH] =?UTF-8?q?docs(huya):=20R16=20dfpReport=E5=8A=A0=E5=AF=86?= =?UTF-8?q?=E4=BD=93=E4=BE=A6=E5=AF=9F=20=E2=80=94=20turing=E5=8F=8D?= =?UTF-8?q?=E6=AC=BA=E8=AF=88=E5=AE=9A=E4=BD=8D+=E5=85=A8=E9=93=BE?= =?UTF-8?q?=E7=BB=93=E6=9E=84=E8=A7=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 结构: getDfpConfig(81B密文配置)->selectOperator(SDID+hyudb_ts)->dfpReport(10B魔数+3988B密文体)->服务端回t1/SDID - 魔数/协议串147库全零: 运行时装配/turing混淆; Java无dfp类 - 关键材料: mpdc_108298_1=32hex设备号0399ED0B...; ga2 1860B; .turing.dat 76B - Java全量hook方案弃用(闪退); turing=工业级SDK, 建议独立子项目 --- docs/HUYA_HDID_ALGORITHM_GEN.md | 29 +++++++++++ tools/frida/capture_20260829_055245.jsonl | 1 + tools/frida/capture_20260829_055429.jsonl | 0 tools/frida/hook_dfp_java.js | 59 +++++++++++++++++++++++ tools/frida/run_capture.py | 2 +- tools/frida/run_scan.py | 23 +++++++++ tools/frida/run_scan2.py | 25 ++++++++++ tools/frida/scan_magic.js | 19 ++++++++ 8 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 tools/frida/capture_20260829_055245.jsonl create mode 100644 tools/frida/capture_20260829_055429.jsonl create mode 100644 tools/frida/hook_dfp_java.js create mode 100644 tools/frida/run_scan.py create mode 100644 tools/frida/run_scan2.py create mode 100644 tools/frida/scan_magic.js diff --git a/docs/HUYA_HDID_ALGORITHM_GEN.md b/docs/HUYA_HDID_ALGORITHM_GEN.md index 7230726..3f53267 100644 --- a/docs/HUYA_HDID_ALGORITHM_GEN.md +++ b/docs/HUYA_HDID_ALGORITHM_GEN.md @@ -1072,3 +1072,32 @@ t1.t0 appSign (32hex, "hdid") = MD5( appId + "_" + appVersion + "_" + k1 ) - tools/frida/hook_otp_capture.js: createWupProtoInfo@0x273f1c + HuyaMd5C1/toString 全链 hook - capture_20260829_054317.jsonl: ctor inHex=353030385f31332e342e32325f383635... (5008_13.4.22_k1) + tostr=ed0db8... - 复核: md5("5008_13.4.22_865a4924a40897ac1fcfe6b4c2cbb0e3") = ed0db8334cadd236c00cadf7e11ab5a5 ✅ + +--- + +## §11.46 R16: dfpReport 加密体侦察 (未破解#2 现状) (2026-08-29) + +### 结构解码 (evidence/dfp_chain_golden.json 全链 WUP) +``` +getDfpConfig: tReq{version="5008"} -> tRsp = 81B 密文配置 (lNLlGtxbxSB2FhJg...=, 非JSON) +selectOperator: _wup_data{version"1.0", hyudb_, "5008", "LV", SDID(PQwem...)} +dfpReport: tReq = [10B 魔数 57 18 82 cf 66 4b b3 94 01 ee][3988B 加密采集体] + tRsp = code"10" + t1(865a4924a40897ac1fcfe6b4c2cbb045=channelKey!) + SDID +``` + +### 侦察结论 (静态全量) +- **魔数/协议串不在任何静态库**: APK 147 lib 全扫 + 设备 lib + 本地 merged = 零 (协议名/魔数均运行时装配或混淆) +- dfp = **腾讯 turing 反欺诈体系** (app_turingdfp/app_turingfd 目录, libturingga.so, 108298=appid) +- 关键材料已拉取: app_turingfd/mpdc_108298_1 = 32hex 设备号(0399ED0BEADBA7124D445AA7D7843BF9!) + app_turingfd/12/108298_ga_2 (1860B), app_turingdfp/1/.turing.dat (76B) +- 链路: getDfpConfig(密文配置) -> selectOperator(交SDID) -> dfpReport(3988B密文体) -> 服务端回 t1/SDID +- Java 侧无 dfp 类 (classes12 仅 URL 黑名单配置); 协议全 native; turing = 工业级混淆 SDK + +### 影响评估 +- 唯一剩余缺口 = "纯代码铸造全新设备身份" (需破 turing 加密体, 多日专项) +- 现有多账号方案 (金样本设备身份) 不受影响; 缺口#1 不影响 hdid/appSign 本身 + +### 工具/资产 +- tools/frida/hook_dfp_java.js (Java 全量 hook — 太重, 会闪退, 弃用) +- /tmp/turing/: mpdc1/ga2/tur 数据存档 +- 建议: turing-dfp 立为独立子项目 (证据链齐全); 或轻量运行时 trace 追 tReq 装配点 diff --git a/tools/frida/capture_20260829_055245.jsonl b/tools/frida/capture_20260829_055245.jsonl new file mode 100644 index 0000000..e792de6 --- /dev/null +++ b/tools/frida/capture_20260829_055245.jsonl @@ -0,0 +1 @@ +{"event": "dfp-classes", "list": ["android.text.util.-$$Lambda$Linkify$7J_-cMhIF2bcttjkxA2jDFP8sKw"], "total": 1} diff --git a/tools/frida/capture_20260829_055429.jsonl b/tools/frida/capture_20260829_055429.jsonl new file mode 100644 index 0000000..e69de29 diff --git a/tools/frida/hook_dfp_java.js b/tools/frida/hook_dfp_java.js new file mode 100644 index 0000000..8995df7 --- /dev/null +++ b/tools/frida/hook_dfp_java.js @@ -0,0 +1,59 @@ +'use strict'; +function emit(row) { + try { send(row); } catch (e) {} +} +// dfpReport 链 Java hook: 找 dfp/udbdf 类 -> hook 请求方法 -> 抓 tReq blob + 调用栈 +function bx() { + try { + return Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()).split("\n").slice(0, 14).join(" | "); + } catch (e) { return ''; } +} +setTimeout(() => { + try { + Java.perform(() => { + const hits = []; + Java.enumerateLoadedClasses({ + onMatch(name) { + if (/dfp|Dfp|DFP|udbdf|Udbdf/.test(name)) hits.push(name); + }, + onComplete() { + emit({ event: 'dfp-classes', list: hits.slice(0, 60), total: hits.length }); + for (const cn of hits.slice(0, 40)) { + try { + const K = Java.use(cn); + const methods = K.class.getDeclaredMethods(); + for (const m of methods) { + const mn = m.getName(); + if (/post|report|send|build|req|encrypt|get|init/i.test(mn) && m.getParameterTypes().length <= 6) { + try { + K[mn].overloads.forEach(ov => { + ov.implementation = function () { + const args = Array.from(arguments).map(a => { + try { + if (a === null || a === undefined) return String(a); + if (a instanceof Java.use("byte[]")) { + const arr = Java.array('byte', a); + return 'byte[' + arr.length + ']:' + Array.from(arr.slice(0, 48)).map(b => (b & 0xff).toString(16).padStart(2, '0')).join(''); + } + const s = String(a); + if (s.length > 200) return s.slice(0, 80) + ('…len' + s.length); + if (s.indexOf('dfp') >= 0 || s.indexOf('udbdf') >= 0 || s.indexOf('571882cf') >= 0) return s; + return s.slice(0, 60); + } catch (e2) { return ''; } + }); + emit({ event: 'dfp-call', cls: cn, method: mn, args: args.slice(0, 6), stack: bx() }); + return ov.apply(this, arguments); + }; + }); + } catch (e3) {} + } + } + } catch (e4) {} + } + }, + }); + }); + } catch (e) { + emit({ event: 'dfp-err', msg: String(e) }); + } +}, 4000); diff --git a/tools/frida/run_capture.py b/tools/frida/run_capture.py index 16a0e67..436c85e 100644 --- a/tools/frida/run_capture.py +++ b/tools/frida/run_capture.py @@ -26,7 +26,7 @@ PKG = "com.duowan.kiwi" SERVER_PATH = "/data/local/tmp/fs152" SERVER_PORT = "31878" BYpass_RE = "/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/evidence/scripts/bypass_msaoaid_maps_art_callsite.js" -HOOK_LOCAL = os.environ.get("CAP_HOOK", "/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/evidence/scripts/hook_huya_crypto.js") +HOOK_LOCAL = os.environ.get("CAP_HOOK") MAX_ATTEMPTS = int(os.environ.get("CAP_MAX_ATTEMPTS", "8")) WINDOW_SEC = int(os.environ.get("CAP_WINDOW", "150")) diff --git a/tools/frida/run_scan.py b/tools/frida/run_scan.py new file mode 100644 index 0000000..16c43f5 --- /dev/null +++ b/tools/frida/run_scan.py @@ -0,0 +1,23 @@ +import subprocess, sys, time +pkg="com.duowan.kiwi" +adb = lambda *a: subprocess.run(["adb"]+list(a), capture_output=True, text=True) +adb("shell","su","-c","if ! pgrep -f /data/local/tmp/fs152; then nohup /data/local/tmp/fs152 -l 127.0.0.1:31878 >/data/local/tmp/fs152.log 2>&1 & fi") +time.sleep(1.5) +import frida +dev = frida.get_device_manager().add_remote_device("127.0.0.1:31878") +# attach the CURRENT running process (not spawn — just attach for the scan) +procs = [p for p in dev.enumerate_processes() if p.name == pkg or (p.parameters or {}).get('name')==pkg or pkg in p.name] +if not procs: + print("app not running, spawn...", file=sys.stderr) + pid = dev.spawn([pkg]); dev.resume(pid); time.sleep(12) + procs = [p for p in dev.enumerate_processes() if pkg in p.name] +target = procs[0] +print(f"attaching {target.pid} {target.name}") +session = dev.attach(target.pid) +def on_message(msg, data): + if msg.get('type')=='send': + print("SCAN:", msg['payload']) +session.create_script(open('/Users/yml/codes/douyu_login_py/tools/frida/scan_magic.js').read()).on('message', on_message).load() +time.sleep(18) +try: session.detach() +except: pass diff --git a/tools/frida/run_scan2.py b/tools/frida/run_scan2.py new file mode 100644 index 0000000..555e2a6 --- /dev/null +++ b/tools/frida/run_scan2.py @@ -0,0 +1,25 @@ +import subprocess, sys, time +pkg="com.duowan.kiwi" +adb = lambda *a: subprocess.run(["adb"]+list(a), capture_output=True, text=True) +adb("shell","su","-c","if ! pgrep -f /data/local/tmp/fs152; then nohup /data/local/tmp/fs152 -l 127.0.0.1:31878 >/data/local/tmp/fs152.log 2>&1 & fi") +time.sleep(1.5) +import frida +dev = frida.get_device_manager().add_remote_device("127.0.0.1:31878") +pid = dev.spawn([pkg]); print("spawned", pid) +session = dev.attach(pid) +bypass = open("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/evidence/scripts/bypass_msaoaid_maps_art_callsite.js").read() +session.create_script(bypass).load() +dev.resume(pid) +print("resumed; waiting 25s for app init + dfpReport...") +time.sleep(8) +def on_message(msg, data): + if msg.get('type')=='send' and isinstance(msg.get('payload'), dict) and msg['payload'].get('event')=='magic-scan': + print("SCAN total:", msg['payload'].get('total')) + for h in (msg['payload'].get('hits') or [])[:30]: + print(" ", h['mod'], h['off']) +script = session.create_script(open("scan_magic.js").read()) +script.on('message', on_message) +script.load() +time.sleep(10) +try: session.detach() +except: pass diff --git a/tools/frida/scan_magic.js b/tools/frida/scan_magic.js new file mode 100644 index 0000000..c0cae16 --- /dev/null +++ b/tools/frida/scan_magic.js @@ -0,0 +1,19 @@ +'use strict'; +// 扫描进程内魔数 571882cf664bb39401ee +const PATTERN = '57 18 82 cf 66 4b b3 94 01 ee'; +const mods = Process.enumerateModules(); +const hits = []; +for (const m of mods) { + if (m.size > 300 * 1024 * 1024) continue; + try { + Memory.scan(m.base, m.size, PATTERN, { + onMatch(addr, size) { + const mm = Process.findModuleByAddress(addr); + const off = mm ? addr.sub(mm.base) : addr; + hits.push({ mod: mm ? mm.name : '?', off: off.toString() }); + }, + onComplete() {}, + }); + } catch (e) {} +} +send({ event: 'magic-scan', hits: hits.slice(0, 40), total: hits.length });