Challenge Info
This program mishandles memory. Can you exploit it to get the flag? Downloads the binary here. Downloads the source here.
Additional details will be available after launching your challenge instance.
Understanding chall.c
The code for your convenience:
1 |
|
- Defines a constant for the max size of the flag string (
FLAGSIZE_MAX
). - A structure
object
is defined with 4 character arrays (a[10]
,b[10]
,c[10]
,flag[5]
). - Pointer
x
is declared globally, but not yet pointing to anything. init()
functionhttps://chirpy.cotes.page/ is declared, it executesx = malloc(sizeof(object))
, ensuring that enough memory is reserved for all of struct’s members (a[10]
,b[10]
,c[10]
, andflag[5]
. Additionally, the global pointerx
is now set to point to this memory block that will hold the previously definedobject
struct.) There’s alsoint num_allocs
, but this is unused.strncpy()
copies the string"bico"
into theflag
member/field of theobject
thatx
is pointing to.alloc_object()
function is declared. It begins by prompting us to input the size of the memory allocation that they want to make.- Then, an integer variable
size
is initialized in order to store the size of the allocation.scanf("%d", &size)
reads an integer input from us and then stores it in the previously initializedsize
variable. - Essentially, the program expects us to enter a value that represents the number of bytes we want to allocate.
- Then, an integer variable
check_win()
function is declared. It checks ifx->flag
matches with'pico'
. And if it does, then we get our flag. This is essentially the ‘win’ condition.alloc_object
function is declared, it prompts us to enter a size for dynamic allocation, it then reads an integer, and allocates memory accordingly. Finally, it accepts input to populate this allocated space.
Vulnerabilities
- Use-After-Free (UAF) vulnerability, because while the
free_memory()
function does free the memory block associated withx
, it’s vulnerable because ifcheck_win()
is called afterward, thenx->flag
can still be accessed. - Buffer overflow vulnerability: while the
flag
member inobject
is only 5 bytes, making it very limited, because ofalloc_object
, we could specify a much larger input for the memory allocated toalloc
. So, if this memory isn’t handled correctly, then we can just overwrite memory structures adjacent toalloc
(hence the buffer overflow).
Before proceeding with the solution, I’ll paste the program’s interface so that it’s easier to visualize:
1 | > nc tethys.picoctf.net 62002 |
The Plan
So, to exploit this program, we can leverage the UAF vulnerability that I previously discussed. If we combine this with heap allocation manipulation (via buffer overflow), we can overwrite a specific field in a freed structure (x->flag
) with the string "pico"
. This is how it would look like step by step:
- This select option
5
to free x. - This select option
2
to allocate a new block of memory, which is likely to use the same memory area thatx
was previously occupying, because they’re goingto have similar size requirements.- The allocation size will be between 20 and 40, this way, we increase the likelihood that our new allocation will overlap with the previously freed
object
struct. - Recall that the
object
struct has 4 members, for a total of 35 bytes.1
2
3
4
5
6typedef struct {
char a[10]; // ten bytes
char b[10]; // ten bytes
char c[10]; // ten bytes
char flag[5]; // 5 bytes
} object;
- The allocation size will be between 20 and 40, this way, we increase the likelihood that our new allocation will overlap with the previously freed
- While still in the “allocate object” option, we now input a payloads string that will contain
"pico"
at the end to overwrite the previousflag
value"bico"
. - Select option 4 (“Check for win”) and get our flag!
Solution
The pwntools payloads that I came up with is as follows:
1 | from pwn import * |
All of the r.sendline
‘s are for selecting options in the interface. As for the actual payloads, it consists of an initial 24 bytes (8 blocks of A’s), and then a final block of 6 A’s, and “pico”- for a total of 34 bytes. The reason we are inputting 34 bytes, rather than 35, is because the flag
field in the object
struct has a 5 byte space, due to the program accounting for a null byte.
flag: picoCTF{now_thats_free_real_estate_a7381726}