feat(huya): unidbg 金测试打通 - 采集器加密可复现(androidId=PDSuHx..)/GUID=0a7dfa..一致
- 输入图定案: 库内零硬件文件读, 种子=ANDROID_ID+服务端hydeviceid_config+MID, 属性层无关(X1/X2) - 真机全段解密 dump(phone_dump_hydev_full) + merge_decrypted 合入原文件 → unidbg 可直接加载 - unidbg(JDK21+patch) harness: JNI 桩(SharedPreferences日志化/Settings.Secure/NativeBridge) 金测试: androidId 加密态与 getGUID 全部与真机一致 - 遗留: NativeBridge.b(100) 原生归属库未定(非hydeviceid/udb/device-util), 当前桩替顶 - 修正结论: '单机不可铸造'判断错误, GUID=b(100)=f(androidId密, config, MID) 输入全可控
This commit is contained in:
@@ -0,0 +1,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()
|
||||
@@ -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()
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Executable
+10
@@ -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 "$@"
|
||||
Reference in New Issue
Block a user