n00bzCTF 2024に参加しました!

n00bzCTF 2024に参加しました!

はじめに

日本時間2024年8月3日~5日に開催されたn00bzCTF 2024に参加しました。

結果はチーム「maybe yes beginner」で7419ポイント(全967チーム中85位)、個人では3726ポイントでした。

今回は前に一緒にCTF参加した方からお声がけをいただきました。本当にありがとうございます……。

公式Writeupはこちら: n00bzUnit3d/n00bzCTF2024-Official-Writeups (github.com)

Crypto

Vinegar

解法

enc.txtが渡される。

Encrypted flag: nmivrxbiaatjvvbcjsf
Key: secretkey

ヴィジュネル暗号というものらしいので復号する(Vigenère Decode – CyberChef (gchq.github.io))。

Flag

n00bz{vigenerecipherisfun}

Random

解法

サーバープログラムを読むとamazingcustomsortingalgorithm()内で69回入力をシャッフルしており、そのうち1回でも昇順に並べばflagを貰えることが分かる。

また、プログラム内でシードが定義されておらず、実行ごとに同じ乱数が生成されていることが分かる。

例えば、入力に0123456789を入れたとき、出力の1番上は必ず4378052169となる。

> nc challs.n00bzunit3d.xyz 10402
0123456789  # 入力
4378052169  # 出力1番目
0578439216  # 出力2番目
5763842019  # 出力3番目
...

この4378052169が0123456789となるように入力の順番を調整すれば良い。

> nc challs.n00bzunit3d.xyz 10402
4761058239
0123456789
n00bz{5up3r_dup3r_ultr4_54f3_p455w0rd_8bc6b6d36d24}

Flag

n00bz{5up3r_dup3r_ultr4_54f3_p455w0rd_8bc6b6d36d24}

Forensics

Plane

解法

画像ファイルのEXIFを見るとGPS情報を得られる。

> exiftool plane.jpg
...
GPS Latitude                    : 13 deg 22' 12.00" N
GPS Longitude                   : 13 deg 22' 12.00" W
GPS Position                    : 13 deg 22' 12.00" N, 13 deg 22' 12.00" W

この座標数値は60進数なので10進数に変換する必要があるので変換サイト等を利用して変換する。

Flag

n00bz{13.37,-13.37}

Disk Golf

解法

とりあえずファイル形式を確認して解凍

> file disk.img.tar.gz 
disk.img.tar.gz: gzip compressed data, from Unix, original size modulo 2^32 2671779840 gzip compressed data, reserved method, ASCII, extra field, from FAT filesystem (MS-DOS, OS/2, NT), original size modulo 2^32 2671779840

> tar -xzvf disk.img.tar.gz

マウントを試みるがエラーが出た。

> sudo mount -t ext4 -o loop ./disk.img ./mounted/
# エラー

> dmseg | tail
[  304.357735] loop0: detected capacity change from 0 to 5218304
[  304.359707] EXT4-fs (loop0): bad geometry: block count 12844795 exceeds size of device (652288 blocks)

エラーで検索するとこのような質問が見つかったので試してみる。

> sudo e2fsck -f ./disk.img
# 最初だけN、後はY連打
> sudo resize2fs -f ./disk.img

これでマウントできるようになる。

> sudo mount -t ext4 -o loop ./disk.img ./mounted/

適当に探すと/home/johnhackerdoe/flag.txtに怪しい数字の羅列があるのが分かる。

/mounted/home/johnhackerdoe/flag.txt: 
156 60 60 142 172 173 67 150 63 137 154 60 156 147 137 64 167 64 61 164 63 144 137 144 61 65 153 137 146 60 162 63 156 163 61 143 65 175
Decode: 
n00bz{7h3_l0ng_4w41t3d_d15k_f0r3ns1c5}

デコード結果

Flag

n00bz{7h3_l0ng_4w41t3d_d15k_f0r3ns1c5}

OSINT

Tail

解法

まずは飛行機の尾翼の画像から航空会社を特定する。Wikipediaから探してエア タヒチ ヌイ (Air Tahiti Nui)であること判明。

