シアン化備忘録

技術系とか諸々

WaniCTF writeup(Crypto,Forensics)


f:id:Cy4n0s:20201124151556j:plain

25問解いて187人中24位でした。

ForensicsとCryptoに加えてMiscも全完出来たので嬉しかったです。

RevとPwnは精進します。

Crypto

Veni, vidi

SYNT{fvzcyr_pynffvpny_pvcure}

ROT13で復号する。

FLAG{simple_classical_cipher}

exclusive

XORを使った暗号です🔐

encrypt.pyとoutput.pyが与えられる。

key = "REDACTED"
flag = "FAKE{this_is_fake_flag}"

assert len(key) == len(flag) == 57
assert flag.startswith("FLAG{") and flag.endswith("}")
assert key[0:3] * 19 == key


def encrypt(s1, s2):
    assert len(s1) == len(s2)

    result = ""
    for c1, c2 in zip(s1, s2):
        result += chr(ord(c1) ^ ord(c2))
    return result


ciphertext = encrypt(flag, key)
print(ciphertext, end="")

コード中の assert key[0:3] * 19 == keyから鍵が3文字と分かり、フラグの形式がFLAG{で始まるので既知平文攻撃が出来る。

with open('output.txt') as c:
    strings = c.read()

known = 'FLAG{'

known = known[0:5]*len(strings)
key=[]
f=[]

for c1,c2 in zip(strings,known):
    key.append(chr(ord(c1)^ord(c2)))

key = key[0:3]*19

for c,m in zip(strings,key):
    f.append(chr(ord(c)^ord(m)))

flag = ''.join(f)

print(flag)

FLAG{xor_c1ph3r_is_vulnera6le_70_kn0wn_plain7ext_@ttack!}

Basic RSA

RSA暗号の基本的な演算ができますか?

接続するとRSAに関する計算を求められる。

p : 素数1 , q : 素数2 , N : 公開鍵1(p*q) , e : 公開鍵2 , m : 平文 , c : 暗号文

として、

・p,qからN

・m,e,nからc

・p,q,e,cからm

の計算を行って順番に投げつけるとフラグが返ってくる。

最後のp,q,e,cからmを導出するには

phi = (p-1)*(q-1)

d = e^-1 (mod phi)

が必要なので適宜計算する。dに関してはpow(e,-1,phi)で出せば早い。

ターミナルからPythonを起動して計算したのでコードは残ってません。

FLAG{y0uv3_und3rst00d_t3xtb00k_RSA}

LCG crack

安全な暗号は安全な乱数から

server.pyが配布される。

import random
import os

from Crypto.Util.number import *

from const import flag, logo, description, menu


class RNG:
    def __init__(self, seed, a, b, m):
        self.a = a
        self.b = b
        self.m = m
        self.x = seed % m

    def next(self):
        self.x = (self.a * self.x + self.b) % self.m
        return self.x


def show_menu():
    print(menu)
    log(description)
    while not (choice := input("> ")) in "123":
        print("Invalid choice.")

    return int(choice)


if __name__ == "__main__":
    print(logo)
    seed = random.getrandbits(64)
    a = random.getrandbits(64)
    b = random.getrandbits(64)
    m = getPrime(64, os.urandom)
    rng = RNG(seed, a, b, m)

    while True:
        choice = show_menu()

        # Print
        if choice == 1:
            print(rng.next())

        # Guess
        elif choice == 2:
            for cnt in range(1, 11):
                print(f"[{cnt}/10] Guess the next number!")
                try:
                    guess = int(input("> "))
                except ValueError:
                    print("Please enter an integer\n\n\n")
                    continue
                if guess == rng.next():
                    print(f"Correct! ")
                    cnt += 1
                else:
                    print(f"Wrong... Try again!")
                    break
            else:
                print(f"Congratz!  {flag}")
                break

        # Exit
        else:
            print("Bye :)")
            break

線形合同法

choiceが1の時に次の乱数を取得、2の時に次の乱数を当てるモードになる。

seed,a,b,mが固定されてそうなので逆算すればいけるかと予想したら実装に困ってしまった...。

色々調べてたら次の記事を見つけた。

satto.hatenadiary.com

今回のケースは記事の「A (multiplier) と B ( increment) と M (modulus) が未知である場合」と同じ。

記事からコードを引用させていただきソルバを書いた。

#https://satto.hatenadiary.com/entry/solve-LCG

from Crypto.Util.number import inverse, GCD, getRandomNBitInteger
from functools import reduce
from pwn import *

def solve_unknown_modulus(states):
    diffs = [X_1 - X_0 for X_0, X_1 in zip(states, states[1:])]
    multiples_of_M = [T_2 * T_0 - T_1 ** 2 for T_0, T_1, T_2, in zip(diffs, diffs[1:], diffs[2:])]

    M = reduce(GCD, multiples_of_M)
    return M

def solve_unknown_multiplier(states, M):
    A = (states[2] - states[1]) * inverse((states[1] - states[0]), M)
    return A

def solve_unknown_increment(states, A, M):
    B = (states[1] - A * states[0]) % M
    return B

state = []
io = remote('lcg.wanictf.org',50001)
i=0
while(i<30):
    io.sendlineafter('> ',"1")
    app = int(io.readline(),10)
    print(app)
    state.append(app)
    i+=1

print('------------------------')


M = solve_unknown_modulus(state)
A = solve_unknown_multiplier(state, M)
B = solve_unknown_increment(state, A, M)

next_value = (A * state[-1] + B) % M
print(next_value)
io.sendlineafter('> ',"2")
io.sendlineafter('> ',str(next_value))
print(io.recvline())

for j in range(9):
    next_value = (A * next_value + B) % M
    print(next_value)
    io.sendlineafter('> ',str(next_value))
    print(io.recvline())

print(io.recvline())

FLAG{y0u_sh0uld_buy_l0tt3ry_t1ck3ts}

l0g0n

🕵️‍♂️

server.pyが与えられる。

from hashlib import pbkdf2_hmac
import os

from Crypto.Cipher import AES

from secret import flag, psk


class AES_CFB8:
    def __init__(self, key):
        self.block_size = 16
        self.cipher = AES.new(key, AES.MODE_ECB)

    def encrypt(self, plaintext: bytes, iv=bytes(16)):
        iv_plaintext = iv + plaintext
        ciphertext = bytearray()

        for i in range(len(plaintext)):
            X = self.cipher.encrypt(iv_plaintext[i : i + self.block_size])[0]
            Y = plaintext[i]
            ciphertext.append(X ^ Y)
        return bytes(ciphertext)


def key_derivation_function(x):
    dk = pbkdf2_hmac("sha256", x, os.urandom(16), 100000)
    return dk


def main():
    while True:
        client_challenge = input("Challenge (hex) > ")
        client_challenge = bytes.fromhex(client_challenge)

        server_challenge = os.urandom(8)
        print(f"Server challenge: {server_challenge.hex()}")

        session_key = key_derivation_function(psk + client_challenge + server_challenge)

        client_credential = input("Credential (hex) > ")
        client_credential = bytes.fromhex(client_credential)

        cipher = AES_CFB8(session_key)
        server_credential = cipher.encrypt(client_challenge)
        if client_credential == server_credential:
            print(f"OK! {flag}")
        else:
            print("Authentication Failed... 🥺")


if __name__ == "__main__":
    main()

AES_CFB8と問題名の「l0g0n」からZerologonを思い出す。

www.youtube.com

動画によると、AES_CFB8ではivを0で固定すると暗号文の先頭バイトが256分の1の確率で0となる。

また、0を暗号化すると0になるため、256回の鍵生成したうち1回は平文と同じ0となる。

ChallengeとCredentialを暗号化した結果を比較して同じであればフラグが取得出来るので、上記の脆弱性にしたがって8Byte分の0をループで送り続けるというソルバを書いた。

from pwn import *

io = remote('l0g0n.wanictf.org',50002)
while(True):
    io.sendlineafter('Challenge (hex) > ','0000000000000000')
    io.sendlineafter('Credential (hex) >','0000000000000000')
    rcv = io.recvline() 
    if(b'OK!' in rcv):
        print(rcv)
        break
    print('not')

FLAG{4_b@d_IV_leads_t0_CVSS_10.0__z3r01090n}

Forensics

logged_flag

ワニ博士が問題を作っていたので、作っているところをキーロガーで勝手に記録してみました。
先に公開してしまいたいと思います。
(ワニ博士は英字配列のキーボードを使っています)

key_log.txtとsecret.jpgが渡される。

key_log.txtを見る限りechoでフラグをflag.txtに書き込んでそれをsteghideでjpgファイルに埋め込んでいる。

echoのコマンド文がそのまま見れるので頑張ってフラグを取り出すか、「steghide embed」で打ち込んでるパスワードmachikanetamachikanesaiを抜き出して、「steghide extract -sf test.jpg」からパスワードを入力してflag.txtを抜き出す。

11:50:15 [E]
11:50:15 [C]
11:50:15 [H]
11:50:15 [O]
11:50:16 [Space]
11:50:23 [Shift]
11:50:24 [F]
11:50:26 [Shift]
11:50:26 [L]
11:50:27 [Shift]
11:50:27 [A]
11:50:28 [Shift]
11:50:28 [G]
11:50:29 [Shift]
11:50:29 [[]
11:50:31 [K]
11:50:32 [3]
11:50:33 [Y]
11:50:35 [Shift]
11:50:35 [-]
11:50:36 [L]
11:50:36 [0]
11:50:37 [G]
11:50:38 [G]
11:50:38 [3]
11:50:38 [R]
11:50:41 [Shift]
11:50:41 [-]
11:50:41 [1]
11:50:42 [S]
11:50:43 [Shift]
11:50:43 [-]
11:50:44 [V]
11:50:45 [3]
11:50:47 [R]
11:50:47 [Y]
11:50:47 [Shift]
11:50:47 [-]
11:50:49 [D]
11:50:49 [4]
11:50:50 [N]
11:50:51 [G]
11:50:52 [3]
11:50:52 [R]
11:50:53 [0]
11:50:54 [U]
11:50:54 [S]
11:50:55 [Shift]
11:50:55 []]

FLAG{k3y_l0gg3r_1s_v3ry_d4ng3r0us}

chunk_eater

pngの必須チャンクをワニ博士が食べてしまいました!

eaten.pngPNGフォーマットの解説サイトが渡される。

eaten.jpgをバイナリエディタで開くとPNGの必須チャンクと思われるIHDR IDAT IENDが全てWANIに書き換わっているので、これらを全て元に戻してやる。最初と最後以外のWANIは全てIDATに書き換えた。

正確に修復出来るとフラグの画像が出てくる。

f:id:Cy4n0s:20201124182641p:plain

FLAG{chunk_is_so_yummy!}

zero_size_png

この画像のサイズは本当に0×0ですか?

dyson.pngとIHDRの解説サイトが渡される。

バイナリエディタで開くと画像の幅(赤)と画像の高さ(青)が0になっている。

ただChunk Type(茶)とCRC(緑)は元から存在している。

f:id:Cy4n0s:20201124184155p:plain

CRCはChunk Typeと幅,高さから導出出来るので、CRCから幅と高さを逆算するっぽいな~と思い色々調べてたら見つけた。

www.programmersought.com

zlibのcrc32を使えばxorの実装せずに計算してくれるので楽っぽい。

上記のサイトのコードを引用させていただき幅と高さを求める。

#https://www.programmersought.com/article/80384546662/

import zlib
import struct

filename = 'dyson.png'
with open(filename, 'rb') as f:
    all_b = f.read()
    crc32key = int(all_b[29:33].hex(),16)
    data = bytearray(all_b[12:29])
    n = 4095
    for w in range(n):
        width = bytearray(struct.pack('>i', w))
        for h in range(n):
            height = bytearray(struct.pack('>i', h))
            for x in range(4):
                data[x+4] = width[x]
                data[x+8] = height[x]
            crc32result = zlib.crc32(data)
            if crc32result == crc32key:
                print("Wide is:",end="")
                print(width.hex())
                print("High as:",end="")
                print(height.hex())
                exit(0)
Wide is:00000257

High as:0000030d

後は出力通りに幅と高さを書き換える。

f:id:Cy4n0s:20201125054014p:plain

f:id:Cy4n0s:20201125054110p:plain

FLAG{Cyclic_Redundancy_CAT}

ALLIGATOR_01

ワニ博士のPCでは,悪意のあるプロセスが実行されているみたいです。

取得したメモリダンプから、”evil.exe”が実行された日時を報告してください。

(注意: スペースはすべて半角のアンダースコアにしてください)

example: FLAG{1234-56-78_99:99:99_UTC+0000}

Memory Forensics三部作。

ALLIGATOR.zipを展開するとALLIGATOR.rawが出てくる。

Volatilityが推奨ツールとして紹介されていたので導入した。

使い方等は下記サイトを参照した。

jpn.nec.com

troushoo.blog.fc2.com

初動調査としてOSのプロファイルを調べる必要があるので下記コマンドで調べる。

volatility -f ALLIGATOR.raw imageinfo

Volatility Foundation Volatility Framework 2.6
INFO    : volatility.debug    : Determining profile based on KDBG search...
          Suggested Profile(s) : Win7SP1x86_23418, Win7SP0x86, Win7SP1x86_24000, Win7SP1x86
                     AS Layer1 : IA32PagedMemoryPae (Kernel AS)
                     AS Layer2 : FileAddressSpace (/root/Documents/wanictf/for/alligator/ALLIGATOR.raw)
                      PAE type : PAE
                           DTB : 0x185000L
                          KDBG : 0x82754de8L
          Number of Processors : 1
     Image Type (Service Pack) : 1
                KPCR for CPU 0 : 0x80b96000L
             KUSER_SHARED_DATA : 0xffdf0000L
           Image date and time : 2020-10-26 03:04:49 UTC+0000
     Image local date and time : 2020-10-25 20:04:49 -0700

Win7SP0x86で良さそう。pstreeでメモリダンプ取得時に動いていたプロセスが確認出来る。

volatility -f ALLIGATOR.raw --profile=Win7SP0x86 pstree

省略
. 0x84dd6b28:evil.exe                                3632   2964      1     21 2020-10-26 03:01:55 UTC+0000
省略

日時が載っているのでこれをフラグ形式にしたらフラグ完成。

FLAG{2020-10-26_03:01:55_UTC+0000}

ALLIGATOR_02

コマンドプロンプトの実行履歴からFLAGを見つけてください。

(ALLIGATOR_01で配布されているファイルを使ってください)

consolesでcmd.exe で実行されたコマンドの出力を表示出来るらしい。

volatility -f ALLIGATOR.raw --profile=Win7SP0x86 consoles

省略

C:\Users\ALLIGATOR>type C:\Users\ALLIGATOR\Desktop\flag.txt                     
FLAG{y0u_4re_c0n50les_master}                                                   
C:\Users\ALLIGATOR> 

FLAG{y0u_4re_c0n50les_master}

ALLIGATOR_03

Dr.WANIはいつも同じパスワードを使うらしいです。

Dr.WANIのパソコンから入手したパス付のzipファイルを開けて、博士の秘密を暴いてしまいましょう。

(ALLIGATOR_01で配布されているファイルを使ってください)

hashdumpでメモリ上のパスワードハッシュをダンプできる。

SYSTEMとSAMのアドレスがなくてもALLIGATORのパスワードハッシュが出てきたけど本来ならば指定するっぽい。

volatility -f ALLIGATOR.raw --profile=Win7SP0x86 hashdump

Administrator:500:aad3b435b51404eeaad3b435b51404ee:fc525c9683e8fe067095ba2ddc971889:::
Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
IEUser:1000:aad3b435b51404eeaad3b435b51404ee:fc525c9683e8fe067095ba2ddc971889:::
sshd:1001:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
sshd_server:1002:aad3b435b51404eeaad3b435b51404ee:8d0a16cfc061c3359db455d00ec27035:::
ALLIGATOR:1003:aad3b435b51404eeaad3b435b51404ee:5e7a211fee4f7249f9db23e4a07d7590:::

5e7a211fee4f7249f9db23e4a07d7590をhashes.txtに移したらJohn the Ripperなどで復号する。

john hashes.txt --wordlist=/usr/share/wordlists/rockyou.txt --format=NT

Loaded 1 password hash (NT [MD4 256/256 AVX2 8x3])
Warning: no OpenMP support for this hash type, consider --fork=4
Press 'q' or Ctrl-C to abort, almost any other key for status
ilovewani        (?)
1g 0:00:00:00 DONE (2020-11-25 06:35) 4.166g/s 7149Kp/s 7149Kc/s 7149KC/s iloveyoh2..iloveus5
Use the "--show --format=NT" options to display all of the cracked passwords reliably
Session completed

ilovewaniをパスワードとしてwani_secret.zipを解凍するとflag.txtが出てきた。

大阪大学 公式マスコットキャラクター「ワニ博士」
【プロフィール】
名前:   ワニ博士(わにはかせ)
誕生日:    5 月 3 日
性別:   オス
出身地:    大阪府 豊中市 待兼山町
【性格】
温厚,好奇心旺盛,努力型,お茶目,社交的,たまに天然,賢い
【趣味】
・阪大キャンパスでコーヒーを飲みながら学生としゃべる
・粉もん屋めぐり
・化石集め。(いつか自分の仲間に会うために)
・CTF: FLAG{The_Machikane_Crocodylidae}

FLAG{The_Machikane_Crocodylidae}