feat(huya): hook阶段结论 - NativeBridge排除动态JNI与各库RN, 静态物证=内联描述符块; 双库unidbg打通; 设备稳定注意

- 排除: Java_导出扫描/各库dlopen JNI_OnLoad RN拦截/健康进程重触发RN = 全0命中
- unidbg verbose: hydeviceid仅FindClass+NewGlobalRef; udb仅注册HuyaAuthCore
- 物证: hydeviceid .data@0x3e21e4 内联方法描述符块(klog/b/c/g/h+getDataDir/getPath) + 类名字符串
- 双库: udb(JNI_OnLoad@0x262620)解密合并 unidbg 加载成功; merge 升级段驱动+动态保留+init_array置零+仅重定位槽清理
- 新输入: udb_deviceID/udb_deviceID_encode/oaid prefs + b(104) + Build.*字段
- 设备稳定: pm clear+frida attach 探测致闪退, 后续禁用; 无frida冷启动健康
- 静态还原重启点: isNative定讫 -> 注册函数(运行期建表模式) 或 Java逻辑; unidbg差分oracle + fnPtr兜底
This commit is contained in:
yml2213
2026-08-28 17:19:45 +08:00
parent 5ba70d5b4e
commit 27c71a3ad2
4 changed files with 294 additions and 28 deletions
+134
View File
@@ -0,0 +1,134 @@
#!/usr/bin/env python3
"""真机: 全 dlopen 拦截 -> 每个库 JNI_OnLoad 前置 RegisterNatives 钩子,
主动触发 getGUID/NativeBridge 类初始化, 抓 NativeBridge 懒注册归属.
用法: python3 scripts/phone_probe_rn_lazy.py [serial] [remote]
"""
from __future__ import annotations
import json, subprocess, sys, time
from pathlib import Path
import frida
SERIAL = sys.argv[1] if len(sys.argv) > 1 else "5dd8c93f"
REMOTE = sys.argv[2] if len(sys.argv) > 2 else "127.0.0.1:31878"
PACKAGE = "com.duowan.kiwi"
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
ART_CALLSITE = RE / "evidence" / "scripts" / "bypass_msaoaid_maps_art_callsite.js"
JS = r"""
'use strict';
function jmod(a){ try{var m=Process.findModuleByAddress(a); return m?{n:m.name,off:'0x'+a.sub(m.base).toString(16)}:{n:'?',off:'?'};}catch(e){return {n:'?',off:'?'};} }
var rnHooked=false;
function hookRN(){
if(rnHooked) return;
try{
var env=Java.vm.tryGetEnv(); if(!env) return;
var rn=env.handle.readPointer().add(215*Process.pointerSize).readPointer();
if(rn.isNull()) return;
Interceptor.attach(rn,{
onEnter:function(args){
var methods=args[3], count=args[4].toInt32(), mm=[];
for(var i=0;i<count;i++){
var m=methods.add(i*Process.pointerSize*3);
try{
var name=m.readPointer().readCString();
var sig=m.add(Process.pointerSize).readPointer().readCString();
var fn=m.add(Process.pointerSize*2).readPointer();
var jm=jmod(fn);
mm.push({name:name, sig:sig, mod:jm.n, off:jm.off});
}catch(e){}
}
send({type:'rn', methods:mm});
}
});
rnHooked=true;
send({type:'rn-hooked'});
}catch(e){}
}
setInterval(hookRN, 30);
function hookJniOnLoad(md){
try{
var jni=md.findExportByName('JNI_OnLoad');
if(!jni) return;
var name=md.name;
Interceptor.attach(jni,{
onEnter:function(){ send({type:'ol', mod:name, base:''+md.base}); hookRN(); },
onLeave:function(ret){ send({type:'ol-out', mod:name, ret:ret.toString()}); }
});
send({type:'jni-hooked', mod:name});
}catch(e){}
}
var hookedMods={};
setInterval(function(){
Process.enumerateModules().forEach(function(md){
if(hookedMods[md.name]) return;
if(md.name.indexOf('libhydeviceid')>=0||md.name.indexOf('libudb')>=0||md.name.indexOf('msaoaid')>=0||
md.name.indexOf('device')>=0||md.name.indexOf('sec')>=0||md.name.indexOf('auth')>=0||
md.name.indexOf('wup')>=0||md.name.indexOf('hycrypto')>=0||md.name.indexOf('jsiwup')>=0){
hookedMods[md.name]=true;
hookJniOnLoad(md);
}
});
}, 50);
setTimeout(function(){
Java.perform(function(){
try{
// 先强制 NativeBridge 类初始化(触发可能的懒注册)
var NB=Java.use('com.huya.security.hydeviceid.NativeBridge');
send({type:'cls-loaded'});
var NE=Java.use('com.huya.security.hydeviceid.NativeEntry');
NE.init();
send({type:'tid', tid:Process.getCurrentThreadId()});
send({type:'trigger', guid: (function(){ try{ return NE.getGUID(); }catch(e){ return 'ERR:'+String(e).slice(0,60);} })(),
mid: (function(){ try{ return NE.getMID(); }catch(e){ return 'ERR';} })() });
try{ var b100=NB.b(100); send({type:'b100', v:String(b100)}); }catch(e){ send({type:'b100err', e:String(e).slice(0,80)}); }
}catch(e){ send({type:'err', e:String(e).slice(0,150)}); }
});
}, 9000);
"""
def adb(*a):
return subprocess.run(["adb", "-s", SERIAL, *a], capture_output=True, text=True)
def main():
adb("shell", "am", "force-stop", PACKAGE)
time.sleep(1.5)
d = frida.get_device_manager().add_remote_device(REMOTE)
pid = d.spawn([PACKAGE])
print(f"[*] spawn {pid}", flush=True)
s = d.attach(pid)
s.create_script(ART_CALLSITE.read_text()).load()
seen = set()
regs = []
def on_m(m, data):
p = m.get("payload") or {}
t = p.get("type")
if t == "rn":
for mm in p.get("methods", []):
k = (mm["name"], mm["sig"], mm["mod"], mm["off"])
if k not in seen:
seen.add(k)
regs.append(mm)
print(f"[RN] {mm['name']} {mm['sig']} @ {mm['mod']} {mm['off']}", flush=True)
elif t in ("rn-hooked", "cls-loaded", "trigger", "b100", "b100err", "err", "tid"):
print(f"[*] {t}: {json.dumps(p, ensure_ascii=False)[:220]}", flush=True)
elif t in ("ol", "ol-out", "jni-hooked"):
print(f"[*] {t}: {json.dumps(p, ensure_ascii=False)[:160]}", flush=True)
elif m.get("type") == "error":
print("[JS-ERR]", m.get("description")[:120], flush=True)
sc = s.create_script(JS)
sc.on("message", on_m)
sc.load()
d.resume(pid)
time.sleep(17)
print(f"[*] total unique regs: {len(regs)}")
if __name__ == "__main__":
main()