<< back
11 September 2022
Gotta Crack Them All
Challenge Source: CSAW2022Challenge Category: Crypto
Challenge Text:
As an intern in the security department, you want to show the admin what a major security issue there is by having all passwords being from a wordlist (even if it is one the admin created) as well as potential issues with stream ciphers.
Here's the list of encrypted passwords (including the admin's), the encryption algorithm and your password. Can you crack them all and get the admin's password?
Here is the web service that the admin made to encrypt a password: nc crypto.chal.csaw.io 5002
NOTE: The flag is just the admin's password.
Solution:
- We’re provided with three files to inspect. Inside of
encrypt.py
, we see thatencrypt(plain)
takes a plaintext string and XORs it with a key:
with open('key.txt','rb') as f:
key = f.read()
def encrypt(plain):
return b''.join((ord(x) ^ y).to_bytes(1,'big') for (x,y) in zip(plain,key))
- Inside of
leaked_password.txt
we have:Cacturne-Grass-Dark
- Inside of
encrypted_passwords.txt
we find what looks like individually encrypted passwords separated by new lines:
┌──(shinris3n㉿kodachi)-[~/sec/CTFs/CSAW2022]
└─$ python3
Python 3.9.13 (main, Jun 8 2022, 09:45:57)
[GCC 11.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> with open('encrypted_passwords.txt','rb') as f:
... passwords = f.read()
...
>>> print(passwords)
b'cr\xcb\xaa\xc0Si\x83\xf0\xb8\xe6\x99\xbf\nlz\xd7\xa6\xdeWr\x83\xe3\xb8\xe0\x97\nks\xcc\xa3\xcbZr\xc0\xc0\xf4\xc2\x8f\xb4\x7f\xab.\xcd\n`z\xd5\xbd\xc5Xb\x83\xe9\xb6\xe0\x91\xacp\nkw\xc4\xba\xc5Ba\xcb\xd5\xf4\xc5\x9d\xb9y\xb1\nkz\xc6\xb9\xd9Du\xcb\x8a\x9e\xe0\x9d\xbeo\xee\x03\xcf\xddd\n{w\xca\xba\xc7_u\xc9\x8a\x89\xfd\x95\xbes\xadj\xfe\xdcv\xf6\xe4\xd2\xaa\n{u\xc0\xac\xdfSw\x83\xe3\xb8\xe0\x97\xe0U\xa0"\n`t\xca\xbd\xcd\x1bK\xdd\xde\xba\xfa\x95\xae1\x84/\xc1\xdc{\nzs\xdc\xbd\xc9Dr\xc1\xd5\xf4\xd5\x8e\xa2i\xad#\x83\xfd`\xf6\xe7\n{~\xc0\xa9\xc3B6\xe9\xd5\xb8\xe1\x8f\nks\xcc\xa3\xcf^t\xdb\x8a\x8e\xf3\x88\xa8n\xee\x02\xc2\xcal\xe1\xfe\xd2\xaa\n|h\xc4\xbf\xc9Su\xcf\x8a\x9e\xe0\x9d\xbeo\nmc\xc6\xac\xc8Dr\xc2\xcb\xf4\xd5\x8e\xa2i\xad#\x83\xfc{\xf0\xe9\xd7\non\xc8\xbe\xc4Yt\xdd\x8a\x97\xfd\x8e\xa0}\xaf\nci\xcc\xae\xc7So\xdb\xc9\xbc\xbf\xbe\xb8{\nlz\xd7\xb9\xde_c\x83\xe0\xab\xf3\x8f\xbe1\x85+\xd7\xc6a\xf2\nxr\xce\xa4\xdcSp\x83\xe9\xb6\xe0\x91\xacp\xee\x01\xc2\xd6f\xfb\xeb\nln\xc2\xb9\xde_t\x83\xe0\xab\xfd\x89\xa3x\xee\x14\xda\xcaj\xf9\njz\xd6\xae\xd9Zr\xc0\x8a\x8e\xf3\x88\xa8n\n`r\xd5\xbd\xc3A\x7f\xc1\xc9\xf4\xd5\x8e\xa2i\xad#\n|t\xc2\xa8\xd8_x\x83\xe1\xb8\xfb\x8e\xb41\x85+\xd7\xc6a\xf2\nnr\xcb\xa3\xc9Yu\x83\xf0\xb8\xe6\x99\xbf\nzr\xca\xa1\xd9\x1b]\xc7\xc0\xb1\xe6\x95\xa3{\nmu\xd1\xa8\xc5\x1b]\xc7\xd5\xbc\n{k\xd7\xa4\xd8L~\xcb\x8a\x9f\xf3\x95\xbfe\nez\xcb\xb9\xc5X~\x83\xf0\xb8\xe6\x99\xbf1\x85+\xd7\xc6a\xf2\n{r\xc9\xbb\xcdZw\xd7\x8a\x97\xfd\x8e\xa0}\xaf\nj~\xc9\xa1\xdfFi\xc1\xd2\xad\xbf\xbb\xbf}\xb04\x83\xff`\xfc\xff\xd4\xa7\n\x7fb\xd7\xa9\xc9Si\x83\xe9\xb6\xe0\x91\xacp\xee\x17\xdd\xd6l\xfd\xe5\xd8\nez\xd7\xa4\xc0Z6\xf9\xc6\xad\xf7\x8e\xe0Z\xa2.\xdc\xd6\n`~\xd7\xa9\xc5Si\x83\xe9\xb6\xe0\x91\xacp\niw\xd1\xac\xde_z\x83\xe3\xab\xf3\x9b\xa2r\xee\x01\xc2\xd6f\xfb\xeb\n|s\xd2\xac\xcf]~\xd7\x8a\x9e\xe0\x9d\xbeo\n{k\xc0\xba\xdcW6\xec\xd2\xbe\nji\xca\xa3\xd6Yu\xc9\x8a\x8a\xe6\x99\xa8p\xee\x17\xdd\xd6l\xfd\xe5\xd8\n`z\xce\xac\xc1Y6\xc1\x8a\x9d\xe0\x9d\xaas\xadj\xe8\xc6h\xfd\xf8\xd2\xa7\x9c\nks\xc0\xbe\xdc_u\x83\xe0\xab\xf3\x8f\xbe\nei\x8b\xed\xe1_v\xcb\x8a\x89\xe1\x85\xaet\xaa$\x83\xe9n\xfc\xfe\xc2\n|t\xd7\xa3\xcdRn\xdd\x8a\x9f\xfe\x85\xa4r\xa4\nxn\xd5\xa4\xd8Wi\x83\xf5\xb6\xf1\x97\xe0[\xb1(\xdb\xc1k\nkt\xc8\xaf\xd9Ep\xcb\xc9\xf4\xd4\x95\xbfy\xee\x01\xc7\xc8g\xe1\xe5\xd5\xae\non\xdf\xb7\xc0Yi\xca\x8a\x9d\xf3\x8e\xa61\x875\xcf\xc8`\xfb\nkz\xd7\xa3\xc5@r\xc0\xc2\xf4\xd5\x8e\xaco\xb0\noi\xca\xba\xc0_o\xc6\xc2\xf4\xd4\x95\xbfy\noi\xd0\xaf\xce_u\x83\xe5\xac\xf5\noz\xd6\xb9\xdeY\x7f\xc1\xc9\xf4\xc5\x9d\xb9y\xb1j\xe9\xdd`\xe0\xe2\xdf\not\xca\xa0\xd5\x1b_\xdc\xc6\xbe\xfd\x92\n|s\xcc\xa8\xdaCw\x83\xe3\xb8\xe0\x97\n\x19u\x90\xfe\xcfC)\x9d\x92\xee\xa0\xcf\xf9q\xa0v\xde\xc7<\xa7\n{~\xc4\xa9\xdeW6\xf9\xc6\xad\xf7\x8e\n'
- Interacting with the server, we see that we may only pass across certain plaintext strings for encryption. The leaked password works as one of the allowed strings:
┌──(shinris3n㉿kodachi)-[~]
└─$ nc crypto.chal.csaw.io 5002
You can encrypt a pre-approved password using this service.
What is the password you would like to encrypt?
>> 0
This is not a pre-approved password! >:(
It will not be encrypted.
┌──(shinris3n㉿kodachi)-[~]
└─$ nc crypto.chal.csaw.io 5002
You can encrypt a pre-approved password using this service.
What is the password you would like to encrypt?
>> Cacturne-Grass-Dark
The encrypted password is: b'kz\xc6\xb9\xd9Du\xcb\x8a\x9e\xe0\x9d\xbeo\xee\x03\xcf\xddd'
Would you like to go again? (Y/N)
- With this information, we can try to find the key by verifying that one of the values in our encrypted passwords list matches what we received from the server (i.e. the same key was used to encrypt the passwords in this file). We then XOR the encrypted value with the leaked plaintext password, recovering the key. We can XOR this result with the entire list of encrypted passwords and inspect what we receive back to check if the key works for the other passwords as well:
known_encrypted_pws = []
leaked_pw = "Cacturne-Grass-Dark"
encrypted_leaked_pw_from_service = b'kz\xc6\xb9\xd9Du\xcb\x8a\x9e\xe0\x9d\xbeo\xee\x03\xcf\xddd'
with open('encrypted_passwords.txt','rb') as f:
for line in f:
known_encrypted_pws.append(f.readline()[:-1])
def decrypt(plain, key):
return b''.join((ord(x) ^ y).to_bytes(1,'big') for (x,y) in zip(plain,key))
def decrypt_bytes(encrypted, key):
return bytes([a ^ b for a, b in zip(encrypted, key)])
for count, encrypted_pw in enumerate(known_encrypted_pws):
if encrypted_leaked_pw_from_service == encrypted_pw:
print("Encrypted Password", count, "Matches Result Provided via the Web Service:", encrypted_pw)
key = decrypt("Cacturne-Grass-Dark", encrypted_pw)
print("Key =", key)
print ('Decrypted Password List:')
for encrypted_pw in known_encrypted_pws:
try:
print(decrypt_bytes(encrypted_pw, key))
except:
print('@#$%^!!!')
- When we run this script, we see that the key works for all passwords, but it turns out to be incomplete:
┌──(shinris3n㉿kodachi)-[~/sec/CTFs/CSAW2022]
└─$ python3 decrypt.py
Encrypted Password 2 Matches Result Provided via the Web Service: b'kz\xc6\xb9\xd9Du\xcb\x8a\x9e\xe0\x9d\xbeo\xee\x03\xcf\xddd'
Key = b'(\x1b\xa5\xcd\xac6\x1b\xae\xa7\xd9\x92\xfc\xcd\x1c\xc3G\xae\xaf\x0f'
Decrypted Password List:
b'Darkrai-Dark'
b'Happiny-Normal'
b'Cacturne-Grass-Dark'
b'Sneasel-Dark-Ice'
b'Rhyperior-Ground-Ro'
b'Chinchou-Water-Elec'
b'Excadrill-Ground-St'
b'Kricketune-Bug'
b'Pikipek-Normal-Flyi'
b'Basculin-Water'
b'Togetic-Fairy-Flyin'
b'Riolu-Fighting'
b'Spritzee-Fairy'
b'Silvally-Normal'
b'Wyrdeer-Normal-Psyc'
b'Herdier-Normal'
b'Thwackey-Grass'
b'Bronzong-Steel-Psyc'
b'Chespin-Grass'
b'Tornadus-Flying'
b'Combusken-Fire-Figh'
b'Carnivine-Grass'
b'Grubbin-Bug'
b'Goomy-Dragon'
b'1n53cu2357234mc1ph3'
b''
- To recover more of the key, we need to interact with the server again and use a longer plaintext string by completing one of the longer decrypted passwords:
┌──(shinris3n㉿kodachi)-[~]
└─$ nc crypto.chal.csaw.io 5002
You can encrypt a pre-approved password using this service.
What is the password you would like to encrypt?
>> Chinchou-Water-Electric
The encrypted password is: b'ks\xcc\xa3\xcf^t\xdb\x8a\x8e\xf3\x88\xa8n\xee\x02\xc2\xcal\xe1\xfe\xd2\xaa'
Would you like to go again? (Y/N)
- Now we can append our original script:
likely_full_password = "Chinchou-Water-Electric"
encrypted_cracked_pw_from_service = b'ks\xcc\xa3\xcf^t\xdb\x8a\x8e\xf3\x88\xa8n\xee\x02\xc2\xcal\xe1\xfe\xd2\xaa'
extended_key = decrypt(likely_full_password, encrypted_cracked_pw_from_service)
print("Extended Key =", extended_key)
print ('Decrypted Password List:')
for encrypted_pw in known_encrypted_pws:
try:
print(decrypt_bytes(encrypted_pw, extended_key))
except:
print('@#$%^!!!')
- The output from this additional code reveals the rest of the passwords in full along with the full admin password, which is the flag:
Extended Key = b'(\x1b\xa5\xcd\xac6\x1b\xae\xa7\xd9\x92\xfc\xcd\x1c\xc3G\xae\xaf\x0f\x95\x8c\xbb\xc9'
Decrypted Password List:
b'Darkrai-Dark'
b'Happiny-Normal'
b'Cacturne-Grass-Dark'
b'Sneasel-Dark-Ice'
b'Rhyperior-Ground-Rock'
b'Chinchou-Water-Electric'
b'Excadrill-Ground-Steel'
b'Kricketune-Bug'
b'Pikipek-Normal-Flying'
b'Basculin-Water'
b'Togetic-Fairy-Flying'
b'Riolu-Fighting'
b'Spritzee-Fairy'
b'Silvally-Normal'
b'Wyrdeer-Normal-Psychic'
b'Herdier-Normal'
b'Thwackey-Grass'
b'Bronzong-Steel-Psychic'
b'Chespin-Grass'
b'Tornadus-Flying'
b'Combusken-Fire-Fighting'
b'Carnivine-Grass'
b'Grubbin-Bug'
b'Goomy-Dragon'
b'1n53cu2357234mc1ph32'
b''