feat(huya): unidbg 金测试打通 - 采集器加密可复现(androidId=PDSuHx..)/GUID=0a7dfa..一致
- 输入图定案: 库内零硬件文件读, 种子=ANDROID_ID+服务端hydeviceid_config+MID, 属性层无关(X1/X2) - 真机全段解密 dump(phone_dump_hydev_full) + merge_decrypted 合入原文件 → unidbg 可直接加载 - unidbg(JDK21+patch) harness: JNI 桩(SharedPreferences日志化/Settings.Secure/NativeBridge) 金测试: androidId 加密态与 getGUID 全部与真机一致 - 遗留: NativeBridge.b(100) 原生归属库未定(非hydeviceid/udb/device-util), 当前桩替顶 - 修正结论: '单机不可铸造'判断错误, GUID=b(100)=f(androidId密, config, MID) 输入全可控
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env python3
|
||||
"""真机 FULL dump libhydeviceid.so (r-x + r-- + rw- 全部段, 含解密后的 .data/.got).
|
||||
|
||||
上次 phone_dump_hydev 只抓 r-- (代码+rodata), .data/.got/.bss 全零 —— unidbg 需要
|
||||
完整解密镜像(密钥/GOT 都在 .data). 本脚本抓到全部可读内存.
|
||||
|
||||
用法:
|
||||
/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/.venv/bin/python \
|
||||
scripts/phone_dump_hydev_full.py [serial] [remote]
|
||||
输出: evidence/diag_phone/libhydeviceid_dump_full.so
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
import 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"
|
||||
REPO = Path("/Users/yml/codes/douyu_login_py")
|
||||
OUT = REPO / "evidence" / "diag_phone" / "libhydeviceid_dump_full.so"
|
||||
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';
|
||||
var dumped = false;
|
||||
function tryDump(){
|
||||
if(dumped) return;
|
||||
var md = Process.findModuleByName('libhydeviceid.so');
|
||||
if(!md) return;
|
||||
dumped = true;
|
||||
var ranges = md.enumerateRanges('r--').concat(md.enumerateRanges('rw-'));
|
||||
send({type:'plan', count:ranges.length, modBase:''+md.base});
|
||||
ranges.forEach(function(r, i){
|
||||
try{
|
||||
var buf = Memory.readByteArray(r.base, r.size);
|
||||
send({type:'seg', i:i, n:ranges.length, off:''+r.base.sub(md.base), size:r.size, prot:r.protection}, buf);
|
||||
}catch(e){ send({type:'seg-err', i:i, off:''+r.base.sub(md.base), e:String(e)}); }
|
||||
});
|
||||
send({type:'done', count:ranges.length});
|
||||
}
|
||||
setInterval(tryDump, 500);
|
||||
"""
|
||||
|
||||
|
||||
def adb(*a):
|
||||
return subprocess.run(["adb", "-s", SERIAL, *a], capture_output=True, text=True)
|
||||
|
||||
|
||||
def main():
|
||||
OUT.parent.mkdir(parents=True, exist_ok=True)
|
||||
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={pid}", flush=True)
|
||||
session = d.attach(pid)
|
||||
session.on("detached", lambda r, dd: print(f"[*] detached {r} {dd}", flush=True))
|
||||
session.create_script(ART_CALLSITE.read_text()).load()
|
||||
result = {"got": 0, "expected": 0, "base": 0}
|
||||
fh = OUT.open("wb")
|
||||
|
||||
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 == "plan":
|
||||
result["expected"] = p["count"]
|
||||
result["base"] = int(p["modBase"], 16)
|
||||
print(f"[*] 计划 dump {p['count']} 段, module base={p['modBase']}", flush=True)
|
||||
elif t == "seg":
|
||||
off = int(p["off"], 16)
|
||||
fh.seek(off)
|
||||
fh.write(data)
|
||||
result["got"] += 1
|
||||
print(f"[*] seg {p['i']+1}/{p['n']} off=0x{p['off']} size={p['size']} prot={p.get('prot')}", flush=True)
|
||||
elif t == "done":
|
||||
print(f"[*] dump done {result['got']}/{result['expected']}", flush=True)
|
||||
elif t == "seg-err":
|
||||
print(f"[seg-err] {p}", flush=True)
|
||||
|
||||
sc = session.create_script(JS)
|
||||
sc.on("message", on_message)
|
||||
sc.load()
|
||||
d.resume(pid)
|
||||
t0 = time.time()
|
||||
while (result["got"] < result["expected"] or result["expected"] == 0) and time.time() - t0 < 25:
|
||||
time.sleep(0.5)
|
||||
fh.close()
|
||||
OUT.chmod(0o644)
|
||||
print(f"[*] done got={result['got']}/{result['expected']} base=0x{result['base']:x} -> {OUT}", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,292 @@
|
||||
#!/usr/bin/env python3
|
||||
"""真机 E2-v4: 身份输入全捕获 — svc 位点 + libc FILE* + Java 侧候选 API.
|
||||
|
||||
背景: GUID 与 serialno/ANDROID_ID/ro.product.*/IMEI属性/ro.boot.cpuid/全清数据 均无关,
|
||||
属性层完全不可触碰 -> 种子来自 (a)内联 svc 文件读(SoC serial 等) (b)JNI->Java
|
||||
(TelephonyManager/NetworkInterface/蓝牙) (c)TEE. libc 钩子看不见 (a)/(b).
|
||||
|
||||
本脚本三层齐抓:
|
||||
1. svc 位点 (dump 定位的 6 个 svc #0): x8=系统调用号 + x0-x3 参数 + (read->内容)
|
||||
2. libc fopen64/fopen/fread/fgets (兜底 FILE* 路径)
|
||||
3. Java 侧: TelephonyManager.getImei/getDeviceId/getSubscriberId/SimSerial、NetworkInterface
|
||||
getHardwareAddress/getNetworkInterfaces、BluetoothAdapter.getAddress/getName、
|
||||
Settings.Secure/Global.getString —— 带参数与返回值
|
||||
窗口: init/get* 全部标注 fn; 6s 后触发一次 Java 重算(捕获 Java 侧).
|
||||
|
||||
用法:
|
||||
/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/.venv/bin/python \
|
||||
scripts/phone_identity_inputs.py [serial] [remote]
|
||||
输出: evidence/diag_phone/identity_inputs.json
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import 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"
|
||||
REPO = Path("/Users/yml/codes/douyu_login_py")
|
||||
OUT = REPO / "evidence" / "diag_phone" / "identity_inputs.json"
|
||||
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
|
||||
ART_CALLSITE = RE / "evidence" / "scripts" / "bypass_msaoaid_maps_art_callsite.js"
|
||||
|
||||
WINDOW_OFFS = {
|
||||
"init": 0x20E6E4, "getGUID": 0x20F8C8, "getCDID": 0x20EB08,
|
||||
"getSDID": 0x20EDE8, "getHDID": 0x20F0C8, "getMID": 0x20FBA8,
|
||||
}
|
||||
SVC_SITES = [0x30BEF4, 0x30BF04, 0x30BF14, 0x30BF24, 0x30BF34, 0x30BF58]
|
||||
|
||||
SYSCALLS = {
|
||||
29: "ioctl", 48: "faccessat", 56: "openat", 57: "close", 63: "read", 64: "write",
|
||||
65: "readv", 66: "writev", 67: "pread64", 79: "fstat", 61: "lseek",
|
||||
97: "futex", 99: "set_robust_list", 113: "clock_gettime", 178: "gettid",
|
||||
222: "mmap", 226: "mprotect", 278: "getrandom", 291: "statx", 263: "faccessat2",
|
||||
}
|
||||
|
||||
JS_TEMPLATE = r"""
|
||||
'use strict';
|
||||
var LIB = 'libhydeviceid.so';
|
||||
var WINDOW = __WINDOW_JSON__;
|
||||
var SITES = __SITES_JSON__;
|
||||
var SYSCALL = __SYSCALL_JSON__;
|
||||
var libBase = null, libSize = 0;
|
||||
var inLibWindow = 0;
|
||||
var hooked = false;
|
||||
|
||||
function inWindow(){ return inLibWindow > 0; }
|
||||
function inLibCall(inv){
|
||||
try{
|
||||
var ra = inv.returnAddress;
|
||||
if (!ra || ra.isNull()) return false;
|
||||
return (ra.compare(libBase) >= 0 && ra.compare(libBase.add(libSize)) < 0);
|
||||
}catch(e){ return false; }
|
||||
}
|
||||
function snap(inv){
|
||||
var c = inv.context;
|
||||
return { x0: ptr(c.x0), x1: ptr(c.x1), x2: ptr(c.x2), x3: ptr(c.x3), x4: ptr(c.x4), x5: ptr(c.x5), x8: ptr(c.x8) };
|
||||
}
|
||||
|
||||
function hookAll(){
|
||||
if(hooked) return;
|
||||
var md = Process.findModuleByName(LIB);
|
||||
if(!md){ return; }
|
||||
libBase = md.base; libSize = md.size;
|
||||
hooked = true;
|
||||
|
||||
Object.keys(WINDOW).forEach(function(fn){
|
||||
try{
|
||||
Interceptor.attach(libBase.add(parseInt(WINDOW[fn], 16)), {
|
||||
onEnter: function(){ inLibWindow++; send({type:'w-open', fn:fn, tid:Process.getCurrentThreadId()}); },
|
||||
onLeave: function(){ inLibWindow--; send({type:'w-close', fn:fn}); }
|
||||
});
|
||||
}catch(e){ send({type:'w-err', fn:fn, e:String(e)}); }
|
||||
});
|
||||
send({type:'windows-hooked'});
|
||||
|
||||
function addHook(name, t, onEnter, onLeave){
|
||||
try{
|
||||
if(!t) return;
|
||||
Interceptor.attach(t, {
|
||||
onEnter: function(){
|
||||
if(!inWindow()) return;
|
||||
if(!inLibCall(this)) return;
|
||||
var s = snap(this);
|
||||
this._valid = true;
|
||||
try{ onEnter(this, s); }catch(e){ send({type:'hook-err', name:name, e:String(e).slice(0,120)}); }
|
||||
},
|
||||
onLeave: function(){
|
||||
if(!this._valid) return;
|
||||
try{ onLeave(this); }catch(e){ send({type:'hook-err', name:name, e:String(e).slice(0,120)}); }
|
||||
}
|
||||
});
|
||||
}catch(e){ send({type:'attach-err', name:name, e:String(e).slice(0,120)}); }
|
||||
}
|
||||
|
||||
// ---- 1) svc 位点: 全 syscall census (x8 在 onEnter 需读寄存器, 此处特化)
|
||||
SITES.forEach(function(site){
|
||||
try{
|
||||
Interceptor.attach(libBase.add(site), {
|
||||
onEnter: function(){
|
||||
if(!inWindow()) return;
|
||||
var s = snap(this);
|
||||
this._nr = s.x8.toInt32();
|
||||
this._s = s;
|
||||
this._valid = true;
|
||||
var name = SYSCALL[this._nr]||('nr'+this._nr);
|
||||
var extra = '';
|
||||
if ((name==='faccessat'||name==='openat'||name==='faccessat2') ) {
|
||||
try{ extra = s.x1.readCString()||'?'; }catch(e){ extra='?'; }
|
||||
}
|
||||
send({type:'svc', site:site, nr:this._nr, name:name, p:extra,
|
||||
a0:s.x0.toString(), a1:s.x1.toString(), a2:s.x2.toString(), a3:s.x3.toString(),
|
||||
tid:Process.getCurrentThreadId()});
|
||||
},
|
||||
onLeave: function(){
|
||||
if(!this._valid) return;
|
||||
var name = SYSCALL[this._nr]||('nr'+this._nr);
|
||||
var ret = 0;
|
||||
try{ ret = this.context.x0.toInt32(); }catch(e){}
|
||||
if ((name==='read'||name==='pread64') && ret>0){
|
||||
var cnt = Math.min(ret,512);
|
||||
var hex='';
|
||||
try{ hex = this._s.x1.readByteArray(cnt) ? Array.from(new Uint8Array(this._s.x1.readByteArray(cnt))).map(function(b){return ('0'+b.toString(16)).slice(-2);}).join('') : ''; }catch(e){}
|
||||
send({type:'svc-read', site:site, nr:this._nr, name:name, n:ret, hex:hex, tid:Process.getCurrentThreadId()});
|
||||
} else if (name==='openat' && ret>=0){
|
||||
var p='';
|
||||
try{ p = this._s.x1.readCString()||'?'; }catch(e){ p='?'; }
|
||||
send({type:'svc-open', site:site, nr:this._nr, name:name, p:p, fd:ret, tid:Process.getCurrentThreadId()});
|
||||
} else if (name==='ioctl'){
|
||||
send({type:'svc-ioctl', site:site, fd:this._s.x0.toInt32(), req:this._s.x1.toInt32(), ret:ret, tid:Process.getCurrentThreadId()});
|
||||
}
|
||||
}
|
||||
});
|
||||
}catch(e){ send({type:'attach-err', name:'svc@'+site.toString(16), e:String(e)}); }
|
||||
});
|
||||
send({type:'svc-hooked'});
|
||||
|
||||
// ---- 2) libc FILE* 兜底
|
||||
addHook('libc:fopen64', Module.findExportByName('libc.so','fopen64'),
|
||||
function(th,s){ th._p = s.x0.readCString()||'?'; },
|
||||
function(th){ send({type:'fopen', p:th._p, ret:th.returnValue.toString(), tid:Process.getCurrentThreadId()}); });
|
||||
addHook('libc:fopen', Module.findExportByName('libc.so','fopen'),
|
||||
function(th,s){ th._p = s.x0.readCString()||'?'; },
|
||||
function(th){ send({type:'fopen', p:th._p, ret:th.returnValue.toString(), tid:Process.getCurrentThreadId()}); });
|
||||
addHook('libc:fread', Module.findExportByName('libc.so','fread'),
|
||||
function(th,s){ th._buf=s.x0; th._len=s.x2.toInt32(); },
|
||||
function(th){ var n=th.returnValue.toInt32(); var hex='';
|
||||
if(n>0){ try{ hex = th._buf.readByteArray(Math.min(n*1,512)) ? Array.from(new Uint8Array(th._buf.readByteArray(Math.min(n*1,512)))).map(function(b){return ('0'+b.toString(16)).slice(-2);}).join('') : ''; }catch(e){} }
|
||||
send({type:'fread-c', n:n, hex:hex, tid:Process.getCurrentThreadId()}); });
|
||||
addHook('libc:__system_property_get', Module.findExportByName('libc.so','__system_property_get'),
|
||||
function(th,s){ th._key=s.x0.readCString()||''; th._vp=s.x1; },
|
||||
function(th){ var v=''; try{ v=th._vp.readCString()||''; }catch(e){}
|
||||
send({type:'prop', k:th._key, v:v, tid:Process.getCurrentThreadId()}); });
|
||||
|
||||
send({type:'libc-hooked'});
|
||||
}
|
||||
|
||||
setInterval(function(){ try{ hookAll(); }catch(e){} }, 150);
|
||||
|
||||
// ---- 3) Java 侧候选 API (启动后装)
|
||||
function hookJava(){
|
||||
try{
|
||||
Java.perform(function(){
|
||||
function wrap(cls, m, showArgs){
|
||||
try{
|
||||
Java.use(cls)[m].overloads.forEach(function(ov){
|
||||
ov.implementation = function(){
|
||||
var r;
|
||||
try{ r = ov.apply(this, arguments); }catch(e){ throw e; }
|
||||
var args = [];
|
||||
try{ if(showArgs){ for(var i=0;i<arguments.length;i++){ var a=arguments[i]; args.push((a!==null&&a!==undefined)?String(a):''); } } }catch(e){}
|
||||
send({type:'java', cls:cls, m:m, r:(r===null||r===undefined)?'null':String(r), args:args, tid:Process.getCurrentThreadId()});
|
||||
return r;
|
||||
};
|
||||
});
|
||||
}catch(e){ send({type:'java-err', cls:cls, m:m, e:String(e).slice(0,120)}); }
|
||||
}
|
||||
wrap('android.telephony.TelephonyManager','getImei',false);
|
||||
wrap('android.telephony.TelephonyManager','getDeviceId',false);
|
||||
wrap('android.telephony.TelephonyManager','getSubscriberId',false);
|
||||
wrap('android.telephony.TelephonyManager','getSimSerialNumber',false);
|
||||
wrap('android.telephony.TelephonyManager','getNetworkOperator',false);
|
||||
wrap('java.net.NetworkInterface','getHardwareAddress',false);
|
||||
wrap('java.net.NetworkInterface','getNetworkInterfaces',false);
|
||||
wrap('android.bluetooth.BluetoothAdapter','getAddress',false);
|
||||
wrap('android.bluetooth.BluetoothAdapter','getName',false);
|
||||
wrap('android.provider.Settings$Secure','getString',true);
|
||||
wrap('android.provider.Settings$Global','getString',true);
|
||||
wrap('android.os.Build','getSerial',false);
|
||||
send({type:'java-hooked'});
|
||||
});
|
||||
}catch(e){ send({type:'java-setup-err', e:String(e).slice(0,150)}); }
|
||||
}
|
||||
setTimeout(hookJava, 4500);
|
||||
|
||||
setTimeout(function(){
|
||||
Java.perform(function(){
|
||||
try{
|
||||
var NE = Java.use('com.huya.security.hydeviceid.NativeEntry');
|
||||
send({type:'cls-ok'});
|
||||
NE.init();
|
||||
var out = {};
|
||||
['getGUID','getCDID','getHDID','getMID','getSDID'].forEach(function(m){
|
||||
try{ out[m] = NE[m]()||''; send({type:'val', m:m, v:out[m]}); }catch(e){ out[m]='ERR'; }
|
||||
});
|
||||
send({type:'all-vals', out:out});
|
||||
}catch(e){ send({type:'java-err', e:String(e).slice(0,150)}); }
|
||||
});
|
||||
}, 6000);
|
||||
"""
|
||||
|
||||
|
||||
def adb(*a):
|
||||
return subprocess.run(["adb", "-s", SERIAL, *a], capture_output=True, text=True)
|
||||
|
||||
|
||||
def main():
|
||||
OUT.parent.mkdir(parents=True, exist_ok=True)
|
||||
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={pid}", flush=True)
|
||||
session = d.attach(pid)
|
||||
det = []
|
||||
session.on("detached", lambda r, dd: det.append({"reason": r, "detail": str(dd)}) or print(f"[*] detached {r} {dd}", flush=True))
|
||||
session.create_script(ART_CALLSITE.read_text()).load()
|
||||
result = {"events": [], "values": {}, "detached": det}
|
||||
printed = [0]
|
||||
|
||||
def on_message(m, data):
|
||||
if m.get("type") == "error":
|
||||
print("[JS-ERR]", str(m)[:180], flush=True); return
|
||||
p = m.get("payload") or {}
|
||||
t = p.get("type")
|
||||
if t in ("svc","svc-read","svc-open","svc-ioctl","fopen","fread-c","prop","java"):
|
||||
result["events"].append(p)
|
||||
if printed[0] < 800:
|
||||
printed[0] += 1
|
||||
if t=="svc" and p.get("p"): print(f"[svc] {p.get('name')} {p.get('p')}", flush=True)
|
||||
elif t=="svc": print(f"[svc] {p.get('name')} a0={p.get('a0')} a1={p.get('a1')} a2={p.get('a2')}", flush=True)
|
||||
elif t=="svc-read": print(f"[svc-read] {p.get('name')} n={p.get('n')} hex={p.get('hex','')[:70]}", flush=True)
|
||||
elif t=="svc-open": print(f"[svc-open] {p.get('p')} fd={p.get('fd')}", flush=True)
|
||||
elif t=="svc-ioctl": print(f"[svc-ioctl] fd={p.get('fd')} req={p.get('req')} ret={p.get('ret')}", flush=True)
|
||||
elif t=="fopen": print(f"[fopen] {p.get('p')} ret={p.get('ret')}", flush=True)
|
||||
elif t=="fread-c": print(f"[fread] n={p.get('n')} hex={p.get('hex','')[:70]}", flush=True)
|
||||
elif t=="prop": print(f"[prop] {p.get('k')}={p.get('v')}", flush=True)
|
||||
elif t=="java": print(f"[java] {p.get('cls')}.{p.get('m')}({p.get('args','')}) -> {p.get('r')}", flush=True)
|
||||
elif t == "val":
|
||||
result["values"][p["m"]] = p["v"]
|
||||
print(f"[*] {p['m']} = {p['v']}", flush=True)
|
||||
elif t == "all-vals":
|
||||
result["values"].update(p["out"])
|
||||
elif t in ("w-open","w-close","windows-hooked","svc-hooked","libc-hooked","java-hooked","cls-ok"):
|
||||
print(f"[*] {t}", flush=True)
|
||||
elif "err" in t:
|
||||
print(f"[!] {t}: {p.get('name','')} {p.get('e')}", flush=True)
|
||||
|
||||
JS = (JS_TEMPLATE
|
||||
.replace("__WINDOW_JSON__", json.dumps({k: hex(v) for k, v in WINDOW_OFFS.items()}))
|
||||
.replace("__SITES_JSON__", json.dumps([hex(s) for s in SVC_SITES]))
|
||||
.replace("__SYSCALL_JSON__", json.dumps(SYSCALLS)))
|
||||
sc = session.create_script(JS)
|
||||
sc.on("message", on_message)
|
||||
sc.load()
|
||||
d.resume(pid)
|
||||
print("[*] resumed; 等待自然 init + 4.5s Java 钩子 + 6s 触发重算", flush=True)
|
||||
time.sleep(12)
|
||||
print("[*] 采集结束", flush=True)
|
||||
time.sleep(2)
|
||||
result["detached"] = det
|
||||
OUT.write_text(json.dumps(result, indent=1, ensure_ascii=False), encoding="utf-8")
|
||||
print(f"[*] saved {OUT} (events={len(result['events'])})", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,300 @@
|
||||
#!/usr/bin/env python3
|
||||
"""真机 E2 v2: init()/get* 库内定向输入图 — 带参数值快照的完整输入清单.
|
||||
|
||||
v2 关键修正:
|
||||
- lib 文件/ioctl 读走自身 hy_syscall(内联 svc), libc 钩子看不见
|
||||
(E2 v1 实测: 窗口内只有 futex/gettid 类系统调用). 直挂:
|
||||
hy_syscall @0x30bf3c (nr=a1..a3 -> 解码 openat/read/ioctl/close...)
|
||||
hyfopen64 @0xf1298 (path)
|
||||
hy_read @0x30bef0 (fp/buf/len, 试探性记录)
|
||||
- 窗口从"单线程"改为"全局计数"(init 可能跨线程); 归属过滤用
|
||||
returnAddress 是否落在 lib 区间(精确, 不用 FUZZY backtrace)
|
||||
- 参数全部从 this.context.xN 寄存器快照取(跨 frida 版本稳定)
|
||||
|
||||
用法:
|
||||
/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/.venv/bin/python \
|
||||
scripts/phone_init_inputmap.py [serial] [remote]
|
||||
输出:
|
||||
evidence/diag_phone/init_inputmap.json
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import 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"
|
||||
REPO = Path("/Users/yml/codes/douyu_login_py")
|
||||
OUT = REPO / "evidence" / "diag_phone" / "init_inputmap.json"
|
||||
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
|
||||
ART_CALLSITE = RE / "evidence" / "scripts" / "bypass_msaoaid_maps_art_callsite.js"
|
||||
|
||||
WINDOW_OFFS = {
|
||||
"init": 0x20E6E4,
|
||||
"getGUID": 0x20F8C8,
|
||||
"getCDID": 0x20EB08,
|
||||
"getSDID": 0x20EDE8,
|
||||
"getHDID": 0x20F0C8,
|
||||
"getMID": 0x20FBA8,
|
||||
}
|
||||
HY_SYSCALL_OFF = 0x30BF3C
|
||||
HY_FOPEN64_OFF = 0xF1298
|
||||
HY_READ_OFF = 0x30BEF0
|
||||
|
||||
# aarch64 关键系统调用号 (linux asm-generic)
|
||||
SYSCALLS = {
|
||||
29: "ioctl", 56: "openat", 57: "close", 63: "read", 64: "write",
|
||||
65: "readv", 67: "pread64", 78: "readlinkat", 79: "fstat", 61: "lseek",
|
||||
97: "futex", 98: "futex2", 99: "set_robust_list", 113: "clock_gettime",
|
||||
169: "gettimeofday", 171: "adjtimex", 174: "rt_sigaction", 178: "gettid",
|
||||
222: "mmap", 226: "mprotect", 278: "getrandom", 291: "statx",
|
||||
263: "faccessat2", 258: "newfstatat",
|
||||
}
|
||||
|
||||
JS_TEMPLATE = r"""
|
||||
'use strict';
|
||||
var LIB = 'libhydeviceid.so';
|
||||
var WINDOW = __WINDOW_JSON__;
|
||||
var libBase = null, libSize = 0;
|
||||
var inLibWindow = 0;
|
||||
var hooked = false;
|
||||
var SYSCALL = __SYSCALL_JSON__;
|
||||
|
||||
function inWindow(){ return inLibWindow > 0; }
|
||||
|
||||
function inLibCall(inv){
|
||||
try{
|
||||
var ra = inv.returnAddress;
|
||||
if (!ra || ra.isNull()) return false;
|
||||
return (ra.compare(libBase) >= 0 && ra.compare(libBase.add(libSize)) < 0);
|
||||
}catch(e){ return false; }
|
||||
}
|
||||
|
||||
function snap(inv){
|
||||
var c = inv.context;
|
||||
return { x0: ptr(c.x0), x1: ptr(c.x1), x2: ptr(c.x2), x3: ptr(c.x3) };
|
||||
}
|
||||
|
||||
function hookAll(){
|
||||
if(hooked) return;
|
||||
var md = Process.findModuleByName(LIB);
|
||||
if(!md){ return; }
|
||||
libBase = md.base; libSize = md.size;
|
||||
hooked = true;
|
||||
|
||||
Object.keys(WINDOW).forEach(function(fn){
|
||||
try{
|
||||
Interceptor.attach(libBase.add(parseInt(WINDOW[fn], 16)), {
|
||||
onEnter: function(){ inLibWindow++; send({type:'w-open', fn:fn, tid:Process.getCurrentThreadId()}); },
|
||||
onLeave: function(){ inLibWindow--; send({type:'w-close', fn:fn}); }
|
||||
});
|
||||
}catch(e){ send({type:'w-err', fn:fn, e:String(e)}); }
|
||||
});
|
||||
send({type:'windows-hooked'});
|
||||
|
||||
function addHook(name, t, onEnter, onLeave){
|
||||
try{
|
||||
if(!t) return;
|
||||
Interceptor.attach(t, {
|
||||
onEnter: function(){
|
||||
if(!inWindow()) return;
|
||||
if(!inLibCall(this)) return;
|
||||
var s = snap(this);
|
||||
this._valid = true;
|
||||
try{ onEnter(this, s); }catch(e){ send({type:'hook-err', name:name, e:String(e).slice(0,120)}); }
|
||||
},
|
||||
onLeave: function(){
|
||||
if(!this._valid) return;
|
||||
try{ onLeave(this); }catch(e){ send({type:'hook-err', name:name, e:String(e).slice(0,120)}); }
|
||||
}
|
||||
});
|
||||
}catch(e){ send({type:'attach-err', name:name, e:String(e).slice(0,120)}); }
|
||||
}
|
||||
|
||||
// ---- libc 层 (兜底: 仍可能走 libc 的路径)
|
||||
addHook('libc:__system_property_get', Module.findExportByName('libc.so', '__system_property_get'),
|
||||
function(th, s){
|
||||
th._key = s.x0.readCString() || '';
|
||||
th._vp = s.x1;
|
||||
}, function(th){
|
||||
var v = '';
|
||||
try{ v = th._vp.readCString() || ''; }catch(e){}
|
||||
send({type:'prop', tid:Process.getCurrentThreadId(), k:th._key, v:v});
|
||||
});
|
||||
|
||||
addHook('libc:openat', Module.findExportByName('libc.so', 'openat'),
|
||||
function(th, s){ th._p = s.x1.readCString() || '?'; },
|
||||
function(th){ send({type:'open', tid:Process.getCurrentThreadId(), p:th._p, fd:th.returnValue.toInt32()}); });
|
||||
|
||||
addHook('libc:open', Module.findExportByName('libc.so', 'open'),
|
||||
function(th, s){ th._p = s.x0.readCString() || '?'; },
|
||||
function(th){ send({type:'open', tid:Process.getCurrentThreadId(), p:th._p, fd:th.returnValue.toInt32()}); });
|
||||
|
||||
['read','pread','pread64'].forEach(function(n){
|
||||
addHook('libc:'+n, Module.findExportByName('libc.so', n),
|
||||
function(th, s){
|
||||
th._fd = s.x0.toInt32(); th._buf = s.x1; th._len = s.x2.toInt32();
|
||||
}, function(th){
|
||||
var n = th.returnValue.toInt32();
|
||||
if(n <= 0) return;
|
||||
var cnt = Math.min(n, 512);
|
||||
var hex = '';
|
||||
try{ hex = th._buf.readByteArray(cnt) ? Array.from(new Uint8Array(th._buf.readByteArray(cnt))).map(function(b){return ('0'+b.toString(16)).slice(-2);}).join('') : ''; }catch(e){}
|
||||
send({type:'read', tid:Process.getCurrentThreadId(), fd:th._fd, n:n, hex:hex});
|
||||
});
|
||||
});
|
||||
|
||||
addHook('libc:ioctl', Module.findExportByName('libc.so', 'ioctl'),
|
||||
function(th, s){ th._fd = s.x0.toInt32(); th._req = s.x1.toInt32(); },
|
||||
function(th){ send({type:'ioctl', tid:Process.getCurrentThreadId(), fd:th._fd, req:th._req}); });
|
||||
|
||||
addHook('libc:syscall', Module.findExportByName('libc.so', 'syscall'),
|
||||
function(th, s){ th._nr = s.x0.toInt32(); th._a1 = s.x1.toInt32(); th._a2 = s.x2.toInt32(); },
|
||||
function(th){ send({type:'sys', tid:Process.getCurrentThreadId(), nr:th._nr, a1:th._a1, a2:th._a2}); });
|
||||
|
||||
// ---- lib 自身壳 (主路径)
|
||||
addHook('hy_syscall', libBase.add(HY_SYSCALL_OFF),
|
||||
function(th, s){
|
||||
th._nr = s.x0.toInt32(); th._a1 = s.x1; th._a2 = s.x2; th._a3 = s.x3;
|
||||
var name = SYSCALL[th._nr] || ('nr'+th._nr);
|
||||
send({type:'hysys', tid:Process.getCurrentThreadId(), nr:th._nr, name:name,
|
||||
a1:th._a1.toString(), a2:th._a2.toString(), a3:th._a3.toString()});
|
||||
}, function(th){
|
||||
var name = SYSCALL[th._nr] || ('nr'+th._nr);
|
||||
if (name === 'read' || name === 'pread64') {
|
||||
var n = th.returnValue.toInt32();
|
||||
if (n > 0) {
|
||||
var cnt = Math.min(n, 512);
|
||||
var hex = '';
|
||||
try{ hex = th._a2.readByteArray(cnt) ? Array.from(new Uint8Array(th._a2.readByteArray(cnt))).map(function(b){return ('0'+b.toString(16)).slice(-2);}).join('') : ''; }catch(e){}
|
||||
send({type:'hysys-read', tid:Process.getCurrentThreadId(), nr:th._nr, name:name, n:n, hex:hex});
|
||||
}
|
||||
} else if (name === 'openat') {
|
||||
try{ var p = th._a1.readCString() || '?'; send({type:'hysys-open', tid:Process.getCurrentThreadId(), p:p, fd:th.returnValue.toInt32()}); }catch(e){}
|
||||
}
|
||||
});
|
||||
|
||||
addHook('hyfopen64', libBase.add(HY_FOPEN64_OFF),
|
||||
function(th, s){ try{ th._p = s.x0.readCString() || '?'; }catch(e){ th._p = '?'; } },
|
||||
function(th){ send({type:'hyfopen', tid:Process.getCurrentThreadId(), p:th._p, ret:th.returnValue.toString()}); });
|
||||
|
||||
addHook('hy_read', libBase.add(HY_READ_OFF),
|
||||
function(th, s){
|
||||
th._x0 = s.x0.toString(); th._buf = s.x1; th._len = s.x2.toInt32();
|
||||
}, function(th){
|
||||
var n = th.returnValue.toInt32();
|
||||
var hex = '';
|
||||
if (n > 0) { try{ hex = th._buf.readByteArray(Math.min(n,512)) ? Array.from(new Uint8Array(th._buf.readByteArray(Math.min(n,512)))).map(function(b){return ('0'+b.toString(16)).slice(-2);}).join('') : ''; }catch(e){} }
|
||||
send({type:'hyread', tid:Process.getCurrentThreadId(), x0:th._x0, len:th._len, n:n, hex:hex});
|
||||
});
|
||||
|
||||
send({type:'libc-hooked'});
|
||||
}
|
||||
|
||||
setInterval(function(){ try{ hookAll(); }catch(e){} }, 150);
|
||||
|
||||
setTimeout(function(){
|
||||
Java.perform(function(){
|
||||
try{
|
||||
var NE = Java.use('com.huya.security.hydeviceid.NativeEntry');
|
||||
send({type:'cls-ok'});
|
||||
var out = {};
|
||||
['init','getGUID','getCDID','getHDID','getMID','getSDID'].forEach(function(m){
|
||||
try{
|
||||
if(m==='init'){ NE[m](); out[m]='ok'; }
|
||||
else{
|
||||
var v = NE[m]();
|
||||
out[m] = v;
|
||||
send({type:'val', m:m, v:v});
|
||||
}
|
||||
}catch(e){ out[m]='ERR:'+String(e).slice(0,60); }
|
||||
});
|
||||
send({type:'all-vals', out:out});
|
||||
}catch(e){ send({type:'java-err', e:String(e).slice(0,150)}); }
|
||||
});
|
||||
}, 6000);
|
||||
"""
|
||||
|
||||
|
||||
def adb(*a):
|
||||
return subprocess.run(["adb", "-s", SERIAL, *a], capture_output=True, text=True)
|
||||
|
||||
|
||||
def main():
|
||||
OUT.parent.mkdir(parents=True, exist_ok=True)
|
||||
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={pid}", flush=True)
|
||||
session = d.attach(pid)
|
||||
det = []
|
||||
session.on("detached", lambda r, dd: det.append({"reason": r, "detail": str(dd)}) or print(f"[*] detached {r} {dd}", flush=True))
|
||||
session.create_script(ART_CALLSITE.read_text()).load()
|
||||
result = {"events": [], "values": {}, "lib": None, "detached": det}
|
||||
|
||||
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 ("prop", "open", "ioctl", "read", "sys", "hysys", "hysys-read", "hysys-open", "hyfopen", "hyread"):
|
||||
result["events"].append(p)
|
||||
# 打印去重: 同一(key)只打印前几次
|
||||
key = (t, p.get("k") or p.get("p") or p.get("name") or str(p.get("nr")) or str(p.get("fd")), p.get("v") or str(p.get("n") or ""))
|
||||
if result["values"].get("_print") is None: result["values"]["_print"] = 0
|
||||
if result["values"]["_print"] < 400:
|
||||
result["values"]["_print"] += 1
|
||||
if t == "hysys":
|
||||
print(f"[hysys] {p.get('name')} nr={p.get('nr')} a1={p.get('a1')} a2={p.get('a2')} a3={p.get('a3')}", flush=True)
|
||||
elif t == "hysys-read":
|
||||
print(f"[hysys-read] {p.get('name')} n={p.get('n')} hex={p.get('hex','')[:60]}...", flush=True)
|
||||
elif t == "hysys-open":
|
||||
print(f"[hysys-open] {p.get('p')} fd={p.get('fd')}", flush=True)
|
||||
elif t == "hyfopen":
|
||||
print(f"[hyfopen] {p.get('p')} ret={p.get('ret')}", flush=True)
|
||||
elif t == "hyread":
|
||||
print(f"[hyread] x0={p.get('x0')} len={p.get('len')} n={p.get('n')} hex={p.get('hex','')[:60]}...", flush=True)
|
||||
elif t == "prop":
|
||||
print(f"[prop] {p.get('k')}={p.get('v')}", flush=True)
|
||||
elif t == "open":
|
||||
print(f"[open] {p.get('p')} fd={p.get('fd')}", flush=True)
|
||||
elif t == "read":
|
||||
print(f"[read] fd={p.get('fd')} n={p.get('n')} hex={p.get('hex','')[:60]}...", flush=True)
|
||||
elif t == "ioctl":
|
||||
print(f"[ioctl] fd={p.get('fd')} req={p.get('req')}", flush=True)
|
||||
elif t == "sys":
|
||||
print(f"[sys] nr={p.get('nr')} a1={p.get('a1')} a2={p.get('a2')}", flush=True)
|
||||
elif t == "val":
|
||||
result["values"][p["m"]] = p["v"]
|
||||
print(f"[*] {p['m']} = {p['v']}", flush=True)
|
||||
elif t == "all-vals":
|
||||
result["values"].update(p["out"])
|
||||
elif t in ("w-open", "w-close", "windows-hooked", "libc-hooked", "cls-ok"):
|
||||
print(f"[*] {t}", flush=True)
|
||||
elif "err" in t:
|
||||
print(f"[!] {t}: {p.get('e')}", flush=True)
|
||||
|
||||
JS = JS_TEMPLATE.replace("__WINDOW_JSON__", json.dumps({k: hex(v) for k, v in WINDOW_OFFS.items()})
|
||||
).replace("__SYSCALL_JSON__", json.dumps(SYSCALLS))
|
||||
sc = session.create_script(JS)
|
||||
sc.on("message", on_message)
|
||||
sc.load()
|
||||
d.resume(pid)
|
||||
print("[*] resumed, 等待 init 窗口(6s 后主动触发 Java 调用)...", flush=True)
|
||||
time.sleep(11)
|
||||
print("[*] 采集结束", flush=True)
|
||||
time.sleep(2)
|
||||
result["detached"] = det
|
||||
OUT.write_text(json.dumps(result, indent=1, ensure_ascii=False), encoding="utf-8")
|
||||
print(f"[*] saved {OUT} (events={len(result['events'])})", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,126 @@
|
||||
#!/usr/bin/env python3
|
||||
"""真机: dlopen 拦截所有目标库, JNI_OnLoad 入口挂 RegisterNatives 钩子,
|
||||
抓 NativeBridge.klog/b 等全部 native 注册归属(库+偏移).
|
||||
|
||||
用法: python3 scripts/phone_probe_rn_all.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 fns = env.handle.readPointer();
|
||||
var rn = fns.add(215 * Process.pointerSize).readPointer();
|
||||
if(rn.isNull()) return;
|
||||
Interceptor.attach(rn, {
|
||||
onEnter: function(args){
|
||||
var methods = args[3];
|
||||
var count = args[4].toInt32();
|
||||
var 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 fnp = m.add(Process.pointerSize*2).readPointer();
|
||||
var jm = jmod(fnp);
|
||||
mm.push({name:name, sig:sig, mod:jm.n, off:jm.off});
|
||||
}catch(e){}
|
||||
}
|
||||
send({type:'rn', methods:mm, tid:Process.getCurrentThreadId()});
|
||||
}
|
||||
});
|
||||
rnHooked = true;
|
||||
send({type:'rn-hooked'});
|
||||
}catch(e){ send({type:'err', e:String(e).slice(0,120)}); }
|
||||
}
|
||||
setInterval(hookRN, 100);
|
||||
|
||||
// 目标库加载即挂 JNI_OnLoad
|
||||
var TARGETS = ['libhydeviceid', 'libudbauthunify', 'libudb'];
|
||||
var loaded = {};
|
||||
function hookLib(md){
|
||||
if(loaded[''+md.base]) return;
|
||||
loaded[''+md.base] = true;
|
||||
var jni = md.findExportByName('JNI_OnLoad');
|
||||
if(!jni) return;
|
||||
var name = md.name;
|
||||
send({type:'lib', name:name, base:''+md.base});
|
||||
Interceptor.attach(jni, {
|
||||
onEnter: function(){ send({type:'ol-enter', lib:name}); hookRN(); },
|
||||
onLeave: function(ret){ send({type:'ol-leave', lib:name, ret:ret.toString()}); }
|
||||
});
|
||||
}
|
||||
setInterval(function(){
|
||||
Process.enumerateModules().forEach(function(md){
|
||||
for(var i=0;i<TARGETS.length;i++){
|
||||
if(md.name.indexOf(TARGETS[i]) >= 0){ hookLib(md); break; }
|
||||
}
|
||||
});
|
||||
}, 100);
|
||||
"""
|
||||
|
||||
|
||||
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",):
|
||||
print("[*] rn-hooked", flush=True)
|
||||
elif t == "lib":
|
||||
print(f"[*] lib {p['name']} base={p['base']}", flush=True)
|
||||
elif t in ("ol-enter", "ol-leave"):
|
||||
print(f"[*] {t} {p.get('lib')} {p.get('ret','')}", flush=True)
|
||||
elif t == "err":
|
||||
print("[!]", p, 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(20)
|
||||
print(f"[*] total unique: {len(regs)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user