Removing the artificial sleep
In reality, there won't be a 1-second sleep for your race condition to occur. This means we instead have to hope that it occurs in the assembly instructions between the two dereferences!
This will not work every time - in fact, it's quite likely to not work! - so we will instead have two loops; one that keeps writing 0
to the ID, and another that writes another value - e.g. 900
- and then calling write
. The aim is for the thread that switches to 0
to sync up so perfectly that the switch occurs inbetween the ID check and the ID "assignment".
If we check the source, we can see that there is no msleep
any longer:
Our exploit is going to look slightly different! We'll create the Credentials
struct again and set the ID to 900
:
Then we are going to write this struct to the module repeatedly. We will loop it 1,000,000 times (effectively infinite) to make sure it terminates:
If the ID returned is 0
, we won the race! It is really important to keep in mind exactly what the "success" condition is, and how you can check for it.
Now, in the second thread, we will constantly cycle between ID 900
and 0
. We do this in the hope that it will be 900
on the first dereference, and 0
on the second! I make this loop infinite because it is a thread, and the thread will be killed when the program is (provided you remove pthread_join()
! Otherwise your main thread will wait forever for the second to stop!).
Compile the exploit and run it, we get the desired result:
Look how quick that was! Insane - two fails, then a success!
You might be wondering how tight the race window can be for exploitation - well, gnote
from TokyoWesterns CTF 2019 had a race of two assembly instructions:
The dereferences [rbx]
have just one assembly instruction between, yet we are capable of racing. THAT is just how tight!