feat(huya): OTP真机逐字节复现 + resinfo解密 + 登录路径六元组live捕获 (R13)
- GoldSweep: V2(真机146B复现292/292hex一致)/H/H2/I/J/J2差分模式 修复mid提取窗口bug(out[4:36]→out[2:34], 旧扫描作废) - 实机捕获(tools/frida两阶段注入): 登录路径OTP六元组 in="" s3="" s4=k1 s5=cred nonce=st<<16 + getHdid=DEVID40实锤 - resinfo解密: 密钥HuyaUdb1928374650qwertyuiop[:16] AES-ECB, 含channelKey/huyaDeviceId(40hex)/safedeviceid - HyDeviceId: b(100)真GUID修复 + Unicorn2后端 + init补桩全通 - docs §11.43: R13全量记录(差分统计/resinfo/getter定案/frida结论) - evidence/live_device: 真机持久化文件存档
This commit is contained in:
@@ -0,0 +1,674 @@
|
||||
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.Unicorn2Factory;
|
||||
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
|
||||
import com.github.unidbg.linux.android.AndroidResolver;
|
||||
import com.github.unidbg.linux.android.dvm.DalvikModule;
|
||||
import com.github.unidbg.linux.android.dvm.VM;
|
||||
import com.github.unidbg.memory.Memory;
|
||||
import com.github.unidbg.file.linux.LinuxFileSystem;
|
||||
import com.github.unidbg.pointer.UnidbgPointer;
|
||||
import java.io.File;
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* GoldSweep: 金样本 appSign(ed0db8) 差分引擎.
|
||||
*
|
||||
* getOtp 反汇编定案 (0x26916c-0x269324):
|
||||
* in = to_string(*(AppLoginData+0)) = to_string(hyOpenId) (密码登录 hyOpenId=0 -> "0")
|
||||
* cnt = AESkeyMgr 计数器 首调=1, 循环 1..15
|
||||
* s3 = BusinessCfg+0x40 (登录数据字段/候选)
|
||||
* s4 = BusinessCfg+0x10 (k1 设备常量 865a4924...)
|
||||
* s5 = AppLoginData+0x28 (登录请求期空)
|
||||
* nonce= nonce_next(serviceTime) = nc | st<<16 (nc 同 st 内递增, 通常 0/1)
|
||||
* 直调 hyudb_otp_encrypt@0x32fa24 与 getOtp 内部逐字节一致 (R12 已验证).
|
||||
*
|
||||
* 目标: 找到 (in,cnt,s3,s4,s5,nonce) 使 mid == ed0db8334cadd236c00cadf7e11ab5a5
|
||||
*/
|
||||
public class GoldSweep {
|
||||
|
||||
static final String G = "ed0db8334cadd236c00cadf7e11ab5a5";
|
||||
static final String K1C = "865a4924a40897ac1fcfe6b4c2cbb0e3";
|
||||
|
||||
private final AndroidEmulator emulator;
|
||||
private final Module module;
|
||||
|
||||
GoldSweep(String soPath) throws Exception {
|
||||
BackendFactory backend = new Unicorn2Factory(true);
|
||||
emulator = AndroidEmulatorBuilder.for64Bit().setProcessName("com.duowan.kiwi").addBackendFactory(backend).build();
|
||||
Memory memory = emulator.getMemory();
|
||||
memory.setLibraryResolver(new AndroidResolver(23));
|
||||
VM vm = emulator.createDalvikVM();
|
||||
vm.setJni(new com.github.unidbg.linux.android.dvm.AbstractJni() {});
|
||||
vm.setVerbose(false);
|
||||
DalvikModule dm = vm.loadLibrary(new File(soPath), false);
|
||||
dm.callJNI_OnLoad(emulator);
|
||||
module = dm.getModule();
|
||||
System.out.println("[*] lib loaded base=0x" + Long.toHexString(module.base));
|
||||
}
|
||||
|
||||
UnidbgPointer writeStdStringBytes(byte[] data) {
|
||||
Memory mem = emulator.getMemory();
|
||||
int len = data.length;
|
||||
UnidbgPointer p;
|
||||
if (len <= 22) {
|
||||
p = mem.malloc(24, false).getPointer();
|
||||
byte[] buf = new byte[24];
|
||||
buf[0] = (byte) (len << 1);
|
||||
System.arraycopy(data, 0, buf, 1, len);
|
||||
p.write(0, buf, 0, buf.length);
|
||||
} else {
|
||||
UnidbgPointer heap = mem.malloc(len + 8, false).getPointer();
|
||||
heap.write(0, data, 0, len);
|
||||
p = mem.malloc(24, false).getPointer();
|
||||
p.setInt(0, (int) (len + 1) | 1);
|
||||
p.setLong(8, len);
|
||||
p.setLong(16, UnidbgPointer.nativeValue(heap));
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
UnidbgPointer writeStdString(String s) {
|
||||
Memory mem = emulator.getMemory();
|
||||
byte[] data = s.getBytes();
|
||||
int len = data.length;
|
||||
UnidbgPointer p;
|
||||
if (len <= 22) {
|
||||
p = mem.malloc(24, false).getPointer();
|
||||
byte[] buf = new byte[24];
|
||||
buf[0] = (byte) (len << 1);
|
||||
System.arraycopy(data, 0, buf, 1, len);
|
||||
p.write(0, buf, 0, buf.length);
|
||||
} else {
|
||||
UnidbgPointer heap = mem.malloc(len + 1, false).getPointer();
|
||||
heap.write(0, data, 0, len);
|
||||
heap.write(len, new byte[]{0}, 0, 1);
|
||||
p = mem.malloc(24, false).getPointer();
|
||||
byte[] cap = new byte[8]; Arrays.fill(cap, (byte) 0xff);
|
||||
p.write(0, cap, 0, 8);
|
||||
byte[] szb = new byte[]{(byte) (len & 0xff), (byte) (len >> 8), 0, 0, 0, 0, 0, 0};
|
||||
p.write(8, szb, 0, 8);
|
||||
byte[] ptrBytes = new byte[8];
|
||||
long pv = UnidbgPointer.nativeValue(heap);
|
||||
for (int i = 0; i < 8; i++) ptrBytes[i] = (byte) (pv >> (8 * i));
|
||||
p.write(16, ptrBytes, 0, 8);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
String readStdString(UnidbgPointer p) {
|
||||
byte[] one = new byte[1];
|
||||
p.read(0, one, 0, 1);
|
||||
int first = one[0] & 0xff;
|
||||
if ((first & 1) == 0) {
|
||||
int len = first >> 1;
|
||||
byte[] dat = new byte[len];
|
||||
p.read(1, dat, 0, len);
|
||||
return new String(dat, java.nio.charset.StandardCharsets.ISO_8859_1);
|
||||
} else {
|
||||
long size = 0;
|
||||
byte[] szb = new byte[8];
|
||||
p.read(8, szb, 0, 8);
|
||||
for (int i = 0; i < 8; i++) size |= ((long) (szb[i] & 0xff)) << (8 * i);
|
||||
byte[] ptrb = new byte[8];
|
||||
p.read(16, ptrb, 0, 8);
|
||||
long pv = 0;
|
||||
for (int i = 0; i < 8; i++) pv |= ((long) (ptrb[i] & 0xff)) << (8 * i);
|
||||
UnidbgPointer data = UnidbgPointer.pointer(emulator, pv);
|
||||
byte[] dat2 = new byte[(int) size];
|
||||
data.read(0, dat2, 0, (int) size);
|
||||
return new String(dat2, java.nio.charset.StandardCharsets.ISO_8859_1);
|
||||
}
|
||||
}
|
||||
|
||||
UnidbgPointer writeCString(String s) {
|
||||
byte[] dat = s.getBytes(java.nio.charset.StandardCharsets.ISO_8859_1);
|
||||
UnidbgPointer p = emulator.getMemory().malloc(dat.length + 1, false).getPointer();
|
||||
p.write(0, dat, 0, dat.length);
|
||||
p.write(dat.length, new byte[]{0}, 0, 1);
|
||||
return p;
|
||||
}
|
||||
|
||||
static String toHex(String s) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : s.getBytes(java.nio.charset.StandardCharsets.ISO_8859_1)) sb.append(String.format("%02x", b));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/** 直调 OTP, 返回 [04][cnt][16B mid][16B tail] 的 hex; 出错返回 null */
|
||||
String otpHex(String in, long cnt, String s3, String s4, String s5, long nonce) {
|
||||
UnidbgPointer pa = writeStdString(in);
|
||||
UnidbgPointer ps3 = writeStdString(s3);
|
||||
UnidbgPointer ps4 = writeStdString(s4);
|
||||
UnidbgPointer ps5 = writeStdString(s5);
|
||||
UnidbgPointer pout = writeStdString("");
|
||||
try {
|
||||
module.callFunction(emulator, 0x32fa24L,
|
||||
UnidbgPointer.nativeValue(pa), 2L, cnt,
|
||||
UnidbgPointer.nativeValue(ps3), UnidbgPointer.nativeValue(ps4), UnidbgPointer.nativeValue(ps5),
|
||||
4L, nonce, UnidbgPointer.nativeValue(pout));
|
||||
return toHex(readStdString(pout));
|
||||
} catch (Throwable tx) { return null; }
|
||||
}
|
||||
|
||||
/** 直调 OTP (s5 二进制) */
|
||||
String otpHexB(String in, long cnt, String s3, String s4, byte[] s5, long nonce) {
|
||||
UnidbgPointer pa = writeStdString(in);
|
||||
UnidbgPointer ps3 = writeStdString(s3);
|
||||
UnidbgPointer ps4 = writeStdString(s4);
|
||||
UnidbgPointer ps5 = writeStdStringBytes(s5);
|
||||
UnidbgPointer pout = writeStdString("");
|
||||
try {
|
||||
module.callFunction(emulator, 0x32fa24L,
|
||||
UnidbgPointer.nativeValue(pa), 2L, cnt,
|
||||
UnidbgPointer.nativeValue(ps3), UnidbgPointer.nativeValue(ps4), UnidbgPointer.nativeValue(ps5),
|
||||
4L, nonce, UnidbgPointer.nativeValue(pout));
|
||||
return toHex(readStdString(pout));
|
||||
} catch (Throwable tx) { return null; }
|
||||
}
|
||||
|
||||
static byte[] hexBytes(String h) {
|
||||
byte[] b = new byte[h.length() / 2];
|
||||
for (int i = 0; i < b.length; i++) b[i] = (byte) Integer.parseInt(h.substring(2 * i, 2 * i + 2), 16);
|
||||
return b;
|
||||
}
|
||||
|
||||
/** 从 OTP hex 取 mid (32 hex) */
|
||||
static String midOf(String hexOut) {
|
||||
if (hexOut == null || hexOut.length() < 36) return "";
|
||||
return hexOut.substring(4, 36);
|
||||
}
|
||||
|
||||
void smoke() {
|
||||
// R12 验证值: 应逐字节命中 04c7537f...
|
||||
String h = otpHex("0", 1L, "hy_300023887", "hy_300023887", "", 0x1a049befebf0000L);
|
||||
System.out.println("[smoke] mid=" + midOf(h) + " expect 04c7537f5b40b45ed13e89ec3f8b1b89 raw=" + h);
|
||||
// 测速
|
||||
long t0 = System.currentTimeMillis();
|
||||
int n = 0;
|
||||
for (int i = 0; i < 500; i++) {
|
||||
if (otpHex("0", 1L, "hy_300023887", "hy_300023887", "", 0x1a049befebf0000L + i) != null) n++;
|
||||
}
|
||||
long dt = System.currentTimeMillis() - t0;
|
||||
System.out.printf("[smoke] %d calls in %d ms -> %.0f calls/s%n", n, dt, n * 1000.0 / Math.max(1, dt));
|
||||
}
|
||||
|
||||
void sweep(String tag, long stLo, long stHi,
|
||||
String[] inCands, long[] cntCands, String[] s3Cands, String[] s4Cands, String[] s5Cands, long[] ncCands) {
|
||||
long total = (stHi - stLo + 1) * inCands.length * cntCands.length * s3Cands.length * s4Cands.length * s5Cands.length * ncCands.length;
|
||||
System.out.println("[sweep:" + tag + "] st[" + stLo + "," + stHi + "] in=" + inCands.length + " cnt=" + cntCands.length
|
||||
+ " s3=" + s3Cands.length + " s4=" + s4Cands.length + " s5=" + s5Cands.length + " nc=" + ncCands.length
|
||||
+ " total=" + total);
|
||||
long t0 = System.currentTimeMillis();
|
||||
long n = 0;
|
||||
int hits = 0;
|
||||
for (long st = stLo; st <= stHi; st++) {
|
||||
for (String in : inCands) {
|
||||
for (long cnt : cntCands) {
|
||||
for (String s3 : s3Cands) {
|
||||
for (String s4 : s4Cands) {
|
||||
for (String s5 : s5Cands) {
|
||||
for (long nc : ncCands) {
|
||||
long nonce = nc | (st << 16);
|
||||
n++;
|
||||
String h = otpHex(in, cnt, s3, s4, s5, nonce);
|
||||
if (h != null) {
|
||||
String mid = midOf(h);
|
||||
if (mid.equals(G)) {
|
||||
System.out.println("!!! HIT st=" + st + " in=" + in + " cnt=" + cnt + " s3=[" + s3 + "] s4=[" + s4 + "] s5=[" + s5 + "] nc=" + nc
|
||||
+ " nonce=" + Long.toHexString(nonce) + " out=" + h);
|
||||
hits++;
|
||||
}
|
||||
}
|
||||
if (n % 4000 == 0) {
|
||||
long dt = System.currentTimeMillis() - t0;
|
||||
System.out.println("[sweep:" + tag + "] n=" + n + "/" + total + " st=" + st + " " + (n * 1000.0 / Math.max(1, dt)) + " calls/s");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
long dt = System.currentTimeMillis() - t0;
|
||||
System.out.println("[sweep:" + tag + "] DONE n=" + n + " hits=" + hits + " in " + dt + "ms (" + (n * 1000.0 / Math.max(1, dt)) + " calls/s)");
|
||||
}
|
||||
|
||||
|
||||
|
||||
void hookGetkeys(long cnt) {
|
||||
com.github.unidbg.arm.backend.CodeHook hook = new com.github.unidbg.arm.backend.CodeHook() {
|
||||
public void hook(com.github.unidbg.arm.backend.Backend backend, long address, int size, Object user) {
|
||||
try {
|
||||
long sp = backend.reg_read(unicorn.Arm64Const.UC_ARM64_REG_SP).longValue();
|
||||
UnidbgPointer kp = UnidbgPointer.pointer(emulator, sp + 0xe0);
|
||||
if (kp != null) {
|
||||
try {
|
||||
byte[] one = new byte[1];
|
||||
kp.read(0, one, 0, 1);
|
||||
System.out.println("[k2] sp+0xe0 flagbyte=0x" + String.format("%02x", one[0] & 0xff) + " str=[" + readStdString(kp) + "]");
|
||||
} catch (Throwable t) { System.out.println("[k2] read err " + t); }
|
||||
}
|
||||
} catch (Throwable t) { System.out.println("[k2] hook err " + t); }
|
||||
}
|
||||
public void onAttach(com.github.unidbg.arm.backend.UnHook unHook) {}
|
||||
public void detach() {}
|
||||
};
|
||||
try {
|
||||
emulator.getBackend().hook_add_new(hook, module.base + 0x32fb7cL, module.base + 0x32fb80L, null);
|
||||
} catch (Throwable t) { System.out.println("[k2] install err " + t); }
|
||||
String h = otpHex("1471224845212", cnt, "5008", K1C, "", 0x1a037881a430000L);
|
||||
System.out.println("[k2] otp cnt=" + cnt + " out=" + h);
|
||||
}
|
||||
|
||||
void dumpKeys() {
|
||||
for (int b = 0; b < 16; b++) {
|
||||
UnidbgPointer self = emulator.getMemory().malloc(0x80, false).getPointer();
|
||||
self.write(0, new byte[0x80], 0, 0x80);
|
||||
try { module.callFunction(emulator, 0x26fcc8L, UnidbgPointer.nativeValue(self)); } catch (Throwable t) {}
|
||||
Number r = module.callFunction(emulator, 0x26871cL, UnidbgPointer.nativeValue(self), 1L, (long) b);
|
||||
UnidbgPointer kp = UnidbgPointer.pointer(emulator, r.longValue());
|
||||
String key = kp == null ? "null" : readStdString(kp);
|
||||
System.out.println("[K] b=" + b + " key=[" + key + "] len=" + key.length());
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
String so = args.length > 0 ? args[0] : "so/libudbauthunify_merged.so";
|
||||
String mode = args.length > 1 ? args[1] : "A";
|
||||
GoldSweep p = new GoldSweep(so);
|
||||
|
||||
// 金样本常量族
|
||||
String NAME = "hy_300023887";
|
||||
String UIDSTR = "300023887";
|
||||
String K1 = "865a4924a40897ac1fcfe6b4c2cbb0e3";
|
||||
String SHA1PW = "772ed992b0e161276f44ec63671e60155c506294";
|
||||
String MID = "1e8bdf7d4f7a01d3";
|
||||
String DEVD40 = "7c5387e0539c023c31c4ff0e807e7256117385ee";
|
||||
String DEVD40B = "02df398797432eadefcc12767119ad5e80999389";
|
||||
String SDID = "*hZrPb62GskrEeTYcLeUTL1fJ1OR8ky3x9qXQq0s6ICJV5v9T";
|
||||
String MASK = "001******7524";
|
||||
String MASK2 = "01******7524";
|
||||
String SESSION = "3251699";
|
||||
String UID = "1199666914671";
|
||||
// 设备指纹 114B 二进制 (final_capture 08-25 实机 hook, 数美 SDK 产物, 设备级稳定)
|
||||
String fpC = "";
|
||||
try {
|
||||
byte[] fpb = new byte[114];
|
||||
String fph = "0a6028ce6a24d74b0e9b503050db127e3390d37d4a696ef46530804aaec7f9fa6ddc2142035729b8cecd102bf9402930aac60f21c293697e3b9b54fdbf976819b70078c2fa8b64bd9d14246eb4adc807ecf6bfdcc585f9a7b39524f88bea62135eb229e65512e6acd7a831ba772d5f374bf3";
|
||||
for (int i = 0; i < 114; i++) fpb[i] = (byte) Integer.parseInt(fph.substring(i * 2, i * 2 + 2), 16);
|
||||
fpC = new String(fpb, java.nio.charset.StandardCharsets.ISO_8859_1);
|
||||
} catch (Throwable t) { System.out.println("[fpC] parse err " + t); }
|
||||
|
||||
switch (mode) {
|
||||
case "smoke":
|
||||
p.smoke();
|
||||
break;
|
||||
case "V": // 真机输出验证: final_capture 08-25 biztoken OTP (in=uid,cnt=2,s3=5008,s4=k1,s5=cred114B,nonce)
|
||||
{
|
||||
String exp = "0402b4024c4a6069cb3c3806e14802a6a253e5cf3d8bde09ef37d8dcf3a3bc05f49100e94373a092";
|
||||
String h = p.otpHex("1471224845212", 2L, "5008", K1, fpC, 0x1a037881a430000L);
|
||||
System.out.println("[V] got=" + (h == null ? "null" : h));
|
||||
if (h != null) System.out.println("[V] head-match=" + h.startsWith(exp));
|
||||
// 也试 s5="" 对照
|
||||
String h2 = p.otpHex("1471224845212", 2L, "5008", K1, "", 0x1a037881a430000L);
|
||||
System.out.println("[V2] s5=[] got=" + (h2 == null ? "null" : h2));
|
||||
// s5=cred 的另一种 0a80 头部? 试 base64 cred 原文
|
||||
byte[] cb = new byte[114];
|
||||
String credHex = "0a6028ce6a24d74b0e9b503050db127e3390d37d4a696ef46530804aaec7f9fa6ddc2142035729b8cecd102bf9402930aac60f21c293697e3b9b54fdbf976819b70078c2fa8b64bd9d14246eb4adc807ecf6bfdcc585f9a7b39524f88bea62135eb229e65512e6acd7a831ba772d5f374bf3";
|
||||
for (int ci = 0; ci < 114; ci++) cb[ci] = (byte) Integer.parseInt(credHex.substring(ci*2, ci*2+2), 16);
|
||||
String cred = new String(cb, java.nio.charset.StandardCharsets.ISO_8859_1);
|
||||
String h3 = p.otpHex("1471224845212", 2L, K1, "5008", cred, 0x1a037881a430000L);
|
||||
System.out.println("[V3] swapped got=" + h3);
|
||||
}
|
||||
break;
|
||||
case "A": // 登录窗口紧扫: in="0"(hyOpenId=0) + s4=K1 (高置信)
|
||||
p.sweep("A", 1787595171900L, 1787595172700L,
|
||||
new String[]{"0"},
|
||||
new long[]{1, 2, 3, 4, 5},
|
||||
new String[]{NAME, "", "5008", K1, UIDSTR},
|
||||
new String[]{K1},
|
||||
new String[]{"", MASK},
|
||||
new long[]{0, 1});
|
||||
break;
|
||||
case "E": // 全 cnt 1..15 (AESkeyMgr 计数器循环) + 宽 s3/s5
|
||||
p.sweep("E", 1787595170000L, 1787595173200L,
|
||||
new String[]{"0", UID},
|
||||
new long[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
|
||||
new String[]{NAME, "", "5008", K1, UIDSTR, SHA1PW},
|
||||
new String[]{K1, SHA1PW, ""},
|
||||
new String[]{"", MASK, MASK2, SDID, MID},
|
||||
new long[]{0, 1, 2});
|
||||
break;
|
||||
case "B": // 宽窗口 + 全族
|
||||
p.sweep("B", 1787595170000L, 1787595173000L,
|
||||
new String[]{"0", UID, UIDSTR, SESSION},
|
||||
new long[]{0, 1, 2, 3, 4, 5},
|
||||
new String[]{NAME, K1, UIDSTR, "", MID},
|
||||
new String[]{K1, SHA1PW, NAME, "", MID},
|
||||
new String[]{"", MASK, MASK2, SDID, MID, DEVD40},
|
||||
new long[]{0, 1, 2});
|
||||
break;
|
||||
case "C": // 应用启动窗口 (18:11:30 app start 前后) - 指纹+5008家族
|
||||
p.sweep("C", 1787595089000L, 1787595096000L,
|
||||
new String[]{"0", UID, ""},
|
||||
new long[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
|
||||
new String[]{"5008", NAME, K1, ""},
|
||||
new String[]{K1},
|
||||
new String[]{fpC, ""},
|
||||
new long[]{0, 1, 2});
|
||||
break;
|
||||
case "D": // 8-28 hook 时刻 (前一会话实机钩子服务时间) 周边
|
||||
p.sweep("D", 1787943647000L, 1787943651000L,
|
||||
new String[]{"0", UID, UIDSTR},
|
||||
new long[]{1, 2, 3},
|
||||
new String[]{NAME, K1, ""},
|
||||
new String[]{K1, SHA1PW, ""},
|
||||
new String[]{"", MASK, SDID},
|
||||
new long[]{0, 1});
|
||||
break;
|
||||
case "P": // 顶配快速 probe: (s3=5008, s4=K1, s5=fpC/"") st 紧窗
|
||||
p.sweep("P", 1787595172350L, 1787595172600L,
|
||||
new String[]{"0"},
|
||||
new long[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
|
||||
new String[]{"5008", NAME, K1},
|
||||
new String[]{K1},
|
||||
new String[]{fpC, ""},
|
||||
new long[]{0, 1, 2});
|
||||
break;
|
||||
case "F": // 指纹聚焦: s5=fpC (114B数美指纹) × cnt 1..15 × 宽 st
|
||||
p.sweep("F", 1787595170000L, 1787595173200L,
|
||||
new String[]{"0"},
|
||||
new long[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
|
||||
new String[]{"5008", NAME, K1, ""},
|
||||
new String[]{K1},
|
||||
new String[]{fpC, ""},
|
||||
new long[]{0, 1, 2});
|
||||
break;
|
||||
case "G": // 备选 s4/s5 全族 (若 F 未中)
|
||||
p.sweep("G", 1787595170000L, 1787595173200L,
|
||||
new String[]{"0", UID},
|
||||
new long[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
|
||||
new String[]{"5008", NAME, K1, "", UIDSTR, SHA1PW},
|
||||
new String[]{K1, SHA1PW, NAME, ""},
|
||||
new String[]{fpC, "", MASK, SDID, MID},
|
||||
new long[]{0, 1, 2});
|
||||
break;
|
||||
case "K": // 打印 harness AESkeyMgr 钥表 + md5_char16 验证
|
||||
p.dumpKeys();
|
||||
break;
|
||||
case "K2": // hook getkey 抓 OTP 内部密钥 (全 1..15)
|
||||
for (long b = 1; b <= 15; b++) p.hookGetkeys(b);
|
||||
break;
|
||||
case "H": { // 金样本: 正确 mid 窗口 out[2:34] == ed0db8
|
||||
String T = "ed0db8334cadd236c00cadf7e11ab5a5";
|
||||
String credHex = "0a80ee56427cac9512d4b1efb6de70f89d402e40cbf4a224e127c8f588f523a917113306f36b1596b3a47f6c0765e5ce3acb549adecb0c9bdcf88f8464155289e0ef46becb08217e4fae6fa87d8702b9e641baa1ebf3e0347669cb9b40bf4c38331020b680c89df4982ab935d5c381a91f";
|
||||
byte[] cb = hexBytes(credHex);
|
||||
String[] ins = {"1471238907296", "0"};
|
||||
long t0 = System.currentTimeMillis();
|
||||
long n = 0;
|
||||
for (String in : ins)
|
||||
for (long cnt = 1; cnt <= 15; cnt++)
|
||||
for (long st = 1787595165000L; st <= 1787595172700L; st += 1)
|
||||
for (long nc = 0; nc <= 2; nc++) {
|
||||
long nonce = (nc & 0xffffL) | (st << 16);
|
||||
String out = p.otpHexB(in, cnt, "5008", K1C, cb, nonce);
|
||||
if (out != null && out.length() >= 34 && out.substring(2, 34).equals(T)) {
|
||||
System.out.println("!!! REAL-HIT in=" + in + " cnt=" + cnt + " st=" + st + " nc=" + nc);
|
||||
}
|
||||
if (++n % 20000 == 0) System.out.println("[H] " + n + " (" + (System.currentTimeMillis()-t0)/1000 + "s)");
|
||||
}
|
||||
// s5="" 第二轮
|
||||
for (String in : ins)
|
||||
for (long cnt = 1; cnt <= 15; cnt++)
|
||||
for (long st = 1787595165000L; st <= 1787595172700L; st += 1)
|
||||
for (long nc = 0; nc <= 2; nc++) {
|
||||
long nonce = (nc & 0xffffL) | (st << 16);
|
||||
String out = p.otpHexB(in, cnt, "5008", K1C, new byte[0], nonce);
|
||||
if (out != null && out.length() >= 34 && out.substring(2, 34).equals(T)) {
|
||||
System.out.println("!!! REAL-HIT(in0) in=" + in + " cnt=" + cnt + " st=" + st + " nc=" + nc);
|
||||
}
|
||||
if (++n % 20000 == 0) System.out.println("[H] " + n + " (" + (System.currentTimeMillis()-t0)/1000 + "s)");
|
||||
}
|
||||
System.out.println("[H] DONE " + n + " calls in " + (System.currentTimeMillis()-t0) + " ms");
|
||||
break;
|
||||
}
|
||||
case "H2": { // st 单点全集: app-start/biztoken-st/WUP 窗 + cnt 0..15 + in 4 种 + s5 cred/""
|
||||
String T = "ed0db8334cadd236c00cadf7e11ab5a5";
|
||||
String credHex = "0a80ee56427cac9512d4b1efb6de70f89d402e40cbf4a224e127c8f588f523a917113306f36b1596b3a47f6c0765e5ce3acb549adecb0c9bdcf88f8464155289e0ef46becb08217e4fae6fa87d8702b9e641baa1ebf3e0347669cb9b40bf4c38331020b680c89df4982ab935d5c381a91f";
|
||||
byte[] cb = hexBytes(credHex);
|
||||
long[] sts = {1787595090906L, 1787595091000L, 1787595102531L, 1787595167182L, 1787595172449L, 1787595172452L, 1787595172455L, 1787595172458L};
|
||||
String[] ins = {"1471238907296", "0", "300023887", ""};
|
||||
long n = 0;
|
||||
for (long st : sts)
|
||||
for (String in : ins)
|
||||
for (long cnt = 0; cnt <= 15; cnt++)
|
||||
for (long nc = 0; nc <= 2; nc++) {
|
||||
long nonce = (nc & 0xffffL) | (st << 16);
|
||||
String out = p.otpHexB(in, cnt, "5008", K1C, cb, nonce);
|
||||
if (out != null && out.length() >= 34 && out.substring(2, 34).equals(T)) {
|
||||
System.out.println("!!! REAL-HIT in=" + in + " cnt=" + cnt + " st=" + st + " nc=" + nc + " s5=cred");
|
||||
}
|
||||
if (++n % 400 == 0) System.out.print(".");
|
||||
}
|
||||
for (long st : sts)
|
||||
for (String in : ins)
|
||||
for (long cnt = 0; cnt <= 15; cnt++)
|
||||
for (long nc = 0; nc <= 2; nc++) {
|
||||
long nonce = (nc & 0xffffL) | (st << 16);
|
||||
String out = p.otpHexB(in, cnt, "5008", K1C, new byte[0], nonce);
|
||||
if (out != null && out.length() >= 34 && out.substring(2, 34).equals(T)) {
|
||||
System.out.println("!!! REAL-HIT in=" + in + " cnt=" + cnt + " st=" + st + " nc=" + nc + " s5=empty");
|
||||
}
|
||||
if (++n % 400 == 0) System.out.print(".");
|
||||
}
|
||||
System.out.println();
|
||||
System.out.println("[H2] DONE " + n + " calls");
|
||||
break;
|
||||
}
|
||||
case "RF": { // 真机 resinfo 文件层解密: 调 UdbFileUtil::readFileEx@0x252158
|
||||
java.io.File root = p.emulator.getFileSystem().getRootDir();
|
||||
java.io.File dir = new java.io.File(root, "data/user/0/com.duowan.kiwi/files/hydevice");
|
||||
dir.mkdirs();
|
||||
java.io.File of = new java.io.File(dir, "resinfo");
|
||||
java.io.File uf = new java.io.File(dir, "64a33427-f53c-4162-8738-81aa9117b950");
|
||||
java.io.File hk = new java.io.File(new java.io.File(root, "data/user/0/com.duowan.kiwi"), "fileshydckey");
|
||||
hk.getParentFile().mkdirs();
|
||||
java.nio.file.Files.copy(new java.io.File("/Users/yml/codes/douyu_login_py/evidence/live_device/resinfo.bin").toPath(), of.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING);
|
||||
java.nio.file.Files.copy(new java.io.File("/Users/yml/codes/douyu_login_py/evidence/live_device/uuid.bin").toPath(), uf.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING);
|
||||
java.nio.file.Files.copy(new java.io.File("/Users/yml/codes/douyu_login_py/evidence/live_device/hydckey.bin").toPath(), hk.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING);
|
||||
System.out.println("[RF] rootDir=" + root.getAbsolutePath());
|
||||
String[][] combos = {
|
||||
{"/data/user/0/com.duowan.kiwi/files/hydevice/resinfo", "/data/user/0/com.duowan.kiwi/files/hydevice/64a33427-f53c-4162-8738-81aa9117b950"},
|
||||
{"/data/user/0/com.duowan.kiwi/files/hydevice/resinfo", ""},
|
||||
{"/files/hydevice/resinfo", ""},
|
||||
{"/data/data/com.duowan.kiwi/files/hydevice/resinfo", ""},
|
||||
{"hydevice/resinfo", ""},
|
||||
{"/data/user/0/com.duowan.kiwi/files/hydevice/64a33427-f53c-4162-8738-81aa9117b950", "/data/user/0/com.duowan.kiwi/files/hydevice/resinfo"},
|
||||
{"/data/user/0/com.duowan.kiwi/files/hydevice/resinfo", "/data/user/0/com.duowan.kiwi/files/hydevice/64a33427-f53c-4162-8738-81aa9117b950"},
|
||||
{"/data/user/0/com.duowan.kiwi/files/hydevice/resinfo", "/data/user/0/com.duowan.kiwi/fileshydckey"},
|
||||
{"/data/user/0/com.duowan.kiwi/fileshydckey", "/data/user/0/com.duowan.kiwi/files/hydevice/resinfo"},
|
||||
{"/data/user/0/com.duowan.kiwi/files/hydevice/resinfo", "/data/user/0/com.duowan.kiwi/fileshydckey"},
|
||||
{"/data/user/0/com.duowan.kiwi/files/hydevice/64a33427-f53c-4162-8738-81aa9117b950", "/data/user/0/com.duowan.kiwi/files/hydevice/resinfo", "/data/user/0/com.duowan.kiwi/fileshydckey"},
|
||||
};
|
||||
// 追加: 完整 dump (resinfo, "") 的解密输出到本地文件
|
||||
{
|
||||
UnidbgPointer pp1 = p.writeCString("/data/user/0/com.duowan.kiwi/files/hydevice/resinfo");
|
||||
UnidbgPointer pp2 = p.writeCString("");
|
||||
UnidbgPointer po1 = p.writeStdString(""), po2 = p.writeStdString("");
|
||||
try {
|
||||
p.module.callFunction(p.emulator, 0x252158L,
|
||||
UnidbgPointer.nativeValue(pp1), UnidbgPointer.nativeValue(pp2),
|
||||
UnidbgPointer.nativeValue(po1), UnidbgPointer.nativeValue(po2));
|
||||
String o1 = p.readStdString(po1);
|
||||
byte[] b1 = o1.getBytes(java.nio.charset.StandardCharsets.ISO_8859_1);
|
||||
java.nio.file.Files.write(new java.io.File("/tmp/resinfo_dec1.bin").toPath(), b1);
|
||||
System.out.println("[RF] FULL dump -> /tmp/resinfo_dec1.bin (" + b1.length + "B)");
|
||||
} catch (Throwable t) { System.out.println("[RF] dump EXC " + t); }
|
||||
}
|
||||
for (String[] c : combos) {
|
||||
UnidbgPointer pp1 = p.writeCString(c[0]);
|
||||
UnidbgPointer pp2 = p.writeCString(c[1]);
|
||||
UnidbgPointer po1 = p.writeStdString(""), po2 = p.writeStdString("");
|
||||
try {
|
||||
p.module.callFunction(p.emulator, 0x252158L,
|
||||
UnidbgPointer.nativeValue(pp1), UnidbgPointer.nativeValue(pp2),
|
||||
UnidbgPointer.nativeValue(po1), UnidbgPointer.nativeValue(po2));
|
||||
String o1 = p.readStdString(po1), o2 = p.readStdString(po2);
|
||||
byte[] b1 = o1.getBytes(java.nio.charset.StandardCharsets.ISO_8859_1);
|
||||
byte[] b2 = o2.getBytes(java.nio.charset.StandardCharsets.ISO_8859_1);
|
||||
System.out.println("[RF] paths=(" + c[0] + "),(" + c[1] + ") o1=" + b1.length + "B o2=" + b2.length + "B");
|
||||
if (b1.length > 0) {
|
||||
System.out.println("[RF] o1 hex=" + hydev.GoldSweep.toHex(o1).substring(0, Math.min(160, b1.length * 2)));
|
||||
System.out.println("[RF] o1 str=" + o1.substring(0, Math.min(300, o1.length())));
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
System.out.println("[RF] EXC " + t);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "I": { // 装机窗暴破: st[1787582670000..1787582810000], in="0", s5="", cnt 1..15, nc 0..2
|
||||
String T = "ed0db8334cadd236c00cadf7e11ab5a5";
|
||||
long stLo = args.length > 2 ? Long.parseLong(args[2]) : 1787582670000L;
|
||||
long stHi = args.length > 3 ? Long.parseLong(args[3]) : 1787582810000L;
|
||||
long n = 0;
|
||||
for (long cnt = 1; cnt <= 15; cnt++)
|
||||
for (long st = stLo; st <= stHi; st += 1)
|
||||
for (long nc = 0; nc <= 2; nc++) {
|
||||
long nonce = (nc & 0xffffL) | (st << 16);
|
||||
String out = p.otpHexB("0", cnt, "5008", K1C, new byte[0], nonce);
|
||||
if (out != null && out.length() >= 34 && out.substring(2, 34).equals(T)) {
|
||||
System.out.println("!!! REAL-HIT cnt=" + cnt + " st=" + st + " nc=" + nc + " in=0 s5=empty");
|
||||
}
|
||||
if (++n % 50000 == 0) System.out.println("[I] " + n + " (st=" + st + ")");
|
||||
}
|
||||
System.out.println("[I] DONE " + n + " calls");
|
||||
break;
|
||||
}
|
||||
case "J": { // 正确语义: in="", s3="", s4=k1, s5=金样本cred — 单点+全窗
|
||||
String T = "ed0db8334cadd236c00cadf7e11ab5a5";
|
||||
String credHex = "0a80ee56427cac9512d4b1efb6de70f89d402e40cbf4a224e127c8f588f523a917113306f36b1596b3a47f6c0765e5ce3acb549adecb0c9bdcf88f8464155289e0ef46becb08217e4fae6fa87d8702b9e641baa1ebf3e0347669cb9b40bf4c38331020b680c89df4982ab935d5c381a91f";
|
||||
byte[] cb = hexBytes(credHex);
|
||||
// 单点阶段
|
||||
long[] sts = {1787595090906L, 1787595102531L, 1787595167182L, 1787595172449L, 1787595172452L, 1787595172455L, 1787595172458L};
|
||||
for (long st : sts)
|
||||
for (long cnt = 1; cnt <= 15; cnt++)
|
||||
for (long nc = 0; nc <= 2; nc++) {
|
||||
long nonce = (nc & 0xffffL) | (st << 16);
|
||||
String out = p.otpHexB("", cnt, "", K1C, cb, nonce);
|
||||
if (out != null && out.length() >= 34 && out.substring(2, 34).equals(T)) {
|
||||
System.out.println("!!! REAL-HIT cnt=" + cnt + " st=" + st + " nc=" + nc);
|
||||
}
|
||||
}
|
||||
System.out.println("[J] singles done");
|
||||
// 全窗阶段: st 登录窗 × cnt 1..15 × nc 0..2
|
||||
long n = 0;
|
||||
for (long cnt = 1; cnt <= 15; cnt++)
|
||||
for (long st = 1787595165000L; st <= 1787595172700L; st += 1)
|
||||
for (long nc = 0; nc <= 2; nc++) {
|
||||
long nonce = (nc & 0xffffL) | (st << 16);
|
||||
String out = p.otpHexB("", cnt, "", K1C, cb, nonce);
|
||||
if (out != null && out.length() >= 34 && out.substring(2, 34).equals(T)) {
|
||||
System.out.println("!!! REAL-HIT cnt=" + cnt + " st=" + st + " nc=" + nc);
|
||||
}
|
||||
if (++n % 50000 == 0) System.out.println("[J] " + n + " (st=" + st + ")");
|
||||
}
|
||||
System.out.println("[J] DONE " + n + " calls");
|
||||
break;
|
||||
}
|
||||
case "J2": { // 全笛卡尔: in{uid,"0",""} × s3{"5008","",k1} × s5{cred,""} × cnt1..15 × nc0..2
|
||||
String T = "ed0db8334cadd236c00cadf7e11ab5a5";
|
||||
String credHex = "0a80ee56427cac9512d4b1efb6de70f89d402e40cbf4a224e127c8f588f523a917113306f36b1596b3a47f6c0765e5ce3acb549adecb0c9bdcf88f8464155289e0ef46becb08217e4fae6fa87d8702b9e641baa1ebf3e0347669cb9b40bf4c38331020b680c89df4982ab935d5c381a91f";
|
||||
byte[] cb = hexBytes(credHex);
|
||||
String[] ins = {"1471238907296", "0", ""};
|
||||
String[] s3s = {"5008", "", K1C};
|
||||
byte[][] s5s = {cb, new byte[0]};
|
||||
long stLo = args.length > 2 ? Long.parseLong(args[2]) : 1787595165000L;
|
||||
long stHi = args.length > 3 ? Long.parseLong(args[3]) : 1787595172700L;
|
||||
long n = 0;
|
||||
for (long cnt = 1; cnt <= 15; cnt++)
|
||||
for (long st = stLo; st <= stHi; st += 1)
|
||||
for (long nc = 0; nc <= 2; nc++)
|
||||
for (String in : ins)
|
||||
for (String s3 : s3s)
|
||||
for (byte[] s5 : s5s) {
|
||||
long nonce = (nc & 0xffffL) | (st << 16);
|
||||
String out = p.otpHexB(in, cnt, s3, K1C, s5, nonce);
|
||||
if (out != null && out.length() >= 34 && out.substring(2, 34).equals(T)) {
|
||||
System.out.println("!!! REAL-HIT in=" + in + " s3=" + s3 + " cnt=" + cnt + " st=" + st + " nc=" + nc + " s5len=" + s5.length);
|
||||
}
|
||||
if (++n % 100000 == 0) System.out.println("[J2] " + n + " (st=" + st + ")");
|
||||
}
|
||||
System.out.println("[J2] DONE " + n + " calls");
|
||||
break;
|
||||
}
|
||||
case "V2": { // 复验: 真实 146B, s5=完整 114B cred, 精确 nonce — 比对输出[2:34](真值 首块=cipher[0:16])
|
||||
String credHex = "0a6028ce6a24d74b0e9b503050db127e3390d37d4a696ef46530804aaec7f9fa6ddc2142035729b8cecd102bf9402930aac60f21c293697e3b9b54fdbf976819b70078c2fa8b64bd9d14246eb4adc807ecf6bfdcc585f9a7b39524f88bea62135eb229e65512e6acd7a831ba772d5f374bf3";
|
||||
byte[] s5 = hexBytes(credHex);
|
||||
String out = p.otpHexB("1471224845212", 2L, "5008", K1C, s5, 0x1a037881a430000L);
|
||||
if (out == null) { System.out.println("V2 ERR"); break; }
|
||||
System.out.println("V2 firstblock=" + out.substring(2, 34));
|
||||
System.out.println("V2 needblock =b4024c4a6069cb3c3806e14802a6a253");
|
||||
System.out.println("V2 B1 MATCH=" + out.substring(2, 34).equals("b4024c4a6069cb3c3806e14802a6a253"));
|
||||
System.out.println("V2 len=" + out.length());
|
||||
System.out.println("V2 out=" + out);
|
||||
break;
|
||||
}
|
||||
case "X": // 真机输出校准: 扫 st 全窗, 固定(in,cnt,s3=5008,s4=K1,s5=cred) 复现真机146B
|
||||
{
|
||||
String EXP = "0402b4024c4a6069cb3c3806e14802a6a253e5cf3d8bde09ef37d8dcf3a3bc05f49100e94373a092";
|
||||
byte[] cb = new byte[114];
|
||||
String credHex = "0a6028ce6a24d74b0e9b503050db127e3390d37d4a696ef46530804aaec7f9fa6ddc2142035729b8cecd102bf9402930aac60f21c293697e3b9b54fdbf976819b70078c2fa8b64bd9d14246eb4adc807ecf6bfdcc585f9a7b39524f88bea62135eb229e65512e6acd7a831ba772d5f374bf3";
|
||||
for (int ci = 0; ci < 114; ci++) cb[ci] = (byte) Integer.parseInt(credHex.substring(ci*2, ci*2+2), 16);
|
||||
String cred = new String(cb, java.nio.charset.StandardCharsets.ISO_8859_1);
|
||||
for (long cnt : new long[]{0, 1, 2, 3, 4, 5}) {
|
||||
long t0 = System.currentTimeMillis();
|
||||
for (long st = 1787595089000L; st <= 1787595173000L; st++) {
|
||||
for (long nc : new long[]{0, 1, 2}) {
|
||||
long nonce = nc | (st << 16);
|
||||
String h = p.otpHex("1471224845212", cnt, "5008", K1, cred, nonce);
|
||||
if (h != null && h.startsWith(EXP)) {
|
||||
System.out.println("!!! REAL-HIT cnt=" + cnt + " st=" + st + " nc=" + nc + " nonce=" + Long.toHexString(nonce) + " out=" + h);
|
||||
}
|
||||
}
|
||||
}
|
||||
System.out.println("[X] cnt=" + cnt + " done in " + (System.currentTimeMillis() - t0) + "ms");
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "W": // 真机输出逐字节复现 - 小组合暴力
|
||||
{
|
||||
String EXP = "0402b4024c4a6069cb3c3806e14802a6a253e5cf3d8bde09ef37d8dcf3a3bc05f49100e94373a092";
|
||||
byte[] cb = new byte[114];
|
||||
String credHex = "0a6028ce6a24d74b0e9b503050db127e3390d37d4a696ef46530804aaec7f9fa6ddc2142035729b8cecd102bf9402930aac60f21c293697e3b9b54fdbf976819b70078c2fa8b64bd9d14246eb4adc807ecf6bfdcc585f9a7b39524f88bea62135eb229e65512e6acd7a831ba772d5f374bf3";
|
||||
for (int ci = 0; ci < 114; ci++) cb[ci] = (byte) Integer.parseInt(credHex.substring(ci*2, ci*2+2), 16);
|
||||
String cred = new String(cb, java.nio.charset.StandardCharsets.ISO_8859_1);
|
||||
String[] IN = {"1471224845212", "0", "", "1471238907296"};
|
||||
long[] CNT = {0, 1, 2, 3, 4, 5};
|
||||
String[] A3 = {"5008", K1, ""};
|
||||
String[] A4 = {K1, "5008", ""};
|
||||
String[] S5 = {cred, ""};
|
||||
long NONCE = 0x1a037881a430000L;
|
||||
int n = 0;
|
||||
long t0 = System.currentTimeMillis();
|
||||
for (String in : IN) for (long cnt : CNT) for (String s3 : A3) for (String s4 : A4) for (String s5 : S5) {
|
||||
n++;
|
||||
String h = p.otpHex(in, cnt, s3, s4, s5, NONCE);
|
||||
if (h != null && h.startsWith(EXP)) {
|
||||
System.out.println("!!! REAL-HIT in=" + in + " cnt=" + cnt + " s3=[" + s3 + "] s4=[" + s4 + "] s5len=" + s5.length() + " out=" + h);
|
||||
}
|
||||
}
|
||||
System.out.println("[W] n=" + n + " done in " + (System.currentTimeMillis() - t0) + "ms");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
p.smoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ 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.arm.backend.Unicorn2Factory;
|
||||
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
|
||||
import com.github.unidbg.linux.android.AndroidResolver;
|
||||
import com.github.unidbg.linux.android.SystemPropertyHook;
|
||||
@@ -38,6 +38,8 @@ public class HyDeviceId extends AbstractJni {
|
||||
PREFS.put("qimei16", "67453f5aef55f776967c748a10001c318101");
|
||||
// 服务端下发配置(真机 prefs 实测): getDfpConfig 可获取, 解密后含设备指纹种子
|
||||
PREFS.put("hydeviceid_config", "2AQq9oUCCZ8MgS1qhJHFp+U7vBdmTBVkH8WlXondx64IE+kvc65rHJtuTazmCLT0Z7FYQEVuosfMmclJOeQrI7PLlAhhZYU5dXoYCKQJ7/ze1y5V6DvozlZZUQLG9jgoNJ4kdEyEi3QQavTQGdiokQ==");
|
||||
PREFS.put("up_channel", "2001");
|
||||
PREFS.put("hydeviceid_applist", "{\"com.duowan.kiwi\":{\"first_install_time\":1787590888,\"last_update_time\":1787590888,\"install_source\":\"com.android.vending\"},\"com.xiaomi.scanner\":{\"first_install_time\":1787590000,\"last_update_time\":1787590000,\"install_source\":\"com.xiaomi.market\"}}");
|
||||
}
|
||||
|
||||
static class Prefs extends DvmObject<Map<String, String>> {
|
||||
@@ -65,7 +67,7 @@ public class HyDeviceId extends AbstractJni {
|
||||
private int unhandledBudget = 80;
|
||||
|
||||
HyDeviceId(String soPath) throws Exception {
|
||||
BackendFactory backend = new DynarmicFactory(true);
|
||||
BackendFactory backend = new Unicorn2Factory(true);
|
||||
emulator = AndroidEmulatorBuilder.for64Bit().setProcessName("com.duowan.kiwi").addBackendFactory(backend).build();
|
||||
Memory memory = emulator.getMemory();
|
||||
memory.setLibraryResolver(new AndroidResolver(23));
|
||||
@@ -136,6 +138,10 @@ public class HyDeviceId extends AbstractJni {
|
||||
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->h()Ljava/lang/String;")) {
|
||||
System.out.println("[JNI] NativeBridge.h() -> (null)");
|
||||
return null;
|
||||
}
|
||||
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);
|
||||
@@ -143,18 +149,23 @@ public class HyDeviceId extends AbstractJni {
|
||||
// b()-语义来自 jadx (NativeBridge.b -> fj3.c): 1=APPID(5008) 2=versionName 3=SDK版本(1.14.68)
|
||||
// 102=l5h.a=ANDROID_ID 104=l5h.f 105=pnc.f 106=l5h.i 107=qimei16 108=qimei36
|
||||
String v = null;
|
||||
if (i == 100) v = System.getProperty("hydev.android_id", "1e8bdf7d4f7a01d3");
|
||||
if (i == 100) v = System.getProperty("hydev.guid", "0a7dfaa882938a6ab502511452142c57");
|
||||
else if (i == 101 || i == 102) v = System.getProperty("hydev.android_id", "1e8bdf7d4f7a01d3");
|
||||
else if (i == 106) v = "a71a0917ab6d0d6a61947a007591cfb97290f88972b047252c0ec7baddcd57df";
|
||||
else if (i == 104 || i == 105) v = "";
|
||||
else if (i == 99 || i == 103) v = "7c5387e0539c023c31c4ff0e807e7256117385ee";
|
||||
else if (i == 5) v = "{\"install\":\"2024-08-24 10:00:00\"}";
|
||||
else if (i == 107) v = "1e8bdf7d4f7a01d3"; // qimei16
|
||||
else if (i == 108) v = "0a7dfaa882938a6ab502511452142c571e8bdf7d"; // qimei36 猜测
|
||||
else if (i == 108) v = System.getProperty("hydev.qimei36", "67453f5aef55f776967c748a10001c318101"); // qimei36 猜测
|
||||
else if (i == 1) v = "5008";
|
||||
else if (i == 2) v = "13.4.22";
|
||||
else if (i == 3) v = "1.14.68";
|
||||
else if (i == 107 || i == 108) v = "67453f5aef55f776967c748a10001c318101";
|
||||
else if (i == 107) v = "1e8bdf7d4f7a01d3";
|
||||
else if (i == 108) v = "0a7dfaa882938a6ab502511452142c571e8bdf7d";
|
||||
else if (i == 6) v = System.getProperty("hydev.b6", "hy_300023887");
|
||||
else if (i == 2001) v = System.getProperty("hydev.b2001", "2001");
|
||||
else if (i == 2002) v = System.getProperty("hydev.b2002", "300023887");
|
||||
else if (i == 2003) v = System.getProperty("hydev.b2003", "1199666914671");
|
||||
System.out.println("[JNI] NativeBridge.b(" + i + ") -> " + v);
|
||||
return v == null ? null : new StringObject(vm, v);
|
||||
}
|
||||
@@ -236,6 +247,24 @@ public class HyDeviceId extends AbstractJni {
|
||||
case "android/content/Context->getFilesDir()Ljava/io/File;": {
|
||||
return new FileObj(vm, "/data/data/com.duowan.kiwi/files");
|
||||
}
|
||||
case "android/app/Application->getPackageName()Ljava/lang/String;":
|
||||
case "android/content/Context->getPackageName()Ljava/lang/String;": {
|
||||
System.out.println("[JNI] getPackageName() -> com.duowan.kiwi");
|
||||
return new StringObject(vm, "com.duowan.kiwi");
|
||||
}
|
||||
case "android/app/Application->getApplicationInfo()Landroid/content/pm/ApplicationInfo;":
|
||||
case "android/content/Context->getApplicationInfo()Landroid/content/pm/ApplicationInfo;": {
|
||||
System.out.println("[JNI] getApplicationInfo() -> new ApplicationInfo");
|
||||
return vm.resolveClass("android/content/pm/ApplicationInfo").newObject(signature);
|
||||
}
|
||||
case "android/content/Context->getPackageManager()Landroid/content/pm/PackageManager;": {
|
||||
System.out.println("[JNI] getPackageManager() -> new PackageManager");
|
||||
return vm.resolveClass("android/content/pm/PackageManager").newObject(signature);
|
||||
}
|
||||
case "android/content/pm/PackageManager->getPackageInfo(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;": {
|
||||
System.out.println("[JNI] getPackageInfo() -> new PackageInfo");
|
||||
return vm.resolveClass("android/content/pm/PackageInfo").newObject(signature);
|
||||
}
|
||||
case "java/io/File->getAbsolutePath()Ljava/lang/String;":
|
||||
case "java/io/File->getPath()Ljava/lang/String;":
|
||||
case "java/io/File->toString()Ljava/lang/String;": {
|
||||
@@ -274,8 +303,41 @@ public class HyDeviceId extends AbstractJni {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DvmObject<?> getObjectField(BaseVM vm, DvmObject<?> dvmObject, String signature) {
|
||||
System.out.println("[JNI-getField] " + signature);
|
||||
if (signature.contains("ApplicationInfo") || signature.contains("PackageInfo")) {
|
||||
String field = signature.substring(signature.lastIndexOf("->") + 2);
|
||||
if (field.startsWith("packageName")) return new StringObject(vm, "com.duowan.kiwi");
|
||||
if (field.startsWith("versionName")) return new StringObject(vm, "13.4.22");
|
||||
if (field.startsWith("versionCode")) return new StringObject(vm, "115315");
|
||||
if (field.startsWith("label")) return new StringObject(vm, "虎牙直播");
|
||||
}
|
||||
try {
|
||||
return super.getObjectField(vm, dvmObject, signature);
|
||||
} catch (Exception e) {
|
||||
noteUnhandled(signature);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntField(BaseVM vm, DvmObject<?> dvmObject, String signature) {
|
||||
if (signature.contains("targetSdkVersion")) { System.out.println("[JNI-intfield] targetSdkVersion->30"); return 30; }
|
||||
if (signature.contains("versionCode")) { System.out.println("[JNI-intfield] versionCode->115315"); return 115315; }
|
||||
if (signature.contains("minSdkVersion")) { System.out.println("[JNI-intfield] minSdkVersion->30"); return 30; }
|
||||
try {
|
||||
return super.getIntField(vm, dvmObject, signature);
|
||||
} catch (Exception e) {
|
||||
noteUnhandled(signature + " -> 0");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int callIntMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
|
||||
if (signature.equals("android/content/pm/PackageInfo->versionCode:I")) { System.out.println("[JNI-int] versionCode->115315"); return 115315; }
|
||||
if (signature.equals("android/content/ApplicationInfo->versionCode:I")) return 115315;
|
||||
try {
|
||||
return super.callIntMethodV(vm, dvmObject, signature, vaList);
|
||||
} catch (UnsupportedOperationException e) {
|
||||
@@ -348,7 +410,9 @@ public class HyDeviceId extends AbstractJni {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
String so = args.length > 0 ? args[0] : "so/libhydeviceid_merged.so";
|
||||
System.setProperty("hydev.android_id", args.length > 1 ? args[1] : "1e8bdf7d4f7a01d3");
|
||||
System.setProperty("hydev.android_id", "1e8bdf7d4f7a01d3");
|
||||
System.setProperty("hydev.guid", "0a7dfaa882938a6ab502511452142c57");
|
||||
if (args.length > 2) System.setProperty("hydev.guid", args[2]);
|
||||
HyDeviceId h = new HyDeviceId(so);
|
||||
h.callInit();
|
||||
String guid = h.callString(0x20f8c8L, "getGUID");
|
||||
@@ -356,7 +420,6 @@ public class HyDeviceId extends AbstractJni {
|
||||
String hdid = h.callString(0x20f0c8L, "getHDID");
|
||||
String sdid = h.callString(0x20ede8L, "getSDID");
|
||||
String mid = h.callString(0x20fba8L, "getMID");
|
||||
// 32hex 探针: 再调一轮以观察缓存 (若 init 后 getGUID 重取 = 缓存)
|
||||
h.emulator.close();
|
||||
System.out.println("=== GOLDEN COMPARE ===");
|
||||
System.out.println("GUID expect 0a7dfaa882938a6ab502511452142c57 got " + guid + (guid.equals("0a7dfaa882938a6ab502511452142c57") ? " MATCH" : " MISMATCH"));
|
||||
|
||||
Reference in New Issue
Block a user