【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()
相关推荐
人工智能AI技术2 小时前
【Agent从入门到实践】33 集成多工具,实现Agent的工具选择与执行
人工智能·python
逐梦苍穹2 小时前
Clawdbot vs ClaudeCode:7x24运行方案全对比
人工智能·claudecode·clawdbot
AI街潜水的八角2 小时前
语义分割实战——基于EGEUNet神经网络印章分割系统3:含训练测试代码、数据集和GUI交互界面
人工智能·深度学习·神经网络
MasonYyp2 小时前
DSPy优化提示词
大数据·人工智能
互联网科技看点2 小时前
园世骨传导耳机:专业之选,X7与Betapro引领游泳运动双潮流
人工智能
大公产经晚间消息2 小时前
天九企服董事长戈峻出席欧洲经贸峰会“大进步日”
大数据·人工智能·物联网
deephub2 小时前
为什么标准化要用均值0和方差1?
人工智能·python·机器学习·标准化
饮哉2 小时前
LLM生成文本每次是把之前所有的token都输入,还是只输入上一个token?
人工智能·大模型
云道轩2 小时前
Planning Analytics Assistant (AI)简介
人工智能