Solved by 4rbit3r
This was one of the easier pwnable challenges in the ctf. I thoroughly enjoyed the ctf. Great job admins!
So the binary basically takes in two arguments namely format and time and executes the command “/bin/date -d @time +format” which returns the unix time.
The first thing I thought of was to overwrite /bin/date with /bin/sh# which would comment out the rest.
But going through the binary, I found that there was no overflow anywhere. So the next option was to insert malicious code into the format which would give me a shell.
No way its that easy. The binary also contains a check() function which checks the input format for invalid characters like ‘/’ .
But the interesting fact is that you can create a string called timezone which doesn’t check for invalid characters. And this string isn’t used anywhere.
Useful information.
So once you enter the format string, the program calls strdup on the string which basically places the string on the heap. Then it checks for bad characters in the string. If there are bad characters, it returns to the main loop.
Only if there are no bad characters, a global pointer is set to point to the newly created chunk. Lets call this ptr.
And then if the user chooses the print option, the string at the address pointed to by ptr is chosen as format and the command is executed.
So that basically shows us what we’re supposed to do, get malicious code into the format string and then choose the print option.
How?
For that, at first, I thought of this:
- Enter a format of n junk bytes + “;/bin/sh#”
- Enter another format of n junk bytes.
So essentially, the format string would be rejected the first time, but the string “;/bin/sh#” would still be there on the heap. The second format would only consist of bytes that reach up to this string. So then choosing the print option should give me a shell.
But unfortunately this wont work since any string you enter is null terminated.
So what next. I started looking through the other functions and noticed a particular section of code in the exit() function. The exit() function free’s the allocated chunk which is pointed to by our pointer ptr and also the chunk containing the timezone string if any (this chunk is also pointed to by a global pointer, call it ptr2 ?). That’s what any sensible program is supposed to do. But wait, it asks us again if we’re sure we want to exit. The point to note is that this confirmation is done after the free’ing of the chunks.
So at this point, if we were to reply in the negative, we have a Use-After-Free vulnerability. The chunk containing the format string has been freed, but the pointer that points to it i.e. ptr isn’t NULL’d.
So combining all the info we have up until now, I plotted my devious plan.
- Create a format string.
- Choose exit option and print ‘n’ when it asks for confirmation.
- Create a timezone string which consists of “;/bin/sh#”
- Choose print option
So the first time the format is created, there are no bad characters. Therefore ptr is set and points to a chunk containing this format. Completing the second step free’s this chunk. During the third step, the chunk returned is the same chunk that was recently free’d i.e the one ptr points to. This chunk now contains our malicious code. Now choose the print option. And voila! Shell..wait. There’s a problem, you’ll get a double free corruption error.
You can resolve this by creating another timezone string. But I was too lazy for that. I replaced the ‘sh’ with ‘cat flag.txt’. And that did the trick. Here’s the exploit script.
from pwn import * p=process('unix') p.sendlineafter(">","1") p.sendlineafter(":","A"*16) p.sendlineafter(">","5") p.sendlineafter("?","N") p.sendlineafter(">","3") msg="';cat flag.txt'" p.sendlineafter(": ",msg) p.sendlineafter(">","4") log.info(p.recvline()) log.success("The flag is: "+p.recvline()) p.close()
And executing it gives the flag.
Flag PCTF{use_after_free_isnt_so_bad}
Leave a Reply