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:
@@ -200,3 +200,40 @@ lib 内置大量模拟器检测串(qemu_pipe / mumuvmm / genymotion / windroye
|
|||||||
需在真机 dlopen 全拦截清单里补齐),让 b(100) 真实计算;
|
需在真机 dlopen 全拦截清单里补齐),让 b(100) 真实计算;
|
||||||
2. **静态还原 b(100) 公式**:基于 (ANDROID_ID→GUID) 差分对拍(unidbg 内改 ANDROID_ID 即可批量
|
2. **静态还原 b(100) 公式**:基于 (ANDROID_ID→GUID) 差分对拍(unidbg 内改 ANDROID_ID 即可批量
|
||||||
产对),用 .data 密钥(`7jbF5I...`/`HuyaUdb...`/config 解密链)猜 MD5/SHA1/AES 结构。
|
产对),用 .data 密钥(`7jbF5I...`/`HuyaUdb...`/config 解密链)猜 MD5/SHA1/AES 结构。
|
||||||
|
|
||||||
|
## 十、Hook 阶段结果(2026-08-28 深夜续)
|
||||||
|
|
||||||
|
### 10.1 NativeBridge 归属排查(穷尽运行时手段)
|
||||||
|
- **动态 JNI 排除**:全模块 `Java_*` 导出扫描,自研库仅 libhypopup/libjsiwup/AI 库有——无
|
||||||
|
`Java_com_huya_security_hydeviceid_NativeBridge_*`。
|
||||||
|
- **RegisterNatives 排除**:①v7 注册表(23 条)= NativeEntry(19@hydeviceid)+HuyaAuthCore(4@udb),
|
||||||
|
无 NativeBridge;②dlopen→JNI_OnLoad 拦截各库(hydeviceid/udb)0 命中;③健康进程 attach+
|
||||||
|
重触发 init/getGUID,RN 全程 0 命中。
|
||||||
|
- **unidbg verbose 实锤**:hydeviceid JNI_OnLoad 只 `FindClass(NativeBridge)+NewGlobalRef`(不注册),
|
||||||
|
udb JNI_OnLoad 只注册 HuyaAuthCore;NativeBridge.b/klog 在 unidbg 内经 JNI 以"未注册 native"
|
||||||
|
路由到 AbstractJni(桩)。
|
||||||
|
- **定位到的关键静态物证**:hydeviceid .data@0x3e21e4 起为内联方法描述符块
|
||||||
|
(`klog()(L...Z)V`、`b (I)L..`、`c (I)J`、`h ()L..`、`g (L)L..`、getDataDir/getPath…),
|
||||||
|
类名字符串 `com/huya/security/hydeviceid/NativeBridge`@0x3e2f20 紧随其后;但代码无任何
|
||||||
|
ADRP/GOT 引用该页(间接/运行期构建)。完整 {name,sig,fnPtr} 指针表未以静态形式存在。
|
||||||
|
- **新输入候选**:采集器还读写 `udb_deviceID`/`udb_deviceID_encode`/`oaid`(prefs)+
|
||||||
|
`NativeBridge.b(104)`(oaid 相关);`Build.*` 静态字段读。
|
||||||
|
|
||||||
|
### 10.2 双库 unidbg 已打通但 b 仍未真注册
|
||||||
|
- libudbauthunify(JNI_OnLoad@0x262620,RN 调用点 0x2629bc)解密合并后 unidbg 加载成功;
|
||||||
|
- merge_decrypted.py 升级为**段驱动**(vaddr→文件偏移,支持 .rodata/delta 各段)+ .dynamic 保留
|
||||||
|
+ .init_array 置零 + 仅重定位槽指针清理(避免误伤字符串);
|
||||||
|
- 卡点:NativeBridge natives 从未在任何可见路径注册 → b(100) 仍走桩。
|
||||||
|
|
||||||
|
### 10.3 设备稳定性注意(重要)
|
||||||
|
- 反复 `pm clear` + frida attach 探测会清空应用数据(弹协议)并导致 App 闪退;
|
||||||
|
已确认无 frida 冷启动 App 健康(首页焦点正常)。**后续禁 pm clear、attach 探测收敛**。
|
||||||
|
|
||||||
|
### 10.4 静态还原重启点(step-2 方案,全部 PC 侧)
|
||||||
|
1. **歧义清除**:确认 NativeBridge.b/klog 到底是否 native(dex 反射 isNative,用 SPAWN+bypass
|
||||||
|
轻探,一次即可);若 native → 在 hydeviceid 代码中用"运行期构建表"模式(构造
|
||||||
|
{name,sig,fnPtr} 时 ldr 常量页)找注册函数;若 Java → 直接读运行期 dex 里 getGuid/b 逻辑。
|
||||||
|
2. **oracle 差分**:unidbg 侧可控 ANDROID_ID/Settings.Secure 已证明 androidId 密文会变;
|
||||||
|
b 一旦真注册,`(ANDROID_ID → GUID)` 差分对即可在 unidbg 内批量生成 → MD5/SHA1/AES 对拍。
|
||||||
|
3. b 的 fnPtr 兜底获取:运行期 dump hydeviceid .data 时同时读 JNINativeMethod 运行期构造表
|
||||||
|
(spawn+bypass 一次抓全,附 base 校验)。
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -1,45 +1,112 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""把 FULL dump 的解密数据合并进 ORIGINAL .so (元数据完好, unidbg 直接可加载).
|
"""通用解密合并 (v3): 按 PT_LOAD 映射 vaddr<->文件偏移, 把 FULL dump 的解密内存
|
||||||
|
合入 ORIGINAL .so 的正确文件位置; 数据段内陈旧运行期指针清掉.
|
||||||
|
|
||||||
关键洞察: unidbg 加载原文件时会自己按 .rela.dyn 重定位(RELATIVE=base+addend,
|
|
||||||
GLOB_DAT/JUMP_SLOT=按符号解析) —— 重定位槽内容会被覆盖, 所以只需要把
|
|
||||||
"非重定位的加密数据"(字符串/密钥/常量)换成解密字节即可.
|
|
||||||
映射: dump 偏移(vaddr 轴) V -> 原文件偏移 = V - 0x1000 (V>=0x3b21c0) 否则 V.
|
|
||||||
随后把数据区里残留的运行期指针(>=0x4000000000)统一回退/清零.
|
|
||||||
用法: python3 merge_decrypted.py <original.so> <dump_full.so> <runtime_base> <out.so>
|
用法: python3 merge_decrypted.py <original.so> <dump_full.so> <runtime_base> <out.so>
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
import struct, sys
|
import struct, sys
|
||||||
|
|
||||||
|
def phdrs(data):
|
||||||
|
e_phoff = struct.unpack_from('<Q', data, 0x20)[0]
|
||||||
|
e_phentsize = struct.unpack_from('<H', data, 0x36)[0]
|
||||||
|
e_phnum = struct.unpack_from('<H', data, 0x38)[0]
|
||||||
|
out = []
|
||||||
|
for i in range(e_phnum):
|
||||||
|
p = struct.unpack_from('<IIQQQQQQ', data, e_phoff + i * e_phentsize)
|
||||||
|
out.append(dict(type=p[0], flags=p[1], off=p[2], vaddr=p[3], filesz=p[4], memsz=p[5]))
|
||||||
|
return out
|
||||||
|
|
||||||
|
def sections(data):
|
||||||
|
e_shoff = struct.unpack_from('<Q', data, 0x28)[0]
|
||||||
|
e_shentsize = struct.unpack_from('<H', data, 0x3A)[0]
|
||||||
|
e_shnum = struct.unpack_from('<H', data, 0x3C)[0]
|
||||||
|
e_shstrndx = struct.unpack_from('<H', data, 0x3E)[0]
|
||||||
|
sh = []
|
||||||
|
for i in range(e_shnum):
|
||||||
|
s = struct.unpack_from('<IIQQQQIIQQ', data, e_shoff + i * e_shentsize)
|
||||||
|
sh.append(dict(name=s[0], type=s[1], addr=s[3], offset=s[4], size=s[5]))
|
||||||
|
shstr = sh[e_shstrndx]
|
||||||
|
tab = data[shstr['offset']:shstr['offset'] + shstr['size']]
|
||||||
|
def nm(idx):
|
||||||
|
e = tab.find(b'\0', idx)
|
||||||
|
return tab[idx:e].decode(errors='replace')
|
||||||
|
for s in sh:
|
||||||
|
s['name'] = nm(s['name'])
|
||||||
|
return sh
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
original, dump, base_s, out = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]
|
original, dump, base_s, out = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]
|
||||||
base = int(base_s, 16)
|
base = int(base_s, 16)
|
||||||
od = bytearray(open(original, 'rb').read())
|
od = bytearray(open(original, 'rb').read())
|
||||||
|
orig_raw = open(original, 'rb').read()
|
||||||
dd = open(dump, 'rb').read()
|
dd = open(dump, 'rb').read()
|
||||||
print(f"[*] original size={len(od):#x} dump size={len(dd):#x} base={base:#x}")
|
print(f"[*] orig={len(od):#x} dump={len(dd):#x} base=0x{base:x}")
|
||||||
|
|
||||||
def d2o(v):
|
ph = phdrs(orig_raw)
|
||||||
"""dump 偏移(vaddr轴) -> 原文件偏移"""
|
secs = sections(orig_raw)
|
||||||
return v - 0x1000 if v >= 0x3b21c0 else v
|
dyn_ranges, zero_ranges = [], []
|
||||||
|
for s in secs:
|
||||||
|
if s['name'] == '.dynamic':
|
||||||
|
dyn_ranges.append((s['offset'], s['offset'] + s['size']))
|
||||||
|
if s['name'] in ('.init_array', '.fini_array'):
|
||||||
|
zero_ranges.append((s['offset'], s['offset'] + s['size']))
|
||||||
|
|
||||||
# 1) 复制解密数据: 覆盖 [0x30000, 0x3eb000) (rodata+data), 含 text 尾部(相同, 无害)
|
# 按段复制: 数据段文件偏移 <- dump(运行时 vaddr=文件段 vaddr) 内容
|
||||||
COPY = [(0x30000, 0x3eb000)]
|
DATA_SECS = ('.rodata', '.data.rel.ro', '.got', '.got.plt', '.data')
|
||||||
for a, b in COPY:
|
n_copied = 0
|
||||||
for off in range(a, b):
|
for s in secs:
|
||||||
pass # 慢, 用切片
|
if s['type'] == 8 or s['name'] not in DATA_SECS:
|
||||||
# 切片方式: 逐块映射(避免逐字节)
|
continue
|
||||||
dst_start, dst_end = 0x30000, min(0x3eb000, len(dd))
|
vaddr, foff, sz = s['addr'], s['offset'], s['size']
|
||||||
# 一次性: dump[0x30000:0x3b21c0) -> orig[0x30000:0x3b21c0) (v<0x3b21c0 1:1)
|
if vaddr + sz > len(dd) or foff + sz > len(od):
|
||||||
s1, e1 = 0x30000, 0x3b21c0
|
print(f"[!] skip {s['name']}: out of bounds vaddr={vaddr:#x} off={foff:#x} sz={sz:#x}")
|
||||||
od[s1:e1] = dd[s1:e1]
|
continue
|
||||||
# dump[0x3b21c0:0x3eb000) -> orig[0x3b11c0:0x3ea000)
|
if dd[vaddr:vaddr+sz] != od[foff:foff+sz]:
|
||||||
s2, e2 = 0x3b21c0, min(0x3eb000, len(dd))
|
od[foff:foff+sz] = dd[vaddr:vaddr+sz]
|
||||||
d2 = e2 - 0x1000
|
n_copied += sz
|
||||||
if d2 <= len(od):
|
print(f"[*] merged {n_copied:#x} bytes via section mapping")
|
||||||
od[s2 - 0x1000:d2] = dd[s2:e2]
|
|
||||||
print(f"[*] data merged: dump[{s1:#x}:{e1:#x}]->orig[{s1:#x}] ; dump[{s2:#x}:{e2:#x}]->orig[{s2-0x1000:#x}:{d2:#x}]")
|
for a, b in dyn_ranges:
|
||||||
|
od[a:b] = orig_raw[a:b]
|
||||||
|
print(f"[*] kept original .dynamic [{a:#x}:{b:#x})")
|
||||||
|
for a, b in zero_ranges:
|
||||||
|
od[a:b] = b'\x00' * (b - a)
|
||||||
|
print(f"[*] zeroed [{a:#x}:{b:#x})")
|
||||||
|
|
||||||
|
# 清理只作用于重定位目标槽: vaddr->文件偏移, 越界运行期指针清零/模块内回退
|
||||||
|
R_AARCH64_RELATIVE, R_AARCH64_ABS64 = 1027, 257
|
||||||
|
R_AARCH64_GLOB_DAT, R_AARCH64_JUMP_SLOT = 1025, 1026
|
||||||
|
def v2foff(vaddr):
|
||||||
|
for s_ in secs:
|
||||||
|
if s_['type'] == 8 or s_['size'] == 0:
|
||||||
|
continue
|
||||||
|
if s_['addr'] <= vaddr < s_['addr'] + s_['size']:
|
||||||
|
return s_['offset'] + (vaddr - s_['addr'])
|
||||||
|
return None
|
||||||
|
n_zero = n_rebase = 0
|
||||||
|
for s_ in secs:
|
||||||
|
if s_['name'] not in ('.rela.dyn', '.rela.plt') or s_['type'] == 8:
|
||||||
|
continue
|
||||||
|
for i in range(0, s_['size'], 24):
|
||||||
|
r_off, r_info, _ = struct.unpack_from('<QQq', orig_raw, s_['offset'] + i)
|
||||||
|
rt = r_info & 0xffffffff
|
||||||
|
if rt not in (R_AARCH64_RELATIVE, R_AARCH64_ABS64, R_AARCH64_GLOB_DAT, R_AARCH64_JUMP_SLOT):
|
||||||
|
continue
|
||||||
|
foff = v2foff(r_off)
|
||||||
|
if foff is None or foff + 8 > len(od):
|
||||||
|
continue
|
||||||
|
v = struct.unpack_from('<Q', od, foff)[0]
|
||||||
|
if v < 0x4000000000:
|
||||||
|
continue
|
||||||
|
if base <= v < base + 0x600000:
|
||||||
|
struct.pack_into('<Q', od, foff, v - base)
|
||||||
|
n_rebase += 1
|
||||||
|
else:
|
||||||
|
struct.pack_into('<Q', od, foff, 0)
|
||||||
|
n_zero += 1
|
||||||
|
print(f"[*] reloc slots: rebased={n_rebase} zeroed={n_zero}")
|
||||||
|
|
||||||
open(out, 'wb').write(od)
|
|
||||||
open(out, 'wb').write(od)
|
open(out, 'wb').write(od)
|
||||||
print(f"[*] -> {out}")
|
print(f"[*] -> {out}")
|
||||||
|
|
||||||
|
|||||||
@@ -98,10 +98,18 @@ public class HyDeviceId extends AbstractJni {
|
|||||||
|
|
||||||
vm = emulator.createDalvikVM();
|
vm = emulator.createDalvikVM();
|
||||||
vm.setJni(this);
|
vm.setJni(this);
|
||||||
vm.setVerbose(false);
|
vm.setVerbose(true);
|
||||||
DalvikModule dm = vm.loadLibrary(new File(soPath), false);
|
DalvikModule dm = vm.loadLibrary(new File(soPath), false);
|
||||||
dm.callJNI_OnLoad(emulator);
|
dm.callJNI_OnLoad(emulator);
|
||||||
module = dm.getModule();
|
module = dm.getModule();
|
||||||
|
// 第二库: libudbauthunify (NativeBridge.klog/b 等 native 注册处, JNI_OnLoad@0x262620)
|
||||||
|
try {
|
||||||
|
DalvikModule udb = vm.loadLibrary(new File("/Users/yml/codes/douyu_login_py/tools/unidbg/hydev/so/libudbauthunify_merged.so"), false);
|
||||||
|
udb.callJNI_OnLoad(emulator);
|
||||||
|
System.out.println("[*] libudbauthunify loaded base=0x" + Long.toHexString(udb.getModule().base));
|
||||||
|
} catch (Throwable t) {
|
||||||
|
System.out.println("[!] udb load failed: " + t);
|
||||||
|
}
|
||||||
NE = vm.resolveClass("com/huya/security/hydeviceid/NativeEntry");
|
NE = vm.resolveClass("com/huya/security/hydeviceid/NativeEntry");
|
||||||
System.out.println("[*] lib loaded base=0x" + Long.toHexString(module.base));
|
System.out.println("[*] lib loaded base=0x" + Long.toHexString(module.base));
|
||||||
}
|
}
|
||||||
@@ -129,6 +137,7 @@ public class HyDeviceId extends AbstractJni {
|
|||||||
vm.resolveClass("android/content/ContextWrapper", vm.resolveClass("android/content/Context"))).newObject(signature);
|
vm.resolveClass("android/content/ContextWrapper", vm.resolveClass("android/content/Context"))).newObject(signature);
|
||||||
}
|
}
|
||||||
if (signature.equals("com/huya/security/hydeviceid/NativeBridge->b(I)Ljava/lang/String;")) {
|
if (signature.equals("com/huya/security/hydeviceid/NativeBridge->b(I)Ljava/lang/String;")) {
|
||||||
|
System.out.println("[JNI] NativeBridge.b FALLBACK-STUB (native not registered?)");
|
||||||
int i = vaList.getIntArg(0);
|
int i = vaList.getIntArg(0);
|
||||||
// 真机实测值 (2026-08-28): 100=GUID 101= MID 102= MID 106=64hex, 99/103=null, 104/105=""
|
// 真机实测值 (2026-08-28): 100=GUID 101= MID 102= MID 106=64hex, 99/103=null, 104/105=""
|
||||||
String v = null;
|
String v = null;
|
||||||
@@ -200,6 +209,12 @@ public class HyDeviceId extends AbstractJni {
|
|||||||
System.out.println("[JNI] SharedPreferences.edit()");
|
System.out.println("[JNI] SharedPreferences.edit()");
|
||||||
return new PrefsEditor(vm);
|
return new PrefsEditor(vm);
|
||||||
}
|
}
|
||||||
|
case "android/content/SharedPreferences$Editor->remove(Ljava/lang/String;)Landroid/content/SharedPreferences$Editor;": {
|
||||||
|
String rk = strArg(vaList, 0);
|
||||||
|
System.out.println("[prefs-remove] " + rk);
|
||||||
|
PREFS.remove(rk);
|
||||||
|
return dvmObject;
|
||||||
|
}
|
||||||
case "android/content/SharedPreferences$Editor->putString(Ljava/lang/String;Ljava/lang/String;)Landroid/content/SharedPreferences$Editor;": {
|
case "android/content/SharedPreferences$Editor->putString(Ljava/lang/String;Ljava/lang/String;)Landroid/content/SharedPreferences$Editor;": {
|
||||||
String k = strArg(vaList, 0);
|
String k = strArg(vaList, 0);
|
||||||
String v = strArg(vaList, 1);
|
String v = strArg(vaList, 1);
|
||||||
@@ -236,6 +251,19 @@ public class HyDeviceId extends AbstractJni {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {
|
||||||
|
// android/os/Build 静态字段读
|
||||||
|
if (signature.equals("android/os/Build->BRAND:Ljava/lang/String;")) return new StringObject(vm, "Xiaomi");
|
||||||
|
if (signature.equals("android/os/Build->MODEL:Ljava/lang/String;")) return new StringObject(vm, "M2102J2SC");
|
||||||
|
if (signature.equals("android/os/Build->MANUFACTURER:Ljava/lang/String;")) return new StringObject(vm, "Xiaomi");
|
||||||
|
if (signature.equals("android/os/Build->DEVICE:Ljava/lang/String;")) return new StringObject(vm, "thyme");
|
||||||
|
if (signature.equals("android/os/Build->BOARD:Ljava/lang/String;")) return new StringObject(vm, "thyme");
|
||||||
|
if (signature.equals("android/os/Build->FINGERPRINT:Ljava/lang/String;")) return new StringObject(vm, "Xiaomi/thyme/thyme:11/RKQ1.200826.002/V12.5.9.0.RKACNXM:user/release-keys");
|
||||||
|
noteUnhandled(signature);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int callIntMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
|
public int callIntMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user