feat(huya): getOtp直调全通(C2初始化+AppLoginData伪造) - OTP真布局定案+堆32hex新候选

This commit is contained in:
yml2213
2026-08-29 02:25:21 +08:00
parent 0185296089
commit f121f41f61
2 changed files with 124 additions and 2 deletions
+113 -2
View File
@@ -145,11 +145,11 @@ public class AesProbe {
UnidbgPointer pout = writeStdString("");
UnidbgPointer pm = writeStdString("mstack");
try {
// 真实布局: (x0=in, x1=2, x2=counter, x3, x4, x5, x6=4, x7=out, [sp]=m)
// 真实布局: (x0=in, x1=2, x2=counter, x3, x4, x5, x6=4, x7=nonce-scalar, [sp]=OUT&)
module.callFunction(emulator, 0x32fa24L,
UnidbgPointer.nativeValue(pa), 2L, counter,
UnidbgPointer.nativeValue(ps3), UnidbgPointer.nativeValue(ps4), UnidbgPointer.nativeValue(ps5),
4L, UnidbgPointer.nativeValue(pout), UnidbgPointer.nativeValue(pm));
4L, 0x12345678L, UnidbgPointer.nativeValue(pout));
String out = readStdString(pout);
String shortIn = in.length() > 20 ? in.substring(0, 20) : in;
String mid = out.length() > 36 ? out.substring(4, 36) : out;
@@ -218,6 +218,105 @@ public class AesProbe {
return "";
}
void callGetOtp() {
try {
// BusinessCfg::getInstance @0x281270
Number inst = module.callFunction(emulator, 0x281270L);
long thisPtr = inst.longValue();
System.out.println("[getOtp] instance=0x" + Long.toHexString(thisPtr));
if (thisPtr == 0) { System.out.println("[getOtp] instance null"); return; }
// 伪造 AppLoginData: 4 个 string 槽 (24B SSO each) @ +0x8 +0x20 +0x38 +0x50
com.github.unidbg.memory.MemoryBlock mb = emulator.getMemory().malloc(0x1000, false);
UnidbgPointer pv = mb.getPointer();
pv.write(0, new byte[0x1000], 0, 0x1000);
writeStrAt(pv, 0x8, "hy_300023887");
writeStrAt(pv, 0x20, "772ed992b0e161276f44ec63671e60155c506294");
writeStrAt(pv, 0x38, "1e8bdf7d4f7a01d3");
writeStrAt(pv, 0x50, "*hZrPb62GskrEeTYcLeUTL1fJ1OR8ky3x9qXQq0s6ICJV5v9T");
// 调用 getOtp(AppLoginData&) @0x26916c
module.callFunction(emulator, 0x26916cL, thisPtr, UnidbgPointer.nativeValue(pv));
byte[] rb = new byte[0x60];
pv.read(0, rb, 0, rb.length);
StringBuilder sb2 = new StringBuilder();
for (byte bb : rb) sb2.append(String.format("%02x", bb));
System.out.println("[getOtp] call ok, struct: " + sb2);
System.out.println("[getOtp] done, scanning heap for outputs...");
byte[] win = new byte[0x10000];
int hits2 = 0;
for (long baseA = 0x12300000L; baseA < 0x13000000L && hits2 < 40; baseA += 0x10000L) {
UnidbgPointer hp2 = UnidbgPointer.pointer(emulator, baseA);
if (hp2 == null) continue;
try { hp2.read(0, win, 0, win.length); } catch (Throwable t) { continue; }
StringBuilder cur = new StringBuilder();
int st = -1;
for (int i = 0; i < win.length; i++) {
int b = win[i] & 0xff;
if ((b >= 0x20 && b < 0x7f)) { if (st < 0) st = i; cur.append((char) b); }
else {
if (cur.length() >= 30 && cur.chars().allMatch(c -> "0123456789abcdef".indexOf(Character.toLowerCase(c)) >= 0 || "hZrPb62GskrEeTYcLeUTL1fJ1OR8ky3x9qXQq0s6ICJV5v9T".indexOf(c) >= 0)) {
System.out.println("[getOtp-heap] @" + String.format("0x%x", baseA + st) + " = " + cur);
hits2++;
}
cur.setLength(0); st = -1;
}
}
}
System.out.println("[getOtp] heap-scan done hits=" + hits2);
} catch (Throwable t) {
System.out.println("[getOtp] err: " + t);
}
}
void writeStrAt(UnidbgPointer p, long off, String s) {
byte[] b = s.getBytes(java.nio.charset.StandardCharsets.UTF_8);
int n = b.length;
if (n < 23) {
p.write(off, new byte[]{(byte)(n << 1)}, 0, 1);
p.write(off + 1, b, 0, n);
p.write(off + 1 + n, new byte[]{0}, 0, 1);
} else {
// 真正堆串: [0]=len<<1|1 [8]=ptr [16]=cap
long cap = (n + 15) & ~15L;
com.github.unidbg.memory.MemoryBlock mb2 = emulator.getMemory().malloc((int)(cap + 8), false);
UnidbgPointer hp = mb2.getPointer();
hp.write(0, b, 0, n);
hp.write(n, new byte[]{0}, 0, 1);
byte[] b8 = new byte[8];
long v = com.github.unidbg.pointer.UnidbgPointer.nativeValue(hp);
for (int k = 0; k < 8; k++) b8[k] = (byte)(v >>> (8 * k));
p.write(off, new byte[]{(byte)(n << 1 | 1)}, 0, 1);
p.write(off + 8, b8, 0, 8);
for (int k = 0; k < 8; k++) b8[k] = (byte)(cap >>> (8 * k));
p.write(off + 16, b8, 0, 8);
}
}
void scanUdbGlobals() {
long base = module.base;
byte[] buf = new byte[0x12000];
int hits = 0;
for (int win = 0; win < 8 && hits < 20; win++) {
long addr = base + 0x470000L + win * 0x10000L;
UnidbgPointer pp = UnidbgPointer.pointer(emulator, addr);
if (pp == null) continue;
try { pp.read(0, buf, 0, buf.length); } catch (Throwable t) { continue; }
StringBuilder cur = new StringBuilder();
int start = -1;
for (int i = 0; i < buf.length; i++) {
int b = buf[i] & 0xff;
if (b >= 0x20 && b < 0x7f) { if (start < 0) start = i; cur.append((char) b); }
else {
if (cur.length() >= 10) {
System.out.println("[globals] @" + String.format("0x%x", addr + start) + " = " + cur);
hits++;
}
cur.setLength(0); start = -1;
}
}
}
System.out.println("[globals] done hits=" + hits);
}
void dumpRegion(String tag, long addr, long size) {
UnidbgPointer pp = UnidbgPointer.pointer(emulator, addr);
if (pp == null) { System.out.println("[dump] " + tag + " null"); return; }
@@ -396,6 +495,7 @@ public class AesProbe {
public static void main(String[] args) throws Exception {
String so = args.length > 0 ? args[0] : "so/libudbauthunify_merged.so";
AesProbe p = new AesProbe(so);
p.callGetOtp();
// 基准: 已知 16B 明文 + 24B 钥
// 64B 钥材料测试 (KeyExpansion 以 16B 步长读!!)
String k64_zmhav = "ZMHAVPRaxJ3MtXDjduUnXAKQ" + "\0".repeat(0);
@@ -469,6 +569,17 @@ public class AesProbe {
p.callOtpReal("0", 0L, "hy_300023887", "772ed992b0e161276f44ec63671e60155c506294", "1e8bdf7d4f7a01d3", 1L);
p.callOtpReal("3251699", 0L, "hy_300023887", "772ed992b0e161276f44ec63671e60155c506294", "7c5387e0539c023c31c4ff0e807e7256117385ee", 1L);
p.callOtpReal("3251699", 0L, "hy_300023887", "772ed992b0e161276f44ec63671e60155c506294", "13.4.22", 1L);
p.callOtpReal("3251699", 0L, "hy_300023887", "772ed992b0e161276f44ec63671e60155c506294", "13.4.22", 0x10101010L);
// SDID 作 s5 (36B 证书族)
p.callOtpReal("3251699", 0L, "hy_300023887", "772ed992b0e161276f44ec63671e60155c506294", "hZrPb62GskrEeTYcLeUTL1fJ1OR8ky3x9qXQq0s6ICJV5v9T", 0x12345678L);
p.callOtpReal("3251699", 0L, "hy_300023887", "772ed992b0e161276f44ec63671e60155c506294", "*hZrPb62GskrEeTYcLeUTL1fJ1OR8ky3x9qXQq0s6ICJV5v9T", 0x12345678L);
p.callOtpReal("", 0L, "", "", "*hZrPb62GskrEeTYcLeUTL1fJ1OR8ky3x9qXQq0s6ICJV5v9T", 0x12345678L);
p.callOtpReal("1", 1L, "hy_300023887", "772ed992b0e161276f44ec63671e60155c506294", "*hZrPb62GskrEeTYcLeUTL1fJ1OR8ky3x9qXQq0s6ICJV5v9T", 0x12345678L);
// nonce=0 + 2024-08-24 时间戳 era + SDID 族
p.callOtpReal("1724464550000", 0L, "hy_300023887", "772ed992b0e161276f44ec63671e60155c506294", "*hZrPb62GskrEeTYcLeUTL1fJ1OR8ky3x9qXQq0s6ICJV5v9T", 0L);
p.callOtpReal("1724464550", 0L, "hy_300023887", "772ed992b0e161276f44ec63671e60155c506294", "*hZrPb62GskrEeTYcLeUTL1fJ1OR8ky3x9qXQq0s6ICJV5v9T", 0L);
p.callOtpReal("1724464550000", 0L, "hy_300023887", "772ed992b0e161276f44ec63671e60155c506294", "hZrPb62GskrEeTYcLeUTL1fJ1OR8ky3x9qXQq0s6ICJV5v9T", 0L);
p.callOtpReal("1724464550000", 0L, "hy_300023887", "772ed992b0e161276f44ec63671e60155c506294", "1e8bdf7d4f7a01d3", 0L);
p.callOtp("LogLoginReq", "ZMHAVPRaxJ3MtXDjduUnXAKQ");
// SDID 块直解 (36B 证书的两个16B块 × 全部钥)
p.callDecodeAes("859acf6fad86b24ac479361c2de5132f", "ZMHAVPRaxJ3MtXDjduUnXAKQ");