There was an OSINT + custom cryptography challenge during the BSidesRDU CTF this year, but no one (else) was able to solve it from start to finish.
Custom Cryptography – Introduction
First, Steve’s challenge mentioned some chatter on Twitter.
While most of this challenge was Open-source intelligence (OSINT) based, I’m most proud of my crypto solution.
First, I checked out the EverSec Twitter account.
There was nothing there, so I decided to search Twitter for EverSec in general.
This brought me to the r3tsuk0_ timeline, and the following tweets.
While custom cryptography sounded interesting, there was still another flag or two on the OSINT side.
To Understand the Future, We Have to Go Back in Time
After some prodding and asking for clues, the r3tsuk0_ account made another tweet.
While Google was no use, I did have some luck with the Wayback Machine.
When I searched for the archived pages under that URL, there was one result.
Viewing the deleted tweet gave me another flag, and a pretty cool challenge!
GitHub Profile, and More Flags!
With the Twitter part of the OSINT challenge completed, I moved on to the cryptography part.
First, I used some professional Google skills (removing the underscore from the username) to find r3tsuk0’s GitHub profile.
The only repository on the account was encrypt, so this was likely the custom cryptography challenge.
Note that there was a reverted commit that had one more flag in it.
output = "" for letter in x: o = int(ord(letter)) o += 2 output = output+str(o)+"23" output = output[::-1] #d3l3t3_aft3r_us3
Breaking the Cipher
First, I grabbed the encryption routine, and took a look at it.
output = "" for letter in x: o = int(ord(letter)) o += 2 output = output+str(o)+"23" output = output[::-1]
While this is fairly straightforward, I will break down the encryption step by step.
- Grab the next character from the input string
- Convert the character to an ordinal, and then an integer. For example: ord(‘a’) = 97, and then o gets cast to an integer.
- Add 2 to the converted value
- Append the string variant of the new value, concatenated with the string “23”, to the output string
- Reverse the entire output string
- Loop back to step #1 until the string is finished
Taking a look back through the Twitter timeline, I guessed that the “32053241132611327932911327932011326115023110231172311123101231232311823” value would be the input string.
In this case, the output string calculation would look like this through the first loop:
- output = “” – no characters added yet
- o = 51 – int(ord(“3”))
- o = 53 – o += 2
- output = “” + “53” + 23″
- output = “5323” – concatenation
- output = “3235” – reverse
With this in mind, I knew that the difficulty would be when the string reverses. While most characters would cause the cipher to store a 2-digit number in ‘o’, some could cause a 3-digit number.
>>> int(ord("z")) 122
This may not have mattered for this specific input, but I wanted to solve for any if it came up again.
In this case, I designed a fairly elegant solution, that didn’t take much actual cryptanalysis.
import string input = "32053241132611327932911327932011326115023110231172311123101231232311823" output = "" curString = input while (len(curString) > 0): curString = curString[::-1] charCheck1 = curString[-4:-2] charCheck2 = curString[-5:-2] charCheck1 = int(charCheck1) - 2 charCheck2 = int(charCheck2) - 2 if (chr(charCheck2) in string.printable): curString = curString[:-5] output = chr(charCheck2) + output elif (chr(charCheck1) in string.printable): curString = curString[:-4] output = chr(charCheck1) + output else: print("ERROR") print(output)
In the end, this reverses the entire encryption algorithm from before, with a quick brute-force step to account for the warning above.
If you are not familiar with Python, then I will break down the steps for you.
- Take the input string, and store it in a temporary variable
- Reverse the entire string
- Grab the last 2 characters of the string, ignoring the “23”, and store them in charCheck1
- Grab the last 3 characters of the string, ignoring the “23”, and store them in charCheck2
- Subtract 2 from charCheck1
- Subtract 2 from charCheck2
- If charCheck2 (the 3-digit string) is printable, then append it to the output string
- If not, and charCheck1 is printable, then append it to the output string
- If neither is printable, then there was likely a problem with the input
- Loop until there is nothing left in curString
As far as the brute-force step, this is really where my solution is elegant. In the case where the input was only a 2-digit ascii character, then grabbing three characters ends up with a 2xx or a 3xx, due to the cipher concatenating with “23”. There are no printable characters in the 200-399 range, so we can safely assume that a 2-digit solution is safe here.
When I ran my finished script, I received the last flag!
PS C:\Users\Ray\Documents> py .\steve.py r0ll_sum_crypt0
Custom Cryptography – Conclusion
While most of this challenge was the OSINT, I really liked the crypto solution.
This was a bit heavier on the programming, rather than crypto, side in the end, which is likely why no one solved it completely.
That said, I was glad that Steve pointed this one out, and I had some fun with it.
Ray Doyle is an avid pentester/security enthusiast/beer connoisseur who has worked in IT for almost 16 years now. From building machines and the software on them, to breaking into them and tearing it all down; he’s done it all. To show for it, he has obtained an OSCE, OSCP, eCPPT, GXPN, eWPT, eWPTX, SLAE, eMAPT, Security+, ICAgile CP, ITIL v3 Foundation, and even a sabermetrics certification!
He currently serves as a Senior Staff Adversarial Engineer for Avalara, and his previous position was a Principal Penetration Testing Consultant for Secureworks.
This page contains links to products that I may receive compensation from at no additional cost to you. View my Affiliate Disclosure page here. As an Amazon Associate, I earn from qualifying purchases.