ImaginaryCTF Round 9 Writeups

5 min readMay 1, 2021
  1. Rotations [Crypto], 30pts
  2. Camouflage [Forensics], 50pts
  3. Salty [Reversing], 70pts
  4. Just in time 1 [Misc], 125pts
  5. Fake crypto [Web], 150pts




Really not much to explain. We have the ciphertext and considering hints (“rotations” and “caesar”) we can assume that this is kind of ROT cipher. But the ciphertext has numbers and non alphanumeric symbols, so it’s not ROT13. Well, if it’s not ROT13 let’s try ROT47 — another commonly used ROT cipher that contains non alphanumeric symbols.



Attachment is an image Camouflage.png:


Following the standard procedure I used exiftool, binwalk and foremost on this image and I got nothing. So, as long as it is a PNG I opened it with Stegsolve and found the flag:



Attachment is the following Python script:


What we can see here is that we are asked to input some command. After that, our input is passed to function checkInput(). This function does the following:

  1. Adds prefix “salt” to our input.
  2. Encodes new string to bytes.
  3. Calculates SHA-512 of that bytes.
  4. Gets hexadecimal string of the hash.
  5. Compares it to a new string calculated as

6. If our hash and calculated string are equal, we get the message “You win”, followed by another string calculated the same way as previous and message “The password is {inp}”.

First, lets run that strings calculations:

>>> "".join([chr(ord(n) ^ 69) for n in "p\'|$!$\'\'rttvp#\'utvw# q \'$#v#v\'#|qupr&t\'| ursvvu#vr&\'!!|q!p\'!q#uuw}&v v}!q\'s\'trrrv!!q|#& s$p$# r#$\'#&&\'#}p!sqpw |\'t#r} pqwv!p&$u&"])
>>> "".join([chr(ord(n) ^ 69) for n in "-1156\x7fjj5$61 \',+k&*(j3\x10rs$\x0f3\x06"])

The first string is predictably some SHA-512 hash. And the second string is a link to pastebin. Opening the link we see the password input request

Seems like we have to crack that hash to get the password for this paste. Fine, let’s try it on And here we go:

Removing “salt” we get the password: water. Entering that on the pastebin we finally get the flag:

Just in time 1


Attachments are the script and the server address where this script is running.


Analyzing the code we find out that to get the flag we have to guess three randomly generated integers in a row. That would have been practically impossible, but we have two hints:

  1. Random generator is initialized with new seed that depends on the time after we connect to the server.
  2. We are given one randomly generated number before guessing numbers.

Having these we can find exact the same seed for random as server uses. And because of the deterministic nature of python random generator we can calculate the same random sequence as server does.

The algorithm would be:

  1. Receive random number from server and remember the moment of time we have received it.
  2. As we know that server has recently initialized its random with the time value, we can use our time value to initialize our random.
  3. Of course our time value is not exactly the same as server had. But as long as it is not far away and it is rounded to two decimal numbers we can now decrease our time value and reinitialize our random until first generated random won’t be equal to the one we received from server.
  4. After we found such value there is an extremely high probability that our random will generate the same random sequence as server does.
  5. Now just send next three generated numbers to server to get the flag.

The final script would look something like this:

And running the script we get the flag:

[+] Opening connection to on port 7331: Done
If you guess my numbers, I'll give you the flag.

I'll give you a hint: 807930125

What is number 1?
What is number 2?
What is number 3?
Well done!


Fake crypto


The attachment link leads us here


Here we have a server that expect us to give him two parameters: a and b. And when we do it check if that parameters are not equal. If they are not it then checks if MD5(“savory salt” + a) equals to MD5(“savory salt” + b).

So it expects us to give it two distinct strings with the equal hashes. Looking for an MD5 collision seems a bit hard. But what if we won’t send it strings?

Well here is what is wrong with PHP: it is stupid. Look at the following code.

What it should output? Well, as I am a Java/Kotlin developer, I would assume that it will throw some kind of error or will join arrays elements into a string or will output some kind of object identifiers. But what actually happens is miraculous. PHP outputs the following:

What?! Is that right? Yeah, it is. When you try using array where the string should be used, PHP just implicitly converts array into string “Array”. That means even two or three or any amount of distinct arrays are converted into the same string “Array”.

Now look back at the server source code and notice that it concatenates our input with “savory salt” string. That’s where a string is expected, and if we give it an array it will just convert it to “Array”.

So all we have to do to get the flag is pass server two distinct arrays.