from sage.allimport * from Crypto.Util.number import long_to_bytes
n = 103581608824736882681702548494306557458428217716535853516637603198588994047254920265300207713666564839896694140347335581147943392868972670366375164657970346843271269181099927135708348654216625303445930822821038674590817017773788412711991032701431127674068750986033616138121464799190131518444610260228947206957 leak = 6614588561261434084424582030267010885893931492438594708489233399180372535747474192128 c = 38164947954316044802514640871285562707869793354907165622336840432488893861610651450862702262363481097538127040490478908756416851240578677195459996252755566510786486707340107057971217557295217072867673485369358370289506549932119879791474279677563080377456592139035501163534305008864900509896586230830001710243 e = 65537
k = 230 p0 = Integer(leak) << k
print("[*] gcd(p0, n) =", gcd(p0, n)) # 应该是 1
R.<x> = PolynomialRing(Zmod(n)) f = (p0 + x).monic() X = 2^k
defvigenere_decrypt(text, key): key_shifts = [string.ascii_lowercase.index(ch) for ch in key.lower() if ch in string.ascii_lowercase] out = [] ki = 0 for ch in text: lo = ch.lower() if lo in string.ascii_lowercase: shift = key_shifts[ki % len(key_shifts)] alpha = string.ascii_lowercase if ch.islower() else string.ascii_uppercase idx = alpha.index(ch) out.append(alpha[(idx - shift) % 26]) ki += 1 else: out.append(ch) return"".join(out)
defshash_full(value: str, key: int) -> int: length = len(value) if length == 0: return0 x = (ord(value[0]) << 7) & MASK256 for ch in value: x = ((key * x) & MASK256) ^ ord(ch) x ^= length & MASK256 return x & MASK256
defshash_mod(value: str, key: int, bits: int) -> int: """仅计算低 bits 位,用于按位提升""" mask = (1 << bits) - 1 length = len(value) if length == 0: return0 x = (ord(value[0]) << 7) & mask k = key & mask for ch in value: x = ((k * x) & mask) ^ ord(ch) x ^= length & mask return x & mask
defread_data(path="data.txt"): withopen(path, "r", encoding="utf-8") as f: data_line = f.readline().strip() n_line = f.readline().strip() data = eval(data_line) # 题目输出格式固定 n = int(n_line) return data, n
defmodinv(a, m): returnpow(a, -1, m)
defsolve_linear_mod(A, b, mod): """模素数下高斯消元,解 A*x=b""" k = len(A) M = [row[:] + [b_i] for row, b_i inzip(A, b)] for col inrange(k): pivot = None for r inrange(col, k): if M[r][col] % mod != 0: pivot = r break if pivot isNone: raise ValueError("singular") M[col], M[pivot] = M[pivot], M[col]
invp = modinv(M[col][col] % mod, mod) for c inrange(col, k + 1): M[col][c] = (M[col][c] * invp) % mod
for r inrange(k): if r == col: continue factor = M[r][col] % mod if factor == 0: continue for c inrange(col, k + 1): M[r][c] = (M[r][c] - factor * M[col][c]) % mod
return [M[i][k] % mod for i inrange(k)]
deftonelli_shanks(a, p): """解 x^2=a (mod p),p 为奇素数;无解返回 None""" a %= p if a == 0: return0 ifpow(a, (p - 1) // 2, p) != 1: returnNone if p % 4 == 3: returnpow(a, (p + 1) // 4, p)
# p-1 = q*2^s,q 奇数 q = p - 1 s = 0 while q % 2 == 0: s += 1 q //= 2
# 找一个二次非剩余 z z = 2 whilepow(z, (p - 1) // 2, p) != p - 1: z += 1
m = s c = pow(z, q, p) t = pow(a, q, p) r = pow(a, (q + 1) // 2, p)
while t != 1: i = 1 t2i = (t * t) % p while t2i != 1: t2i = (t2i * t2i) % p i += 1 b = pow(c, 1 << (m - i - 1), p) m = i c = (b * b) % p t = (t * c) % p r = (r * b) % p return r
defrecover_h_candidates_from_flux(x1, x2, x3, x4, n): # 由三次更新解出 a,b,c A = [ [pow(x1, 2, n), x1 % n, 1], [pow(x2, 2, n), x2 % n, 1], [pow(x3, 2, n), x3 % n, 1], ] bvec = [x2 % n, x3 % n, x4 % n] a, b, c = solve_linear_mod(A, bvec, n)
# 代回第一步,得到 x0=h 的二次方程,求平方根得到两解 D = (b*b - 4*a*((c - x1) % n)) % n sqrtD = tonelli_shanks(D, n) if sqrtD isNone: return []
inv2a = modinv((2*a) % n, n) h1 = ((-b + sqrtD) * inv2a) % n h2 = ((-b - sqrtD) * inv2a) % n return [h1, h2]
deflift_key(value: str, target_h: int, bits: int = 70): """按位提升 key(只保留满足低位 shash 一致的候选)""" # t=1 初始化:k 只有 0/1 两种 cands = [0, 1] cands = [k for k in cands if shash_mod(value, k, 1) == (target_h & 1)] for t inrange(1, bits): nxt = [] for k in cands: nxt.append(k) nxt.append(k | (1 << t)) mask = (1 << (t + 1)) - 1 want = target_h & mask cands = [k for k in nxt if shash_mod(value, k, t + 1) == want] ifnot cands: return [] return cands
defmain(): data, n = read_data("data.txt") x1, x2, x3, x4 = data
value = "Welcome to HGAME 2026!" magic_word = "I get the key now!"
hs = recover_h_candidates_from_flux(x1, x2, x3, x4, n) for h in hs: ks = lift_key(value, h, 70) for key in ks: if key < (1 << 70) and shash_full(value, key) == h: flag_hash = shash_full(magic_word, key) flag = "VIDAR{" + hex(flag_hash)[2:] + "}" print("key =", key) print("flag =", flag) return
print("not found")
if __name__ == "__main__": main()
运行输出:
key = 860533
flag = VIDAR{1069466028b4c4a9694a3175f2f9410ab398b939bdb52afb39534b6f8cc59abc}
from Crypto.Util.number import * import string, itertools, bisect, multiprocessing as mp, os, time
# 已知参数 c = 451420045234442273941376910979916645887835448913611695130061067762180161 p = 722243413239346736518453990676052563 q = 777452004761824304315754169245494387 n = p*q phi = (p-1)*(q-1) e = 65537
# 解密得到 m_mod m_mod = pow(c, pow(e, -1, phi), n)
prefix = b'VIDAR{' suffix = b'}' allowed_bytes = list((string.digits + string.ascii_letters + '_@').encode()) allowed_table = bytearray(256) for b in allowed_bytes: allowed_table[b] = 1
L = 42# 实际长度(运行中会找到) h = 4# 先枚举前 h 个未知字符 r = 4# 约束最后 r 个字节
P = bytes_to_long(prefix) mod = 256**r inv_n_mod = pow(n, -1, mod)
# 预计算 t 的 residue(满足最后 r 字节) residues = [] for combo in itertools.product(allowed_bytes, repeat=r-1): tail = bytes(combo) + suffix x = int.from_bytes(tail, 'big') t0 = ((x - (m_mod % mod)) * inv_n_mod) % mod residues.append(t0) residues = sorted(set(residues))
pow_tail = 256**(L-6-h)
# 并行搜索 b1_values = allowed_bytes cpu = os.cpu_count() or4 chunks = [b1_values[i::cpu] for i inrange(cpu)]
found_flag = mp.Event() result_queue = mp.Queue()
defworker(b1_list): for b1 in b1_list: P1 = P*256 + b1 for b2 in allowed_bytes: P2 = P1*256 + b2 for b3 in allowed_bytes: P3 = P2*256 + b3 base = P3*256 for b4 in allowed_bytes: if found_flag.is_set(): return P4 = base + b4 low = P4 * pow_tail high = low + pow_tail - 1
a = low - m_mod t_min = (a + n - 1)//n if a >= 0else -((-a)//n) t_max = (high - m_mod)//n if t_max < t_min: continue
for r0 in cand: t = t_min + ((r0 - t_min_mod) % mod) if t > t_max: continue m = m_mod + t*n b = m.to_bytes(L, 'big') ifnot (b.startswith(prefix) and b.endswith(suffix)): continue mid = b[6:-1] ok = True for ch in mid: ifnot allowed_table[ch]: ok = False break if ok: result_queue.put(b) found_flag.set() return
if __name__ == '__main__': procs = [] for chunk in chunks: p = mp.Process(target=worker, args=(chunk,)) p.start() procs.append(p)
try: whileTrue: ifnot result_queue.empty(): flag = result_queue.get() print(flag.decode()) break if found_flag.is_set() andall(not p.is_alive() for p in procs): break time.sleep(0.5) finally: for p in procs: p.terminate() for p in procs: p.join()
1
VIDAR{Congr@tulations_you_re4lly_konw_RS4}
Reverse
PVZ
exe 其实是 zip/jar,直接解包即可:
1
unzip -qq gpvz.exe -d out
然后看 FlagScreen 的字节码:
1
javap -classpath out -c -p com.pvz.vidar.game.wsdx233.top.screen.FlagScreen
encrypted = bytes([(b+256)%256for b in [ 0,-8,-6,6,31,-39,-104,114,86,-23,-35,28,-122,56,29,-126,-29,94,23,-29,46,-126,-4,45,20,-57 ]]) xorKey1 = 102 xorKey2 = 119 aes_key = bytes([(b+256)%256for b in [ 74,-111,-61,127,46,-75,104,-44,28,-119,58,-14,93,-90,113,-66 ]])
rotation_str = "PLANTS_VS_ZOMBIES_2025" rot_offset = sum(ord(c) for c in rotation_str) % 26# 20
# substitutionMap: 原始->替换后;解密时用反向表 pairs = [ (65,81),(66,87),(67,69),(68,82),(69,84),(70,89),(71,85),(72,73),(73,79),(74,80), (75,65),(76,83),(77,68),(78,70),(79,71),(80,72),(81,74),(82,75),(83,76),(84,90), (85,88),(86,67),(87,86),(88,66),(89,78),(90,77), (95,33),(123,91),(125,93) ] sub_map = {chr(a): chr(b) for a,b in pairs} rev_map = {v:k for k,v in sub_map.items()}
deflcg_key(seed): state = seed & 0xffffffff key = [] for _ inrange(16): state = (state * 1103515245 + 12345) & 0x7fffffff key.append(((state >> 16) % 256) & 0xff) return key
defdecrypt_with_seed(seed): key = lcg_key(seed) out = bytearray(len(encrypted)) for i,b inenumerate(encrypted): mask = (i*13 + 7) % 256 out[i] = b ^ key[i % 16] ^ mask returnbytes(out)
defrotate_decrypt(s, offset): res=[] for ch in s: o=ord(ch) if65<=o<=90: res.append(chr((o-65-offset+26)%26 + 65)) elif97<=o<=122: res.append(chr((o-97-offset+26)%26 + 97)) else: res.append(ch) return"".join(res)
defsubstitution_decrypt(s): return"".join(rev_map.get(ch,ch) for ch in s)
defsolve(): for seed inrange(65536): b = decrypt_with_seed(seed) mid = len(b)//2 p1 = bytes([x ^ xorKey1 for x in b[:mid]]) p2 = bytes([x ^ xorKey2 for x in b[mid:]]) combined = p1 + p2 out = bytes([combined[i] ^ aes_key[i % len(aes_key)] for i inrange(len(combined))])
s = out.decode("utf-8", errors="replace") s = rotate_decrypt(s, rot_offset) s = substitution_decrypt(s)
iflen(s)==26and s.startswith("flag{") and s.endswith("}"): return seed, s, s.replace("flag","hgame") returnNone
seed, plain, final = solve() print(seed, plain, final)
# 2) 严格按题目顺序生成邻居: # 棋子编号从小到大 + 方向 wasd for p insorted(pieces): for d in DIRS: if can_move(cur, p, d): nxt = move(cur, p, d) nk = state_key(nxt) if nk notin visited: visited.add(nk) parent[nk] = (ck, p, d) q.append(nxt)
raise RuntimeError("No solution")
defrecover_path(win_state, parent): """从 parent 回溯出(棋子编号, 方向)序列""" path = [] k = state_key(win_state) while parent[k] isnotNone: prev_k, p, d = parent[k] path.append((p, d)) k = prev_k path.reverse() return path
# ----------------- main ----------------- start_state, pieces = parse_game_bin("game.bin")
voidfree(void *mem) { mchunkptr p; /* chunk corresponding to mem */ INTERNAL_SIZE_T size; /* its size */ mchunkptr nextchunk; /* next contiguous chunk */ INTERNAL_SIZE_T nextsize; /* its size */ int nextinuse; /* true if nextchunk is used */ INTERNAL_SIZE_T prevsize; /* size of previous contiguous chunk */ mchunkptr bck; /* misc temp for linking */ mchunkptr fwd; /* misc temp for linking */ if (__builtin_expect (hook != NULL, 0)) { (*hook)(mem); return; } if(mem == NULL){ return; } p = mem2chunk (mem); size = chunksize(p); nextchunk = chunk_at_offset(p, size); nextsize = chunksize(nextchunk); /* consolidate backward */ if (!prev_inuse(p)) { prevsize = prev_size (p); size += prevsize; p = chunk_at_offset(p, -((long) prevsize)); if (__glibc_unlikely (chunksize(p) != prevsize)) malloc_printerr ("corrupted size vs. prev_size while consolidating"); unlink_chunk (p); } if (nextchunk != main_arena.top) { /* get and clear inuse bit */ nextinuse = inuse_bit_at_offset(nextchunk, nextsize); /* consolidate forward */ if (!nextinuse) { unlink_chunk (nextchunk); size += nextsize; } else clear_inuse_bit_at_offset(nextchunk, 0); bck = bin_at(&main_arena, 1); fwd = bck->fd; //if (__glibc_unlikely (fwd->bk != bck)) //malloc_printerr ("free(): corrupted unsorted chunks"); p->fd = fwd; p->bk = bck; bck->fd = p; fwd->bk = p;
set_head(p, size | PREV_INUSE); set_foot(p, size); //check_free_chunk(av, p); } /* If the chunk borders the current high end of memory, consolidate into top */ else { size += nextsize; set_head(p, size | PREV_INUSE); main_arena.top = p; //check_chunk(av, p); } }
if __name__ == '__main__': io, libc = start() exploit(io, libc)
if args.REMOTE: # try to read flag automatically io.sendline(b'cat flag') io.sendline(b'cat /flag') try: for _ inrange(4): line = io.recvline(timeout=2) if line: print(line.decode(errors='ignore').rstrip()) except Exception: pass io.interactive()
show 中对 index 的检查:取 abs(index),只校验 index <= 0xc7。没有检查 index 是否为负数。当 index 为某个负值,访问 dis[index] 发生越界读。程序在 init_canary 内将 canary = &local_var(即栈地址)写入全局 canary。show 的格式化输出会打印 dis[index] 对应的 8 字节值。
defencrypt(self, plain: int, bit: int) -> int: self._choice(1) self._recvuntil(b"plz give me your plaintext:\n") self._sendline(str(plain)) self._recvuntil(b"and the bit you want to flip:\n") self._sendline(str(bit)) returnself._b64_to_int(self._recvline())
defdecrypt_int(self, cipher: int) -> int: self._choice(2) self._recvuntil(b"plz give me your ciphertext:\n") self._sendline(str(cipher)) returnself._b64_to_int(self._recvline())
defdecrypt_bytes(self, cipher: int) -> bytes: self._choice(2) self._recvuntil(b"plz give me your ciphertext:\n") self._sendline(str(cipher)) returnself._b64_to_bytes(self._recvline())
defrecover_n(io: EzRSAClient) -> int: n = 0 rounds = 0 while rounds < 40or n.bit_length() < 1000: m = random.randrange(2, 1 << 40) a = io.decrypt_int(m) b = io.encrypt(m, 0) z = io.decrypt_int(b) delta = abs(a * z - m) if delta == 0: continue n = math.gcd(n, delta) if n else delta rounds += 1
for _ inrange(20): m = random.randrange(2, 1 << 40) a = io.decrypt_int(m) b = io.encrypt(m, 0) z = io.decrypt_int(b) delta = abs(a * z - m) if delta: n = math.gcd(n, delta) return n
defrecover_e(io: EzRSAClient, n: int) -> int: g = 2 gd = io.decrypt_int(g) e = 0
for x inrange(E_BITS): cx = io.encrypt(g, x) zx = io.decrypt_int(cx) bd = pow(gd, 1 << x, n) plus = (g * bd) % n minus = (g * inverse(bd, n)) % n if zx == minus: e |= 1 << x elif zx != plus: raise RuntimeError(f"recover e failed at bit {x}") return e
from sage.allimport * import base64, hashlib from Crypto.Cipher import AES from Crypto.Util.Padding import unpad from Crypto.Util.number import long_to_bytes import sympy as sp
n, a, b = load('data.sobj') ct = base64.b64decode('ieJNk5335o9lCy6Ar2XymrDy+HVHcQhikluNSra0kBafw1WDCyyuNPkLACeBsavy')
# FactorDB 查询到的分解 p = Integer('282964522500710252996522860321128988886949295243765606602614844463493284542147924563568163094392590450939540920228998768405900675902689378522299357223754617695943') q = Integer('511405127645157121220046316928395473344738559750412727565053675377154964183416414295066240070803421575018695355362581643466329860038567115911393279779768674224503') assert p * q == n
# B = uA + vI R = Zmod(n) u = R(b[0, 1]) / R(a[0, 1]) v = R(b[0, 0]) - u * R(a[0, 0])
# --- mod p --- Fp = GF(p) Ap = a.change_ring(Fp) lam_p = Ap.charpoly().roots(multiplicities=False)[0] mu_p = Fp(Integer(u)) * lam_p + Fp(Integer(v)) ord_p = Integer(lam_p.multiplicative_order()) k_p = Integer(discrete_log(mu_p, lam_p, ord=ord_p))
# --- mod q (in GF(q^2)) --- fac_qp1 = sp.factorint(int(q + 1)) r = Integer(max(fac_qp1)) # q+1 的最大素因子
Fq = GF(q) Aq = a.change_ring(Fq) K = GF(q**2, 'z') lam_q = Aq.charpoly().change_ring(K).roots(multiplicities=False)[0] mu_q = K(Fq(Integer(u))) * lam_q + K(Fq(Integer(v)))
g = lam_q**r h = mu_q**r ord_g = Integer(g.multiplicative_order()) k_q = Integer(discrete_log(h, g, ord=ord_g))
# CRT + 1000-bit prime 约束 k0 = Integer(CRT(k_p, k_q, ord_p, ord_g)) L = Integer(lcm(ord_p, ord_g)) low, high = Integer(2)**999, Integer(2)**1000 - 1 if k0 < low: k0 += ((low - k0 + L - 1) // L) * L
k = None x = k0 while x <= high: if is_prime(x): k = Integer(x) break x += L
assert k isnotNone assert a**int(k) == b
key = hashlib.md5(long_to_bytes(int(k))).digest() flag = unpad(AES.new(key, AES.MODE_ECB).decrypt(ct), 16) print(flag.decode())
#!/usr/bin/env sage -python import ast import math import random from pathlib import Path
from sage.allimport GF, Matrix, ZZ, vector from fpylll import CVP, IntegerMatrix, LLL
q = 256708627612544299823733222331047933697 n = 25 m = 15
defcentered(x, mod): x %= mod return x - mod if x > mod // 2else x
defbuild_basis(A_rows, mod): """ 构造格 L = {A*s + mod*z} 的一组整数基。 A_rows: k x n 返回: k x k 基矩阵(行基) """ k = len(A_rows) gens = []
# mod * e_i for i inrange(k): row = [0] * k row[i] = mod gens.append(row)
# A 的列向量 for j inrange(n): gens.append([int(A_rows[i][j]) for i inrange(k)])
H = Matrix(ZZ, gens).hermite_form(include_zero_rows=False) return [[int(H[i, j]) for j inrange(k)] for i inrange(k)]
defbabai_closest_vector(B_rows, target): k = len(B_rows) B = IntegerMatrix(k, k) for i inrange(k): for j inrange(k): B[i, j] = int(B_rows[i][j])
LLL.reduction(B) v = CVP.babai(B, [int(x) for x in target]) return [int(x) for x in v]
deftriple_distance(enc, idxs): A_rows, b = [], [] for gid in idxs: for t in enc[gid]: A_rows.append(list(t[:-1])) b.append(int(t[-1]))
B = build_basis(A_rows, q) v = babai_closest_vector(B, b) dist = math.sqrt(sum((int(b[i]) - int(v[i])) ** 2for i inrange(len(b)))) return dist
deffind_good_triple(enc, rounds=80, seed=0): rnd = random.Random(seed) N = len(enc) best = None for _ inrange(rounds): idxs = sorted(rnd.sample(range(N), 3)) d = triple_distance(enc, idxs) if best isNoneor d < best[0]: best = (d, idxs) return best
defrecover_secret_from_triple(enc, idxs): A_rows, b = [], [] for gid in idxs: for t in enc[gid]: A_rows.append([int(x) for x in t[:-1]]) b.append(int(t[-1]))
# CVP 找到最近的格点 v ≈ A*s + q*z B = build_basis(A_rows, q) v = babai_closest_vector(B, b)
# 在 GF(q) 上解 A*s = v k = len(A_rows) F = GF(q) A = Matrix(F, k, n, [A_rows[i][j] for i inrange(k) for j inrange(n)]) rhs = vector(F, [x % q for x in v]) s = A.solve_right(rhs) return [int(x) for x in s]
defrecover_bits(enc, s, threshold=(1 << 20)): bits = [] for group in enc: max_abs = 0 for t in group: a = t[:-1] bb = int(t[-1]) dot = sum(int(a[j]) * s[j] for j inrange(n)) r = centered(bb - dot, q) max_abs = max(max_abs, abs(r)) bits.append('1'if max_abs < threshold else'0') return''.join(bits)
# 3) CRT 合并 k mod p 与 k mod q -> 枚举少量候选 M = p*q LOW = 1 << 659 HIGH = (1 << 660) - 1
sols = [] for r in [x1, x2]: base = crt(kp, p, r, q) % M sols.append(base)
candidates = [] for base in sols: t_start = 0if base >= LOW else (LOW - base + M - 1)//M t = t_start whileTrue: kk = base + t*M if kk > HIGH: break if kk % 2 == 1and is_probable_prime(kk): # 验算矩阵关系 if mat_pow(A, kk, n) == B: candidates.append(kk) t += 1
assertlen(candidates) == 1 k = candidates[0] print("[+] recovered k =", k)
functionreadCString(mem, offset, max = 160) { const bytes = newUint8Array(mem.buffer, offset, max); let out = ''; for (let i = 0; i < bytes.length; i++) { if (bytes[i] === 0) break; out += String.fromCharCode(bytes[i]); } return out; }
asyncfunctiongetVidarCoinFromTokenURI(entrance) { const data = '0xc87b56dd' + encU256(0); // tokenURI(uint256) const ret = awaitrpc('eth_call', [{ to: entrance, data }, 'latest']); const uri = decodeAbiString(ret); if (!uri.startsWith('data:application/json;base64,')) { thrownewError(`unexpected tokenURI: ${uri.slice(0, 80)}`); } const meta = JSON.parse(Buffer.from(uri.split(',')[1], 'base64').toString('utf8')); if (!meta.vidar_coin) thrownewError('vidar_coin not found in tokenURI json'); return meta.vidar_coin; }
functionreadCString(mem, offset, max = 160) { const bytes = newUint8Array(mem.buffer, offset, max); let out = ""; for (let i = 0; i < bytes.length; i++) { if (bytes[i] === 0) break; out += String.fromCharCode(bytes[i]); } return out; }
functiondecodeAbiString(hex) { const data = hex.slice(2); const off = Number(BigInt("0x" + data.slice(0, 64))); const len = Number(BigInt("0x" + data.slice(off * 2, off * 2 + 64))); const start = off * 2 + 64; returnBuffer.from(data.slice(start, start + len * 2), "hex").toString("utf8"); }
functiondecrypt(cipher) { let out = ""; for (let i = 0; i < cipher.length; i++) { const k = i % 2 === 0 ? 0x01 : 0x07; out += String.fromCharCode(cipher.charCodeAt(i) ^ k); } return out; }
asyncfunctiongetCoinFromTokenURI(entrance) { const data = "0xc87b56dd" + encU256(0); // tokenURI(uint256) const ret = awaitrpc("eth_call", [{ to: entrance, data }, "latest"]); const uri = decodeAbiString(ret); if (!uri.startsWith("data:application/json;base64,")) { thrownewError(`unexpected tokenURI: ${uri.slice(0, 80)}`); } const json = JSON.parse(Buffer.from(uri.split(",")[1], "base64").toString("utf8")); if (!json.vidar_coin) thrownewError("vidar_coin not found in tokenURI json"); return json.vidar_coin; }
import os import re import shutil import subprocess import sys import zipfile from pathlib import Path
defpick_executable(candidates): for c in candidates: ifnot c: continue p = Path(c) if p.exists(): returnstr(p) w = shutil.which(c) if w: return w raise FileNotFoundError(f"Cannot find executable from: {candidates}")
defensure_extract(outer_jar: Path, out_dir: Path): if out_dir.exists(): return out_dir.mkdir(parents=True, exist_ok=True) with zipfile.ZipFile(outer_jar, "r") as zf: zf.extractall(out_dir)
defcompile_and_dump_firmware(java_bin, javac_bin, tmp_dir: Path, extracted_dir: Path, api_jar: Path): src = tmp_dir / "DumpFromShadowLoader.java" src.write_text( """ import com.seal.ouroborosapi.RiskEngine; import com.seal.ouroborosapp.infra.ShadowLoader; import java.lang.reflect.Field; public class DumpFromShadowLoader { public static void main(String[] args) throws Exception { int key = com.seal.ouroborosapi.IntegrityVerifier.getDeriveKey(); ShadowLoader loader = new ShadowLoader(); RiskEngine eng = loader.load(); if (eng == null) { throw new RuntimeException("loader.load() returned null"); } ClassLoader cl = eng.getClass().getClassLoader(); Class<?> vm = Class.forName("com.seal.ouroboroscore.OuroborosVM", true, cl); Field f = vm.getDeclaredField("FIRMWARE"); f.setAccessible(true); byte[] fw = (byte[]) f.get(null); System.out.println("key=" + key + " low8=" + (key & 0xff) + " len=" + fw.length); for (int i = 0; i < fw.length; i++) { if (i > 0) System.out.print(" "); System.out.print(fw[i] & 0xff); } System.out.println(); } } """.strip() + "\n", encoding="ascii", )
cp_run = os.pathsep.join( [ str(tmp_dir), str(extracted_dir / "BOOT-INF" / "classes"), str(api_jar), ] ) out = run([java_bin, "-Denv=prod", "-cp", cp_run, "DumpFromShadowLoader"], cwd=tmp_dir) lines = [line.strip() for line in out.strip().splitlines() if line.strip()] iflen(lines) < 2: raise RuntimeError(f"unexpected helper output:\n{out}")
m = re.search(r"key=(\d+)\s+low8=(\d+)\s+len=(\d+)", lines[0]) ifnot m: raise RuntimeError(f"cannot parse header: {lines[0]}") low8 = int(m.group(2)) raw = [int(x) for x in lines[1].split()] return low8, raw