继续破解进度

This commit is contained in:
yml2213
2026-08-26 18:28:33 +08:00
parent da2cf5a61d
commit 36325cf639
11 changed files with 608 additions and 15 deletions
+156
View File
@@ -0,0 +1,156 @@
#!/usr/bin/env python3
"""铸造验证 v1: 重放 dfpReport wire → 服务端回显(不动设备)。
路线(XOR 自反性,不需要 keystream):
密文 = 明文 XOR ks => 新密文 = 原密文 XOR (原JSON XOR 新JSON)
JSON 区(前 554B)等长替换 hdid/deviceId/appkey,collection 区保持原样。
"""
from __future__ import annotations
import binascii
import json
import random
import ssl
import socket
import time
from pathlib import Path
EVID = Path("/Users/yml/codes/douyu_login_py/evidence")
HOST = "wsapi.huya.com"
MAGIC = bytes.fromhex("571882cf664bb39401ee")
def load_first_wire():
for f in sorted((EVID / "dfp_pipeline_170031.json", EVID / "dfp_pipeline_160855.json"),
reverse=True):
d = json.load(open(f))
for e in d:
if e.get("type") == "wire":
return binascii.unhexlify(e["hex"])
raise SystemExit("no wire found")
def send_http(host, path, body, timeout=60):
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
raw = socket.create_connection((host, 443), timeout=10)
s = ctx.wrap_socket(raw, server_hostname=host)
req = (
f"POST {path} HTTP/1.1\r\n"
f"i-ver: 1\r\n"
f"Content-Type: application/octet-stream\r\n"
f"Content-Length: {len(body)}\r\n"
f"Host: {HOST}\r\n"
f"Connection: Keep-Alive\r\n"
f"Accept-Encoding: identity\r\n"
f"User-Agent: okhttp/3.14.9\r\n"
f"\r\n"
).encode() + body
s.sendall(req)
s.settimeout(timeout)
buf = b""
try:
while True:
chunk = s.recv(4096)
if not chunk:
break
buf += chunk
# 响应头拿到 + 至少等 2s 累积 body
if b"\r\n\r\n" in buf and len(buf) > 300:
time.sleep(2)
try:
while True:
c = s.recv(4096)
if not c:
break
buf += c
except socket.timeout:
break
break
finally:
try:
s.close()
except Exception:
pass
return buf
def gen_random_hex(n):
return "".join(random.choice("0123456789abcdef") for _ in range(n))
def main():
wire = load_first_wire()
idx = wire.find(b"\r\n\r\n")
body = wire[idx + 4:]
mp = body.find(MAGIC)
assert mp >= 0, "magic not in body"
cipher = body[mp + 10:]
print(f"[*] wire={len(wire)}B body={len(body)}B cipher={len(cipher)}B")
# 明文 JSON (canonical 554B from pair_sync)
ps = json.load(open(EVID / "dfp_pair_sync.json"))
jsonb = None
for p in ps:
if p.get("type") != "plain":
continue
for item in p["plains"]:
b = bytes.fromhex(item["hex"])
if b[:8] == b'{"appId"' and len(b) >= 554:
jsonb = b[:554]
break
if jsonb:
break
assert jsonb, "no json"
orig = jsonb.decode("utf-8", "replace")
# --- 实验1: 原样重放 ---
print("\n[实验1] 原样重放")
r = send_http(HOST, "/", body)
status = r.split(b"\r\n", 1)[0].decode("utf-8", "replace")
print(f" resp: {status} len={len(r)}")
hdrs, _, rbody = r.partition(b"\r\n\r\n")
print(f" resp body head: {rbody[:80].hex(' ')}")
# --- 实验2: 改明文 hdid/deviceId/appkey (等长) → Δ XOR ---
new_hdid = gen_random_hex(40)
new_devid = gen_random_hex(40)
new_appkey = gen_random_hex(32)
new = orig
new = new.replace(f'"hdid":"{orig.split(chr(34)+"hdid"+chr(34))[1].split(chr(34))[1]}"',
f'"hdid":"{new_hdid}"')
# 更稳妥: 正则替换
import re
new = re.sub(r'"hdid":"[0-9a-f]{40}"', f'"hdid":"{new_hdid}"', new)
new = re.sub(r'"deviceId":"[0-9a-f]{40}"', f'"deviceId":"{new_devid}"', new)
new = re.sub(r'"appkey":"[0-9a-f]{32}"', f'"appkey":"{new_appkey}"', new)
assert new != orig, "no field replaced"
nb = new.encode("utf-8")
assert len(nb) == 554, f"len {len(nb)}"
delta = bytes(a ^ b for a, b in zip(nb, jsonb))
new_cipher = bytes(a ^ b for a, b in zip(cipher, delta + b"\x00" * (len(cipher) - len(delta))))
new_body = body[:mp + 10] + new_cipher
# Content-Length 不变 (等长)
print(f"\n[实验2] 改 hdid={new_hdid[:12]}... deviceId={new_devid[:12]}... appkey={new_appkey[:8]}...")
r = send_http(HOST, "/", new_body)
status = r.split(b"\r\n", 1)[0].decode("utf-8", "replace")
print(f" resp: {status} len={len(r)}")
hdrs, _, rbody = r.partition(b"\r\n\r\n")
print(f" resp body head: {rbody[:160].hex(' ')}")
out = {
"new_hdid": new_hdid, "new_devid": new_devid, "new_appkey": new_appkey,
"new_json": new,
"exp1_status": status,
"exp2_status": status,
"exp2_resp": rbody[:160].hex(),
}
(EVID / "forge_replay_result.json").write_text(json.dumps(out, ensure_ascii=False, indent=1))
print(f"\n[*] saved -> evidence/forge_replay_result.json")
if __name__ == "__main__":
main()
+99 -15
View File
@@ -19,7 +19,8 @@ import frida
PACKAGE = "com.duowan.kiwi"
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
OUT = Path("/Users/yml/codes/douyu_login_py/evidence/dfp_pipeline_v2.json")
import time as _t
OUT = Path("/Users/yml/codes/douyu_login_py/evidence/dfp_pipeline_" + _t.strftime("%H%M%S") + ".json")
JS = r"""
'use strict';
@@ -63,7 +64,7 @@ function decodeArg(p, tag){
if (startsWith(p, MAGIC_HDR)){
res.kind = 'MAGIC';
var pre = '';
try{ pre = hexb(p.sub(1536), 1536); }catch(e){}
try{ pre = hexb(p.sub(8192), 8192); }catch(e){}
res.pre = pre;
res.full = hexb(p, 9000);
return res;
@@ -109,6 +110,49 @@ function decodeArg(p, tag){
}catch(e){ return null; }
}
// deep decode: scan 1024B at p for plausible pointers, dump 9000B at each
function deepDeref(p){
var out = {reg: 'deep', addr: String(p)};
var found = [];
var seen = {};
try{
// also dump page-aligned base 12000B
try{
var pg = p.and(ptr('0xfffffffffffff000'));
if (isReadable(pg)){
found.push({kind: 'page', ptr: String(pg), full: hexb(pg, 12000)});
seen[String(pg)] = true;
}
}catch(e){}
var base = p.sub(512);
for (var i = 0; i < 1024; i += 8){
var v;
try{ v = base.add(i).readU64(); }catch(e){ continue; }
var cand = v.and(ptr('0x7fffffffffff'));
var s = cand.toString();
if (cand == ptr('0')) continue;
// heap-ish: 0x7[7-9ca].. or 0xb4.. or 0x77..
if (!(/^0x(7[78a-c9]|b4)[0-9a-f]{9,}$/.test(s))) continue;
if (seen[s]) continue;
if (!isReadable(cand)) continue;
seen[s] = true;
var entry = {off: i - 512, ptr: s};
var kind = 'raw';
if (startsWith(cand, JSON_HDR)) kind = 'JSON';
else if (startsWith(cand, MAGIC_HDR)) kind = 'MAGIC';
entry.kind = kind;
if (kind === 'JSON' || kind === 'MAGIC'){
entry.full = hexb(cand, 9000);
} else {
entry.head = hexb(cand, 64) || '';
}
found.push(entry);
}
}catch(e){}
out.entries = found;
return out;
}
function hookFn(off, name){
try{
var base = Process.getModuleByName('libhydeviceid.so').base;
@@ -120,6 +164,10 @@ function hookFn(off, name){
var pr = decodeArg(ctx[r], r);
if (pr) ev[r] = pr;
});
// cryptoA: deep deref x0
if (name === 'cryptoA_5b1f4'){
ev.deep = deepDeref(ctx.x0);
}
send({type:'pipe', ev: ev});
}
});
@@ -146,6 +194,29 @@ function attachOne(base, off, name){
var pr = decodeArg(ctx[r], r);
if (pr) ev[r] = pr;
});
// cryptoA: deep deref x0
if (name === 'cryptoA_5b1f4'){
ev.deep = deepDeref(ctx.x0);
}
// copy_61098 second call (x0=MAGIC): heap scan for JSON + dump 4600B
if (name === 'copy_61098' && (ev.x0||{}).kind === 'MAGIC'){
var hits = [];
try{
var pat = '7b 22 61 70 70 49 64 22 3a 22 35 30 30 38';
var ranges = Process.enumerateRanges('rw-');
for (var ri = 0; ri < ranges.length; ri++){
var rng = ranges[ri];
if (rng.size > 0x10000000) continue;
var m = Memory.scanSync(rng.base, rng.size, pat);
for (var mi = 0; mi < m.length; mi++){
var full = hexb(m[mi].address, 4600) || '';
var h = full.substring(0, 96);
hits.push({addr: String(m[mi].address), head: h, full: full});
}
}
}catch(e){}
send({type:'jsonhits', n: hits.length, hits: hits, t: Date.now() - t0});
}
send({type:'pipe', ev: ev});
}
});
@@ -183,6 +254,20 @@ try{
"""
def load_with_retry(session, js, n=5):
last = None
for i in range(n):
try:
s = session.create_script(js)
s.load()
return s
except Exception as e:
last = e
print(f"[retry {i}] {e}", flush=True)
time.sleep(3)
raise last
def main():
device = frida.get_device_manager().add_remote_device("127.0.0.1:31877")
pid = device.spawn([PACKAGE])
@@ -226,26 +311,25 @@ def main():
print(f"[{ev.get('t')}ms] {ev.get('fn')}::{ev.get('phase')} " + " ".join(parts), flush=True)
events.append({"type": "pipe", "ev": ev})
OUT.write_text(json.dumps(events))
elif t == "jsonhits":
print(f"[{p.get('t')}ms] [jsonhits] n={p.get('n')}", flush=True)
events.append({"type": "jsonhits", "t": p.get('t'), "hits": p.get('hits')})
OUT.write_text(json.dumps(events))
elif t == "wire":
print(f"[{p.get('t')}ms] [wire] len={p.get('len')}", flush=True)
events.append({"type": "wire", "len": p.get('len'), "t": p.get('t'), "hex": p.get('hex')})
OUT.write_text(json.dumps(events))
script = session.create_script(JS)
script.on("message", on_message)
script.load()
print("[*] 主JS 已装载, 加载 bypass + resume", flush=True)
session.create_script(
(RE / "evidence/scripts/bypass_msaoaid_maps_art_callsite.js").read_text()
).load()
session.create_script(
(RE / "evidence/scripts/mask_frida_maps_only.js").read_text()
).load()
# 严格按约定顺序: spawn -> bypass x2 -> resume + sleep 11 -> patch_guard -> 最后主 hook JS
print("[*] loading bypass", flush=True)
load_with_retry(session, (RE / "evidence/scripts/bypass_msaoaid_maps_art_callsite.js").read_text())
load_with_retry(session, (RE / "evidence/scripts/mask_frida_maps_only.js").read_text())
device.resume(pid)
time.sleep(11)
session.create_script(
(RE / "evidence/scripts/patch_guard_block_termination.js").read_text()
).load()
load_with_retry(session, (RE / "evidence/scripts/patch_guard_block_termination.js").read_text())
print("[*] loading 主 hook JS (最后)", flush=True)
script = load_with_retry(session, JS)
script.on("message", on_message)
print("[*] 已 resume。等待 dfpReport", flush=True)
try:
while True:
+194
View File
@@ -0,0 +1,194 @@
#!/usr/bin/env python3
"""间接调用解析器 v3 (按需启用, 零常驻开销):
默认不挂任何桩 hook; copy_61098 轮次边界(x1=MAGIC)触发时动态 attach 72 个 br 桩,
3 秒后全部 detach。捕获 crypto 调用图谱定位 keystream 内核。
"""
from __future__ import annotations
import json
import time
from pathlib import Path
import frida
PACKAGE = "com.duowan.kiwi"
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
OUT = Path("/Users/yml/codes/douyu_login_py/evidence/got_resolve.json")
JS = r"""
'use strict';
Process.setExceptionHandler(function(d){ return true; });
send({type:'armed'});
var t0 = Date.now();
var MAGIC_HDR = [0x57,0x18,0x82,0xcf,0x66,0x4b,0xb3,0x94,0x01,0xee];
var modBase = null;
var counts = new Map();
var roundN = 0;
var stubHandles = []; // Interceptor handles to detach
var stubsActive = false;
var activeSince = 0;
function hexb(p, n){ try{ return Array.from(new Uint8Array(p.readByteArray(n))).map(function(b){return ('0'+b.toString(16)).slice(-2)}).join(''); }catch(e){ return ''; } }
function startsWith(p, arr){
try{ var b = new Uint8Array(p.readByteArray(arr.length)); for (var i=0;i<arr.length;i++) if (b[i]!==arr[i]) return false; return true; }catch(e){ return false; }
}
function hookBrSite(addr){
try{
var h = Interceptor.attach(addr, {
onEnter: function(a){
var ctx = this.context;
var tgt = ctx.x17;
if (tgt.isNull()) return;
var off = tgt.sub(modBase).toInt32();
counts.set(off, (counts.get(off) || 0) + 1);
}
});
return h;
}catch(e){ return null; }
}
function enableStubs(){
if (stubsActive) return;
stubsActive = true;
activeSince = Date.now();
stubHandles = [];
for (var i = 0; i < 24; i++){
var h = hookBrSite(modBase.add(0x461c0 + i*0x10 + 0xc));
if (h) stubHandles.push(h);
}
for (var i = 0; i < 48; i++){
var h = hookBrSite(modBase.add(0x45aa0 + i*0x10 + 0xc));
if (h) stubHandles.push(h);
}
send({type:'stubs_on', n: stubHandles.length, t: Date.now()-t0});
// 3s 后自动摘除
setTimeout(function(){
if (!stubsActive) return;
for (var j = 0; j < stubHandles.length; j++){
try{ stubHandles[j].detach(); }catch(e){}
}
stubHandles = [];
stubsActive = false;
send({type:'stubs_off', t: Date.now()-t0});
}, 3000);
}
function tryHookRound(){
try{
var m = Process.getModuleByName('libhydeviceid.so');
modBase = m.base;
Interceptor.attach(m.base.add(0x61098), {
onEnter: function(a){
var ctx = this.context;
if (!startsWith(ctx.x1, MAGIC_HDR)) return;
roundN++;
// dump round data BEFORE enabling stubs (crypto already done)
var arr = [];
counts.forEach(function(v, k){ if (v > 2) arr.push([k, v]); });
send({type:'round', n: roundN, t: Date.now()-t0, counts: arr, activeMs: Date.now()-activeSince});
counts = new Map();
send({type:'magic', t: Date.now()-t0, full: hexb(ctx.x1, 4300)});
// 延迟到回调外再挂桩(frida 回调内 attach 会失败)
setTimeout(function(){ counts = new Map(); enableStubs(); }, 15);
}
});
send({type:'round_hooked'});
}catch(e){ setTimeout(tryHookRound, 500); }
}
setTimeout(tryHookRound, 400);
try{
var r = new ApiResolver('module');
r.enumerateMatchesSync('exports:*!SSL_write').forEach(function(m){
Interceptor.attach(m.address, {
onEnter: function(a){
var len = a[2].toInt32();
if (len < 100 || len > 20000) return;
var head = '';
try{ head = a[1].readCString(Math.min(len, 2000)); }catch(e){ return; }
if (head.indexOf('dfpReport') < 0) return;
send({type:'wire', len:len, t: Date.now()-t0});
}
});
});
send({type:'ssl_hooked'});
}catch(e){ send({type:'ssl_err', e:String(e)}); }
"""
def load_with_retry(session, js, n=4):
last = None
for i in range(n):
try:
s = session.create_script(js)
s.load()
return s
except Exception as e:
last = e
print(f"[retry {i}] {e}", flush=True)
time.sleep(3)
raise last
def main():
device = frida.get_device_manager().add_remote_device("127.0.0.1:31877")
pid = device.spawn([PACKAGE])
print(f"[*] spawned {PACKAGE} pid={pid}", flush=True)
session = device.attach(pid)
events = []
def on_message(message, data):
if message.get("type") == "error":
print("[JS-ERR]", str(message)[:200], flush=True)
return
if message.get("type") != "send":
return
p = message.get("payload") or {}
t = p.get("type")
if t == "armed":
print("[*] JS armed", flush=True)
elif t == "round_hooked":
print("[*] round boundary hooked", flush=True)
elif t == "stubs_on":
print(f"[t={p.get('t')}ms] stubs ON x{p.get('n')}", flush=True)
elif t == "stubs_off":
print(f"[t={p.get('t')}ms] stubs OFF", flush=True)
elif t == "round":
c = dict(p.get('counts') or [])
top = sorted(c.items(), key=lambda x: -x[1])[:15]
print(f"[t={p.get('t')}ms round{p.get('n')}] (stubs active {p.get('activeMs')}ms) calls={sum(c.values())} distinct={len(c)}", flush=True)
for off, cnt in top:
print(f" off=0x{off:x} x{cnt}", flush=True)
events.append({"type": "round", **p})
OUT.write_text(json.dumps(events))
elif t == "magic":
events.append({"type": "magic", "t": p.get('t'), "full": p.get('full')})
OUT.write_text(json.dumps(events))
elif t == "wire":
print(f"[t={p.get('t')}ms] [wire] len={p.get('len')}", flush=True)
events.append({"type": "wire", **p})
OUT.write_text(json.dumps(events))
# 严格按约定顺序: spawn -> bypass x2 -> resume + sleep 11 -> patch_guard -> 最后主 hook JS
print("[*] loading bypass", flush=True)
load_with_retry(session, (RE / "evidence/scripts/bypass_msaoaid_maps_art_callsite.js").read_text())
load_with_retry(session, (RE / "evidence/scripts/mask_frida_maps_only.js").read_text())
device.resume(pid)
time.sleep(11)
load_with_retry(session, (RE / "evidence/scripts/patch_guard_block_termination.js").read_text())
print("[*] loading 主 hook JS (最后)", flush=True)
script = load_with_retry(session, JS)
script.on("message", on_message)
print("[*] all loaded. waiting dfpReport", flush=True)
try:
while True:
time.sleep(5)
except KeyboardInterrupt:
pass
print(f"[*] done -> {OUT}", flush=True)
if __name__ == "__main__":
main()