DEF CON CTF 2015 - fuckup (pwn3) Writeup
Description
fuckup_56f604b0ea918206dcb332339a819344.quals.shallweplayaga.me:2000
OR
fuckup_56f604b0ea918206dcb332339a819344.quals.shallweplayaga.me:46387
Download
Introduction
This is a PoC service for the new and improved ASLR, “Fully Unguessable Convoluted Kinetogenic Userspace Pseudoransomization”(F.U.C.K.U.P. for short).
Each time a user executes a command, F.U.C.K.U.P. changes the base address of memory where the binary is mapped according to a random number produced by the generation algorithm similar to WELL512.
We can select from the following commands:
- Quit: simply
return 0;
. - Display info: Display an introduction. Nothing interesting.
- Change random: Generate a random value and move mappings correspondingly.
- View state info: Show the current random value and then change the value as same as “Change random”.
- Test stack smash: Cause stack based buffer overflow by 100 bytes against a 10-byte buffer.
Actually, I don’t know the detailed implementations of these commands except for “Test stack smash”, for it was not I but another team member who coped with this challenge at first.
It seems that the author’s intended solution is to use SMT solver like z3 to predict random values generated, and my teammate attempted to do that.
It, however, didn’t work correctly since we were unfamiliar with and poor at using SMT solver.
So I decided to try to solve this problem by the really “pwnwise” solution.
First, I suspected Partial Overwrite could be used.
Yes, actually it can be.
Reading stack_smash(sub_8048521)
, there is called read_n(sub_8048363)
which simply receives input as this:1
2
3
4
5sum = 0;
do {
nread = read(0, addr, n-sum);
if (nread != -1) sum += nread;
} while (sum < n);
As you may see, this implementation is weird because using read(0, addr, n-sum)
instead of read(0, addr+sum, n-sum)
.
Therefore, it is possible to do Partial Overwrite by splitting input into several.
@wapiflapi, a great hacker in France shares the exploit using this method(http://hastebin.com/iyinepaxen.py).
Very simple, isn’t it?
BUT I COULD NOT COME UP WITH IT.
Because I misread read_n
as read(0, addr+sum, n-sum)
.
So at that time I thought “Wow, nice security. I have no choice but to overwrite a buffer completely by 100 bytes. If I can’t use Partial Overwrite, then how can I solve this…?”. Too stupid.
Okay, let me explain how I solved this problem even though I couldn’t use z3 and Partial Overwrite.
Solution
Thinking that the return address is always overwritten by a buffer overflow, I had to overwrite it with some valid address.
Here, valid address means a address being mapped and executable.
So there are two possible ways to exploit the binary:
- Fix valid addresses somehow.
- Use the addresses which are always fixed.
I thought the former could be realized because the number of mapped addresses goes on increasing by change_mapping(sub_80481A6)
.
In change_mapping, mmap
is called like this:1
2
3
4
5
6
7do
{
seedf = randf(state) * 4294967295.0;
seedl = (int)seedf;
expect = (void *)(seedl & 0xFFFFF000);
actual = mmap(expect, 0x7000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
} while (expect != actual);
As you can see, the mapped addresses won’t be unmapped even if it fails to establish mappings at expected addresses.
Therefore, the more the number of mapped addresses has increased, the less the number of the possible addresses capable of being becomes.
But this approach isn’t realistic because it needs to do “Change random” many times(about thouthands or hundreds of thouthands times).
The latter, actually, can be realized: using VDSO.
I think everyone knows this, but VDSO ASLR is weaker than ASLR on the other sections(that entropy is usually only 2 bytes) and there is a famous exploit method, Sigreturn Oriented Programming(SROP).
That means we can solve this problem by doing brute force 256 times.
It was a little bit difficult for me to write the exploit due to the limitation that I had to do ROP only with gadgets on VDSO and that I was allowed to use only 78 bytes for ROP.
Why stack_addr = vdso - 0x800
does work correctly is described in my paper.
sysenter is a good gadget for stack pivotting!
1 | import subprocess |
Using Frame.py.
1 | ['\nbin\nboot\ndev\netc\nhome\ninitrd.img\ninitrd.img.old\nlib\nlib64\nlost+found\nmedia\nmnt\nopt\nproc\nroot\nrun\nsbin\nsrv\nsys\ntmp\nusr\nvar\nvmlinuz\nvmlinuz.old\nfuckup\nubuntu\nflag\nfuckup\n/home/fuckup/flag\n/home/fuckup/flag\nThe flag is: z3 always helps\n'] |
##Summary
Sleep enough not to misread disas.
written by hugeh0ge(@hugeh0ge)