【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()
相关推荐
那个村的李富贵3 小时前
光影魔术师:CANN加速实时图像风格迁移,让每张照片秒变大师画作
人工智能·aigc·cann
腾讯云开发者4 小时前
“痛点”到“通点”!一份让 AI 真正落地产生真金白银的实战指南
人工智能
CareyWYR4 小时前
每周AI论文速递(260202-260206)
人工智能
hopsky5 小时前
大模型生成PPT的技术原理
人工智能
禁默6 小时前
打通 AI 与信号处理的“任督二脉”:Ascend SIP Boost 加速库深度实战
人工智能·信号处理·cann
心疼你的一切6 小时前
昇腾CANN实战落地:从智慧城市到AIGC,解锁五大行业AI应用的算力密码
数据仓库·人工智能·深度学习·aigc·智慧城市·cann
AI绘画哇哒哒6 小时前
【干货收藏】深度解析AI Agent框架:设计原理+主流选型+项目实操,一站式学习指南
人工智能·学习·ai·程序员·大模型·产品经理·转行
数据分析能量站6 小时前
Clawdbot(现名Moltbot)-现状分析
人工智能
那个村的李富贵6 小时前
CANN加速下的AIGC“即时翻译”:AI语音克隆与实时变声实战
人工智能·算法·aigc·cann
二十雨辰6 小时前
[python]-AI大模型
开发语言·人工智能·python