/* This code decompilated by GHIDRA This is the main application code which run in infinite loop */ voidmain(void) { long action_input; uint free_count; env_setup(); printf("Name:"); // read data in global buffer read_input_to_buf(DAT_username,32); free_count = 0; do { while( true ) { while( true ) { action_menu(); action_input = read_int(); if (action_input != 2) break; if (free_count < 8) { // Free the allocated memory chunk free(DAT_input_buf); free_count = free_count + 1; } } if (2 < action_input) break; if (action_input == 1) { // Allocate object and feed the data to obj_malloc(); } else { LAB_00400c75: puts("Invalid choice"); } } if (action_input != 3) { if (action_input == 4) { /* WARNING: Subroutine does not return */ exit(0); } goto LAB_00400c75; } print_username(); } while( true ); }
There is a vulnerability on line 21 where it frees the allocated memory chunk but it doesn’t null-out that pointer.
There is another vulnerability is in object allocation code. The code is as show below, try to understand the find the vulnerability yourself.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
voidobj_malloc(void) { ulong buf_size; printf("Size:"); buf_size = read_int(); if (buf_size < 256) { DAT_input_buf = (char *)malloc(buf_size); printf("Data:"); // read the user input into the buffer and with // buffer size check read_input_to_buf(DAT_input_buf,(int)buf_size + -16); puts("Done !"); } return; }
The size of the buffer is taken from user and 16 is subtracted from that, the result is converted to unsigned integer and used as max user input buffer size. So if you provide the number less then 16, then the subtraction result is negative number, which if converted to unsigned number will result in very high value. So if we provide buffer size less then 16 you will get out-of-bound write. This kind of vulnerability is called Integer overflow. We will utilize this out-of-bound write with other vulnerability to get further exploit this challange.
There are some of the constraints of the application that we influence our exploitation:
You can’t allocate buffer size more than 256 bytes, this restricts the buffer allocation to only Tcache bins.
Application tracks only one allocated buffer at a time.
In the main code loop, the pointer of the memory chuck allocated by obj_malloc(on line 15) is not nulled-out after free(on line 15) this can be used to do double free. In Tcache Tear there is no check for dublicate entry check in free list so we can do double free to do duplicate Tcache Tear Attack. This attack return arbitrary address on calling malloc which will give opportunity write data to that address.
We will use this write primitive to create read primitive which will help us to Leak Libc address. Below is the code to create arbitrary write :
In the attack we just discussed, a fake chuck has to be crafted in the tcachebin and the fake chunk will be constructed in the DAT_username buffer. The reason for crafting fake chunk in DAT_username is that its in bss segment and the PIE of the binary is disabled. So the address of the DAT_username will be fixed (0x602060). The size(0x500) fake chuck will be such that on freeing, it will land in unsorted bin. To put it more clearly the fake chunk we will put will be in tcache bin(lets say of size 0x10) but the fake chunk itself will be of larger size(0x500). Then later we will allocate chunk from the same tcache bin(0x10) till it return the fake chunk, immediately we will do free on the memory, it will put the fake chunk in the unsorted-bin.
Since the Unsorted bins is doubly linked list when we put the chunk in the unsorted bin, it is populated with fd and bk pointer with the next and previous memory chunk. When there is just one chunk in the Unsorted in then the fd and bk pointer has main arena address. We can print the content DAT_username in the application and since this chunk is in Unsorted bin we have leaked the main arena address. We can calculate the Libc base address using the main arena address.
To get code execution we will overwrite __free_hook with the system function to get system shell. To eloborate more on this first you have to allocate chunk with ‘/bin/sh’ as its content, and when you free that chunk. The pointer to that chunk is passed to __free_hook if the hook is not null. Since the free hook address is system function address, the call effectively execute system(/bin/sh) which give us a shell. Below is the code to run above mentioned steps.
1 2 3 4 5 6 7
defstage_final(): app.malloc(50, '/bin/sh') log.info('Triggering shell') app.free(shell=True) log.info('You should have the flag now') io.sendline('cat /home/tcache_tear/flag') log.info('Flag : ' + io.read().decode('utf-8'))
defstage_final(): app.malloc(50, '/bin/sh') log.info('Triggering shell') app.free(shell=True) log.info('You should have the flag now') io.sendline('cat /home/tcache_tear/flag') log.info('Flag : ' + io.read().decode('utf-8'))