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:
@@ -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()
|
||||
Reference in New Issue
Block a user