nitic-ctf2 writeup
2021/8/5~2021/8/6まで、nitic-ctf2が開催されました。
どうやら、ctfでは、コンテスト後に解けた問題について、どうやって解いたたかをwrite-upに書くのが定番なので、私も初参加の記録として、書いてみます。
web
web_meta
添付ファイルをプレビューで開いてみると、このようにnitic ctf2
しか表示されません。
しかし、ファイルの中身を見てみると、ヘッダー部に欲しい情報が入っています。
<head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="description" content="flag is nitic_ctf{You_can_see_dev_too1!}"> <title>nitic_ctf_2</title> </head>
よって、この問題のFlagはnitic_ctf{You_can_see_dev_too1!}
です。
long flag
同じく、添付ファイルを開いてみると、次のようになります。
このflag
ではさまれた間が怪しそうなので、開発者ツールで見てみましょう。
<p id="flag" onmousedown="return false;" onselectstart="return false;"> <span>n</span><span>i</span><span>t</span><span>i</span><span>c</span>...(略)...<span>3</span><span>}</span> </p>
このままでは、<span>
が邪魔なので、コンソールを使って、文字を抜き出してしましましょう。
ちょうど<script>
でflagを取得しているところがあるので、これを借ります。
> e.textContent < '\n nitic_ctf{Jy!Hxj$RdB$uA,b$uM.bN7AidL6qe4gkrB9dMU-jY8KU828ByP9E#YDi9byaF4sQ-p/835r26MT!QwWWM|c!ia(ynt48hBs&-,|3}\n '
あとは、これの余計な空白部分を取り除けば、Flag:nitic_ctf{Jy!Hxj$RdB$uA,b$uM.bN7AidL6qe4gkrB9dMU-jY8KU828ByP9E#YDi9byaF4sQ-p/835r26MT!QwWWM|c!ia(ynt48hBs&-,|3}
になります。
Pwn
pwn monster1
まずは、普通にやってみましょう
nabefuta@LAPTOP-RD6M1S1K:~/ctf$ 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: nabefuta +--------+--------------------+----------------------+ |name | 0x617475666562616e | nabefuta | | | 0x0000000000000000 | | |HP | 0x0000000000000064 | 100 | |ATK | 0x000000000000000a | 10 | +--------+--------------------+----------------------+ OK, Nice name. Let's battle with Rival! If you win, give you FLAG. [You] nabefuta HP: 100 [Rival] pwnchu HP: 9999 Your Turn. Rival monster took 10 damage! [You] nabefuta HP: 100 [Rival] pwnchu HP: 9989 Rival Turn. Your monster took 9999 damage! [You] nabefuta HP: -9899 [Rival] pwnchu HP: 9989 Lose... nabefuta@LAPTOP-RD6M1S1K:~/ctf$
負けました...
そこで、添付ファイルのソースコードを見てみましょう。
(略) typedef struct { char name[16]; int64_t hp; int64_t attack; } Monster; void print_monster_infomation(Monster monster) { printf("+--------+--------------------+----------------------+\n"); printf("|name | 0x%016lx | %20.8s |\n", *(int64_t*)monster.name , monster.name); printf("| | 0x%016lx | %20.8s |\n", *(int64_t*)(monster.name + 8), monster.name + 8); printf("|HP | 0x%016lx | % 20ld |\n", monster.hp , monster.hp); printf("|ATK | 0x%016lx | % 20ld |\n", monster.attack , monster.attack); printf("+--------+--------------------+----------------------+\n"); } void give_monster_name(Monster* monster) { printf("Let's give your monster a name!\n"); print_monster_infomation(*monster); printf("Input name: "); scanf("%s%*c", monster->name); (略)
どうやら、モンスターにかかわる情報は構造体で取っているようです。
つまり、名前を入力する際に、17文字以上の文字を入れれば、体力などのほかの情報に干渉できそうです。
そのため、名前を不必要に長くしてみます。
nabefuta@LAPTOP-RD6M1S1K:~/ctf$ 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: 123456789012345678901234567890 +--------+--------------------+----------------------+ |name | 0x3837363534333231 | 12345678 | | | 0x3635343332313039 | 90123456 | |HP | 0x3433323130393837 | 3761405300628338743 | |ATK | 0x0000303938373635 | 53022314411573 | +--------+--------------------+----------------------+ OK, Nice name. Let's battle with Rival! If you win, give you FLAG. [You] 123456789012345678901234567890 HP: 3761405300628338743 [Rival] pwnchu HP: 9999 Your Turn. Rival monster took 53022314411573 damage! [You] 123456789012345678901234567890 HP: 3761405300628338743 [Rival] pwnchu HP: -53022314401574 Win! nitic_ctf{We1c0me_t0_pwn_w0r1d!}
ということでこの問題のFlagはnitic_ctf{We1c0me_t0_pwn_w0r1d!}
です。
pwn monster2
先ほどと同じ方法でやってみましょう。
nabefuta@LAPTOP-RD6M1S1K:~/ctf$ 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: 123456789012345678901234567890 +--------+--------------------+----------------------+ |name | 0x3837363534333231 | 12345678 | | | 0x3635343332313039 | 90123456 | |HP | 0x3433323130393837 | 3761405300628338743 | |ATK | 0x0000303938373635 | 53022314411573 | +--------+--------------------+----------------------+ Checksum: 3761458322942750316 Detect cheat. nabefuta@LAPTOP-RD6M1S1K:~/ctf$
ズルがばれてしまいました...
ここで、添付ファイルのソースファイルを見てみると、Cheaksumは次のように計算されていることがわかります。
int64_t checksum = monster->hp + monster->attack; printf("Checksum: %ld\n", checksum); printf("Input name: "); scanf("%s%*c", monster->name); print_monster_infomation(*monster); printf("Checksum: %ld\n", monster->hp + monster->attack); if (monster->hp + monster->attack != checksum) { puts("Detect cheat."); exit(1); }
つまりは、モンスターのhpと攻撃力の総和を変えずに何とかいじれば、うまくいけそうです。
また、攻撃時の終了判定についてみてみましょう。
bool battle(Monster my_monster, Monster rival_monster) { bool my_turn = true; while (1) { printf("[You] %s HP: %ld\n", my_monster.name, my_monster.hp); printf("[Rival] %s HP: %ld\n", rival_monster.name, rival_monster.hp); if (rival_monster.hp < 0) { puts("Win!"); return true; } if (my_monster.hp < 0) { puts("Lose..."); return false; } if (my_turn) { puts("Your Turn."); printf("Rival monster took %ld damage!\n", my_monster.attack); rival_monster.hp -= my_monster.attack; } else { puts("Rival Turn."); printf("Your monster took %ld damage!\n", rival_monster.attack); my_monster.hp -= rival_monster.attack; } my_turn = !my_turn; } }
このことから、モンスターの最初の体力は正である必要がありそうです。
また、モンスターの体力と攻撃値はint64_t
=signed long int
なので、オーバーフローをさせて、マイナスにさせても行けそうです。
そのため、VSCodeの拡張機能であるHex Editerを使って、入力をバイナリ単位で調節します。
この内容をパイプを使って、標準入力に入れてみます。
nabefuta@LAPTOP-RD6M1S1K:~/ctf/pwn_monster_2$ cat cat.out | 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 | 0xffffffffffffffff | �������� | | | 0xffffffffffffffff | �������� | |HP | 0x7fffffffffffffff | 9223372036854775807 | |ATK | 0x800000000000006f | -9223372036854775697 | +--------+--------------------+----------------------+ Checksum: 110 OK, Nice name. Let's battle with Rival! If you win, give you FLAG. [You] �����������������������o HP: 9223372036854775807 [Rival] pwnchu HP: 9999 Your Turn. Rival monster took -9223372036854775697 damage! [You] �����������������������o HP: 9223372036854775807 [Rival] pwnchu HP: -9223372036854765920 Win! nitic_ctf{buffer_and_1nteger_overfl0w} *** stack smashing detected ***: terminated Aborted (core dumped) nabefuta@LAPTOP-RD6M1S1K:~/ctf/pwn_monster_2$
スタックは壊れましたが、この問題のFlagはnitic_ctf{buffer_and_1nteger_overfl0w}
です。
Misc
Excel
添付ファイルを開くと、このようにセルだけの画面が広がっています。
そこで、エクセルの検索機能を使い、nit
で始まるセルを探してみます。
すると、白文字で書かれていました。
ということで、この問題のFlagはnitic_ctf{plz_find_me}
です。
image_conv
まずは、添付ファイルを開いてみます。
すると、白に近い文字で何かが書かれているように感じます。
そこで、Wordで問題の画像を貼り付け、この画像にアート効果にチョーク:スケッチを加えます。
このとおり、はっきり見えました。
よって、この問題のFlagはnitic_ctf{high_contrast}
です。
Rev
protected
まずは、添付ファイルの拡張子がないので、どのようなタイプかを調べます。
nabefuta@LAPTOP-RD6M1S1K:~/ctf/protected/protected$ file chall chall: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=34e0075bd7d601785c00143e049a88c6ec927e50, for GNU/Linux 3.2.0, not stripped
どうやら、このファイルは64bitのELFで、Linux系の実行ファイルのようです。(https://eng-blog.iij.ad.jp/archives/3376 より)
そのため、このファイルを実行してみます。
nabefuta@LAPTOP-RD6M1S1K:~/ctf/protected/protected$ ./chall bash: ./chall: Permission denied nabefuta@LAPTOP-RD6M1S1K:~/ctf/protected/protected$ chmod u+x chall nabefuta@LAPTOP-RD6M1S1K:~/ctf/protected/protected$ ./chall PASSWORD:
やろうとしたが、権限がなかったので、権限をあたえて、再び実行したら、パスワードを聞かれました。
そこで、このファイルを開き、パスワードらしきものがないか調べてみます。
すると、怪しげな文字列が出てきました。そこで、その文字列であるsUp3r_s3Cr37_P4s5w0Rd
を入力してみると、
nabefuta@LAPTOP-RD6M1S1K:~/ctf/protected/protected$ ./chall PASSWORD: sUp3r_s3Cr37_P4s5w0Rd nitic_ctf{hardcode_secret}
無事、Flagを抜き出せました。
Crypto
Caesar Cipher
フラグの中身をシーザー暗号で暗号化したものがfdhvdu
だそうです。
そのため、次のようなコードを書いて、あり得る候補を探してみます。
#include <stdio.h> #include <string.h> char str[] = "fdhvdu"; int main() { for (int i = 'a'; i <= 'z'; i++) { for (int j = 0; j < strlen(str); j++) { str[j]++; if(str[j] > 'z'){ str[j] = 'a'; } } printf("%s\n", str); } }
nabefuta@LAPTOP-RD6M1S1K:~/ctf/seeeser$ gcc see.c -o see.out nabefuta@LAPTOP-RD6M1S1K:~/ctf/seeeser$ ./see.out geiwev hfjxfw igkygx jhlzhy kimaiz ljnbja mkockb nlpdlc omqemd pnrfne qosgof rpthpg squiqh trvjri uswksj vtxltk wuymul xvznvm ywaown zxbpxo aycqyp bzdrzq caesar dbftbs ecguct fdhvdu
この中で、意味がある文字列はcaesar
です。
よって、この問題のFlagはnitic_ctf{caesar}
です。
ord_xor
添付ファイルを見てみると、flagは次のような方法で暗号化されているようです。
import os flag = os.environ["FLAG"] def xor(c: str, n: int) -> str: temp = ord(c) for _ in range(n): temp ^= n return chr(temp) enc_flag = "" for i in range(len(flag)): enc_flag += xor(flag[i], i) with open("./flag", "w") as f: f.write(enc_flag)
どうやら、flagのi番目の文字は元の文字にiをxorした後の文字になっているようです。
そして、できたflagが次のようになるそうです。
nhtjcZcsfroydRx`rl
ここで、xorの演算は、a xor b =c
が成り立つとき、a = b xor c
が成り立ちます。
つまり、できたflagを先ほどのコードを同じようにすれば、複合できそうです。
inv.py
flag = "nhtjcZcsfroydRx`rl" def xor(c: str, n: int) -> str: temp = ord(c) for _ in range(n): temp ^= n return chr(temp) enc_flag = "" for i in range(len(flag)): enc_flag += xor(flag[i], i) print(enc_flag)
nabefuta@LAPTOP-RD6M1S1K:~/ctf/ord_xor$ python inv.py nitic_ctf{ord_xor}
ということで、この問題のFlagはnitic_ctf{ord_xor}
です。
nitic-ctf2に参加してみて
今回のctfは、初参加なので、すべての分野の~200点問題+競技プログラミングに近いCryptoの300点問題を解くことを目標にして、すべての問題の~200点問題は解けましたが、Cryptoの300点問題がとけなくて、悔しかったです。(特に3問目がグラフ理論系だったので、とても悔しい)
また、全体をとおして、まるで宝探しをしているような感覚で楽しかったです。
最後に、この企画を提案された、茨城高専の有志の方々、ありがとうございました!