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:
yml2213
2026-08-28 16:06:25 +08:00
parent 6ded82ac8b
commit 70621cff8f
16 changed files with 35749 additions and 2 deletions
+47
View File
@@ -0,0 +1,47 @@
#!/usr/bin/env python3
"""把 FULL dump 的解密数据合并进 ORIGINAL .so (元数据完好, unidbg 直接可加载).
关键洞察: 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>
"""
from __future__ import annotations
import struct, sys
def main():
original, dump, base_s, out = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]
base = int(base_s, 16)
od = bytearray(open(original, 'rb').read())
dd = open(dump, 'rb').read()
print(f"[*] original size={len(od):#x} dump size={len(dd):#x} base={base:#x}")
def d2o(v):
"""dump 偏移(vaddr轴) -> 原文件偏移"""
return v - 0x1000 if v >= 0x3b21c0 else v
# 1) 复制解密数据: 覆盖 [0x30000, 0x3eb000) (rodata+data), 含 text 尾部(相同, 无害)
COPY = [(0x30000, 0x3eb000)]
for a, b in COPY:
for off in range(a, b):
pass # 慢, 用切片
# 切片方式: 逐块映射(避免逐字节)
dst_start, dst_end = 0x30000, min(0x3eb000, len(dd))
# 一次性: dump[0x30000:0x3b21c0) -> orig[0x30000:0x3b21c0) (v<0x3b21c0 1:1)
s1, e1 = 0x30000, 0x3b21c0
od[s1:e1] = dd[s1:e1]
# dump[0x3b21c0:0x3eb000) -> orig[0x3b11c0:0x3ea000)
s2, e2 = 0x3b21c0, min(0x3eb000, len(dd))
d2 = e2 - 0x1000
if d2 <= len(od):
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}]")
open(out, 'wb').write(od)
open(out, 'wb').write(od)
print(f"[*] -> {out}")
if __name__ == '__main__':
main()
+75
View File
@@ -0,0 +1,75 @@
#!/usr/bin/env python3
"""重定位 dump (v3): 用 ORIGINAL .so 的段表定位 .rela.dyn/.init_array (布局一致),
对 DUMP 内同偏移的槽做"运行期地址 -> 链接期偏移"回退 / 导入槽清零.
用法: python3 rebase_dump.py <original.so> <dump.so> <runtime_base> <out.so>
"""
from __future__ import annotations
import struct, sys
R_AARCH64_ABS64 = 257
R_AARCH64_GLOB_DAT = 1025
R_AARCH64_JUMP_SLOT = 1026
R_AARCH64_RELATIVE = 1027
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]
shdrs = []
for i in range(e_shnum):
sh = struct.unpack_from('<IIQQQQIIQQ', data, e_shoff + i * e_shentsize)
shdrs.append(dict(name=sh[0], type=sh[1], addr=sh[3], offset=sh[4], size=sh[5], entsize=sh[9]))
shstr = shdrs[e_shstrndx]
shstrtab = data[shstr['offset']:shstr['offset'] + shstr['size']]
def sname(idx):
end = shstrtab.find(b'\0', idx)
return shstrtab[idx:end].decode(errors='replace')
for s in shdrs:
s['name'] = sname(s['name'])
return shdrs
def main():
original, dump, base_s, out = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]
base = int(base_s, 16)
odata = open(original, 'rb').read()
ddata = bytearray(open(dump, 'rb').read())
count = {'rebase': 0, 'zero': 0}
# dump 文件偏移 == vaddr (运行期镜像与 vaddr 同轴, 已实证)
def rebase_ptr(vaddr):
off = vaddr
if off + 8 > len(ddata):
return
v = struct.unpack_from('<Q', ddata, off)[0]
if off == 0x3b23f0: print('DBG rebase_ptr sees %#x' % v)
if v == 0 or v < 0x700000000000:
return
if base <= v < base + 0x600000:
struct.pack_into('<Q', ddata, off, v - base)
count['rebase'] += 1
else:
struct.pack_into('<Q', ddata, off, 0)
count['zero'] += 1
for s in sections(odata):
if s['name'] in ('.rela.dyn', '.rela.plt'):
for i in range(0, s['size'], 24):
r_offset, r_info, _ = struct.unpack_from('<QQq', odata, s['offset'] + i)
rtype = r_info & 0xffffffff
if rtype in (R_AARCH64_RELATIVE, R_AARCH64_ABS64):
rebase_ptr(r_offset)
elif rtype in (R_AARCH64_GLOB_DAT, R_AARCH64_JUMP_SLOT):
if r_offset + 8 <= len(ddata):
struct.pack_into('<Q', ddata, r_offset, 0)
count['zero'] += 1
elif s['name'] in ('.init_array', '.fini_array'):
for i in range(0, s['size'], 8):
rebase_ptr(s['addr'] + i)
open(out, 'wb').write(ddata)
print(f"[*] rebased {count['rebase']} slots, zeroed {count['zero']} -> {out}")
if __name__ == '__main__':
main()
+103
View File
@@ -0,0 +1,103 @@
#!/usr/bin/env python3
"""rebase v4: 直白版 - 原地改写 dump 的重定位槽, 无嵌套函数.
用法: python3 rebase_v4.py <original.so> <dump_full.so> <runtime_base> <out.so>
"""
from __future__ import annotations
import struct, sys
def main():
original, dump, base_s, out = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]
base = int(base_s, 16)
od = open(original, 'rb').read()
dd = bytearray(open(dump, 'rb').read())
# 原文件段表
e_shoff = struct.unpack_from('<Q', od, 0x28)[0]
e_shentsize = struct.unpack_from('<H', od, 0x3A)[0]
e_shnum = struct.unpack_from('<H', od, 0x3C)[0]
e_shstrndx = struct.unpack_from('<H', od, 0x3E)[0]
shdrs = []
for i in range(e_shnum):
sh = struct.unpack_from('<IIQQQQIIQQ', od, e_shoff + i * e_shentsize)
shdrs.append([sh[0], sh[3], sh[4], sh[5]]) # name, vaddr, offset, size
shstr = shdrs[e_shstrndx]
shstrtab = od[shstr[2]:shstr[2] + shstr[3]]
def nm(idx):
e = shstrtab.find(b'\0', idx)
return shstrtab[idx:e].decode(errors='replace')
reb, zer, low, oob = 0, 0, 0, 0
for s in shdrs:
n = nm(s[0])
if n not in ('.rela.dyn', '.rela.plt', '.init_array', '.fini_array'):
continue
if n in ('.rela.dyn', '.rela.plt'):
for i in range(0, s[3], 24):
r_off, r_info, _ = struct.unpack_from('<QQq', od, s[2] + i)
rt = r_info & 0xffffffff
if r_off + 8 > len(dd):
oob += 1
continue
v = struct.unpack_from('<Q', dd, r_off)[0]
if rt == 1027 or rt == 257: # RELATIVE / ABS64
if v == 0 or v < 0x4000000000:
low += 1
continue
if base <= v < base + 0x600000:
struct.pack_into('<Q', dd, r_off, v - base)
reb += 1
else:
struct.pack_into('<Q', dd, r_off, 0)
zer += 1
elif rt == 1025 or rt == 1026: # GLOB_DAT / JUMP_SLOT
struct.pack_into('<Q', dd, r_off, 0)
zer += 1
else: # init/fini arrays
for i in range(0, s[3], 8):
off = s[1] + i # vaddr 轴
if off + 8 > len(dd):
oob += 1
continue
v = struct.unpack_from('<Q', dd, off)[0]
if v == 0 or v < 0x4000000000:
low += 1
continue
if base <= v < base + 0x600000:
struct.pack_into('<Q', dd, off, v - base)
reb += 1
else:
struct.pack_into('<Q', dd, off, 0)
zer += 1
# 段名表修复: dump 的 shstrtab 为空, 复制原文件段头表 + shstrtab (布局一致)
import struct as _s
e_shoff = _s.unpack_from('<Q', od, 0x28)[0]
e_shentsize = _s.unpack_from('<H', od, 0x3A)[0]
e_shnum = _s.unpack_from('<H', od, 0x3C)[0]
e_shstrndx = _s.unpack_from('<H', od, 0x3E)[0]
# 原文件 shstrtab 段内容
sh = _s.unpack_from('<IIQQQQIIQQ', od, e_shoff + e_shstrndx * e_shentsize)
shstr_off, shstr_size = sh[4], sh[5]
if e_shoff + e_shnum * e_shentsize <= len(dd) and shstr_off + shstr_size <= len(dd):
dd[e_shoff:e_shoff + e_shnum * e_shentsize] = od[e_shoff:e_shoff + e_shnum * e_shentsize]
dd[shstr_off:shstr_off + shstr_size] = od[shstr_off:shstr_off + shstr_size]
# 重定位文件为全 vaddr 轴(offset==vaddr): 逐段修正 sh_offset = sh_addr
for si in range(e_shnum):
basee = e_shoff + si * e_shentsize
sh = list(_s.unpack_from('<IIQQQQIIQQ', dd, basee))
sh[4] = sh[3] # offset := addr
_s.pack_into('<IIQQQQIIQQ', dd, basee, *sh)
print(f"[*] section header table + shstrtab copied & offset:=addr (shoff={e_shoff:#x})")
else:
print("[!] WARN: section table copy skipped (out of bounds)")
open(out, 'wb').write(dd)
print(f"[*] rebase={reb} zero={zer} low={low} oob={oob}")
# 验证
import subprocess
v1 = struct.unpack_from('<Q', dd, 0x3b21c0)[0]
v2 = struct.unpack_from('<Q', dd, 0x3b9b68)[0] if 0x3b9b68 + 8 <= len(dd) else -1
print(f"[*] check init_array[0]={v1:#x} (.got@0x3b9b68)={v2:#x}")
if __name__ == '__main__':
main()
@@ -0,0 +1,326 @@
package hydev;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.BackendFactory;
import com.github.unidbg.arm.backend.DynarmicFactory;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.SystemPropertyHook;
import com.github.unidbg.linux.android.SystemPropertyProvider;
import com.github.unidbg.linux.android.dvm.AbstractJni;
import com.github.unidbg.linux.android.dvm.BaseVM;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.DvmClass;
import com.github.unidbg.linux.android.dvm.DvmObject;
import com.github.unidbg.linux.android.dvm.StringObject;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.linux.android.dvm.VaList;
import com.github.unidbg.linux.android.dvm.VarArg;
import com.github.unidbg.memory.Memory;
import java.io.File;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* unidbg 仿真 libhydeviceid.so: 解密数据合入原文件 + JNI 桩(日志化 SharedPreferences 等).
*
* 直接按模块偏移调 native: init@0x20e6e4, getGUID@0x20f8c8, getCDID@0x20eb08,
* getHDID@0x20f0c8, getMID@0x20fba8.
*/
public class HyDeviceId extends AbstractJni {
// 真机抓取的 qimei 等持久化值; 未知 key 返回默认并打印
static final Map<String, String> PREFS = new LinkedHashMap<>();
static {
PREFS.put("qimei36", "67453f5aef55f776967c748a10001c318101");
PREFS.put("qimei16", "67453f5aef55f776967c748a10001c318101");
// 服务端下发配置(真机 prefs 实测): getDfpConfig 可获取, 解密后含设备指纹种子
PREFS.put("hydeviceid_config", "2AQq9oUCCZ8MgS1qhJHFp+U7vBdmTBVkH8WlXondx64IE+kvc65rHJtuTazmCLT0Z7FYQEVuosfMmclJOeQrI7PLlAhhZYU5dXoYCKQJ7/ze1y5V6DvozlZZUQLG9jgoNJ4kdEyEi3QQavTQGdiokQ==");
}
static class Prefs extends DvmObject<Map<String, String>> {
Prefs(BaseVM vm) {
super(vm.resolveClass("android/content/SharedPreferences"), new LinkedHashMap<>());
}
}
static class PrefsEditor extends DvmObject<String> {
PrefsEditor(BaseVM vm) {
super(vm.resolveClass("android/content/SharedPreferences$Editor"), "");
}
}
static class FileObj extends DvmObject<String> {
FileObj(BaseVM vm, String path) {
super(vm.resolveClass("java/io/File"), path);
}
}
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
private final DvmClass NE;
private int unhandledBudget = 80;
HyDeviceId(String soPath) throws Exception {
BackendFactory backend = new DynarmicFactory(true);
emulator = AndroidEmulatorBuilder.for64Bit().setProcessName("com.duowan.kiwi").addBackendFactory(backend).build();
Memory memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));
Map<String, String> props = new LinkedHashMap<>();
String[][] propArr = {
{"init.svc.qemu-props",""}, {"init.svc.qemud",""}, {"qemu.sf.fake_camera",""},
{"qemu.sf.lcd_density",""}, {"ro.board.platform","kona"}, {"ro.boot.hardware","qcom"},
{"ro.boot.qemu",""}, {"ro.bootloader","unknown"}, {"ro.build.display.id","RKQ1.200826.002 test-keys"},
{"ro.build.flavor","qssi-user"}, {"ro.build.host","m1-xm-ota-bd197.bj.idc.xiaomi.com"},
{"ro.build.tags","release-keys"}, {"ro.build.type","user"}, {"ro.build.user","builder"},
{"ro.build.version.sdk","30"}, {"ro.hardware","qcom"}, {"ro.hardware.egl","adreno"},
{"ro.hardware.power",""}, {"ro.kernel.android.qemud",""}, {"ro.kernel.androidboot.hardware",""},
{"ro.kernel.qemu",""}, {"ro.kernel.qemu.avd_name",""}, {"ro.product.board","thyme"},
{"ro.product.brand","Xiaomi"}, {"ro.product.device","thyme"}, {"ro.product.manufacturer","Xiaomi"},
{"ro.product.marketname","Mi 10S"}, {"ro.product.model","M2102J2SC"}, {"ro.product.name","thyme"},
{"ro.setupwizard.mode",""}, {"ro.soc.model",""}, {"vendor.qemu.sf.fake_camera",""},
};
for (String[] kv : propArr) props.put(kv[0], kv[1]);
SystemPropertyHook propHook = new SystemPropertyHook(emulator);
propHook.setPropertyProvider(new SystemPropertyProvider() {
@Override
public String getProperty(String key) {
String v = props.get(key);
return v == null ? "" : v;
}
});
memory.addHookListener(propHook);
vm = emulator.createDalvikVM();
vm.setJni(this);
vm.setVerbose(false);
DalvikModule dm = vm.loadLibrary(new File(soPath), false);
dm.callJNI_OnLoad(emulator);
module = dm.getModule();
NE = vm.resolveClass("com/huya/security/hydeviceid/NativeEntry");
System.out.println("[*] lib loaded base=0x" + Long.toHexString(module.base));
}
// ==================== JNI 桩 ====================
private static String strArg(VaList vaList, int idx) {
try {
DvmObject<?> o = vaList.getObjectArg(idx);
return o == null ? null : String.valueOf(o.getValue());
} catch (Exception e) {
return null;
}
}
private void noteUnhandled(String what) {
if (unhandledBudget-- > 0) System.out.println("[JNI-unhandled] " + what);
}
@Override
public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
if (signature.equals("android/app/AppGlobals->getInitialApplication()Landroid/app/Application;")) {
System.out.println("[JNI] AppGlobals.getInitialApplication()");
return vm.resolveClass("android/app/Application",
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;")) {
int i = vaList.getIntArg(0);
// 真机实测值 (2026-08-28): 100=GUID 101= MID 102= MID 106=64hex, 99/103=null, 104/105=""
String v = null;
if (i == 100) v = "0a7dfaa882938a6ab502511452142c57";
else if (i == 101 || i == 102) v = "1e8bdf7d4f7a01d3";
else if (i == 106) v = "a71a0917ab6d0d6a61947a007591cfb97290f88972b047252c0ec7baddcd57df";
else if (i == 104 || i == 105) v = "";
System.out.println("[JNI] NativeBridge.b(" + i + ") -> " + v);
return v == null ? null : new StringObject(vm, v);
}
if (signature.equals("android/provider/Settings$Secure->getString(Landroid/content/ContentResolver;Ljava/lang/String;)Ljava/lang/String;") ||
signature.equals("android/provider/Settings$Global->getString(Landroid/content/ContentResolver;Ljava/lang/String;)Ljava/lang/String;")) {
String key = strArg(vaList, 1);
// 应用内实测 ANDROID_ID = MID
String v = key != null && key.equals("android_id") ? "1e8bdf7d4f7a01d3" : null;
System.out.println("[JNI] Settings.getString(" + key + ") -> " + v);
return v == null ? null : new StringObject(vm, v);
}
if (signature.equals("com/duowan/biz/wup/WupHelper->getGuid()Ljava/lang/String;")) {
String v = PREFS.get("hydeviceid_guid");
System.out.println("[JNI] WupHelper.getGuid() -> " + (v == null ? "null" : v));
return v == null ? null : new StringObject(vm, v);
}
try {
return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
} catch (UnsupportedOperationException e) {
noteUnhandled(signature);
return null;
}
}
@Override
public void callStaticVoidMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
if (signature.equals("com/huya/security/hydeviceid/NativeBridge->klog(Ljava/lang/String;Ljava/lang/String;Z)V")) {
String tag = "", msg = "";
try { tag = strArg(vaList, 0); } catch (Exception e) {}
try { msg = strArg(vaList, 1); } catch (Exception e) {}
System.out.println("[klog] " + tag + ": " + msg);
return;
}
try {
super.callStaticVoidMethodV(vm, dvmClass, signature, vaList);
} catch (UnsupportedOperationException e) {
noteUnhandled(signature);
}
}
@Override
public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
switch (signature) {
case "android/content/Context->getSharedPreferences(Ljava/lang/String;I)Landroid/content/SharedPreferences;": {
String name = strArg(vaList, 0);
System.out.println("[JNI] getSharedPreferences(" + name + ")");
return new Prefs(vm);
}
case "android/app/Application->getDataDir()Ljava/io/File;":
case "android/content/Context->getDataDir()Ljava/io/File;": {
System.out.println("[JNI] getDataDir() -> /data/data/com.duowan.kiwi");
return new FileObj(vm, "/data/data/com.duowan.kiwi");
}
case "android/content/SharedPreferences->getString(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;": {
String key = strArg(vaList, 0);
String def = strArg(vaList, 1);
String v = PREFS.getOrDefault(key, def);
System.out.println("[prefs-get] " + key + " -> " + (v == null ? "null" : v));
return v == null ? null : new StringObject(vm, v);
}
case "android/content/SharedPreferences->edit()Landroid/content/SharedPreferences$Editor;": {
System.out.println("[JNI] SharedPreferences.edit()");
return new PrefsEditor(vm);
}
case "android/content/SharedPreferences$Editor->putString(Ljava/lang/String;Ljava/lang/String;)Landroid/content/SharedPreferences$Editor;": {
String k = strArg(vaList, 0);
String v = strArg(vaList, 1);
System.out.println("[prefs-put] " + k + " = " + (v == null ? "null" : v));
PREFS.put(k, v == null ? "" : v);
return dvmObject;
}
case "android/app/Application->getFilesDir()Ljava/io/File;":
case "android/content/Context->getFilesDir()Ljava/io/File;": {
return new FileObj(vm, "/data/data/com.duowan.kiwi/files");
}
case "java/io/File->getAbsolutePath()Ljava/lang/String;":
case "java/io/File->getPath()Ljava/lang/String;":
case "java/io/File->toString()Ljava/lang/String;": {
String p = dvmObject instanceof FileObj ? ((FileObj) dvmObject).getValue() : "/data/data/com.duowan.kiwi";
return new StringObject(vm, p);
}
case "java/io/File->getParent()Ljava/lang/String;": {
String p = dvmObject instanceof FileObj ? ((FileObj) dvmObject).getValue() : "/data/data";
int i = p.lastIndexOf('/');
return new StringObject(vm, i > 0 ? p.substring(0, i) : "/");
}
case "java/io/File->getName()Ljava/lang/String;": {
String p = dvmObject instanceof FileObj ? ((FileObj) dvmObject).getValue() : "";
int i = p.lastIndexOf('/');
return new StringObject(vm, i >= 0 ? p.substring(i + 1) : p);
}
}
try {
return super.callObjectMethodV(vm, dvmObject, signature, vaList);
} catch (UnsupportedOperationException e) {
noteUnhandled(signature);
return null;
}
}
@Override
public int callIntMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
try {
return super.callIntMethodV(vm, dvmObject, signature, vaList);
} catch (UnsupportedOperationException e) {
noteUnhandled(signature);
return 0;
}
}
@Override
public boolean callBooleanMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
if (signature.equals("android/content/SharedPreferences$Editor->commit()Z")) {
System.out.println("[prefs] commit()");
return true;
}
if (signature.startsWith("java/io/File->") && (signature.contains("exists") || signature.contains("isDirectory")
|| signature.contains("mkdirs") || signature.contains("createNewFile") || signature.contains("isFile"))) {
System.out.println("[file] " + signature + " -> true");
return true;
}
try {
return super.callBooleanMethodV(vm, dvmObject, signature, vaList);
} catch (UnsupportedOperationException e) {
noteUnhandled(signature);
return false;
}
}
@Override
public void callVoidMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
if (signature.equals("android/content/SharedPreferences$Editor->apply()V")) {
System.out.println("[prefs] apply()");
return;
}
try {
super.callVoidMethodV(vm, dvmObject, signature, vaList);
} catch (UnsupportedOperationException e) {
noteUnhandled(signature);
}
}
// VarArg 变体也接到同名 V 变体(防御)
@Override
public DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
return callStaticObjectMethodV(vm, dvmClass, signature, null);
}
@Override
public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
return callObjectMethodV(vm, dvmObject, signature, null);
}
// ==================== native 调用 ====================
private Object[] jniArgs() {
return new Object[]{vm.getJNIEnv(), NE.hashCode()};
}
public void callInit() {
module.callFunction(emulator, 0x20e6e4L, jniArgs());
System.out.println("[*] init() ok");
}
public String callString(long off, String name) {
Number ret = module.callFunction(emulator, off, jniArgs());
DvmObject<?> o = vm.getObject(ret.intValue());
String v = o == null ? "null" : String.valueOf(o.getValue());
System.out.println("[*] " + name + " = " + v);
return v;
}
public static void main(String[] args) throws Exception {
String so = args.length > 0 ? args[0] : "so/libhydeviceid_merged.so";
HyDeviceId h = new HyDeviceId(so);
h.callInit();
String guid = h.callString(0x20f8c8L, "getGUID");
String cdid = h.callString(0x20eb08L, "getCDID");
String hdid = h.callString(0x20f0c8L, "getHDID");
String mid = h.callString(0x20fba8L, "getMID");
h.emulator.close();
System.out.println("=== GOLDEN COMPARE ===");
System.out.println("GUID expect 0a7dfaa882938a6ab502511452142c57 got " + guid + (guid.equals("0a7dfaa882938a6ab502511452142c57") ? " MATCH" : " MISMATCH"));
System.out.println("CDID expect 02df398797432eadefcc12767119ad5e80999389 got " + cdid);
System.out.println("HDID expect 7c5387e0539c023c31c4ff0e807e7256117385ee got " + hdid);
System.out.println("MID expect 1e8bdf7d4f7a01d3 got " + mid);
}
}
+10
View File
@@ -0,0 +1,10 @@
#!/bin/bash
# unidbg 仿真运行: 编译 + 执行 HyDeviceId
set -e
HERE="$(cd "$(dirname "$0")" && pwd)"
UNI="$HERE/.."
JAVA_HOME_21="/opt/homebrew/opt/openjdk@21"
CP="$(cat /tmp/unidbg_cp.txt):$UNI/unidbg-api/target/unidbg-api-0.9.10-SNAPSHOT.jar:$UNI/unidbg-android/target/unidbg-android-0.9.10-SNAPSHOT.jar"
mkdir -p "$HERE/out"
"$JAVA_HOME_21/bin/javac" -cp "$CP" -d "$HERE/out" "$HERE/src/hydev/HyDeviceId.java"
exec "$JAVA_HOME_21/bin/java" -Xmx2g -cp "$CP:$HERE/out" hydev.HyDeviceId "$@"