エア タヒチ ヌイのハブ空港はタヒチ・ファアア国際空港であり、そのIATAはPPTである。

Flag

n00bz{PPT}

Web

Focus on yourSELF

解法

GETリクエストのimageパラメータでパストラバーサルができることが分かる。

例えば、image=../../../../../../etc/passwdとしたとき、/etc/passwdの中身がbase64でエンコードされてから渡される。

色々探し回った結果、Dockerの環境変数は/proc/self/environに書かれているのでimage=../../../../../proc/self/environとしてファイルを参照した。

Flag

n00bz{Th3_3nv1r0nm3nt_det3rmine5_4h3_S3lF_ca8686b2a4cc}

Rev

Vacation

解法

入力の各バイトを3とXORして出力しているだけなので再度3とXORすれば良い。

$bytes = [System.Text.Encoding]::ASCII.GetBytes((cat .\output.txt))
[System.Collections.Generic.List[byte]]$newBytes = @()
$bytes.ForEach({
    $newBytes.Add($_ -bxor 3)
    })
$newString =  [System.Text.Encoding]::ASCII.GetString($newBytes)
echo $newString | Out-File -Encoding ascii .\flag.txt

Flag

n00bz{from_paris_wth_xor}

FlagChecker

解法

マクロの中身を見ると文字を参照して様々な条件を満たしているか判定している。

Flagの1~6文字目が”n00bz{“、24文字目(最後)が”}”なのは明らかなのでここから逆算すれば良い。

import math

char_all = [-1] * 25

char_all[1] = ord('n')
char_all[2] = ord('0')
char_all[3] = ord('0')
char_all[4] = ord('b')
char_all[5] = ord('z')
char_all[6] = ord('{')
char_all[24] = ord('}')

# Asc(char_1) Xor Asc(char_8) = 22
char_all[8] = char_all[1] ^ 22

# Asc(char_19) = 107
char_all[19] = 107

# Asc(char_20) + 501 = (Asc(char_1) * 5)
char_all[20] = char_all[1] * 5 - 501

# Asc(char_23) + Asc(char_8) = 235
char_all[23] = 235 - char_all[8]

# Asc(char_18) = Asc(char_23)
char_all[18] = char_all[23]

# Asc(char_10) + Asc(char_24) = 176
char_all[10] = 176 - char_all[24]

