Here's a walk-through of the Norwegian Police Security Service's latest job ad riddle. Were you able to crack the code?
Published: | Mon, October 14, 2019, 06:25 |
Category: |
Security
|
Tags: |
Behind the news
Challenge
|
The Norwegian Police Security Service (with the Norwegian abbreviation PST which I'll use for this write-up) is the police security agency of Norway. Once in a while they have job ads with some more or less hidden challenges - almost Capture the Flag style.
In December 2018 they posted a job listing were they included a riddle that they wanted people to solve. I didn't hear of the job posting until it suddenly in January was on the front page of most Norwegian newspapers. I published my version of the solution just after the application deadline. The solution was also translated to Norwegian and published at kode24.no - a site for Norwegian developers.
PST themselves approved the walk-through:
The solution to PSTs shark riddle advert perfectly explained by @roysolberg https://t.co/Fsejj3NWgi @LeoDiCaprio
โ PST (@PSTnorge) January 15, 2019#hungryshark
pic.twitter.com/1qNImmhQms
Now, I didn't expect to do another one of these walk-throughs this soon, but all of the sudden the interesting Twitter handleThings I did not expect: Seeing my own JavaScript code live on the TV channel @NRKno a Saturday night. Achievement unlocked. ๐ CC: @kode24no @PSTnorge pic.twitter.com/FIN0afWgYW
— Roy Solberg (@roysolberg) February 2, 2019
twitt3rhai
(=Twitter shark) tweeted what might appear as random characters. Twitt3rhai
was part of the challenge in December. Of course I had to try to figure what the tweet was all about..
Luckily this turned out to be less of a challenge than the previous one.
It all starts with PST's job ad where they want both digital forensics specialists and a system developer. The text in the job advertisement doesn't hint about any challenges as I could see, but there is an interesting header image:
It's got all the nerdy details you'd want: A pcap (packet capture) hat, lotsa computer screens, some file dump or something, some code, and more.
While the image doesn't have the world's highest resolution you can see that the person in the image (possibly nicknamed HackerMan) has got the following Python 3 code file named encrypt\.py
open:
#!/usr/bin/python3 from base64 import b64encode from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad def get_primes(count): primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61] return(primes[0:count]) with open('plain.txt', 'rb') as f: plaintext = f.read() iv = "" for i in get_primes(16): iv += chr(plaintext[i + 16]) key = b'\xba\xda\x55 HackerMan \x13\x37' # <----- DESTROY AFTER USE cipher = AES.new(key, AES.MODE_CBC, IV=iv) ciphertext_b64 = b64encode(cipher.encrypt(pad(plaintext, AES.block_size))).decode('utf-8') print(ciphertext_b64) #TODO: automate posting of ciphertext to twitter.com/twitt3rhai
Now, this is some interesting code. Let's follow the flow of it:
plain.txt
(aka unencrypted information) into a variable plaintext
.iv
by using 16 characters from plaintext
offset by 16 + a prime number.key
(nerd bonus for using the hex numbers BA, DA, 55, 13, 37) which is indicated that should be deleted later on.key
variable as key and the iv
variable as initialization vector.twitt3rhai
. (And what do you know, the same Twitter handle is also in that header image.)Our ultimate goal seems to be to get the plaintext to see what it says. So lets start by heading over to Twitter:
This does indeed seem like it could be some Base64 encoded text and can be assumed to be the ciphertext. Now we've got quite a few pieces of the puzzle./lb0WZDpaIDJVJwy+Q04LCqERqVj7AUItWGREJuXJeWtZN77yP6grehn1gRif31hjTEjLNFyxESweea81/QluWUyhZV9vmabm8NYkkSc6JJWuylGJKQJzA/wC2cM2ScrQQ8gV7GcnVyBCh7eq/N0jUm/L4xrX6IUIDi5CAkVZ9xSS5Tb4o01onOTbGWLd1EZwzZOMlq88wsTPZ6zY7dqj+LKq3Pj6SKlZfaR9eo6PXrRUOARCe9sQVtWVKc5DJfI
— twitt3rhai (@twitt3rhai) September 20, 2019
So, how can we decrypt the ciphertext in the tweet? Let's just tweak the encrypt.py
to make our own decrypt.py
:
#!/usr/bin/python3 from base64 import b64encode, b64decode from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad def get_primes(count): primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61] return(primes[0:count]) # TODO: automate reading of ciphertext from twitter.com/twitt3rhai ;) ciphertext_b64 = '/lb0WZDpaIDJVJwy+Q04LCqERqVj7AUItWGREJuXJeWtZN77yP6grehn1gRif31hjTEjLNFyxESweea81/QluWUyhZV9vmabm8NYkkSc6JJWuylGJKQJzA/wC2cM2ScrQQ8gV7GcnVyBCh7eq/N0jUm/L4xrX6IUIDi5CAkVZ9xSS5Tb4o01onOTbGWLd1EZwzZOMlq88wsTPZ6zY7dqj+LKq3Pj6SKlZfaR9eo6PXrRUOARCe9sQVtWVKc5DJfI' ciphertext = b64decode(ciphertext_b64.encode('utf-8')) iv = " " * 16 # Must be 16 bytes key = b'\xba\xda\x55 HackerMan \x13\x37' cipher = AES.new(key, AES.MODE_CBC, IV=bytes(iv, 'utf-8')) plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size) print(plaintext) # Output: b'\x15\x01Tx)5,d%,*<&:u%erer! Du klarte det! Beklager, men denne gangen har vi ikke laget flere oppgaver. H\xc3\xa5per du vil s\xc3\xb8ke jobben. Hvis du blir ansatt kan vi love deg mange utfordrende oppgaver.'
Here's what the script does:
The output is the following:
b'\x15\x01Tx)5,d%,*<&:u%erer! Du klarte det! Beklager, men denne gangen har vi ikke laget flere oppgaver. H\xc3\xa5per du vil s\xc3\xb8ke jobben. Hvis du blir ansatt kan vi love deg mange utfordrende oppgaver.'
When I did the challenge I was at first happy with getting most of the plaintext. And I was thinking that the first part missing probably was the start of the word "Gratulerer" (=congratulations).
But what is an "initialization vector" and why is it used here? AES (Advanced Encryption Standard) is a block cipher, meaning that the algorithm is operating on a fixed-length groups of bits (blocks). To avoid equal plaintext blocks to become equal ciphertext blocks many of the "modes of operation" of the encryption algrithms use some part of the previous block part of the input to the following one. The mode used here is Cipher Block Chaining (CBC). In this mode each block of plaintext is XORed with the previous ciphertext block before being encrypted. To produce distinct ciphertexts even if the same plaintext is encrypted multiple times - and to protect the first block - there must be some be some unique input to the first block; an initialization vector.
Looking at the output there are actually 16 bytes that are "garbage" (the AES' block size). That means that the first word can't just be "Gratulerer".
But how can we get hold of that initialization vector? The answer lies in the code. It is generated from the plaintext - and luckily for us it only uses plaintext above character number 16, meaning that we have everyhting we need. So let's tweak the script:
#!/usr/bin/python3 from base64 import b64encode, b64decode from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad def get_primes(count): primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61] return(primes[0:count]) # TODO: automate reading of ciphertext from twitter.com/twitt3rhai ;) ciphertext_b64 = '/lb0WZDpaIDJVJwy+Q04LCqERqVj7AUItWGREJuXJeWtZN77yP6grehn1gRif31hjTEjLNFyxESweea81/QluWUyhZV9vmabm8NYkkSc6JJWuylGJKQJzA/wC2cM2ScrQQ8gV7GcnVyBCh7eq/N0jUm/L4xrX6IUIDi5CAkVZ9xSS5Tb4o01onOTbGWLd1EZwzZOMlq88wsTPZ6zY7dqj+LKq3Pj6SKlZfaR9eo6PXrRUOARCe9sQVtWVKc5DJfI' ciphertext = b64decode(ciphertext_b64.encode('utf-8')) iv = " " * 16 # Must be 16 bytes key = b'\xba\xda\x55 HackerMan \x13\x37' cipher = AES.new(key, AES.MODE_CBC, IV=bytes(iv, 'utf-8')) plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size) iv = "" for i in get_primes(16): iv += chr(plaintext[i + 16]) cipher = AES.new(key, AES.MODE_CBC, IV=bytes(iv, 'utf-8')) plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size).decode('utf-8') print(iv) # Output: er uate!k,mngn i print(plaintext) # Output: PST-haien gratulerer! Du klarte det! Beklager, men denne gangen har vi ikke laget flere oppgaver. Hรฅper du vil sรธke jobben. Hvis du blir ansatt kan vi love deg mange utfordrende oppgaver.
The script now does the decryption in two rounds; first without knowing the init vector, and then again with it correctly initialized.
Success!
The solution to the challenge is this:
PST-haien gratulerer! Du klarte det! Beklager, men denne gangen har vi ikke laget flere oppgaver. Hรฅper du vil sรธke jobben. Hvis du blir ansatt kan vi love deg mange utfordrende oppgaver.
The PST shark congratulates you! You made it! Sorry, but this time we haven't created any more challenges. Hope you will apply for the job. If you get hired we can promise you many challenging assignments.