docs(huya): 逆向定位 32hex hdid = NativeEntry.getGUID() + 真机注入通道证据
核心结论:
- 32hex hdid (WUP 登录帧 field1.tag0) = com.huya.security.hydeviceid.NativeEntry.getGUID()
- 金样本 ed0db8 为金样本设备 GUID; 当前真机 M2102J2SC getGUID()=0a7dfaa882938a6ab502511452142c57(稳定)
- NativeEntry 全字段: getGUID(32hex)/getCDID(40hex)/getHDID(40hex)/getMID(16hex)/getSDID(base64 safedeviceid)
- 单机不可铸造: 改 serialno/ANDROID_ID/删files/hydevice持久化 均不变(硬锚底层硬件)
- libhydeviceid.so 为 OLLVM+datadiv 壳; 用 dlopen->JNI_OnLoad->RegisterNatives(env表索引215) 绕开混淆
脚本: phone_probe_registernatives2(注册表)/phone_java_call_nativeentry(Java读getGUID)/phone_read_hdid(native hook)
phone_dump_hydev(dump解密so)/phone_probe_login_frame(登录帧)/arm64_disasm(反汇编)
证据: hdid_regtable/java_nativeentry/hdid_read/login_frames/hdid_probe* + libhydeviceid_{original,dump}.so
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python3
|
||||
"""通用 aarch64 反汇编工具: 分析内存 dump 的 libhydeviceid.so (解密后).
|
||||
|
||||
用法:
|
||||
arm64_disasm.py <dump.so> <模块基址> <偏移hex> [长度字节]
|
||||
例:
|
||||
反汇编 JNI_OnLoad(0x22c4c8) 前 0x300 字节:
|
||||
arm64_disasm.py evidence/diag_phone/libhydeviceid_dump.so 0x7814211000 0x22c4c8 0x300
|
||||
打印所有 BL/BLR 分支目标到模块偏移.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from capstone import Cs, CS_ARCH_ARM64, CS_MODE_ARM
|
||||
|
||||
|
||||
def load(fn):
|
||||
with open(fn, "rb") as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
def main():
|
||||
dump_path = sys.argv[1]
|
||||
base = int(sys.argv[2], 16)
|
||||
start_off = int(sys.argv[3], 16) # 相对模块基址的偏移 (内存态文件里约等于文件偏移)
|
||||
length = int(sys.argv[4], 16) if len(sys.argv) > 4 else 0x200
|
||||
data = load(dump_path)
|
||||
md = Cs(CS_ARCH_ARM64, CS_MODE_ARM)
|
||||
md.detail = True
|
||||
code = data[start_off:start_off + length]
|
||||
print(f"# base=0x{base:x} start_off=0x{start_off:x} len=0x{length:x}")
|
||||
for insn in md.disasm(code, base + start_off):
|
||||
disp = ""
|
||||
if insn.mnemonic.startswith(("b.", "b ")) or insn.mnemonic in ("bl", "br", "blr", "cbz", "cbnz"):
|
||||
if insn.operands:
|
||||
disp = f" ; -> 0x{insn.operands[0].imm:x}"
|
||||
elif insn.mnemonic == "blr":
|
||||
disp = " ; [indirect call]"
|
||||
print(f" 0x{insn.address:x}\t{insn.mnemonic:8s} {insn.op_str}{disp}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env python3
|
||||
"""真机 dump 解密后的 libhydeviceid.so (JNI_OnLoad 壳解密完成后).
|
||||
|
||||
用真机稳定注入通道 spawn+art_callsite, 等 libhydeviceid.so 加载且壳解密
|
||||
(JNI_OnLoad 返回)后 Memory.dump 整个模块, 保存为本地 .so 供反汇编分析.
|
||||
|
||||
用法:
|
||||
/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/.venv/bin/python \
|
||||
scripts/phone_dump_hydev.py [serial] [remote]
|
||||
输出:
|
||||
evidence/diag_phone/libhydeviceid_dump.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.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--');
|
||||
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}, 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}
|
||||
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"]
|
||||
print(f"[*] 计划 dump {p['count']} 个 r-x 段", 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']}", flush=True)
|
||||
elif t == "done":
|
||||
print(f"[*] dump done, got {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)
|
||||
print("[*] resumed, waiting dump...", flush=True)
|
||||
t0 = time.time()
|
||||
while (result["got"] < result["expected"] or result["expected"] == 0) and time.time() - t0 < 20:
|
||||
time.sleep(0.5)
|
||||
fh.close()
|
||||
OUT.chmod(0o644)
|
||||
print(f"[*] done got={result['got']}/{result['expected']} -> {OUT}", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env python3
|
||||
"""真机: 主动 Java 调用 NativeEntry 的 getGUID/getMID/getCDID/getSDID/getHDID, 找 32hex hdid(field1.tag0).
|
||||
|
||||
hypasswordLogin 登录帧 field1.tag0 的 32hex hdid 未在 SSL 帧/其他 get* 中抓到,
|
||||
最可能是 getGUID()/getMID()(之前未触发) 的返回值。本脚本在 Java VM 就绪后
|
||||
用 Java.use 主动调用全部 get 方法并打印, 确定 32hex hdid 的来源方法。
|
||||
|
||||
用法:
|
||||
/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/.venv/bin/python \
|
||||
scripts/phone_java_call_nativeentry.py [serial] [remote]
|
||||
输出:
|
||||
evidence/diag_phone/java_nativeentry.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" / "java_nativeentry.json"
|
||||
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';
|
||||
setTimeout(function(){
|
||||
Java.perform(function(){
|
||||
var out = {};
|
||||
try{
|
||||
var NE = Java.use('com.huya.security.hydeviceid.NativeEntry');
|
||||
send({type:'class-ok', cls:'com.huya.security.hydeviceid.NativeEntry'});
|
||||
['init','getGUID','getMID','getCDID','getSDID','getHDID'].forEach(function(m){
|
||||
try{ out[m] = NE[m](); }catch(e){ out[m] = '<ERR:'+String(e).slice(0,50)+'>'; }
|
||||
send({type:'val', method:m, value:out[m]});
|
||||
});
|
||||
send({type:'all', out:out});
|
||||
}catch(e){
|
||||
send({type:'use-err', e:String(e).slice(0,200)});
|
||||
}
|
||||
});
|
||||
}, 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 = {"vals": {}, "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 == "val":
|
||||
result["vals"][p["method"]] = p["value"]
|
||||
print(f"[val] {p['method']} = {p['value']}", flush=True)
|
||||
elif t == "class-ok":
|
||||
print(f"[*] class found", flush=True)
|
||||
elif t == "all":
|
||||
print(f"[*] 全部读取完成", flush=True)
|
||||
elif t in ("use-err",):
|
||||
print(f"[!] use-err: {p.get('e')}", flush=True)
|
||||
sc = session.create_script(JS)
|
||||
sc.on("message", on_message)
|
||||
sc.load()
|
||||
d.resume(pid)
|
||||
print("[*] resumed", flush=True)
|
||||
time.sleep(20)
|
||||
result["detached"] = det
|
||||
OUT.write_text(json.dumps(result, indent=1, ensure_ascii=False), encoding="utf-8")
|
||||
print(f"[*] saved {OUT}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env python3
|
||||
"""真机探针: 枚举 libhydeviceid.so / libudbauthunify.so 导出符号, 定位 32hex hdid 生成入口.
|
||||
|
||||
用真机稳定注入通道 (spawn + art_callsite bypass + resume) 长时存活。
|
||||
App 启动即加载这两个 so, 枚举 exports 拿函数名+基址偏移, 为下一步
|
||||
针对性 hook 生成函数(抓输入)做准备。
|
||||
|
||||
用法:
|
||||
/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/.venv/bin/python \
|
||||
scripts/phone_probe_hdid.py [serial] [remote]
|
||||
输出:
|
||||
evidence/diag_phone/hdid_probe_exports.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" / "hdid_probe_exports.json"
|
||||
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
|
||||
ART_CALLSITE = RE / "evidence" / "scripts" / "bypass_msaoaid_maps_art_callsite.js"
|
||||
|
||||
PROBE_JS = r"""
|
||||
'use strict';
|
||||
function listExports(modName, tag){
|
||||
try{
|
||||
var md = Process.findModuleByName(modName);
|
||||
if(!md){ send({type:'mod', name:modName, state:'not-loaded'}); return false; }
|
||||
var ex = md.enumerateExports();
|
||||
var sym = md.enumerateSymbols();
|
||||
send({type:'mod', name:modName, base:''+md.base, size:md.size,
|
||||
exports: ex.map(function(e){return {name:e.name, type:e.type, off:''+e.address.sub(md.base)};}),
|
||||
symCount: sym.length,
|
||||
syms: sym.map(function(s){return {name:s.name, off:''+s.address.sub(md.base)};})});
|
||||
return true;
|
||||
}catch(e){ send({type:'err', name:modName, e:String(e)}); return false; }
|
||||
}
|
||||
function hookPropGet(){
|
||||
var t = Module.findExportByName('libc.so','__system_property_get');
|
||||
if(!t) return;
|
||||
Interceptor.attach(t,{
|
||||
onEnter:function(a){ this.k = a[0].readCString()||''; },
|
||||
onLeave:function(ret){
|
||||
try{ send({type:'prop', k:this.k, v:(this.ctx.x1.readCString()||'')}); }catch(e){}
|
||||
}
|
||||
});
|
||||
send({type:'propget-hooked'});
|
||||
}
|
||||
// App 启动早期就抓 libhydeviceid.so 加载后的符号 + 派生属性读取
|
||||
setTimeout(function(){ listExports('libhydeviceid.so','hydev'); }, 2000);
|
||||
setTimeout(function(){ listExports('libudbauthunify.so','udb'); }, 2000);
|
||||
setTimeout(hookPropGet, 2000);
|
||||
"""
|
||||
|
||||
|
||||
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()
|
||||
d.resume(pid)
|
||||
print("[*] bypass loaded + resumed", flush=True)
|
||||
|
||||
result = {"modules": {}, "props": []}
|
||||
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 == "mod":
|
||||
result["modules"][p["name"]] = p
|
||||
print(f"[mod] {p['name']} base={p.get('base')} exports={len(p.get('exports') or [])} syms={p.get('symCount')}", flush=True)
|
||||
elif t == "prop":
|
||||
result["props"].append({"k": p.get("k"), "v": p.get("v")})
|
||||
elif t == "propget-hooked":
|
||||
print("[*] __system_property_get hooked", flush=True)
|
||||
elif t == "err":
|
||||
print(f"[err] {p.get('name')}: {p.get('e')}", flush=True)
|
||||
|
||||
sc = session.create_script(PROBE_JS)
|
||||
sc.on("message", on_message)
|
||||
sc.load()
|
||||
print("[*] probe loaded, collecting 12s", flush=True)
|
||||
time.sleep(12)
|
||||
result["detached"] = det
|
||||
OUT.write_text(json.dumps(result, indent=1, ensure_ascii=False), encoding="utf-8")
|
||||
# 打印关键: 各 so 的导出符号名(去重)
|
||||
for name, m in result["modules"].items():
|
||||
print(f"\n=== {name} exports ({len(m.get('exports') or [])}) ===")
|
||||
for e in (m.get("exports") or [])[:60]:
|
||||
print(f" 0x{e['off']} {e['name']}")
|
||||
print("\n=== 派生属性 (去重) ===")
|
||||
seen = {}
|
||||
for pr in result["props"]:
|
||||
seen[pr["k"]] = pr["v"]
|
||||
for k, v in seen.items():
|
||||
print(f" {k} = {v}")
|
||||
print(f"\n[*] saved {OUT}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,143 @@
|
||||
#!/usr/bin/env python3
|
||||
"""真机探针 v3: 等 so 加载后 hook, 抓 32hex hdid 生成/使用链路 + 登录触发.
|
||||
|
||||
修正 v1/v2: 挂起时 libhydeviceid.so 尚未加载 -> 用 setInterval 轮询,
|
||||
so 一加载即 hook:
|
||||
- libhydeviceid.so: 枚举非 runtime 导出 + hook JNI_OnLoad(0x22c4c8)
|
||||
- libudbauthunify.so: hook BusinessCfg::getHdid(0x26a484) / setSafeDeviceId(0x26a2e0) 带调用栈
|
||||
resume 后 am start GameSdkLoginActivity 触发设备上报/登录, 让 hdid 被读取/生成.
|
||||
|
||||
用法:
|
||||
/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/.venv/bin/python \
|
||||
scripts/phone_probe_hdid3.py [serial] [remote]
|
||||
输出:
|
||||
evidence/diag_phone/hdid_probe3.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"
|
||||
LOGIN_ACT = "com.duowan.kiwi/.loginui.impl.gamesdk.GameSdkLoginActivity"
|
||||
REPO = Path("/Users/yml/codes/douyu_login_py")
|
||||
OUT = REPO / "evidence" / "diag_phone" / "hdid_probe3.json"
|
||||
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 stdstr(p){
|
||||
try{
|
||||
if(p.isNull()) return '';
|
||||
var first = p.readU8();
|
||||
if((first&1)===0){ var l=first>>1; return l? p.add(1).readUtf8String(l):''; }
|
||||
else { var d=p.readPointer(); var l=p.add(8).readU64(); return (l&&l<256)? d.readUtf8String(l):''; }
|
||||
}catch(e){ return ''; }
|
||||
}
|
||||
var hydev=null, udb=null;
|
||||
function hookHydev(){
|
||||
if(hydev) return;
|
||||
var md = Process.findModuleByName('libhydeviceid.so');
|
||||
if(!md) return;
|
||||
hydev = md;
|
||||
send({type:'hydev', base:''+md.base, size:md.size});
|
||||
var ex = md.enumerateExports();
|
||||
var useful = ex.filter(function(e){ return !/^_Z/.test(e.name) && !/^__/.test(e.name) && e.name!==''; });
|
||||
send({type:'hydev-exports', count:useful.length,
|
||||
exports: useful.map(function(e){ return {off:''+e.address.sub(md.base), name:e.name}; })});
|
||||
try{
|
||||
Interceptor.attach(md.base.add(0x22c4c8), {
|
||||
onEnter:function(){ send({type:'jni-onload-enter', tid:Process.getCurrentThreadId()}); },
|
||||
onLeave:function(ret){ send({type:'jni-onload-leave', ret:''+ret}); }
|
||||
});
|
||||
send({type:'jni-onload-hooked'});
|
||||
}catch(e){ send({type:'hydev-err', e:String(e)}); }
|
||||
}
|
||||
function hookUdb(){
|
||||
if(udb) return;
|
||||
var md = Process.findModuleByName('libudbauthunify.so');
|
||||
if(!md) return;
|
||||
udb = md;
|
||||
send({type:'udb', base:''+md.base});
|
||||
try{
|
||||
Interceptor.attach(md.base.add(0x26a484), {
|
||||
onLeave:function(ret){ send({type:'hdid', str:stdstr(ret), tid:Process.getCurrentThreadId()}); }
|
||||
});
|
||||
send({type:'hdid-hooked'});
|
||||
}catch(e){ send({type:'udb-err', e:String(e)}); }
|
||||
try{
|
||||
Interceptor.attach(md.base.add(0x26a2e0), {
|
||||
onEnter:function(a){
|
||||
send({type:'setsd', sd:stdstr(a[1]), hd:stdstr(a[2]), tid:Process.getCurrentThreadId()});
|
||||
try{
|
||||
var bt=Thread.backtrace(this.context, Backtracer.ACCURATE).slice(0,20).map(function(x){return x.toString();});
|
||||
send({type:'bt', bt:bt});
|
||||
}catch(e){}
|
||||
}
|
||||
});
|
||||
send({type:'setsd-hooked'});
|
||||
}catch(e){}
|
||||
}
|
||||
setInterval(hookHydev, 40);
|
||||
setInterval(hookUdb, 40);
|
||||
"""
|
||||
|
||||
|
||||
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": [], "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 {}
|
||||
result["events"].append(p)
|
||||
t = p.get("type")
|
||||
if t in ("hdid", "setsd"):
|
||||
print(f"[{t}] {p.get('hd') or p.get('str')} tid={p.get('tid')}", flush=True)
|
||||
elif t == "bt":
|
||||
print(" bt:", " <- ".join(p["bt"][:8]), flush=True)
|
||||
elif t == "hydev":
|
||||
print(f"[*] libhydeviceid loaded base={p.get('base')}", flush=True)
|
||||
elif t == "hydev-exports":
|
||||
print(f"[*] hydev 非runtime导出 {p.get('count')}:", flush=True)
|
||||
for e in (p.get("exports") or [])[:40]:
|
||||
print(f" 0x{e['off']} {e['name']}")
|
||||
elif t in ("udb","hdid-hooked","setsd-hooked","jni-onload-hooked","jni-onload-enter","jni-onload-leave"):
|
||||
print(f"[*] {t}", p if t in ("jni-onload-leave",) else "", flush=True)
|
||||
sc = session.create_script(JS)
|
||||
sc.on("message", on_message)
|
||||
sc.load()
|
||||
d.resume(pid)
|
||||
print("[*] resumed", flush=True)
|
||||
time.sleep(5)
|
||||
print("[*] 触发登录 Activity...", flush=True)
|
||||
adb("shell", "am", "start", "-n", LOGIN_ACT)
|
||||
time.sleep(15)
|
||||
result["detached"] = det
|
||||
OUT.write_text(json.dumps(result, indent=1, ensure_ascii=False), encoding="utf-8")
|
||||
print(f"[*] saved {OUT}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,145 @@
|
||||
#!/usr/bin/env python3
|
||||
"""真机探针 v4: 抓 libhydeviceid.so 生成 hdid 的输入(系统属性/文件/随机源).
|
||||
|
||||
libhydeviceid.so 加壳, 生成函数不导出。改为抓它的"输入":
|
||||
- hook libc __system_property_get (spawn 挂起时已加载) -> App 启动读的属性
|
||||
- hook libhydeviceid 的 hy_fopen64/hy_read/hy_syscall (壳文件包装) -> 访问的文件
|
||||
- hook getHdid(0x26a484) 时同时 dump x0 原始字节, 定位为何返回空
|
||||
这能判断 hdid 依赖 ro.serialno / ANDROID_ID / /proc 信息 / 随机源等, 决定能否复现.
|
||||
|
||||
用法:
|
||||
/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/.venv/bin/python \
|
||||
scripts/phone_probe_hdid4.py [serial] [remote]
|
||||
输出:
|
||||
evidence/diag_phone/hdid_probe4.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"
|
||||
LOGIN_ACT = "com.duowan.kiwi/.loginui.impl.gamesdk.GameSdkLoginActivity"
|
||||
REPO = Path("/Users/yml/codes/douyu_login_py")
|
||||
OUT = REPO / "evidence" / "diag_phone" / "hdid_probe4.json"
|
||||
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 stdstr(p){
|
||||
try{
|
||||
if(p.isNull()) return '';
|
||||
var first = p.readU8();
|
||||
if((first&1)===0){ var l=first>>1; return l? p.add(1).readUtf8String(l):''; }
|
||||
else { var d=p.readPointer(); var l=p.add(8).readU64(); return (l&&l<512)? d.readUtf8String(l):''; }
|
||||
}catch(e){ return ''; }
|
||||
}
|
||||
// 1) spawn 挂起时 hook __system_property_get (libc 已加载)
|
||||
(function(){
|
||||
try{
|
||||
var t = Module.findExportByName('libc.so','__system_property_get');
|
||||
if(!t){ send({type:'no-propget'}); return; }
|
||||
Interceptor.attach(t,{
|
||||
onEnter:function(a){ this.k = a[0].readCString()||''; this.vp = a[1]; },
|
||||
onLeave:function(ret){ try{ send({type:'prop', k:this.k, v:this.vp.readCString()||''}); }catch(e){ send({type:'prop', k:this.k, v:'<err>'}); } }
|
||||
});
|
||||
send({type:'propget-hooked'});
|
||||
}catch(e){ send({type:'propget-err', e:String(e)}); }
|
||||
})();
|
||||
|
||||
var hydev=null;
|
||||
function hookHydev(){
|
||||
if(hydev) return;
|
||||
var md = Process.findModuleByName('libhydeviceid.so');
|
||||
if(!md) return;
|
||||
hydev = md;
|
||||
send({type:'hydev', base:''+md.base});
|
||||
// 文件操作壳包装: hy_fopen64(0xf1298), hy_read(0x30bef0), hy_syscall(0x30bf3c)
|
||||
var targets = {0xf1298:'hy_fopen64', 0x30bef0:'hy_read', 0x30bf3c:'hy_syscall', 0x30bf00:'hy_write', 0xf3360:'hyftell'};
|
||||
Object.keys(targets).forEach(function(off){
|
||||
try{
|
||||
Interceptor.attach(md.base.add(parseInt(off,16)), {
|
||||
onEnter:function(a){
|
||||
var s='';
|
||||
try{ s = (a[0]&&!a[0].isNull()) ? a[0].readCString(256)||'' : ''; }catch(e){}
|
||||
send({type:'hyio', fn:targets[off], arg0:s, tid:Process.getCurrentThreadId()});
|
||||
}
|
||||
});
|
||||
}catch(e){}
|
||||
});
|
||||
send({type:'hyio-hooked'});
|
||||
}
|
||||
// getHdid 返回空排查: dump x0 原始字节
|
||||
function hookUdb(){
|
||||
var md = Process.findModuleByName('libudbauthunify.so');
|
||||
if(!md) return;
|
||||
try{
|
||||
Interceptor.attach(md.base.add(0x26a484), {
|
||||
onLeave:function(ret){
|
||||
var s=stdstr(ret);
|
||||
var hex='';
|
||||
try{ hex = Array.from(new Uint8Array(ret.readByteArray(64))).map(function(b){return ('0'+b.toString(16)).slice(-2);}).join(''); }catch(e){}
|
||||
send({type:'hdid', str:s, ret:''+ret, hex:hex, tid:Process.getCurrentThreadId()});
|
||||
}
|
||||
});
|
||||
send({type:'hdid-hooked'});
|
||||
}catch(e){ send({type:'udb-err', e:String(e)}); }
|
||||
}
|
||||
setInterval(hookHydev, 40);
|
||||
setInterval(hookUdb, 40);
|
||||
"""
|
||||
|
||||
|
||||
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 = {"props": [], "hyio": [], "hdid": [], "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 == "prop":
|
||||
result["props"].append(p); print(f"[prop] {p.get('k')} = {p.get('v')}", flush=True)
|
||||
elif t == "hyio":
|
||||
result["hyio"].append(p); print(f"[hyio] {p.get('fn')} arg0={p.get('arg0')}", flush=True)
|
||||
elif t == "hdid":
|
||||
result["hdid"].append(p); print(f"[hdid] str={p.get('str')!r} ret={p.get('ret')} hex={p.get('hex')}", flush=True)
|
||||
elif t in ("propget-hooked","hydev","hyio-hooked","hdid-hooked","no-propget"):
|
||||
print(f"[*] {t}", flush=True)
|
||||
sc = session.create_script(JS)
|
||||
sc.on("message", on_message)
|
||||
sc.load()
|
||||
d.resume(pid)
|
||||
print("[*] resumed", flush=True)
|
||||
time.sleep(4)
|
||||
print("[*] 触发登录 Activity...", flush=True)
|
||||
adb("shell", "am", "start", "-n", LOGIN_ACT)
|
||||
time.sleep(15)
|
||||
result["detached"] = det
|
||||
OUT.write_text(json.dumps(result, indent=1, ensure_ascii=False), encoding="utf-8")
|
||||
print(f"[*] saved {OUT}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,162 @@
|
||||
#!/usr/bin/env python3
|
||||
"""真机 v6: hook SSL_write 抓 hypasswordLogin 登录帧, 提取实际 hdid + 追来源.
|
||||
|
||||
目标: 确认登录帧 t1.t0 的 32hex hdid 实际值(应为金样本 ed0db8...),
|
||||
并 hook 它进入 WUP 帧前被读取的调用链。同时 hook:
|
||||
- libc strstr/strcpy 等 (登录帧组装时 hdid 字符串被拷贝处)
|
||||
- BusinessCfg::getSafeDeviceId(0x26a3c4) / setDeviceInfo(0x26a894)
|
||||
|
||||
用法:
|
||||
/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/.venv/bin/python \
|
||||
scripts/phone_probe_hdid6.py [serial] [remote]
|
||||
输出:
|
||||
evidence/diag_phone/hdid_probe6.json
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
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"
|
||||
LOGIN_ACT = "com.duowan.kiwi/.loginui.impl.gamesdk.GameSdkLoginActivity"
|
||||
REPO = Path("/Users/yml/codes/douyu_login_py")
|
||||
OUT = REPO / "evidence" / "diag_phone" / "hdid_probe6.json"
|
||||
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 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 stdstr(p){
|
||||
try{
|
||||
if(p.isNull()) return '';
|
||||
var f = p.readU8();
|
||||
if((f&1)===0){ var l=f>>1; return l? p.add(1).readUtf8String(l):''; }
|
||||
else { var d=p.readPointer(); var l=p.add(8).readU64(); return (l&&l<512)? d.readUtf8String(l):''; }
|
||||
}catch(e){ return ''; }
|
||||
}
|
||||
var sslDone=false, udbHooked=false;
|
||||
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=''; try{ h=a[1].readCString(Math.min(len,600))||''; }catch(e){}
|
||||
if(h.indexOf('hypasswordLogin')>=0){
|
||||
send({type:'wup_login', len:len, hex:hexb(a[1],len)});
|
||||
}
|
||||
}});
|
||||
});
|
||||
send({type:'sslwrite-hooked'});
|
||||
}catch(e){}
|
||||
sslDone=true;
|
||||
}
|
||||
function hookUdb(){
|
||||
if(udbHooked) return;
|
||||
var md = Process.findModuleByName('libudbauthunify.so');
|
||||
if(!md) return;
|
||||
udbHooked=true;
|
||||
send({type:'udb', base:''+md.base});
|
||||
// 设备信息读取点
|
||||
try{
|
||||
Interceptor.attach(md.base.add(0x26a3c4), { // getSafeDeviceId
|
||||
onLeave:function(ret){ send({type:'getsd', ret:''+ret}); }
|
||||
});
|
||||
}catch(e){}
|
||||
try{
|
||||
Interceptor.attach(md.base.add(0x26a894), { // setDeviceInfo(a1=this, a2..a5=std::string*)
|
||||
onEnter:function(a){
|
||||
send({type:'setdi', args:[stdstr(a[1]), stdstr(a[2]), stdstr(a[3]), stdstr(a[4])]});
|
||||
}
|
||||
});
|
||||
}catch(e){}
|
||||
send({type:'udb-hooked'});
|
||||
}
|
||||
// libhydeviceid 导出函数调用探测
|
||||
var hyTried=false;
|
||||
function hookHy(){
|
||||
if(hyTried) return;
|
||||
var md = Process.findModuleByName('libhydeviceid.so');
|
||||
if(!md) return;
|
||||
hyTried=true;
|
||||
send({type:'hydev', base:''+md.base});
|
||||
['hyfopen64','hy_read','hy_syscall','hy_write','hyftell'].forEach(function(n){
|
||||
try{
|
||||
var a = Module.findExportByName('libhydeviceid.so', n);
|
||||
if(a) Interceptor.attach(a,{onEnter:function(args){
|
||||
var s=''; try{ s=args[0].readCString(128)||''; }catch(e){}
|
||||
send({type:'hycall', fn:n, a0:s, tid:Process.getCurrentThreadId()});
|
||||
}});
|
||||
}catch(e){}
|
||||
});
|
||||
send({type:'hydev-hooked'});
|
||||
}
|
||||
setInterval(hookSSL, 800);
|
||||
setInterval(hookUdb, 50);
|
||||
setInterval(hookHy, 50);
|
||||
"""
|
||||
|
||||
|
||||
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 = {"frames": [], "events": [], "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")
|
||||
result["events"].append(p)
|
||||
if t == "wup_login":
|
||||
result["frames"].append(p)
|
||||
b = bytes.fromhex(p.get("hex", ""))
|
||||
# 提取帧里的 32hex hdid
|
||||
h32 = re.findall(rb"[0-9a-f]{32}", b)
|
||||
print(f"[WUP_LOGIN] len={p.get('len')} hdid候选: {[h.decode() for h in h32[:3]]}", flush=True)
|
||||
elif t in ("udb","udb-hooked","sslwrite-hooked","hydev","hydev-hooked"):
|
||||
print(f"[*] {t}", p if t=="udb" else "", flush=True)
|
||||
elif t == "setdi":
|
||||
print(f"[setDeviceInfo] args={p.get('args')}", flush=True)
|
||||
elif t == "getsd":
|
||||
print(f"[getSafeDeviceId] ret={p.get('ret')}", flush=True)
|
||||
elif t == "hycall":
|
||||
print(f"[hycall] {p.get('fn')} a0={p.get('a0')}", flush=True)
|
||||
sc = session.create_script(JS)
|
||||
sc.on("message", on_message)
|
||||
sc.load()
|
||||
d.resume(pid)
|
||||
print("[*] resumed", flush=True)
|
||||
time.sleep(4)
|
||||
print("[*] 触发登录...", flush=True)
|
||||
adb("shell", "am", "start", "-n", LOGIN_ACT)
|
||||
time.sleep(18)
|
||||
result["detached"] = det
|
||||
OUT.write_text(json.dumps(result, indent=1, ensure_ascii=False), encoding="utf-8")
|
||||
print(f"[*] 登录帧数: {len(result['frames'])} saved {OUT}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env python3
|
||||
"""真机 v9: 触发一键登录, hook SSL_write 抓 WUP 登录帧 + createWupDeviceInfo(0x2746a0),
|
||||
从登录帧提取 32hex hdid (t1.t0) 和其来源.
|
||||
|
||||
触发: 点击 GameSdkLoginActivity 的"本机号码一键登录" (mBtnLogin, 中心 540,1287),
|
||||
App 会自动发起登录 WUP 请求, 帧内经 JCE 编码含 t1.t0=hdid 字段.
|
||||
|
||||
用法:
|
||||
/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/.venv/bin/python \
|
||||
scripts/phone_probe_login_frame.py [serial] [remote]
|
||||
输出:
|
||||
evidence/diag_phone/login_frames.json
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
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"
|
||||
LOGIN_ACT = "com.duowan.kiwi/.loginui.impl.gamesdk.GameSdkLoginActivity"
|
||||
REPO = Path("/Users/yml/codes/douyu_login_py")
|
||||
OUT = REPO / "evidence" / "diag_phone" / "login_frames.json"
|
||||
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 sslDone=false, udbHooked=false, cdiHooked=false;
|
||||
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<200||len>80000) return;
|
||||
try{
|
||||
var buf=a[1].readByteArray(len);
|
||||
send({type:'sslfr', len:len, hex:Array.from(new Uint8Array(buf)).map(function(b){return ('0'+b.toString(16)).slice(-2);}).join('')});
|
||||
}catch(e){}
|
||||
}});
|
||||
});
|
||||
send({type:'ssl-hooked'});
|
||||
}catch(e){ send({type:'ssl-err', e:String(e)}); }
|
||||
sslDone=true;
|
||||
}
|
||||
function hookUdb(){
|
||||
if(udbHooked) return;
|
||||
var md=Process.findModuleByName('libudbauthunify.so');
|
||||
if(!md) return;
|
||||
udbHooked=true;
|
||||
send({type:'udb', base:''+md.base});
|
||||
try{ // BusinessCfg::getHdid
|
||||
Interceptor.attach(md.base.add(0x26a484),{onLeave:function(ret){
|
||||
var s='<n>';
|
||||
try{ s = ret.isNull()? '<null>' : ret.readCString(64)||'<?>'; }catch(e){ s='<err>'; }
|
||||
var bt='';
|
||||
try{ bt = Thread.backtrace(this.context, Backtracer.ACCURATE).slice(0,16).map(function(x){return x.toString();}).join(' <- '); }catch(e){}
|
||||
send({type:'getHdid', ret:''+ret, str:s, bt:bt});
|
||||
}});
|
||||
}catch(e){}
|
||||
try{ // createWupDeviceInfo(wup::DeviceInfo*)
|
||||
Interceptor.attach(md.base.add(0x2746a0),{onEnter:function(a){ send({type:'createWupDev-enter', tid:Process.getCurrentThreadId()}); }});
|
||||
}catch(e){}
|
||||
send({type:'udb-hooked'});
|
||||
}
|
||||
setInterval(hookSSL, 500);
|
||||
setInterval(hookUdb, 60);
|
||||
"""
|
||||
|
||||
|
||||
def adb(*a):
|
||||
return subprocess.run(["adb", "-s", SERIAL, *a], capture_output=True, text=True)
|
||||
|
||||
|
||||
def tap(x, y):
|
||||
adb("shell", f"input tap {x} {y}")
|
||||
|
||||
|
||||
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 = {"frames": [], "events": [], "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")
|
||||
result["events"].append(p)
|
||||
if t == "sslfr":
|
||||
result["frames"].append(p)
|
||||
b = bytes.fromhex(p.get("hex", ""))
|
||||
# WUP/JCE 帧特征: 找 t1.t0 的 32hex 候选
|
||||
h32 = list(set(x.decode() for x in re.findall(rb"[0-9a-f]{32}", b)))
|
||||
h40 = list(set(x.decode() for x in re.findall(rb"[0-9a-f]{40}", b)))
|
||||
print(f"[ssl] len={p['len']} 32hex候选={h32[:4]} 40hex候选={h40[:2]}", flush=True)
|
||||
elif t in ("udb","udb-hooked","ssl-hooked"):
|
||||
print(f"[*] {t}", p if t=="udb" else "", flush=True)
|
||||
elif t == "getHdid":
|
||||
print(f"[getHdid] ret={p.get('ret')} str={p.get('str')[:60]!r}", flush=True)
|
||||
if p.get("bt"):
|
||||
print(f" bt: {p['bt']}", flush=True)
|
||||
elif t == "createWupDev-enter":
|
||||
print(f"[createWupDeviceInfo] enter tid={p.get('tid')}", flush=True)
|
||||
sc = session.create_script(JS)
|
||||
sc.on("message", on_message)
|
||||
sc.load()
|
||||
d.resume(pid)
|
||||
print("[*] resumed", flush=True)
|
||||
time.sleep(6)
|
||||
# 打开登录页 (不自动点击, 交给用户手动切账号密码登录)
|
||||
adb("shell", "am", "start", "-n", LOGIN_ACT)
|
||||
time.sleep(3)
|
||||
duration = int(sys.argv[3]) if len(sys.argv) > 3 else 240
|
||||
print(f"[*] 探针持续抓帧 {duration}s, 请手动登录...", flush=True)
|
||||
time.sleep(duration)
|
||||
result["detached"] = det
|
||||
OUT.write_text(json.dumps(result, indent=1, ensure_ascii=False), encoding="utf-8")
|
||||
print(f"[*] 帧数: {len(result['frames'])} saved {OUT}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,153 @@
|
||||
#!/usr/bin/env python3
|
||||
"""真机探针 v2: 抓 libhydeviceid.so 经 RegisterNatives 注册的 native 方法 + getHdid 调用栈.
|
||||
|
||||
libhydeviceid.so 没有 JNI/生成函数导出(核心方法经 JNI_OnLoad -> RegisterNatives 动态注册)。
|
||||
本脚本在 spawn 挂起中 hook libart.so 的 RegisterNatives, App 启动早期注册会被捕获,
|
||||
拿到 libhydeviceid.so 注册的 native 方法指针 -> 基址偏移, 定位 hdid 生成入口。
|
||||
同时 hook libudbauthunify.so 的 BusinessCfg::getHdid(0x26a484) 抓 hdid 值+调用栈。
|
||||
|
||||
用法:
|
||||
/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/.venv/bin/python \
|
||||
scripts/phone_probe_registernatives.py [serial] [remote]
|
||||
输出:
|
||||
evidence/diag_phone/hdid_registernatives.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" / "hdid_registernatives.json"
|
||||
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 stdstr(p){
|
||||
try{
|
||||
if(p.isNull()) return '';
|
||||
var first = p.readU8();
|
||||
if((first & 1)===0){ var l=first>>1; return l? p.add(1).readUtf8String(l):''; }
|
||||
else { var d=p.readPointer(); var l=p.add(8).readU64(); return (l&&l<256)? d.readUtf8String(l):''; }
|
||||
}catch(e){ return ''; }
|
||||
}
|
||||
function modOf(a){ try{ var m=Process.findModuleByAddress(a); return m? {n:m.name, off:''+a.sub(m.base)}: {n:'?', off:'?'}; }catch(e){ return {n:'?',off:'?'}; } }
|
||||
|
||||
// 1) hook libart RegisterNatives
|
||||
(function(){
|
||||
var RN = Module.findExportByName('libart.so','_ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi');
|
||||
if(!RN){ send({type:'rn-notfound'}); return; }
|
||||
send({type:'rn-found', a:''+RN});
|
||||
Interceptor.attach(RN,{
|
||||
onEnter:function(a){
|
||||
var count = a[3].toInt32();
|
||||
if(count<=0||count>500) return;
|
||||
var cls='';
|
||||
try{ cls = Java.vm.tryGetEnv().getClassName(a[1])||''; }catch(e){ cls='?'; }
|
||||
for(var i=0;i<count;i++){
|
||||
try{
|
||||
var base=a[2].add(i*24);
|
||||
var name=base.readPointer().readCString()||'';
|
||||
var sig =base.add(8).readPointer().readCString()||'';
|
||||
var fn =base.add(16).readPointer();
|
||||
var mo=modOf(fn);
|
||||
send({type:'reg', cls:cls, name:name, sig:sig, mod:mo.n, off:mo.off});
|
||||
}catch(e){}
|
||||
}
|
||||
}
|
||||
});
|
||||
send({type:'rn-hooked'});
|
||||
})();
|
||||
|
||||
// 2) hook BusinessCfg::getHdid (libudbauthunify.so 0x26a484)
|
||||
(function(){
|
||||
var md = Process.findModuleByName('libudbauthunify.so');
|
||||
if(!md){ send({type:'udb-not-loaded'}); return; }
|
||||
try{
|
||||
Interceptor.attach(md.base.add(0x26a484),{
|
||||
onLeave:function(ret){
|
||||
var s=stdstr(ret);
|
||||
send({type:'hdid', str:s, tid:Process.getCurrentThreadId()});
|
||||
}
|
||||
});
|
||||
Interceptor.attach(md.base.add(0x26a2e0),{ // setSafeDeviceId(a2=sd, a3=hdid)
|
||||
onEnter:function(a){
|
||||
send({type:'setsd', sd:stdstr(a[1]), hd:stdstr(a[2]), tid:Process.getCurrentThreadId()});
|
||||
try{
|
||||
var bt=Thread.backtrace(this.context, Backtracer.ACCURATE).slice(0,24).map(function(x){return x.toString();});
|
||||
send({type:'bt', bt:bt});
|
||||
}catch(e){}
|
||||
}
|
||||
});
|
||||
send({type:'udb-hooked'});
|
||||
}catch(e){ send({type:'udb-err', e:String(e)}); }
|
||||
})();
|
||||
"""
|
||||
|
||||
|
||||
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()
|
||||
print("[*] art_callsite loaded", flush=True)
|
||||
result = {"regs": [], "hdid": [], "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 == "reg":
|
||||
result["regs"].append(p)
|
||||
elif t in ("hdid", "setsd"):
|
||||
result["hdid"].append(p)
|
||||
print(f"[{t}] {p}", flush=True)
|
||||
elif t == "bt":
|
||||
print(" bt:", " <- ".join(p["bt"][:8]), flush=True)
|
||||
elif t in ("rn-found","rn-hooked","udb-hooked"):
|
||||
print(f"[*] {t}", flush=True)
|
||||
sc = session.create_script(JS)
|
||||
sc.on("message", on_message)
|
||||
sc.load()
|
||||
d.resume(pid)
|
||||
print("[*] resumed, collecting 18s", flush=True)
|
||||
time.sleep(18)
|
||||
result["detached"] = det
|
||||
OUT.write_text(json.dumps(result, indent=1, ensure_ascii=False), encoding="utf-8")
|
||||
|
||||
# 汇总 libhydeviceid.so 注册的方法
|
||||
print("\n=== libhydeviceid.so 注册的 native 方法 ===")
|
||||
seen = set()
|
||||
for r in result["regs"]:
|
||||
if r.get("mod") == "libhydeviceid.so" and (r["name"], r["off"]) not in seen:
|
||||
seen.add((r["name"], r["off"]))
|
||||
print(f" 0x{r['off']} {r['name']}{r['sig']} (class={r.get('cls')})")
|
||||
print(f"\n=== 所有 so 注册统计 ===")
|
||||
from collections import Counter
|
||||
c = Counter(r.get("mod") for r in result["regs"])
|
||||
for mod, n in c.most_common():
|
||||
print(f" {mod}: {n}")
|
||||
print(f"\n[*] saved {OUT}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,168 @@
|
||||
#!/usr/bin/env python3
|
||||
"""真机 v7: dlopen 拦截 libhydeviceid -> attach JNI_OnLoad -> hook JNINativeInterface::RegisterNatives
|
||||
拿到 libhydeviceid.so 动态注册的全部 native 方法(名字+签名+函数地址).
|
||||
|
||||
绕过 OLLVM 混淆: 不去静态反汇编 JNI_OnLoad, 而是运行时在 JNI_OnLoad 里用 env 的
|
||||
JNINativeInterface 函数表定位 RegisterNatives 槽位并 hook, 抓 JNINativeMethod 数组.
|
||||
|
||||
用法:
|
||||
/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/.venv/bin/python \
|
||||
scripts/phone_probe_registernatives2.py [serial] [remote]
|
||||
输出:
|
||||
evidence/diag_phone/hdid_regtable.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" / "hdid_regtable.json"
|
||||
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
|
||||
ART_CALLSITE = RE / "evidence" / "scripts" / "bypass_msaoaid_maps_art_callsite.js"
|
||||
|
||||
# JNINativeInterface 中 RegisterNatives 的索引 (JNI 1.6 规范, 含 4 个 reserved)
|
||||
RN_INDEX = 215
|
||||
|
||||
|
||||
def mod_of(a):
|
||||
try:
|
||||
m = Process.findModuleByAddress(a)
|
||||
return (m.name, str(a.sub(m.base))) if m else ("?", "?")
|
||||
except Exception:
|
||||
return ("?", "?")
|
||||
|
||||
|
||||
JS = r"""
|
||||
'use strict';
|
||||
function jmod(a){
|
||||
try{ var m = Process.findModuleByAddress(a); return m ? {n:m.name, off:''+a.sub(m.base)} : {n:'?',off:'?'}; }catch(e){ return {n:'?',off:'?'}; }
|
||||
}
|
||||
var hooked = false;
|
||||
function attachJniOnload(md){
|
||||
try{
|
||||
Interceptor.attach(md.base.add(0x22c4c8), {
|
||||
onEnter:function(a){
|
||||
send({type:'jni-onload-enter', tid:Process.getCurrentThreadId()});
|
||||
try{
|
||||
var env = Java.vm.tryGetEnv();
|
||||
var fns = env.handle.readPointer(); // JNINativeInterface*
|
||||
// 打印 200..230 槽位的所属模块, 验证表结构
|
||||
var dump=[];
|
||||
for(var i=200;i<=230;i++){
|
||||
var p = fns.add(i*8).readPointer();
|
||||
var m = jmod(p);
|
||||
dump.push({i:i, a:''+p, mod:m.n, off:m.off});
|
||||
}
|
||||
send({type:'fns-scan', dump:dump});
|
||||
var rn = fns.add(%d*8).readPointer();
|
||||
send({type:'rn-addr', rn:''+rn, mod:jmod(rn)});
|
||||
Interceptor.attach(rn, {
|
||||
onEnter:function(args){
|
||||
// args[1]=jclass clazz, args[2]=JNINativeMethod* methods, args[3]=jint n
|
||||
var count = args[3].toInt32();
|
||||
var methods = args[2];
|
||||
var cls='';
|
||||
try{ cls = Java.vm.tryGetEnv().getClassName(args[1]||Java.vm.tryGetEnv().getClass(args[1])); }catch(e){ cls='?'; }
|
||||
send({type:'reg-call', cls:cls, count:count, tid:Process.getCurrentThreadId()});
|
||||
for(var i=0;i<count;i++){
|
||||
try{
|
||||
var base = methods.add(i*24);
|
||||
var name = base.readPointer().readCString()||'';
|
||||
var sig = base.add(8).readPointer().readCString()||'';
|
||||
var fn = base.add(16).readPointer();
|
||||
var m = jmod(fn);
|
||||
send({type:'reg', cls:cls, name:name, sig:sig, mod:m.n, off:m.off});
|
||||
}catch(e){}
|
||||
}
|
||||
}
|
||||
});
|
||||
send({type:'rn-hooked'});
|
||||
}catch(e){ send({type:'rn-err', e:String(e)}); }
|
||||
}
|
||||
});
|
||||
send({type:'jni-onload-hooked'});
|
||||
}catch(e){ send({type:'jni-err', e:String(e)}); }
|
||||
}
|
||||
function hookDlopen(){
|
||||
if(hooked) return;
|
||||
var dl = Module.findExportByName(null,'android_dlopen_ext');
|
||||
var dlname = 'android_dlopen_ext';
|
||||
if(!dl){ dl = Module.findExportByName(null,'dlopen'); dlname='dlopen'; }
|
||||
if(!dl) return;
|
||||
hooked = true;
|
||||
send({type:'dlopen-hooked', fn:dlname});
|
||||
Interceptor.attach(dl, {
|
||||
onEnter:function(a){ try{ this.name = a[0].readCString()||''; }catch(e){ this.name=''; } },
|
||||
onLeave:function(){
|
||||
if(this.name && this.name.indexOf('libhydeviceid')>=0){
|
||||
send({type:'dlopen-hydev', name:this.name});
|
||||
var md = Process.findModuleByName('libhydeviceid.so');
|
||||
if(md){ attachJniOnload(md); }
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
setInterval(hookDlopen, 40);
|
||||
""" % RN_INDEX
|
||||
|
||||
|
||||
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 = {"regs": [], "fns_scan": [], "events": [], "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")
|
||||
result["events"].append(p)
|
||||
if t == "reg":
|
||||
result["regs"].append(p)
|
||||
print(f"[reg] {p.get('name')}{p.get('sig')} @ {p.get('mod')}+0x{p.get('off')} (class={p.get('cls')})", flush=True)
|
||||
elif t == "reg-call":
|
||||
print(f"[*] RegisterNatives called count={p.get('count')} cls={p.get('cls')}", flush=True)
|
||||
elif t == "rn-addr":
|
||||
print(f"[*] RegisterNatives addr 0x{p['rn']} mod={p.get('mod')}", flush=True)
|
||||
elif t == "fns-scan":
|
||||
result["fns_scan"].append(p)
|
||||
print("[*] JNINativeInterface 槽位 200-230:", flush=True)
|
||||
for e in p["dump"]:
|
||||
print(f" [{e['i']}] {e.get('mod')}+0x{e.get('off')}", flush=True)
|
||||
elif t in ("dlopen-hooked","jni-onload-hooked","rn-hooked","dlopen-hydev","jni-onload-enter"):
|
||||
print(f"[*] {t}", p if t=="dlopen-hydev" else "", flush=True)
|
||||
elif t in ("jni-err","rn-err"):
|
||||
print(f"[!] {t}: {p.get('e')}", flush=True)
|
||||
sc = session.create_script(JS)
|
||||
sc.on("message", on_message)
|
||||
sc.load()
|
||||
d.resume(pid)
|
||||
print("[*] resumed, waiting RegisterNatives...", flush=True)
|
||||
time.sleep(22)
|
||||
result["detached"] = det
|
||||
OUT.write_text(json.dumps(result, indent=1, ensure_ascii=False), encoding="utf-8")
|
||||
print(f"[*] 注册方法数: {len(result['regs'])} saved {OUT}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,149 @@
|
||||
#!/usr/bin/env python3
|
||||
"""真机 v8: 用 Java 层直接调用 com.huya.security.hydeviceid.NativeEntry 采集 hdid 族.
|
||||
|
||||
拿到 libhydeviceid 注册表后, 用 frida Java.use 调 NativeEntry 的 native 方法:
|
||||
init() -> getHDID()/getSDID()/getCDID()/getGUID()/getMID()
|
||||
直接可靠读取每台真机的 hdid(32hex) 及同族设备 ID —— 这是多机采集 hdid 池的前提.
|
||||
同时 hook getHDID 的 native 入口(0x20f0c8)记录调用栈确认来源.
|
||||
|
||||
用法:
|
||||
/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/.venv/bin/python \
|
||||
scripts/phone_read_hdid.py [serial] [remote]
|
||||
输出:
|
||||
evidence/diag_phone/hdid_read.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" / "hdid_read.json"
|
||||
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
|
||||
ART_CALLSITE = RE / "evidence" / "scripts" / "bypass_msaoaid_maps_art_callsite.js"
|
||||
GETHDID_OFF = 0x20F0C8 # libhydeviceid.so 相对偏移
|
||||
|
||||
JS = r"""
|
||||
'use strict';
|
||||
var NE_CLASS = 'com.huya.security.hydeviceid.NativeEntry';
|
||||
var nativeHooked = false;
|
||||
var GETS = [
|
||||
{n:'getCDID', off:0x20eb08},
|
||||
{n:'getSDID', off:0x20ede8},
|
||||
{n:'getHDID', off:0x20f0c8},
|
||||
{n:'getGUID', off:0x20f8c8},
|
||||
{n:'getMID', off:0x20fba8}
|
||||
];
|
||||
function readJstr(env, ret){
|
||||
try{
|
||||
var fns = env.readPointer(); // JNINativeInterface*
|
||||
var g = new NativeFunction(fns.add(169*8).readPointer(), 'pointer', ['pointer','pointer','pointer']); // GetStringUTFChars
|
||||
var rel = new NativeFunction(fns.add(170*8).readPointer(), 'void', ['pointer','pointer','pointer']); // ReleaseStringUTFChars
|
||||
var cv = g(env, ret, ptr(0));
|
||||
if(cv && !cv.isNull()){ var s=cv.readCString()||''; rel(env, ret, cv); return s; }
|
||||
return '<null>';
|
||||
}catch(e){ return '<err>'; }
|
||||
}
|
||||
function hookAll(){
|
||||
if(nativeHooked) return;
|
||||
var m = Process.findModuleByName('libhydeviceid.so');
|
||||
if(!m) return;
|
||||
nativeHooked = true;
|
||||
GETS.forEach(function(g){
|
||||
try{
|
||||
Interceptor.attach(m.base.add(g.off), {
|
||||
onEnter:function(a){ this.env = a[0]; this.name = g.n; },
|
||||
onLeave:function(ret){ send({type:'getval', name:this.name, str:readJstr(this.env, ret)}); }
|
||||
});
|
||||
}catch(e){}
|
||||
});
|
||||
send({type:'all-hooked', mods:GETS.map(function(g){return g.n;})});
|
||||
}
|
||||
setInterval(hookAll, 300);
|
||||
|
||||
Java.perform(function(){
|
||||
function tryRead(){
|
||||
try{
|
||||
var NE = Java.use('com.huya.security.hydeviceid.NativeEntry');
|
||||
send({type:'class-found', cls:NE_CLASS});
|
||||
// 先 init (可能构造指纹), 再逐个读
|
||||
var out = {};
|
||||
['init','getHDID','getSDID','getCDID','getGUID','getMID'].forEach(function(m){
|
||||
try{ out[m] = NE[m](); }catch(e){ out[m] = '<ERR:'+e+'>'; }
|
||||
});
|
||||
send({type:'read', values:out});
|
||||
}catch(e){ send({type:'java-err', e:String(e)}); }
|
||||
}
|
||||
// 类可能尚未注册(等 libhydeviceid 加载)
|
||||
var n=0;
|
||||
var timer = setInterval(function(){
|
||||
n++;
|
||||
try{
|
||||
Java.use('com.huya.security.hydeviceid.NativeEntry');
|
||||
clearInterval(timer);
|
||||
tryRead();
|
||||
}catch(e){ if(n>60){ clearInterval(timer); send({type:'cls-not-found'}); } }
|
||||
}, 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)
|
||||
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 = {"read": None, "nativetrap": [], "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 == "read":
|
||||
result["read"] = p["values"]
|
||||
print("[*] NativeEntry 读取:", flush=True)
|
||||
for k, v in p["values"].items():
|
||||
print(f" {k} = {v}", flush=True)
|
||||
elif t == "getval":
|
||||
print(f"[getval] {p.get('name')} = {p.get('str')}", flush=True)
|
||||
# 存起来
|
||||
result.setdefault("getvals", []).append(p)
|
||||
elif t == "all-hooked":
|
||||
print(f"[*] all get methods hooked: {p.get('mods')}", flush=True)
|
||||
elif t in ("class-found","native-hooked"):
|
||||
print(f"[*] {t}", flush=True)
|
||||
elif t == "cls-not-found":
|
||||
print("[!] 类未找到(可能未加载)", flush=True)
|
||||
elif t == "java-err":
|
||||
print(f"[java-err] {p.get('e')}", flush=True)
|
||||
sc = session.create_script(JS)
|
||||
sc.on("message", on_message)
|
||||
sc.load()
|
||||
d.resume(pid)
|
||||
print("[*] resumed, waiting for class + read...", flush=True)
|
||||
time.sleep(35)
|
||||
result["detached"] = det
|
||||
OUT.write_text(json.dumps(result, indent=1, ensure_ascii=False), encoding="utf-8")
|
||||
print(f"[*] saved {OUT}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user