🔐

E4syCTF Writeup

に公開

trick OR flag?(Crypto)

配布ファイルをダウンロードするとPythonファイルと暗号文が渡されます

ฅ^-ﻌ-^ฅ < Happy Halloween! I gave you my sweets:
public_key:[57, 114, 228, 194, 126, 252, 242, 222]
encrypted_flag:[588, 674, 1000, 884, 578, 560, 608, 1126, 482, 782, 578, 704, 944, 1000, 1150, 594, 816, 720, 690, 806, 690, 1184, 1010, 1000, 1150, 1000, 1252, 816, 816, 788, 1000, 1150, 1252, 816, 778, 816, 1150, 468, 690, 594, 594, 816, 962, 1150, 690, 962, 1150, 932, 962, 564, 536, 1000, 564, 806, 932, 1136]

solver

問題文に「knapsack」と記述がある通りナップサック暗号で作成されています。
Lenstra–Lenstra–Lovász格子基底簡約アルゴリズム・格子簡約アルゴリズムを使って簡単なベクトルにして復号することが出来ます

# solver.py
import random
from typing import List
import numpy as np

# fpylllが利用できない場合の代替実装
try:
    from fpylll import LLL, BKZ, IntegerMatrix
    FPYLLL_AVAILABLE = True
except ImportError:
    FPYLLL_AVAILABLE = False
    print("警告: fpylllがインストールされていません。簡易版のLLL実装を使用します。")

def gcd(a, b):
    while b:
        a, b = b, a % b
    return a

def modinv(a, m):
    # 拡張ユークリッド互除法
    g, x, y = extended_gcd(a, m)
    if g != 1:
        raise Exception('modular inverse does not exist')
    else:
        return x % m

