Last active
January 4, 2016 03:22
-
-
Save grnd/c1c4cd03cb06212fa8e6 to your computer and use it in GitHub Desktop.
Revisions
-
grnd revised this gist
Jan 4, 2016 . 1 changed file with 44 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,44 @@ from Crypto.Cipher import AES import string import itertools import time import hashlib def bruteforce(charset, minlength, maxlength): return (''.join(candidate) for candidate in itertools.chain.from_iterable(itertools.product(charset, repeat=i) for i in range(minlength, maxlength + 1))) data = open('config.bin', 'r').read() header = data[:48] expected_md5 = header[32:48].encode('hex') first_block = data[48:64] all_data = data[48:] charset = string.ascii_lowercase + string.ascii_uppercase + string.digits progress = 0 progress_interval = 1000000 total = len(charset)**5 start = time.time() for key in bruteforce(charset, 5, 5): progress = progress + 1 plain = AES.new(key + '\x00'*27, AES.MODE_ECB, "").decrypt(first_block) # identify gzip 1f8b08 if (plain[0:3] == '\x1f\x8b\x08'): plain = AES.new(key + '\x00'*27, AES.MODE_ECB, "").decrypt(all_data) if (hashlib.md5(plain).hexdigest() == expected_md5): print 'Found key: ' + key open('plain.tar.gz', 'wb').write(plain) break if (progress % progress_interval == 0): took = time.time() - start start = time.time() pps = progress_interval/took mins_left = (total-progress)/pps/60 print '%s: %d pps; %d minutes left' % (key, pps, mins_left) -
grnd created this gist
Jan 4, 2016 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,84 @@ # 32C3 CTF 2015 : config.bin **Category:** Forensics **Points:** 150 **Solves:** 27 **Description:** > We have obtained what we believe is a configuration backup of an embedded device. However, it seems to be encrypted. Maybe you can help us with decryption? ## Write-up It's pretty clear that the file has a short header, and the rest of the data is either encrypted or compressed. Searching for the magic `CFG1` brings up few interesting results. It seems like this is a configuration file for Sphairon routers. There are few decryptors available (here's [one](https://github.com/5sw/Decrypt)), but they all fail to decrypt our file. The md5 hash does not match, meaning that the key used (dummy1) is wrong. From the source code we learn that the header is: ``` struct header { char magic[4]; // magic bytes \x43 \x46 \x47 \x31 (CFG1) uint32_t payload_size; // length of ciphertext = length of padded plaintext (big endian) uint8_t header_md5[8]; // first 8 bytes of MD5 computed over header (assuming the 8 bytes of "header_md5" are \x00) char etl[7]; // blank electronic label (etl), always "000000" (null-terminated char array) uint8_t unused1; // not used at the moment uint16_t password_len; // length of the password used in AES encryption (big endian) uint16_t padding_len; // number of padding bytes added to plaintext (big endian) uint8_t unused2[4]; // not used at the moment uint8_t plaintext_md5[16]; // MD5 hash of the plaintext }; ``` It's time for bruteforcing the key. Enumerating over five character long alphanumeric key is slightly less than 30 bits. Python can totally be enough here. The file is encrypted with AES, in ECB mode, so the naive approach would be to do something like: ``` all = open('config.bin', 'r').read() data = all[48:] expected_md5 = all[32:48].encode('hex') charset = string.ascii_lowercase + string.ascii_uppercase + string.digits for key in bruteforce(charset, 5, 5): plain = AES.new(key + '\x00' * 27 , AES.MODE_ECB, "").decrypt(data) h = hashlib.md5(plain).hexdigest() if (h == expected_md5): print 'Found key: ' + key open('plain.tar.gz', 'wb').write(plain) break ``` The above code gives us 8500 passwords per second (30hours) on my laptop. But we can improve that dramatically, by decrypting the first block only, and checking if the first 3 bytes correspond to gzip header `1f8b08`. If they do, only then we decrypt the whole data and calculate the md5. This little optimization gets us to 220000pps and takes about an hour to complete on my old macbook air. ``` all = open('config.bin', 'r').read() data = all[48:] first_block = data[0:16] expected_md5 = all[32:48].encode('hex') charset = string.ascii_lowercase + string.ascii_uppercase + string.digits for key in bruteforce(charset, 5, 5): plain = AES.new(key + '\x00' * 27, AES.MODE_ECB, "").decrypt(first_block) # identify gzip 1f8b08 if (plain[0:3] == '\x1f\x8b\x08'): plain = AES.new(key, AES.MODE_ECB, "").decrypt(data) h = hashlib.md5(plain).hexdigest() if (h == expected_md5): print 'Found pwd: ' + pwd open('plain.tar.gz', 'wb').write(plain) break ``` After about 10 minutes we find the password: `oVX09` There are two files in the archive. Inside `rc.conf`, line 344 we find a base64 encoded string. Flag is inside. ``` $ echo MzJDM19jNDQ2ZWRlMjMzY2RmY2IxNzdmNGQwZTU2NzQ0NjU0Mjg5YzhkZWE0YzRlZTY1MTI2NGU4\nNWU5YWU2MmFiZjc3 | base64 -D 32C3_c446ede233cdfcb177f4d0e56744654289c8dea4c4ee651264e8 ```