【AI 解题】Yusa的密码学课堂 2026.1.25

已经将近 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()
相关推荐
taWSw5OjU11 分钟前
从模型评估、梯度难题到科学初始化:一步步解析深度学习的训练问题
人工智能·深度学习
刘佬GEO12 分钟前
【无标题】
网络·人工智能·搜索引擎·ai·语言模型
用户20187928316719 分钟前
/export之一个程序员与AI的“破案笔记”
人工智能
Ricardo-Yang26 分钟前
SCNP语义分割边缘logits策略
数据结构·人工智能·python·深度学习·算法
新缸中之脑34 分钟前
微调BERT进行命名实体识别
人工智能·深度学习·bert
用户20187928316736 分钟前
故事:小白的“无限循环”噩梦与大师的 /loop 魔法
人工智能
段小二36 分钟前
Token 费用失控、VIP 用户体验一样烂:Context Engineering 才是关键
人工智能·后端
用户20187928316738 分钟前
/branch 你点了一份代码,Cli 帮你分成了两碗
人工智能
kishu_iOS&AI41 分钟前
机器学习 —— 线性回归
人工智能·机器学习·线性回归
阿里云大数据AI技术1 小时前
OpenClaw 长记忆增强:基于 Hologres + Mem0 的企业级方案
人工智能