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?
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), 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