Skip to content

Instantly share code, notes, and snippets.

@grnd
Last active January 4, 2016 03:22
Show Gist options
  • Select an option

  • Save grnd/c1c4cd03cb06212fa8e6 to your computer and use it in GitHub Desktop.

Select an option

Save grnd/c1c4cd03cb06212fa8e6 to your computer and use it in GitHub Desktop.

Revisions

  1. grnd revised this gist Jan 4, 2016. 1 changed file with 44 additions and 0 deletions.
    44 changes: 44 additions & 0 deletions brute.py
    Original 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)

  2. grnd created this gist Jan 4, 2016.
    84 changes: 84 additions & 0 deletions 32C3-CTF-2015-config-bin.md
    Original 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
    ```