已经将近 4 年没有打开过 CSDN 😀
23 年毕业于网络工程,在校参加了网安社团,负责密码学,大学四年经历的是无 AI 时代
后从事嵌入式软开,时常怀念过去打 CTF 的日子,在如今后 AI 时代,解题流程已经天翻地覆
昨天搜了下今年全国大学生信息安全竞赛,发现我们学校竟然进线下半决赛了
然后找了下题目做,发现利用 Gemini Pro 可以在半小时内解决全部三道密码学题目
所以现在解决密码学题目,肯定要么死难,要么直接 AI 秒杀
周末又特地参加了某个外国 CTF 比赛,发现不看题,直接把全部题目丢个 AI 的情况下,能解出四道
然后正好又遇上某个 XCTF 的联赛,用 AI 解了一天,额度都用光了、AI 都跑绷了,一道都解不出
然后顺便找了道 BUUCTF 上很少人解出的题目来做,发现部分题目还是比较轻松的(无靶机)

【题目】[2021DASCTF实战精英夏令营暨DASCTF July X CBCTF 4th]Yusa的密码学课堂------SEDSED
【答案】
正在暴力搜索所有可能的密钥组合 (Brute-forcing)...
--------------------------------------------------
!!! 找到正确密钥 !!!
K1: 101010111001000111101111110110100101011111101001
K2: 110110111001011011011011011111011111110010100110
Flag: flag{6023026ffe2c044dd81377feba90adf9}
--------------------------------------------------
【AI 给出的 WP 脚本】
python
import itertools
# --- Constants from the Challenge ---
KEY_P1 = [57,49,41,33,25,17,9,1,58,50,42,34,26,18,10,2,59,51,43,35,27,19,11,3,60,52,44,36,63,55,47,39,31,23,15,7,62,54,46,38,30,22,14,6,61,53,45,37,29,21,13,5,28,20,12,4]
round_shifts = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]
KEY_P2 = [14,17,11,24,1,5,3,28,15,6,21,10,23,19,12,4,26,8,16,7,27,20,13,2,41,52,31,37,47,55,30,40,51,45,33,48,44,49,39,56,34,53,46,42,50,36,29,32]
IP = [58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6, 64, 56, 48, 40, 32, 24, 16, 8, 57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3, 61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7]
E = [32,1,2,3,4,5,4,5,6,7,8,9,8,9,10,11,12,13,12,13,14,15,16,17,16,17,18,19,20,21,20,21,22,23,24,25,24,25,26,27,28,29,28,29,30,31,32,1]
S = [
[[14,4,13,1,2,15,11,8,3,10,6,12,5,9,0,7],[0,15,7,4,14,2,13,1,10,6,12,11,9,5,3,8],[4,1,14,8,13,6,2,11,15,12,9,7,3,10,5,0],[15,12,8,2,4,9,1,7,5,11,3,14,10,0,6,13]],
[[15,1,8,14,6,11,3,4,9,7,2,13,12,0,5,10],[3,13,4,7,15,2,8,14,12,0,1,10,6,9,11,5],[0,14,7,11,10,4,13,1,5,8,12,6,9,3,2,15],[13,8,10,1,3,15,4,2,11,6,7,12,0,5,14,9]],
[[10,0,9,14,6,3,15,5,1,13,12,7,11,4,2,8],[13,7,0,9,3,4,6,10,2,8,5,14,12,11,15,1],[13,6,4,9,8,15,3,0,11,1,2,12,5,10,14,7],[1,10,13,0,6,9,8,7,4,15,14,3,11,5,2,12]],
[[7,13,14,3,0,6,9,10,1,2,8,5,11,12,4,15],[13,8,11,5,6,15,0,3,4,7,2,12,1,10,14,9],[10,6,9,0,12,11,7,13,15,1,3,14,5,2,8,4],[3,15,0,6,10,1,13,8,9,4,5,11,12,7,2,14]],
[[2,12,4,1,7,10,11,6,8,5,3,15,13,0,14,9],[14,11,2,12,4,7,13,1,5,0,15,10,3,9,8,6],[4,2,1,11,10,13,7,8,15,9,12,5,6,3,0,14],[11,8,12,7,1,14,2,13,6,15,0,9,10,4,5,3]],
[[12,1,10,15,9,2,6,8,0,13,3,4,14,7,5,11],[10,15,4,2,7,12,9,5,6,1,13,14,0,11,3,8],[9,14,15,5,2,8,12,3,7,0,4,10,1,13,11,6],[4,3,2,12,9,5,15,10,11,14,1,7,6,0,8,13]],
[[4,11,2,14,15,0,8,13,3,12,9,7,5,10,6,1],[13,0,11,7,4,9,1,10,14,3,5,12,2,15,8,6],[1,4,11,13,12,3,7,14,10,15,6,8,0,5,9,2],[6,11,13,8,1,4,10,7,9,5,0,15,14,2,3,12]],
[[13,2,8,4,6,15,11,1,10,9,3,14,5,0,12,7],[1,15,13,8,10,3,7,4,12,5,6,11,0,14,9,2],[7,11,4,1,9,12,14,2,0,6,10,13,15,3,5,8],[2,1,14,7,4,10,8,13,15,12,9,0,3,5,6,11]]
]
P = [16,7,20,21,29,12,28,17,1,15,23,26,5,18,31,10,2,8,24,14,32,27,3,9,19,13,30,6,22,11,4,25]
Inv_IP = [40,8,48,16,56,24,64,32,39,7,47,15,55,23,63,31,38,6,46,14,54,22,62,30,37,5,45,13,53,21,61,29,36,4,44,12,52,20,60,28,35,3,43,11,51,19,59,27,34,2,42,10,50,18,58,26,33,1,41,9,49,17,57,25]
# --- Helper Functions ---
def hex2bin(s, bits):
return bin(int(s, 16))[2:].zfill(bits)
def bin2hex(s):
return hex(int(s, 2))[2:]
def permute(text, table):
return "".join(text[x-1] for x in table)
def xor(a, b):
return "".join('1' if x != y else '0' for x, y in zip(a, b))
def inv_p(p_out):
# Reconstruct inverse P table since it wasn't given explicitly, or just map it
res = [''] * 32
for i, x in enumerate(P):
res[x-1] = p_out[i]
return "".join(res)
def get_sbox_output(s_idx, s_in_6bit):
row = int(s_in_6bit[0] + s_in_6bit[5], 2)
col = int(s_in_6bit[1:5], 2)
return S[s_idx][row][col]
def solve_sbox_key(s_idx, input_6bit, target_4bit_val):
candidates = []
# Try all 64 possible 6-bit keys
for k in range(64):
k_bin = bin(k)[2:].zfill(6)
xored = xor(input_6bit, k_bin)
val = get_sbox_output(s_idx, xored)
if val == target_4bit_val:
candidates.append(k_bin)
return candidates
def f_function(r_in, k_sub):
exp = permute(r_in, E)
xored = xor(exp, k_sub)
s_out = ""
for i in range(8):
chunk = xored[i*6 : (i+1)*6]
val = get_sbox_output(i, chunk)
s_out += '{0:04b}'.format(val)
return permute(s_out, P)
# --- Decryption Function ---
def decrypt_block(cipher_hex, k1, k2):
c_bin = hex2bin(cipher_hex, 64)
# Undo Final Inv Permutation -> this gives us the state after round 2
# Note: The code creates final via inv_permutation(left+right).
# To reverse: permute(cipher) -> gets back (left+right).
# In the code, after round 2: left=L2, right=R1.
temp = permute(c_bin, IP)
l2, r1 = temp[:32], temp[32:]
# Round 2 Inverse:
# L2 = R0 ^ f(R1, K2) => R0 = L2 ^ f(R1, K2)
f_out2 = f_function(r1, k2)
r0 = xor(l2, f_out2)
# Round 1 Inverse:
# R1 = L0 ^ f(R0, K1) => L0 = R1 ^ f(R0, K1)
f_out1 = f_function(r0, k1)
l0 = xor(r1, f_out1)
# Initial Permutation Inverse
plain = permute(l0 + r0, Inv_IP)
return plain
# --- Main Attack ---
# 1. Prepare Data
test_plain = "testtest"
test_plain_hex = test_plain.encode().hex() # 7465737474657374
test_cipher_hex = "77a35598c47aeea6"
# Get L0, R0
p_bin = hex2bin(test_plain_hex, 64)
p_perm = permute(p_bin, IP)
L0, R0 = p_perm[:32], p_perm[32:]
# Get L_final, R_final
c_bin = hex2bin(test_cipher_hex, 64)
c_perm = permute(c_bin, IP) # This reverses the inv_permutation at the end of encrypt
Lf, Rf = c_perm[:32], c_perm[32:] # Lf corresponds to "left" variable, Rf to "right" (which is R1)
# 2. Determine needed S-box I/O
# Round 1: R1 = L0 ^ f(R0, K1) -> f(R0, K1) = L0 ^ R1. Note: Rf is R1.
target_f1 = xor(L0, Rf)
target_s_out1 = inv_p(target_f1) # The output of S-boxes for K1
input_e1 = permute(R0, E) # The input to XOR for K1
# Round 2: Lf = R0 ^ f(R1, K2) -> f(R1, K2) = Lf ^ R0.
target_f2 = xor(Lf, R0)
target_s_out2 = inv_p(target_f2)
input_e2 = permute(Rf, E) # The input to XOR for K2
# 3. Find candidates for K1 and K2 chunks
k1_candidates = []
k2_candidates = []
for i in range(8):
# K1
in_chunk = input_e1[i*6 : (i+1)*6]
out_val = int(target_s_out1[i*4 : (i+1)*4], 2)
k1_candidates.append(solve_sbox_key(i, in_chunk, out_val))
# K2
in_chunk = input_e2[i*6 : (i+1)*6]
out_val = int(target_s_out2[i*4 : (i+1)*4], 2)
k2_candidates.append(solve_sbox_key(i, in_chunk, out_val))
# 4. Search for valid Key Schedule consistency
print("正在暴力搜索所有可能的密钥组合 (Brute-forcing)...")
valid_keys_found = 0
# 遍历 K1 的所有候选组合
for k1_parts in itertools.product(*k1_candidates):
k1_guess = "".join(k1_parts)
# 根据 K1 重构 CD1 (部分位)
cd1 = ['?'] * 56
possible_k1 = True
for k_idx, bit in enumerate(k1_guess):
origin = KEY_P2[k_idx] - 1
# 如果在这个位置已经填过值,且不一致,说明冲突(虽然在这里不太可能发生,作为保险)
if cd1[origin] != '?' and cd1[origin] != bit:
possible_k1 = False
break
cd1[origin] = bit
if not possible_k1: continue
# 模拟密钥编排:由 CD1 移位得到 CD2
c1 = cd1[:28]
d1 = cd1[28:]
c2 = c1[1:] + c1[:1] # 左移1位
d2 = d1[1:] + d1[:1]
cd2 = c2 + d2
# 检查 CD2 是否能生成合法的 K2 (必须在 k2_candidates 列表中)
consistent = True
for i in range(8):
chunk_indices_in_k2 = range(i*6, (i+1)*6)
implied_chunk = ""
is_complete = True
for k2_bit_idx in chunk_indices_in_k2:
origin_in_cd2 = KEY_P2[k2_bit_idx] - 1
bit_val = cd2[origin_in_cd2]
if bit_val == '?':
is_complete = False
break
implied_chunk += bit_val
if is_complete:
# 如果这一块推导出的 K2 不在候选列表中,则此路径无效
if implied_chunk not in k2_candidates[i]:
consistent = False
break
if not consistent:
continue
# 如果到了这一步,说明 K1 和推导出的 K2 骨架是兼容的
# 现在填补 CD1 中剩余的 '?' (未被 K1 使用的位)
missing_indices = [i for i, x in enumerate(cd1) if x == '?']
# 暴力破解剩余的几位 (通常只有8位左右,2^8=256次,非常快)
for missing_vals in itertools.product('01', repeat=len(missing_indices)):
temp_cd1 = list(cd1)
for idx, val in zip(missing_indices, missing_vals):
temp_cd1[idx] = val
# 生成完整的 CD2
tc1 = temp_cd1[:28]
td1 = temp_cd1[28:]
tc2 = tc1[1:] + tc1[:1]
td2 = td1[1:] + td1[:1]
temp_cd2 = tc2 + td2
# 生成完整的 K2
k2_guess = ""
for x in KEY_P2:
k2_guess += temp_cd2[x-1]
# 再次严格检查 K2 的每一块是否都在候选列表中
k2_valid = True
for i in range(8):
chunk = k2_guess[i*6 : (i+1)*6]
if chunk not in k2_candidates[i]:
k2_valid = False
break
if k2_valid:
# === 关键修改:不要 break,而是直接尝试解密 Flag ===
flag_enc = "86721c7c1ebe2d0af8aa8e073073931b4a5ae6dcf03c784e3c70b5f8ce71cf9eb87f9b836eea0118"
try:
flag_dec = ""
for i in range(0, len(flag_enc), 16):
block = flag_enc[i:i+16]
dec = decrypt_block(block, k1_guess, k2_guess)
chars = ""
for j in range(0, 64, 8):
byte = dec[j:j+8]
chars += chr(int(byte, 2))
flag_dec += chars
# 只有当结果看起来像 Flag 时才输出
if "wctf{" in flag_dec or "flag" in flag_dec.lower():
print("-" * 50)
print(f"!!! 找到正确密钥 !!!")
print(f"K1: {k1_guess}")
print(f"K2: {k2_guess}")
print(f"Flag: {flag_dec}")
print("-" * 50)
import sys
sys.exit() # 找到正确 Flag 后直接退出程序
else:
# 可以在这里打印出那些错误的解密结果来看看,通常是乱码
pass
except Exception as e:
continue
[2022DASCTF MAY 出题人挑战赛]Yusa的密码学课堂------一见如故
【答案】
[*] 正在读取 output.txt...
[*] 正在生成逆变换矩阵 (瞬间完成)...
[*] 正在恢复内部状态...
[*] 状态恢复成功!预测下一个随机数...
[*] Next Random Number: 2443521996
========================================
DASCTF{49e225e5b1b57a1d3c9803b5ddfd38f9}
========================================
【AI 给出的 WP 脚本】
python
import sys
import os
from hashlib import md5
# ==========================================
# 1. 定义题目中的位运算逻辑 (用于生成矩阵)
# ==========================================
def cs2l(y, shift):
return ((y << shift) ^ (y >> (32 - shift))) & 0xffffffff
def cs2r(y, shift):
return ((y >> shift) ^ (y << (32 - shift))) & 0xffffffff
def temper(y):
"""题目中的正向混淆函数"""
# Step 1
y = y ^ cs2l(y, 11) ^ cs2l(y, 15)
# Step 2
y = y ^ cs2r(y, 7) ^ cs2r(y, 19)
return y
# ==========================================
# 2. 矩阵求逆工具 (GF(2)域)
# ==========================================
def get_bit(val, i):
return (val >> i) & 1
def set_bit(val, i, b):
if b: return val | (1 << i)
else: return val & ~(1 << i)
def build_transform_matrix():
"""生成 32x32 的变换矩阵"""
# 矩阵的每一行 i 对应输入 (1 << i) 经过混淆后的结果
mat = []
for i in range(32):
mat.append(temper(1 << i))
return mat
def invert_matrix(mat):
"""
对 32x32 的二进制矩阵求逆 (高斯消元法)
输入 mat: 32个整数的列表,每个整数代表矩阵的一行
返回 inv: 逆矩阵
"""
n = 32
# 构造增广矩阵 [M | I]
# src 存储原矩阵 M, dst 存储单位矩阵 I (最终变成 M^-1)
src = list(mat)
dst = [1 << i for i in range(n)]
for i in range(n):
# 1. 寻找主元:在第 i 列及之后寻找第 i 位为 1 的行
pivot = -1
for j in range(i, n):
if get_bit(src[j], i):
pivot = j
break
if pivot == -1:
raise ValueError("矩阵不可逆!")
# 2. 交换行,将主元换到第 i 行
src[i], src[pivot] = src[pivot], src[i]
dst[i], dst[pivot] = dst[pivot], dst[i]
# 3. 消元:用第 i 行消去其他所有行在第 i 列的 1
for j in range(n):
if i != j and get_bit(src[j], i):
src[j] ^= src[i]
dst[j] ^= dst[i]
return dst
def apply_matrix(mat, val):
"""向量乘矩阵:计算 val * mat"""
res = 0
for i in range(32):
if get_bit(val, i):
res ^= mat[i]
return res
# ==========================================
# 3. 模拟 MT19937 生成器逻辑
# ==========================================
class Myrand_Predictor():
def __init__(self, recovered_state):
# 我们恢复的是生成完 624 个数后的状态
self.MT = recovered_state
self.index = 0
def generate(self):
# 标准 MT Twist,注意题目修改了系数为 0x9908B080 (2567483520)
for i in range(624):
y = (self.MT[i] & 0x80000000) + (self.MT[(i+1)%624] & 0x7fffffff)
self.MT[i] = self.MT[(i+397)%624] ^ (y >> 1)
if y & 1:
self.MT[i] ^= 2567483520
def rand(self):
if self.index == 0:
self.generate()
y = self.MT[self.index]
# 应用题目中的混淆 (预测下一个输出)
y = temper(y)
self.index = (self.index + 1) % 624
return y
# ==========================================
# 4. 主程序
# ==========================================
def main():
print("[*] 正在读取 output.txt...")
try:
with open("output.txt", "r") as f:
content = f.read()
cleaned = content.replace('[', '').replace(']', '').replace('\n', '')
outputs = [int(x) for x in cleaned.split(',') if x.strip()]
except Exception as e:
print(f"Error: {e}")
return
if len(outputs) != 624:
print(f"Error: 需要 624 个数字,只找到了 {len(outputs)} 个")
return
print("[*] 正在生成逆变换矩阵 (瞬间完成)...")
# 1. 建立正向矩阵
forward_mat = build_transform_matrix()
# 2. 计算逆矩阵
inverse_mat = invert_matrix(forward_mat)
print("[*] 正在恢复内部状态...")
state = []
for val in outputs:
# 将每个输出值乘以逆矩阵,得到原始状态
recovered = apply_matrix(inverse_mat, val)
state.append(recovered)
print("[*] 状态恢复成功!预测下一个随机数...")
# 初始化预测器
predictor = Myrand_Predictor(state)
# output.txt 包含前 624 个数,生成器刚好转完一圈
# 下一次调用 rand() 会触发 generate() 更新状态,然后产生第 625 个数
next_rand = predictor.rand()
print(f"[*] Next Random Number: {next_rand}")
# 计算 Flag
flag_content = md5(str(next_rand).encode()).hexdigest()
flag = f"DASCTF{{{flag_content}}}"
print("\n" + "="*40)
print(flag)
print("="*40)
if __name__ == "__main__":
main()