Description
Jean-Michel est un grand fan de jeu de rôle. Malheureusement, il n’a jamais eu l’occasion de rejouer avec ses amis depuis la crise du Covid-19.
Ils ont travaillé sur une application de chat qui permet aux joueurs de tirer toute sorte de dés, sur la base d’un CSPRNG.
Voici la première version de leur application. Trouvez une vulnérabilité pour lire le fichier flag.txt.
nc challenges.france-cybersecurity-challenge.fr 2056
Translation:
Jean-Michel is a great role-playing game player. Unfortunately, he has never had the opportunity to play with his friends since the Covid-19 crisis.
They have worked on an application that allows players to throw dice, based on a CSPRNG.
Here is the first version of their application. Find a vulnerability to read the file flag.txt.
nc challenges.france-cybersecurity-challenge.fr 2056
The source code
There is a single file, rpg.c
, which contains the following code:
|
|
It is an application with a menu, which allows the user to choose a username and do some other commands … looks really like a heap based overflow challenge 👀
Analysis
Based on my intuition, I searched for a heap overflow vulnerability in the application. The only code that doesn’t looks safe is the strcpy
function, which is used to copy the username into a buffer.
|
|
Before the call to strcpy
, we can see that a check is performed to ensure that we do not overflow the buffer, but this check is not performed correctly: the size variable contains the size of the user input, which is not the size of the destination buffer.
Moreover, even if size
was the size of the destination buffer, realloc(name, size+1)
seems stupid, the correct code would be realloc(name, strlen(arg)+1)
.
So we can trigger an overflow by providing a long username.
What can we overflow ?
The first idea that came to my mind was to craft a heap chunk, which would allow me to choose where malloc would allocate the chunk. This would be useful for an arbitrary write, since we could choose where the buffer returned by getline
would be written. But I don’t see any ways to use this for an arbitrary read 🙁.
But, for those who already read some of my write ups, you know how much I love to think about the reasons of why something is implemented in a challenge. So here is the question:
Why is there a command to trigger a file read ? Is it just for the lore ? Or is it really useful ?
So I looked closer, and I found that the FILE
structure was allocated just after the username buffer, so we can overflow on it 😏.
Moreover, if we look at the FILE
structure, we can see that its second member is a pointer to where it stores the data readed from the file. So if we overflow the beginning of the struct, and trigger a write, we can leak this address (and therefore bypass the ASLR).
|
|
[+] Opening connection to challenges.france-cybersecurity-challenge.fr on port 2056: Done
[*] leak: 0x5876bac23888
Our buffer (which stores the username) is not really at leak & 0xfffffffffffffff0
, but we will bruteforce its position later.
Arbitrary write
Now, we can modify the FILE
structure in order to write what we want, where we want.
There is a simple technique for this:
- Use the correct flags (I will just copy those used by the initial
FILE
structure) - Change the
_IO_buf_base
member to the address of the buffer we want to write to - Change the
_IO_buf_end
member to the address of the end of the buffer we want to write to - Change the file descriptor to 0, so that the
read
function will read from stdin and write where we want
Note: We don’t have to write every bytes of the new FILE
structure, we can just write the beginning, which contains all the data we need to change.
Since the fread
function will return the data we wrote, we can check if everything went well by returning the value of the fread
function.
|
|
Arbitrary read
The arbitrary read is almost the same code. But we will take advantage of the fact that the FILE
structure is using buffering:
When we read from a file, the data is stored in a buffer. If we want to read 8 bytes, but fread
reads 10 bytes from the file, it will store those 10 bytes in a buffer, and next time we want to read data, it will use the buffer to return the 2 bytes he already read (and then he read from the file in order to return as much data as the program asked).
|
|
The actual exploit
Using those 2 primitives, we can get our shell:
- We find our buffer on the heap
- We leak an address from
libc
- We leak the address of the stack (using
__environ
) - We write a ROP chain / a one gadget on the stack
Finding our buffer
To find our buffer, I used a loop that would leak data at the given address, and compare it to the flags of the FILE
structure. If they match, the buffer is just a bit before this address.
|
|
[+] Buffer address: 0x5876bac232a0
Find the address of libc
By using gdb on the program, I found that the address of _IO_file_jumps
is a member of the FILE
structure:
gef➤ x/40a 0x0000555555559320
0x555555559320: 0xfbad2488 0x0
0x555555559330: 0x0 0x0
0x555555559340: 0x0 0x0
0x555555559350: 0x0 0x0
0x555555559360: 0x0 0x0
0x555555559370: 0x0 0x0
0x555555559380: 0x0 0x7ffff7f7d4c0 <_IO_2_1_stderr_>
0x555555559390: 0x3 0x0
0x5555555593a0: 0x0 0x555555559400
0x5555555593b0: 0xffffffffffffffff 0x0
0x5555555593c0: 0x555555559410 0x0
0x5555555593d0: 0x0 0x0
0x5555555593e0: 0x0 0x0
0x5555555593f0: 0x0 0x7ffff7f7e3a0 <__GI__IO_file_jumps>
0x555555559400: 0x0 0x0
0x555555559410: 0x0 0x0
0x555555559420: 0x0 0x0
0x555555559430: 0x0 0x0
0x555555559440: 0x0 0x0
0x555555559450: 0x0 0x0
Since we know the address of this structure, we can use our read primitive to leak it.
|
|
[+] libc.address: 0x79a490ce2000
Leaking the stack address
This part is easy, we just use the __environ
symbol to leak it.
|
|
[+] env: 0x7fffa5a51518
Write our ROP chain
Now, we know where our stack is, we can just build a simple ROP chain and write it on the stack. We then just quit the main loop in order get our shell.
|
|
0x79a490da9f32
0x0
0x79a490d0c4cf
0x0
0x79a490d0aa55
0x79a490e8df05
0x79a490dbfdb0
[+] Flag: FCSC{7b6c4e7464a2f4ccfd219b9456de3820aad908dca721c71362b636f10f621424}
Conclusion
As always with FCSC, the challenges are just incredible.
I learned how we could use a FILE
structure to get a read
and a write
primitive, which can be really powerful in some cases.