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
+11
View File
@@ -806,3 +806,14 @@ hypasswordLogin/MsgLoginReq/LogLoginReq → mid 各异, tail 恒定 8b38f1 → a
- AppLoginData 字段布局: +0x40/+0x10/+0x8 (多 string), 全局材料=0x484000+0xa0 - AppLoginData 字段布局: +0x40/+0x10/+0x8 (多 string), 全局材料=0x484000+0xa0
### OTP tail = f(c,d) 差分 (推翻"固定"结论) + 真实布局空输出 (参数微差待调) ### OTP tail = f(c,d) 差分 (推翻"固定"结论) + 真实布局空输出 (参数微差待调)
### 下轮 (R8): getInstance→getOtp(AppLoginData) 直调, 金样本值注满结构 → 输出 vs ed0db8 ### 下轮 (R8): getInstance→getOtp(AppLoginData) 直调, 金样本值注满结构 → 输出 vs ed0db8
## §11.31 getOtp 直调成功 (R8) - C2 全链路在 emulator 内跑通
### 实验定案
- BusinessCfg::getInstance@0x281270 直调 ✓ → instance=0x12491540 (C2 真初始化!)
- BusinessCfg::getOtp(AppLoginData&)@0x26916c 直调 ✓ 无崩溃 (此路可行!)
- AppLoginData 伪造: std::string 双形态 (SSO<23 内联 / heap: len<<1|1 + [8]ptr + [16]cap) ✓
- OTP 真布局 (269324): x0=to_string(serviceTime), x1=2, x2=counter(w25=0首调), x3/x4=AppLoginData+0x40/+0x10,
x5=全局串, x6=4, x7=nonce-scalar, [sp]=OUT& (nonce 不入 mid!)
- mid = f(in, cnt, s3, s4, s5); OTP 输出=[h6][00][16B mid][s5 加密块...]
### 新候选: 堆 0x127d4b90 = 275d0ff676c0d65114acdefbd2ad87a3 (32hex! 来源待查)
### R9: ① hook 269328 (post-OTP, X29 读 [x29-0x40] = OUT!) ② 0x123M-0x130M 扫窗 before/after 差分
+113 -2
View File
@@ -145,11 +145,11 @@ public class AesProbe {
UnidbgPointer pout = writeStdString(""); UnidbgPointer pout = writeStdString("");
UnidbgPointer pm = writeStdString("mstack"); UnidbgPointer pm = writeStdString("mstack");
try { 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, module.callFunction(emulator, 0x32fa24L,
UnidbgPointer.nativeValue(pa), 2L, counter, UnidbgPointer.nativeValue(pa), 2L, counter,
UnidbgPointer.nativeValue(ps3), UnidbgPointer.nativeValue(ps4), UnidbgPointer.nativeValue(ps5), 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 out = readStdString(pout);
String shortIn = in.length() > 20 ? in.substring(0, 20) : in; String shortIn = in.length() > 20 ? in.substring(0, 20) : in;
String mid = out.length() > 36 ? out.substring(4, 36) : out; String mid = out.length() > 36 ? out.substring(4, 36) : out;
@@ -218,6 +218,105 @@ public class AesProbe {
return ""; 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) { void dumpRegion(String tag, long addr, long size) {
UnidbgPointer pp = UnidbgPointer.pointer(emulator, addr); UnidbgPointer pp = UnidbgPointer.pointer(emulator, addr);
if (pp == null) { System.out.println("[dump] " + tag + " null"); return; } 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 { public static void main(String[] args) throws Exception {
String so = args.length > 0 ? args[0] : "so/libudbauthunify_merged.so"; String so = args.length > 0 ? args[0] : "so/libudbauthunify_merged.so";
AesProbe p = new AesProbe(so); AesProbe p = new AesProbe(so);
p.callGetOtp();
// 基准: 已知 16B 明文 + 24B 钥 // 基准: 已知 16B 明文 + 24B 钥
// 64B 钥材料测试 (KeyExpansion 以 16B 步长读!!) // 64B 钥材料测试 (KeyExpansion 以 16B 步长读!!)
String k64_zmhav = "ZMHAVPRaxJ3MtXDjduUnXAKQ" + "\0".repeat(0); 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("0", 0L, "hy_300023887", "772ed992b0e161276f44ec63671e60155c506294", "1e8bdf7d4f7a01d3", 1L);
p.callOtpReal("3251699", 0L, "hy_300023887", "772ed992b0e161276f44ec63671e60155c506294", "7c5387e0539c023c31c4ff0e807e7256117385ee", 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", 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"); p.callOtp("LogLoginReq", "ZMHAVPRaxJ3MtXDjduUnXAKQ");
// SDID 块直解 (36B 证书的两个16B块 × 全部钥) // SDID 块直解 (36B 证书的两个16B块 × 全部钥)
p.callDecodeAes("859acf6fad86b24ac479361c2de5132f", "ZMHAVPRaxJ3MtXDjduUnXAKQ"); p.callDecodeAes("859acf6fad86b24ac479361c2de5132f", "ZMHAVPRaxJ3MtXDjduUnXAKQ");