{"id":12998,"date":"2016-06-23T19:24:42","date_gmt":"2016-06-23T17:24:42","guid":{"rendered":"http:\/\/www.codilime.com\/?p=12998"},"modified":"2016-11-03T17:27:30","modified_gmt":"2016-11-03T16:27:30","slug":"backdoorctf16-crc","status":"publish","type":"post","link":"https:\/\/codisec.com\/backdoorctf16-crc\/","title":{"rendered":"CRC"},"content":{"rendered":"

Link: https:\/\/backdoor.sdslabs.co\/challenges\/CRC<\/a>
\nAuthor: IamLupo
\nPoints: 250
\nCategory: crypto<\/p>\n

Description<\/h2>\n

Backdoor user IamLupo wanted to submit his challenge for BackdoorCTF16 but he was struck by lightening and his challenge file shattered into 26 pieces. We want you to recover it for us (and maybe get the flag while you do it). Thanks in advance! Here is the zipped file:
\n
http:\/\/hack.bckdr.in\/CRC\/challenge.zip<\/a><\/p><\/blockquote>\n

tl;dr<\/h2>\n

27 encrypted zip archives, each contains 5 character txt file. All files together make a php program. CRC32 checksum can be retrieved from zip archives. Brute force is reasonable, but takes a long time (too long). Use knowledge of php and already decrypted text to guess some characters in encrypted archives to speed up brute force attack.<\/p>\n

Solution<\/h2>\n

In this task we get a zip file (challenge.zip) and we need to somehow get a flag out of it. The archive extracts to 27 separate .zip files, named 0.zip to 26.zip. Obviously we need to get the content of those archives, but they’re password protected…<\/p>\n

The name of the challenge suggests us what to do next. A zip archive contains a CRC32 checksum for each archived file. Surprisingly most popular zip tools leave this checksum unencrypted for password protected archive. In fact all the metadata is easily available. We can just use python zipfile module to write simple script and list it all:<\/p>\n

for x in xrange(27):                                                            \n   with zipfile.ZipFile('{}.zip'.format(x), 'r') as zf:                        \n       print \"{} ({} files)\".format(x, len(zf.infolist()))                        \n       for info in zf.infolist():                                              \n           print '    name={}'.format(info.filename)                          \n           print '    comment={}'.format(info.comment)                                          \n           print '    compressed_size={}'.format(info.compress_size)          \n           print '    file_size={}'.format(info.file_size)                    \n           print '    crc={:x}'.format(info.CRC)<\/pre>\n
$ python list_crc.py\n0 (1 files)\n    name=0.txt\n    comment=\n    compressed_size=17\n    file_size=5\n    crc=a36bb2ae\n...\n26 (1 files)\n    name=26.txt\n    comment=\n    compressed_size=14\n    file_size=2\n    crc=d8662568\n<\/pre>\n

Huh, so each archive contains a single text file with 5 characters only? Except the last one is only 2 characters long? That one should be easy to brute-force, right? Python binascii module provides crc32() method that we can use for that. We just loop over 2-character printable strings (it’s txt after all) and look for the matching one:<\/p>\n

import binascii\nimport itertools\nimport string\n\nfor chars in itertools.product(string.printable, repeat=2):\n    text = ''.join(chars)\n    if (binascii.crc32(text) & 0xffffffff) == int('d8662568', 16):\n        print text\n        break<\/pre>\n

Ok, that took 0.016 second to run and returned ?><\/code> as a result. Let’s try the same approach with other files.<\/p>\n

Unfortunately brute forcing 5 character strings turns out to be waaay too slow on my laptop.<\/p>\n

Maybe we can be a bit smarter about that? That string we got from 26.zip looks awfully familiar – I wonder if the message we’re trying to extract starts with <?php <\/code>? Turns out it does. Also turns out, that if we can guess just one symbol in each archive (or just guess a reasonably small set of possible characters for it) we can brute the rest in just a few minutes.<\/p>\n

For example we find out that one of the files contains \u00a00x19<\/code>. There is only a limited number of things that make sense before number literal in php, right? I would expect arithmetic and bit operators, =, <, > to be most likely candidates. Let’s just assume the last character of previous archive is one of those things and see if we’re right.<\/p>\n

So I updated the initial script a bit to accept “hints” and we (Robert and Michal helped me there) spend a few hours guessing and waiting for the script to brute force full php code based on out guesses. Finally we get the full php code. Running it returns the flag.<\/p>\n


\n

The final script<\/h2>\n

I’ve removed most hints, to let you try yourself \ud83d\ude42<\/p>\n

import binascii\nimport itertools\nimport os\nimport string\nimport sys\nimport zipfile\n\nchars = string.ascii_lowercase + string.digits + '\\n $(){};[]=+-'\n#chars = string.printable\n\nhints = {\n    5: {'start': 'cho'},\n    6: [{'start': 'fla'},\n        {'start': 'FLA'},\n        {'start': \"Fla\"},\n        {'start': 'the'},\n        {'start': 'The'},\n        {'start': 'THE'}],\n    24: [{'end': 'fun'}],\n    25: [{'end': ' '}],\n}\n\nm = 24\nattack_chars = string.ascii_lowercase + string.digits + string.punctuation + ' '\n\n\ndef _brute_crc(fname, crc, length, hint, charset):\n    result = []\n    start_hint = hint.get('start', '')\n    end_hint = hint.get('end', '')\n    eff_len = length - len(start_hint) - len(end_hint)\n    combs = len(charset) ** eff_len\n    print \"Attacking file {} (length={} (minus hint={}), crc={}, combinations={})\".format(fname, length, eff_len, crc, combs)\n    if start_hint or end_hint:\n        print \"Hint: '{}' -- '{}'\".format(start_hint, end_hint)\n    print \"Using chars: {}\".format(charset)\n    last_p = 0\n    for i, guess in enumerate(itertools.product(charset, repeat=eff_len)):\n        if (i * 100) \/ combs > last_p:\n            last_p = (i * 100) \/ combs\n            print '{}% '.format(last_p),\n            sys.stdout.flush()\n        text = ''.join(itertools.chain([start_hint], guess, [end_hint]))\n        c = binascii.crc32(text) & 0xffffffff\n        if c == crc:\n            print\n            print \"found: '{}'\".format(text)\n            result.append(text)\n    return result\n\n\ndef attack(fname, hints=None, charset=None):\n    if charset is None:\n        charset = chars\n    if not hints:\n        hints = [{}]\n    with zipfile.ZipFile(fname, 'r') as zf:\n        info = zf.infolist()[0]\n        length = info.file_size\n        crc = info.CRC\n    if isinstance(hints, dict):\n        hints = [hints]\n\n    results = []\n    for hint in hints:\n        results += _brute_crc(fname, crc, length, hint, charset)\n\n    if not results:\n        print \"Attack failed\"\n        return\n    print\n    print \"Attack successfull!\"\n    print \"Found matching strings: '\" + \",'\".join(results) + \"'\"\n\n\nattack('{}.zip'.format(m), hints.get(m), attack_chars)<\/pre>\n","protected":false},"excerpt":{"rendered":"

Link: https:\/\/backdoor.sdslabs.co\/challenges\/CRC Author: IamLupo Points: 250 Category: crypto Description Backdoor user IamLupo wanted to submit his challenge for BackdoorCTF16 but he was struck by lightening and his challenge file shattered into 26 pieces. We want you to recover it for…<\/span> <\/p>\n

Read more ›<\/div>\n

<\/a><\/p>\n","protected":false},"author":5,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[16],"tags":[20],"yoast_head":"\n\n\n\n\n\n\n\n\n\n\n\n\t\n