# (Asc(char_12) / 5) ^ (Asc(char_3) / 12) = 130321
char_all[12] = int(math.pow(130321, 1 / (char_all[3] // 12))) * 5

# Asc(char_12) Xor (Asc(char_17) - 5) = 5
char_all[17] = (char_all[12] ^ 5) + 5

# Asc(char_16) = Asc(char_17) + 19
char_all[16] = char_all[17] + 19

# Asc(char_22) Xor Asc(char_6) = 23
char_all[22] = char_all[6] ^ 23

# 1365 = Asc(char_22) Xor 1337
assert(char_all[22] ^ 1337 == 1365)

# Asc(char_9) - Asc(char_22) = -9
char_all[9] = char_all[22] - 9

# char_22 = char_11
char_all[11] = char_all[22]

# Asc(char_21) = Asc(char_22)
char_all[21] = char_all[22]

# Asc(char_14) Xor Asc(char_24) = 77
char_all[14] = char_all[24] ^ 77

# Asc(char_15) * Asc(char_8) = 14040
char_all[15] = 14040 // char_all[8]

# Asc(char_13) Xor Asc(char_14) Xor Asc(char_2) = 121
char_all[13] = char_all[14] ^ char_all[2] ^ 121

# Asc(char_10) = Asc(char_7)
char_all[7] = char_all[10]


for c in char_all:
    if c != -1:
        print(chr(c), end="")

print()
> python solver.py
n00bz{3xc3l_y0ur_sk1lls}

Flag

n00bz{3xc3l_y0ur_sk1lls}

Programming

Sillygoose

解法

0~10^100の間で二分探索をするだけ。

from pwn import *

section_min = 0
section_max = pow(10, 100)

r = remote("24.199.110.35", 41199)

while True:
    section_mid = (section_min + section_max) // 2

    r.sendline(f"{section_mid}".encode())

    result = r.recvline().decode()

    if "large" in result:
        section_max = section_mid
    elif "small" in result:
        section_min = section_mid
    else:
        r.interactive()
> python ./solver.py
n00bz{y0u_4r3_4_sm4rt_51l1y_g0053}

Flag

n00bz{y0u_4r3_4_sm4rt_51l1y_g0053}

Back From Brazil

上位チームの検証のために書いたらあかんっぽいのでそれが済んでからOKになって覚えていたら書きます……。

Blockchain

EVM – The Basics

解法

EVM Codes – An Ethereum Virtual Machine Opcodes Interactive Reference を見て頑張って解読する。

5f346113370265fdc29ff358a314601257ff00

5f: PUSH0
34: CALLVALUE
61: PUSH2 0x1337
02: MUL (0x1337 * INPUT)
65: PUSH6 0xfdc29ff358a3
14: EQ (0x1337 * INPUT == 0xfdc29ff358a3)
60: PUSH1 12
57: JUMPI ff 00

EQの条件を満たすINPUTを計算する。

>>> 0xfdc29ff358a3 / 0x1337
56721355765.0

>>> hex(0xfdc29ff358a3 // 0x1337)
'0xd34db33f5'

Flag

n00bz{0xd34db33f5}

EVM – Conditions

解法

こちらも同様に解読する。

5f600f607002610258525f60056096046090525f600760090A61FFFA526105396126aa18620bfabf52600361fffa5102620bfabf51013461025851600402016090510114604857ff00

5f: PUSH0
60 0f: PUSH1 0x0f
60 70: PUSH1 0x70
02: MUL (0x70 * 0x0f = 0x690)
61 02 58: PUSH2 0x0258
52: MSTORE (0x0258 <= 0x690)

5f: PUSH0
60 05: PUSH1 0x05
60 96: PUSH1 0x96
04: DIV (0x96 // 0x05 = 0x1e)
60 90: PUSH1 0x90
52: MSTORE (0x90 <= 0x1e)

5f: PUSH0
60 07: PUSH1 0x07
60 09: PUSH1 0x09
0A: EXP (0x09 ** 0x07 = 0x48fb79)
61 FF FA: PUSH2 0xfffa
52: MSTORE (0xfffa <= 0x48fb79)

61 05 39: PUSH2 0x0539
61 26 aa: PUSH2 0x26aa
18: XOR (0x26aa ^ 0x0539 = 0x2393)
62 0b fa bf: PUSH3 0x0bfabf
52: MSTORE (0x0bfabf <= 0x2393)

60 03: PUSH1 0x03
61 ff fa: PUSH2 0xfffa
51: MLOAD (0x48fb79)
02: MUL (0x48fb79 * 0x03 = 0xdaf26b)

62 0b fa bf: PUSH3 0x0bfabf
51: MLOAD (0x2393)
01: ADD (0x2393 + 0xdaf26b = 0xdb15fe)

34: CALLVALUE (INPUT)
61 02 58: PUSH2 0x0258
51: MLOAD (0x690)
60 04: PUSH1 0x04
02: MUL (0x04 * 0x690 = 0x1a40)
01: ADD (0x1a40 + INPUT)

60 90: PUSH1 0x90
51: MLOAD (0x1e)
01: ADD (0x1e + (0x1a40 + INPUT))

14: EQ (INPUT + 0x1a5e == 0xdb15fe)
60 48: PUSH1 0x48
57: JUMPI (0x48)
ff: SELFDESTRUCT
00: STOP

条件を満たすINPUTは0xdb15fe - 0x1a5e = 0xdafba0である。

Flag

n00bz{0xdafba0}

おわりに

最近少しドタバタしてたので久しぶりのCTF参加でしたが楽しめました。

他にも勉強モチベ爆上がりイベントもあったのでまた勉強頑張ります。