Within this post I’ll be doing a write up of the Android in the Middle challenge from the HackTheBox Cyber Apocalypse 2022 CTF competition (14/05/2022). This write up will be written according to my throught process whilst I was trying to complete the challenge.
Reconnaissance
First look
After downloading the challenge from HackTheBox’s CTF platform, I decided to open the zip file and take a look at the contents. Within the zip is a single file, ‘source.py’. I went through this file and trimmed out the non-essential parts in order to make the source easier to read. The result is as follows.
|
|
So it appears that the file provided is the source code for the challenge, which will be operating on a container on the CTF platform and which I have to exploit in order to get it to spit out the flag.
Analysis
Looking through the source, it seems that the program is using my input (variable M) to calculate a shared secret. The calculation can be described as shared_secret = M ^ c % p, where ‘c’ is a random number between 2 and p - 1, with the value of ‘p’ being defined at the top of the source as a huge number. This immediately stuck out to me. There must have been a reason I was able to control the value of ‘M’, but I couldn’t quite figure out why.
So I decided to create the following script called ’example.py’.
|
|
This script contained the variables ‘p’ and ‘c’ as seen within the source, but the power calculation was instead done with M being the hardcoded value of 1. The output from running the script is as follows:
I ran it a couple more times just to be sure that the output was consistent and this wasn’t a fluke.
So it seemed that when the variable ‘M’ is set to 1, ‘shared_secret’ would also be 1, but why? Well, if you broke the equation down, it made an awful lot of sense. If you subsituted M = 1 into shared_secret = M ^ c % p, you got shared_secret = 1 ^ c % p. As a rule, 1 times itself any number of times will always be 1, no matter what, so the ^ c can be removed from the equation. So I had 1 % p, in which case % p can also be ignored, as 1 is far less than the value of p, so having % p in this equation will not change the outcome. So effectively, I had ‘shared_secret’ = 1, when ‘M’ = 1.
Exploitation
Understanding the key generation
Brilliant. So I was able to control the shared_secret, but what was it actually used for? I took at look back at the source to find out, with these particular sections of the source becoming of interest to me.
|
|
So it appeared that the ‘shared_secret’ was used to calculate an MD5 value, which was then used as the key to decrypt a message encrypted with AES submitted to the program. Then, if the content of the decrypted message is ‘Initialization Sequence - Code 0’, then the program spits out the flag. So all I needed to do was write a script which, firstly, implemented an encryption function using AES and then used that function to encrypt the string ‘Initialization Sequence - Code 0’. Then I would be able to send this to the program and get the flag.
Implementing the exploit
I ended up creating the following script called ‘genmessage.py’:
|
|
So all I needed to do was test it! After running the script it gave me the following output:
Hmm, I wasn’t entirely sure if it would work with the hex in that format, but I tried regardless. I connected to the container running on the CTF platform and entered in the values.
Unfortunately it didn’t work. I decided to do some research on how to get python to output the hex in a nicer way. Eventually, I discovered that I could use the .hex() function on the bytes object to convert it to a hex representation. Therefore, line 13 instead was:
|
|
Retry
I tried running the script again.
Great, that looks better! Now to exploit the program.
Brilliant, it worked! This was an interesting challenge for sure.
Mitigations
Key generation
Values less than 2 should not be provided to the python ‘pow’ function in order to ensure acceptable entropy and keep the key unpredictable.