【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 4thYusa的密码学课堂------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()
相关推荐
程序员z77 分钟前
OpenClaw 完整安装教程(2026 全平台通用版)
人工智能
安逸sgr10 分钟前
优化器是什么?SGD、Momentum、Adam 有什么区别?
人工智能·ai·大模型·agent·智能体
动物园猫12 分钟前
狗行为目标检测数据集:6类别、近3,000张图像 | 目标检测
人工智能·目标检测·计算机视觉
知识燃料18 分钟前
企业技术决策者落地 AI Agent 体系,AWS 中国峰会有哪些战略与工程内容可看?
大数据·人工智能
一个王同学24 分钟前
从零到一 | CV转多模态大模型 | week22 | 实战项目-DocuMind-VL:基于 OCR 与 Qwen-VL 的文档多模态问答系统(二)
人工智能·深度学习·机器学习·计算机视觉·ocr
Canace27 分钟前
笔记本都合上了,Claude 为什么还能在手机上执行电脑上装的技能?
前端·人工智能·ai编程
u01030552727 分钟前
昇腾+AtomGit打造AI甘特图应用生产线
人工智能
美狐美颜SDK开放平台27 分钟前
第三方美颜SDK接入教程:直播APP实现实时美颜、滤镜与美型效果的方法
人工智能·深度学习·音视频·sdk·美颜sdk·视频美颜sdk·直播app开发
乱世刀疤28 分钟前
AI Weekly 8.3-8.9
人工智能
AI导出鸭28 分钟前
怎么让豆包做表格?AI导出鸭苹果版将豆包输出的管道表格智能解析为二维结构,一键导出为Excel或Word标准表格。
人工智能·chatgpt·word·excel·ai导出鸭