Files
live-hub-py/scripts/hook_emu_hdid_spawn_v8.py
T
yml2213 49c5c36c05 docs(huya): 模拟器存活闪退诊断报告与 Frida 探测脚本证据
- 诊断报告: attach 主进程静默退出/EGL 崩溃, 仅约 4s 窗口可抓帧
- scripts: attach/spawn/hook/emu 系列 Frida 脚本与抓帧/验证工具
- evidence: identity/reqchain/frame/inputbuf/magic_buf/propedge 抓取样本,
  emu_* 存活对比, diag_* 策略实验, baseline 裸测基准
2026-08-27 17:58:32 +08:00

194 lines
7.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""模拟器 hdid v8: spawn + 三件套 + 内存扫描 32hex (App 启动即扫, 无需触发登录).
时序(成功验证): spawn → bypass×2(挂起) → resume → 立即 patch_guard → 主JS:
1. 轮询 hook setSafeDeviceId/getHdid/createWupDeviceInfo (保险)
2. 轮询: mod-base 出现后 3s, 扫描进程内存找 [0-9a-f]{32} ASCII 字符串并去重
3. SSL_write 抓 hypasswordLogin (万一触发登录)
"""
import frida, time, re, subprocess, json
from pathlib import Path
REMOTE = "127.0.0.1:31878"
PACKAGE = "com.duowan.kiwi"
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
OUT = Path("/tmp/emu_hdid_v8.json")
MAIN_JS = r"""
'use strict';
function readStdString(p){
if (p.isNull()) return null;
try{
var first = p.readU8();
if ((first & 1) === 0) {
var len = first >> 1;
return (len>0 && len<256) ? p.add(1).readUtf8String(len) : '';
} else {
var data = p.readPointer();
var len = p.add(8).readU64();
return (len>0 && len<1024) ? data.readUtf8String(len) : null;
}
}catch(e){ return null; }
}
function hexb(p,n){try{return Array.from(new Uint8Array(p.readByteArray(n))).map(b=>('0'+b.toString(16)).slice(-2)).join('');}catch(e){return '';}}
function head(p,n){try{return p.readCString(n);}catch(e){return '';}}
function emit(tag,val){ send({type:tag, tag:tag, val:''+(val||''), ts:Date.now()}); }
var hooked=false, scanned=false, sslDone=false;
function hookUdb(){
if (hooked) return;
var md = Process.findModuleByName('libudbauthunify.so');
if (!md) return;
emit('mod-base', ''+md.base);
try{
Interceptor.attach(md.base.add(0x26A2E0), {
onEnter:function(a){ this.sd=readStdString(a[1]); this.hd=readStdString(a[2]); },
onLeave:function(){
var hd=(this.hd||''); emit('SETSD', hd+' | sd='+(this.sd||'').slice(0,24));
if(/^[0-9a-f]{32}$/.test(hd)) emit('FOUND_HDID_32', hd);
}
});
}catch(e){}
try{
Interceptor.attach(md.base.add(0x26A484), {
onEnter:function(a){ try{ this.thiz=this.context.x8; }catch(e){ this.thiz=null; } },
onLeave:function(){
if(!this.thiz) return;
try{ var s=readStdString(this.thiz.add(1088)); if(s){ emit('GETHDID',''+s); if(/^[0-9a-f]{32}$/.test(s)) emit('FOUND_HDID_32',''+s); } }catch(e){}
}
});
}catch(e){}
try{
Interceptor.attach(md.base.add(0x2746A0), {
onLeave:function(ret){
try{ var s=readStdString(ret.add(152)); if(s){ emit('WUPDEV_HDID',''+s); if(/^[0-9a-f]{32}$/.test(s)) emit('FOUND_HDID_32',''+s); } }catch(e){}
}
});
}catch(e){}
emit('ready','all hooked');
hooked = true;
}
function scanMem(){
// 扫描所有可读内存, 找 32hex ASCII 字符串 (含大小写hex)
var found = {};
var re32 = /[0-9a-f]{32}/i;
var ranges = Process.enumerateRanges({protection:'r--', coalesce:true});
ranges.forEach(function(r){
try{
var size = Math.min(r.size, 24*1024*1024);
var buf = Memory.readByteArray(r.base, size);
if (!buf) return;
var s = String.fromCharCode.apply(null, new Uint8Array(buf));
var m;
var re = /[0-9a-f]{32,40}/gi;
while ((m = re.exec(s)) !== null){
var v = m[0].toLowerCase();
if (!/^[0-9a-f]{32}$/.test(v)) continue;
if (found[v]) { found[v]++; continue; }
found[v] = 1;
}
}catch(e){}
});
var keys = Object.keys(found);
emit('scan-result', 'found32=' + keys.length + ' | ' + keys.slice(0,40).join(' '));
keys.forEach(function(k){ if(found[k]>=1) emit('H32CAND', k); });
scanned = true;
}
function hookSSL(){
if (sslDone) return;
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<50||len>50000)return;
var h=head(a[1],Math.min(len,600));
if(h.indexOf('hypasswordLogin')>=0){
emit('WUP_LOGIN', len+'B '+hexb(a[1],len));
}
}});
});
send({type:'sslwrite-hooked', tag:'ssl'});
}catch(e){}
sslDone=true;
}
setInterval(hookUdb, 100);
setInterval(hookSSL, 1000);
// 模块出现后 3s 启动内存扫描
var scanLaunched = false;
setInterval(function(){
if (!scanLaunched && hooked && !scanned){
scanLaunched = true;
setTimeout(scanMem, 3000);
}
}, 500);
"""
def load(session, js, wait=0.2):
try:
s = session.create_script(js); s.load(); return s
except Exception as e:
print("[load-err]", str(e)[:160], flush=True)
return None
def main():
subprocess.run(["adb","-s","127.0.0.1:5555","shell","am","force-stop",PACKAGE],
capture_output=True)
time.sleep(1.5)
d = frida.get_device_manager().add_remote_device(REMOTE)
pid = d.spawn([PACKAGE])
print(f"[*] spawn pid={pid}", flush=True)
session = d.attach(pid)
load(session, (RE/"evidence/scripts/bypass_msaoaid_maps_skip_cleanup.js").read_text())
print("[*] bypass_msaoaid loaded", flush=True)
load(session, (RE/"evidence/scripts/mask_frida_maps_only.js").read_text())
print("[*] mask_frida loaded", flush=True)
d.resume(pid)
print("[*] resumed", flush=True)
load(session, (RE/"evidence/scripts/patch_guard_block_termination.js").read_text(), wait=0.1)
print("[*] patch_guard loaded", flush=True)
events = []
def on_message(m, data):
if m.get('type') == 'error':
print("[JS-ERR]", str(m)[:200], flush=True); return
p = m.get('payload') or {}
t = p.get('type')
if t in ('mod-base','ready','setSd-err','getHdid-err','wupdev-err'):
print(f"[{t}] {p.get('val')}", flush=True)
elif t == 'SETSD' or t == 'GETHDID' or t == 'WUPDEV_HDID':
print(f"[{t}] {p.get('val')}", flush=True)
events.append(p); OUT.write_text(json.dumps(events, indent=1))
elif t == 'FOUND_HDID_32':
print(f"\n★★★ FOUND 32HEX HDID = {p.get('val')} ★★★", flush=True)
events.append(p); OUT.write_text(json.dumps(events, indent=1))
elif t == 'scan-result':
print(f"[SCAN] {p.get('val')}", flush=True)
events.append(p); OUT.write_text(json.dumps(events, indent=1))
elif t == 'H32CAND':
print(f"[H32CAND] {p.get('val')}", flush=True)
events.append(p); OUT.write_text(json.dumps(events, indent=1))
elif t == 'WUP_LOGIN':
print(f"[WUP_LOGIN] {p.get('val')[:80]}", flush=True)
events.append(p); OUT.write_text(json.dumps(events, indent=1))
elif t == 'sslwrite-hooked':
print("[*] SSL_write hooked", flush=True)
events.append(p); OUT.write_text(json.dumps(events, indent=1))
main_sc = load(session, MAIN_JS)
if main_sc:
main_sc.on('message', on_message)
print("[*] running 160s", flush=True)
t0 = time.time()
while time.time() - t0 < 160:
time.sleep(3)
if any(e.get('tag')=='FOUND_HDID_32' for e in events):
print("[*] 命中, 提前停止", flush=True)
break
print(f"[*] done, {len(events)} events", flush=True)
if __name__ == "__main__":
main()