はじめに
今回は2024/06/21-23にかけて開催されたWani CTF 2024のWriteupを書いていきたいと思います.
結果としては732ptで420位でした.
Crypto
beginners_rsa
from Crypto.Util.number import *
p = getPrime(64)
q = getPrime(64)
r = getPrime(64)
s = getPrime(64)
a = getPrime(64)
n = p*q*r*s*a
e = 0x10001
FLAG = b'FLAG{This_is_a_fake_flag}'
m = bytes_to_long(FLAG)
enc = pow(m, e, n)
print(f'n = {n}')
print(f'e = {e}')
print(f'enc = {enc}')
n = 317903423385943473062528814030345176720578295695512495346444822768171649361480819163749494400347
e = 65537
enc = 127075137729897107295787718796341877071536678034322988535029776806418266591167534816788125330265
シンプルなRSA暗号に関する問題ですね.ちょうどWaniCTFの前の週にあったSECCON Beginers CTFで似たような問題を復習したばかりだったのでスムーズに説くことができました.
n
が5つの素数で構成される場合もあるんですね.
from Crypto.Util.number import *
import pdb
from sympy.ntheory import factorint
n = 317903423385943473062528814030345176720578295695512495346444822768171649361480819163749494400347
e = 65537
enc = 127075137729897107295787718796341877071536678034322988535029776806418266591167534816788125330265
# factors = list(factorint(n).keys())
p = 9953162929836910171
q = 13079524394617385153
r = 11771834931016130837
s = 12109985960354612149
a = 17129880600534041513
phi = (p-1)*(q-1)*(r-1)*(s-1)*(a-1)
d = pow(e, -1, phi)
m = pow(enc,d,n)
flag = long_to_bytes(m).decode()
print(flag)
とりあえずsympyとか使って,n
を素因数分解します.
素因数分解した結果を用いて,秘密鍵を求めていきます.
後は定番の流れ?で復号していけばFlagが得られます.
この問題はn
が二つ以上からなる素数の場合でも復号できるということがポイントですかね?
beginners_aes
from Crypto.Util.Padding import pad
from Crypto.Cipher import AES
from os import urandom
import hashlib
key = b'the_enc_key_is_'
iv = b'my_great_iv_is_'
key += urandom(1)
iv += urandom(1)
cipher = AES.new(key, AES.MODE_CBC, iv)
FLAG = b'FLAG{This_is_a_dummy_flag}'
flag_hash = hashlib.sha256(FLAG).hexdigest()
msg = pad(FLAG, 16)
enc = cipher.encrypt(msg)
print(f'enc = {enc}') # bytes object
print(f'flag_hash = {flag_hash}') # str object
enc = b'\x16\x97,\xa7\xfb_\xf3\x15.\x87jKRaF&"\xb6\xc4x\xf4.K\xd77j\xe5MLI_y\xd96\xf1$\xc5\xa3\x03\x990Q^\xc0\x17M2\x18'
flag_hash = 6a96111d69e015a07e96dcd141d31e7fc81c4420dbbef75aef5201809093210e
from Crypto.Util.Padding import unpad
from Crypto.Cipher import AES
import hashlib
# 既知のencとflag_hash
enc = b'\x16\x97,\xa7\xfb_\xf3\x15.\x87jKRaF&"\xb6\xc4x\xf4.K\xd77j\xe5MLI_y\xd96\xf1$\xc5\xa3\x03\x990Q^\xc0\x17M2\x18'
flag_hash = "6a96111d69e015a07e96dcd141d31e7fc81c4420dbbef75aef5201809093210e"
# 固定されたキーとIVの部分
key_fixed_part = b'the_enc_key_is_'
iv_fixed_part = b'my_great_iv_is_'
# 全ての組み合わせを試す
for i in range(256):
for j in range(256):
key = key_fixed_part + bytes([i])
iv = iv_fixed_part + bytes([j])
# AES復号器の初期化
cipher = AES.new(key, AES.MODE_CBC, iv)
# 暗号文を復号化
dec = cipher.decrypt(enc)
FLAG = unpad(dec, 16)
# FLAGのハッシュを計算
calculated_flag_hash = hashlib.sha256(FLAG).hexdigest()
# ハッシュが一致するか確認
if calculated_flag_hash == flag_hash:
print(f'Found FLAG: {FLAG.decode()}')
print(f'Key: {key}')
print(f'IV: {iv}')
break
あまり良い解き方ではありませんが,256*256通りを総当たりで探索する方法で溶きました.
今回はurandom(1)
だったので総当たり可能な量でしたがもっと増えた場合を考えるとほかの解き方の方がよさそうですね.
replacement
from secret import cal
import hashlib
enc = []
for char in cal:
x = ord(char)
x = hashlib.md5(str(x).encode()).hexdigest()
enc.append(int(x, 16))
with open('my_diary_11_8_Wednesday.txt', 'w') as f:
f.write(str(enc))
長いので省略
import hashlib
import pdb
# 暗号化されたデータを読み込む
with open('my_diary_11_8_Wednesday.txt', 'r') as f:
enc = eval(f.read())
pdb.set_trace()
ascii_range = range(32, 127) # 通常の印刷可能なASCII文字の範囲
# 元の文字列を再構築
decoded_cal = ''
for enc_value in enc:
for ascii_code in ascii_range:
# ASCIIコードを文字列に変換し、MD5ハッシュを計算
hash_value = hashlib.md5(str(ascii_code).encode()).hexdigest()
# ハッシュ値を16進数整数に変換して比較
if int(hash_value, 16) == enc_value:
decoded_cal += chr(ascii_code)
break
print(decoded_cal)
これも総当たりで解きました.ASCIIコードの値の範囲を無理やり設定した部分はもう少し良いやり方とかないんですかね?
Forensics
tiny_usb
chal_tiny_usb.isoファイルが与えられます.拡張子がiso
だったのでとりあえず任意の場所にmountしてみます.
sudo mount chal_tiny_usb.iso ./mount_point
中にはflag.png
という画像があり,画像にFlagが書かれています.
pwnable
nc
nc chal-lz56g6.wanictf.org 9003
でつなぎます.
15+1=0x
というのが表示されるので当てはまるものを答えればOKです.
16進数なので0x10
ですね.
Reversing
lambda
import sys
sys.setrecursionlimit(10000000)
(lambda _0: _0(input))(lambda _1: (lambda _2: _2('Enter the flag: '))(lambda _3: (lambda _4: _4(_1(_3)))(lambda _5: (lambda _6: _6(''.join))(lambda _7: (lambda _8: _8(lambda _9: _7((chr(ord(c) + 12) for c in _9))))(lambda _10: (lambda _11: _11(''.join))(lambda _12: (lambda _13: _13((chr(ord(c) - 3) for c in _10(_5))))(lambda _14: (lambda _15: _15(_12(_14)))(lambda _16: (lambda _17: _17(''.join))(lambda _18: (lambda _19: _19(lambda _20: _18((chr(123 ^ ord(c)) for c in _20))))(lambda _21: (lambda _22: _22(''.join))(lambda _23: (lambda _24: _24((_21(c) for c in _16)))(lambda _25: (lambda _26: _26(_23(_25)))(lambda _27: (lambda _28: _28('16_10_13_x_6t_4_1o_9_1j_7_9_1j_1o_3_6_c_1o_6r'))(lambda _29: (lambda _30: _30(''.join))(lambda _31: (lambda _32: _32((chr(int(c,36) + 10) for c in _29.split('_'))))(lambda _33: (lambda _34: _34(_31(_33)))(lambda _35: (lambda _36: _36(lambda _37: lambda _38: _37 == _38))(lambda _39: (lambda _40: _40(print))(lambda _41: (lambda _42: _42(_39))(lambda _43: (lambda _44: _44(_27))(lambda _45: (lambda _46: _46(_43(_45)))(lambda _47: (lambda _48: _48(_35))(lambda _49: (lambda _50: _50(_47(_49)))(lambda _51: (lambda _52: _52('Correct FLAG!'))(lambda _53: (lambda _54: _54('Incorrect'))(lambda _55: (lambda _56: _56(_41(_53 if _51 else _55)))(lambda _57: lambda _58: _58)))))))))))))))))))))))))))
ただ頑張って解読します.(見やすいように改行しました.)
import pdb
correct_flag = [chr(int(c, 36) + 10) for c in '16_10_13_x_6t_4_1o_9_1j_7_9_1j_1o_3_6_c_1o_6r'.split('_')]
# XORの逆
xor_result = ''.join(chr(ord(c) ^ 123) for c in correct_flag)
# 3を引くの逆
plus3_result = ''.join(chr(ord(c) + 3) for c in xor_result)
# 12を足すの逆
original_flag = ''.join(chr(ord(c) - 12) for c in plus3_result)
おわりに
6月中旬に3週連続で行われたDIVER OSINT CTF,SECCON BEGINNERS CTF,WaniCTFという怒涛のCTFラッシュが終了しました.まだ出題された問題の復習などが全然追いついていないので,早いうちに取り組みたいと思います.
少しずつではありますがセキュリティ・CTFの勉強をし始めて多少は解けたり,理解できるようになってきているので少しは成長できているのかなと思います.