def extended_gcd(a, b):
    if a == 0:
        return (b, 0, 1)
    else:
        g, y, x = extended_gcd(b % a, a)
        return (g, x - (b // a) * y, y)

def simple_lll_reduction(matrix):
    """
    簡易版のLLL還元アルゴリズム
    """
    matrix = np.array(matrix, dtype=float)
    n, m = matrix.shape
    
    # Gram-Schmidt直交化
    def gram_schmidt(B):
        n, m = B.shape
        B_star = np.zeros_like(B)
        mu = np.zeros((n, n))
        
        for i in range(n):
            B_star[i] = B[i].copy()
            for j in range(i):
                mu[i, j] = np.dot(B[i], B_star[j]) / np.dot(B_star[j], B_star[j])
                B_star[i] -= mu[i, j] * B_star[j]
        
        return B_star, mu
    
    # LLL還元のメインループ
    k = 1
    while k < n:
        B_star, mu = gram_schmidt(matrix)
        
        # Size reduction
        for j in range(k-1, -1, -1):
            if abs(mu[k, j]) > 0.5:
                q = round(mu[k, j])
                matrix[k] -= q * matrix[j]
                for i in range(j):
                    mu[k, i] -= q * mu[j, i]
                mu[k, j] -= q
        
        # Lovász condition
        if np.dot(B_star[k], B_star[k]) >= (0.75 - mu[k, k-1]**2) * np.dot(B_star[k-1], B_star[k-1]):
            k += 1
        else:
            # Swap
            matrix[[k-1, k]] = matrix[[k, k-1]]
            k = max(1, k-1)
    
    return matrix.astype(int)

def lll_attack(ciphertext: int, public_key: List[int]) -> List[int]:
    """
    LLL/BKZ攻撃を使用してナップサック暗号を解読
    """
    n = len(public_key)
    
    # 格子の基底行列を作成
    # 各行は [public_key[i], 0, ..., 0, 1, 0, ..., 0] の形式
    # 最後の行は [ciphertext, 0, ..., 0, 0, ..., 0, -1]
    matrix = []
    
    # 公開鍵の行を追加
    for i in range(n):
        row = [0] * (n + 1)
        row[i] = 1
        row[n] = public_key[i]
        matrix.append(row)
    
    # 暗号文の行を追加
    row = [0] * (n + 1)
    row[n] = -ciphertext
    matrix.append(row)
    
    if FPYLLL_AVAILABLE:
        # fpylllを使用
        A = IntegerMatrix.from_matrix(matrix)
        LLL.reduction(A)
        
        # 短いベクトルを探す
        for i in range(A.nrows):
            row = A[i]
            if row[n] == 0:
                solution = []
                valid = True
                for j in range(n):
                    if row[j] == 0 or row[j] == 1:
                        solution.append(row[j])
                    else:
                        valid = False
                        break
                if valid and sum(solution) > 0:
                    return solution
    else:
        # 簡易版LLLを使用
        reduced_matrix = simple_lll_reduction(matrix)
        
        # 短いベクトルを探す
        for i in range(len(reduced_matrix)):
            row = reduced_matrix[i]
            if row[n] == 0:
                solution = []
                valid = True
                for j in range(n):
                    if row[j] == 0 or row[j] == 1:
                        solution.append(row[j])
                    else:
                        valid = False
                        break
                if valid and sum(solution) > 0:
                    return solution
    
    return None

def bkz_attack(ciphertext: int, public_key: List[int], block_size: int = 10) -> List[int]:
    """
    BKZ攻撃を使用してナップサック暗号を解読
    """
    n = len(public_key)
    
    # 格子の基底行列を作成
    matrix = []
    
    # 公開鍵の行を追加
    for i in range(n):
        row = [0] * (n + 1)
        row[i] = 1
        row[n] = public_key[i]
        matrix.append(row)
    
    # 暗号文の行を追加
    row = [0] * (n + 1)
    row[n] = -ciphertext
    matrix.append(row)
    
    if FPYLLL_AVAILABLE:
        # fpylllを使用
        A = IntegerMatrix.from_matrix(matrix)
        BKZ.reduction(A, BKZ.Param(block_size=block_size))
        
        # 短いベクトルを探す
        for i in range(A.nrows):
            row = A[i]
            if row[n] == 0:
                solution = []
                valid = True
                for j in range(n):
                    if row[j] == 0 or row[j] == 1:
                        solution.append(row[j])
                    else:
                        valid = False
                        break
                if valid and sum(solution) > 0:
                    return solution
    else:
        # fpylllが利用できない場合はLLL攻撃を使用
        print("BKZ攻撃にはfpylllが必要です。LLL攻撃を使用します。")
        return lll_attack(ciphertext, public_key)
    
    return None

def brute_force_attack(ciphertext: int, public_key: List[int]) -> List[int]:
    """
    小さなナップサック問題に対する総当たり攻撃
    """
    n = len(public_key)
    
    # 2^n通りの組み合わせを試す
    for i in range(2**n):
        bits = [(i >> j) & 1 for j in range(n)]
        if sum(bit * pk for bit, pk in zip(bits, public_key)) == ciphertext:
            return bits
    
    return None

def attack_knapsack(ciphertext: List[int], public_key: List[int]) -> str:
    """
    ナップサック暗号に対する攻撃を実行
    """
    plaintext_bytes = []
    
    for s in ciphertext:
        # まず総当たり攻撃を試す(小さな問題の場合)
        bits = brute_force_attack(s, public_key)
        
        # 総当たりが失敗した場合はLLL攻撃を試す
        if bits is None:
            bits = lll_attack(s, public_key)
        
        # LLL攻撃が失敗した場合はBKZ攻撃を試す
        if bits is None:
            bits = bkz_attack(s, public_key)
        
        if bits is None:
            print(f"攻撃に失敗しました: 暗号文 {s}")
            return None
        
        # ビット列をバイトに変換
        byte = 0
        for bit in bits:
            byte = (byte << 1) | bit
        plaintext_bytes.append(byte)
    
    return bytes(plaintext_bytes).decode()

if __name__ == "__main__":
    public_key=[57, 114, 228, 194, 126, 252, 242, 222]
    encrypted=[588, 674, 1000, 884, 578, 560, 608, 1126, 482, 782, 578, 704, 944, 1000, 1150, 594, 816, 720, 690, 806, 690, 1184, 1010, 1000, 1150, 1000, 1252, 816, 816, 788, 1000, 1150, 1252, 816, 778, 816, 1150, 468, 690, 594, 594, 816, 962, 1150, 690, 962, 1150, 932, 962, 564, 536, 1000, 564, 806, 932, 1136]

    # LLL/BKZ攻撃
    print(f"\n=== LLL/BKZ攻撃を実行 ===")
    attacked = attack_knapsack(encrypted, public_key)
    if attacked:
        print(f"攻撃による復号結果: {attacked}")
    else:
        print("攻撃に失敗しました")
E4syCTF{JUCK's_delicious_sweets_were_hidden_in_knapsack}

spining-cat(Reversing)

「o」「i」「a」で構成された.cファイルとヘッダファイルが配布されます

#include <stdio.h>
#include <string.h>
#include ".cat.h"

oiao oiiaoiia oi
 oiai flag ao iiai oa oo oi
  aaaa iio aaaa iio aaaa iio aaaa iio aaaa iio aaaa iio aaaa oio aaa o
  aaaa iio aaaa iio aaaa iio aaaa iio aaaa iio aaa iio aaa o
  aaaaa iio aaaa iio aaa iio aaa iio aaa iio aaa iio aaa o
  aaaaa iio aaaa iii ai aaa iio aaa ia iio aaa o
  aaaaa oio aaaa oio aaaa oio aaaa oio aaa oio aaa oio aaa o
  aaaaa oio aaaa oio aaaa iio aaa iio aaa iio aaa iio aaa o
  aaaaa oio aaaa oio aaaa oio aaaa o
  aaaaa iio aaaa iio aaaa iio aaa iio aaa iio aaa o
  aaaaa oio aaaa oio aaaa oio aaa o
  aaaaa oio aaaa oio aaaa oio aaaa iio aaa iio aaa iio aaa o
  aaaaa oio aaaa oio aaaa oio aaaa iio aaa iio aaa iio aaa o
  aaaa iio aaaa iio aaaa iio aaaa iio aaaa iio aaaa iio aaa iio aaa iio aaa iio aaa iio aaa o
  aaaa iio aaaa iio aaaa iio aaaa iio aaa iio aaa iio aaa iio aaa iio aaa o
  aaaaa oio aaaa oio aaaa oio aaa o
  aaaaa oio aaaa oio aaaa oio aaaa iio aaa iio aaa iio aaa o
  aaaaa oio aaaa oio aaaa oio aaaa iio aaa iio aaa iio aaa o
  ai aaa iio aaa iio aaa iio aaa iio aaa ia iii ai aaaa iio aaa iio aaa iio aaa ia o
  aaaaa oio aaa oio aaa oio aaa oio aaa oio aaa o
  aaaaa iio aaaa iio aaa o
  aaaa iio aaaa iio aaaa iio aaaa iio aaaa iio aaaa iio aaaa iio aaa iio aaa iio aaa o
  aaaaa oio aaaa oio aaaa oio aaaa iio aaa iio aaa iio aaa o
  aaaa iio aaaa iio aaaa iio aaaa iio aaaa iio aaa iio aaa o
  aaaa iio aaaa iio aaaa iio aaaa iio aaa iio aaa iio aaa iio aaa iio aaa o
  aaaa iio aaaa iio aaaa iio aaaa iio aaaa oio aaa oio aaa o
  aaaa iio aaaa iio aaaa iio aaaa iio aaaa oio aaa o
  aaaa iio aaaa iio aaaa iio aaaa iio aaaa oio aaa o
  ai aaa iio aaa iio aaa iio aaa ia iii ai aaaa iio aaa iio aaa iio aaa ia o
  aaaaa oio aaa oio aaa oio aaa oio aaa oio aaa o
  aaaaa oio aaa o
  aaaa iio aaaa iio aaaa iio aaaa iio aaaa iio aaa iio aaa o
  aaaaa iio aaaa iio aaaa oio aaa oio aaa oio aaa oio aaa o
  aaaaa oio aaa oio aaa oio aaa oio aaa oio aaa o
  aaaaa iio aaa iio aaa iio aaa iio aaa iio aaa o
  aaaa iio aaaa iio aaaa iio aaaa iio aaaa iio aaa iio aaa iio aaa o
  aaaaa oio aaa oio aaa oio aaa oio aaa oio aaa o
  aaaa iio aaaa iio aaaa iio aaaa iio aaaa iio aaa iio aaa iio aaa o
  aaaaa iio aaaa iio aaa iio aaa o
  aaaaa iio aaa iio aaa iio aaa iio aaa iio aaa o
  aaaaa iio aaaa o
  aaaaa iio aaaa o
  aaaaa iio aaa iio aaa iio aaa iio aaa iio aaa o
  aaaaa iio aaaa o
  aaaa iio aaaa iio aaaa iio aaaa iio aaaa iio aaaa oio aaa oio aaa oio aaa o
  aaaaa iio aaaa iio aaaa iio aaaa oio aaa oio aaa oio aaa oio aaa oio aaa
 io i

 oiai input ao iiai oa i
 oiiaoaia ai iaii o input ia i
 oiiaoiaa ai oiao count oo ooooo i count aa iiai  i count iioiio ia oi
  oiiaoiai ai flag ao count oa aa input ao count oa ia oi
   oiiaoaii ai aiaia ia i
   a aaa i
  io
 io

 oiiaoaii ai aiaio ia i
 a ooooo i

io
#ifndef CAT_H
#define CAT_H

#define oiiaoiia main()
#define oiiaoaia scanf
#define oiiaoaii printf
#define oiiaoiaa for
#define oiiaoiai if

#define o ,
#define i ;
#define a return
#define oi {
#define io }
#define ao [
#define oa ]
#define ai (
#define ia )

#define oo =
#define aa !=

#define iio +  
#define iioiio ++
#define iii  * 
#define oio - 

#define oiai char
#define oiao int
#define iaii "%44c"

#define ooooo 0
#define aaa 1
#define aaaa 10
#define aaaaa 100
#define iiai 44

#define aiaia "wrong.\n"
#define aiaio "correct :)\n"


#endif

solver

gccでコンパイルしたのちstringsもしくはデコンパイラを使って見つけることが出来ます
GDBでmainの中身を見ていくと0x1151あたりから0x7b46544379733445({FTCys4E)が出てくるのでこの部分に埋め込まれている事が分かります

E4syCTF{OIIA-OIIA_oII4-0114_c4t_i5_5pinnin9}

Common Language(Reversing)

exeファイルが配布されるので解析する問題

solver

メモ帳か何かで強引に開くと「Console Write ReadLine」と断片的にdotnetの文法が出てきます。
dotnetのデコンパイラであるdnSpyで開くとすぐにflagが表示されます

E4syCTF{d0tn3t_h4s_4_CLR_50_634utifu1...}

最後に

3問全てに共通して言える事として問題の質がかなり低かったように感じます…(ぽちさんごめんなさい)
その攻撃手法をチョットワカルようにならないと作問はできないので修業します…
E4syCTFに参加していただきありがとうございました!!

Discussion