Friday 4 November 2016

CTF Writeup - Flare-On 2016 - 06: khaki


  • Name - khaki
  • Category - Reverse Engineering
  • Points - 6
  • Description - n/a
  • Binary - Download here

The binary is a simple game which requires you to guess the randomly-generated number:

Command Prompt
C:\>khaki.exe (Guesses: 1) Pick a number between 1 and 100:50 Too high, try again (Guesses: 2) Pick a number between 1 and 100:25 Too low, try again (Guesses: 3) Pick a number between 1 and 100:37 Too high, try again (Guesses: 4) Pick a number between 1 and 100:30 Wahoo, you guessed it with 4 guesses Status: 4 guesses C:\>


Running strings on the binary reveals a lot of python functions and files. More specifically, the program has been compiled using py2exe:

root@kali: ~/Desktop
root@kali:~/Desktop# strings khaki.exe | grep -i py2exe PY2EXE_VERBOSE PY2EXE_VERBOSE C:\Python27\lib\site-packages\py2exe\boot_common.pyR C:\Python27\lib\site-packages\py2exe\boot_common.pyR C:\Python27\lib\site-packages\py2exe\boot_common.pyR C:\Python27\lib\site-packages\py2exe\boot_common.pyR C:\Python27\lib\site-packages\py2exe\boot_common.pyR C:\Python27\lib\site-packages\py2exe\boot_common.pyR C:\Python27\lib\site-packages\py2exe\boot_common.pyt C:\Python27\lib\site-packages\py2exe\boot_common.pyt root@kali:~/Desktop#


The next step is to decompile the program and hopefully get the original python file back. We first use unpy2exe to get back the compiled python files (.pyc):

root@kali: ~/Desktop
root@kali:~/Desktop# python unpy2exe.py -o OUTPUT khaki.exe Magic value: 78563412 Code bytes length: 4386 Archive name: - Extracting C:\Python27\lib\site-packages\py2exe\boot_common.py.pyc Extracting poc.py.pyc root@kali:~/Desktop#


Then we try and decompile the pyc file back to it's original python code:

root@kali: ~/Desktop
root@kali:~/Desktop# python uncompyler.py OUTPUT\poc.py.pyc # 2016.09.30 23:29:57 GMT Daylight Time --- This code section failed: --- 0 LOAD_CONST -1 3 LOAD_CONST '' [ ... snip ... ] 900 ROT_THREE '' 901 ROT_THREE '' 902 ROT_THREE '' Syntax error at or near `POP_TOP' token at offset 18 # decompiled 0 files: 0 okay, 1 failed, 0 verify failed # 2016.09.30 23:29:57 GMT Daylight Time root@kali:~/Desktop#


The decompilation fails but we still get back some of the successfully-decompiled bytecode which is enough to complete the challenge. The bytecode contains a lot of NOPs which makes it difficult to read such as NOP, double ROT_TWO, triple ROT_THREE and LOAD_CONST -1; POP_TOP.

The following is the entire NOP-free python bytecode:


    0 LOAD_CONST        -1
    3 LOAD_CONST        ''
    6 IMPORT_NAME       'sys'
    9 STORE_NAME        'sys'
    12 LOAD_CONST        -1
    19 LOAD_CONST        ''
    24 IMPORT_NAME       'random'
    28 STORE_NAME        'random'
    31 LOAD_CONST        'Flare-On ultra python obfuscater 2000'
    34 STORE_NAME        '__version__'
    39 LOAD_NAME         'random'
    46 LOAD_ATTR         'randint'
    49 LOAD_CONST        1
    52 LOAD_CONST        101
    55 CALL_FUNCTION_2   ''
    58 STORE_NAME        'target'
    61 LOAD_CONST        1
    64 STORE_NAME        'count'
    70 LOAD_CONST        ''
    79 STORE_NAME        'error_input'
    82 SETUP_LOOP        '288'
    88 LOAD_NAME         'True'
    97 POP_JUMP_IF_FALSE '287'
    100 LOAD_CONST        '(Guesses: %d) Pick a number between 1 and 100:'
    108 LOAD_NAME         'count'
    111 BINARY_MODULO     ''
    112 PRINT_ITEM        ''
    113 LOAD_NAME         'sys'
    116 LOAD_ATTR         'stdin'
    119 LOAD_ATTR         'readline'
    122 CALL_FUNCTION_0   ''
    125 STORE_NAME        'input'
    128 SETUP_EXCEPT      '154'
    131 LOAD_NAME         'int'
    134 LOAD_NAME         'input'
    140 LOAD_CONST        ''
    143 CALL_FUNCTION_2   ''
    146 STORE_NAME        'input'
    150 POP_BLOCK         ''
    151 JUMP_FORWARD      '197'
    154_0 COME_FROM         '128'
    154 POP_TOP           ''
    159 POP_TOP           ''
    160 POP_TOP           ''
    161 LOAD_NAME         'input'
    167 STORE_NAME        'error_input'
    174 LOAD_CONST        'Invalid input: %s'
    178 LOAD_NAME         'error_input'
    181 BINARY_MODULO     ''
    182 PRINT_ITEM        ''
    183 PRINT_NEWLINE     ''
    184 JUMP_BACK         '88'
    189 JUMP_FORWARD      '197'
    194 END_FINALLY       ''
    197_0 COME_FROM         '151'
    197_1 COME_FROM         '189'
    197 LOAD_NAME         'target'
    200 LOAD_NAME         'input'
    207 COMPARE_OP        '=='
    211 POP_JUMP_IF_FALSE '221'
    214 BREAK_LOOP        ''
    218 JUMP_FORWARD      '221'
    221_0 COME_FROM         '218'
    221 LOAD_NAME         'input'
    224 LOAD_NAME         'target'
    227 COMPARE_OP        '<'
    234 POP_JUMP_IF_FALSE '253'
    241 LOAD_CONST        'Too low, try again'
    244 PRINT_ITEM        ''
    245 PRINT_NEWLINE     ''
    246 JUMP_FORWARD      '262'
    253 LOAD_CONST        'Too high, try again'
    256 PRINT_ITEM        ''
    257 PRINT_NEWLINE     ''
    262_0 COME_FROM         '246'
    262 LOAD_NAME         'count'
    265 LOAD_CONST        1
    271 INPLACE_ADD       ''
    279 STORE_NAME        'count'
    284 JUMP_BACK         '88'
    287 POP_BLOCK         ''
    288_0 COME_FROM         '82'
    288 LOAD_NAME         'target'
    291 LOAD_NAME         'input'
    294 COMPARE_OP        '=='
    297 POP_JUMP_IF_FALSE '342'
    305 LOAD_CONST        'Wahoo, you guessed it with %d guesses\n'
    309 LOAD_NAME         'count'
    312 BINARY_MODULO     ''
    313 STORE_NAME        'win_msg'
    316 LOAD_NAME         'sys'
    319 LOAD_ATTR         'stdout'
    322 LOAD_ATTR         'write'
    331 LOAD_NAME         'win_msg'
    334 CALL_FUNCTION_1   ''
    338 POP_TOP           ''
    339 JUMP_FORWARD      '342'
    342_0 COME_FROM         '339'
    342 LOAD_NAME         'count'
    347 LOAD_CONST        1
    352 COMPARE_OP        '=='
    355 POP_JUMP_IF_FALSE '391'
    358 LOAD_CONST        'Status: super guesser %d'
    361 LOAD_NAME         'count'
    364 BINARY_MODULO     ''
    366 PRINT_ITEM        ''
    367 PRINT_NEWLINE     ''
    372 LOAD_NAME         'sys'
    378 LOAD_ATTR         'exit'
    381 LOAD_CONST        1
    384 CALL_FUNCTION_1   ''
    387 POP_TOP           ''
    388 JUMP_FORWARD      '391'
    391_0 COME_FROM         '388'
    391 LOAD_NAME         'count'
    396 LOAD_CONST        25
    401 COMPARE_OP        '>'
    413 POP_JUMP_IF_FALSE '451'
    419 LOAD_CONST        'Status: took too long %d'
    425 LOAD_NAME         'count'
    429 BINARY_MODULO     ''
    430 PRINT_ITEM        ''
    431 PRINT_NEWLINE     ''
    432 LOAD_NAME         'sys'
    435 LOAD_ATTR         'exit'
    438 LOAD_CONST        1
    444 CALL_FUNCTION_1   ''
    447 POP_TOP           ''
    448 JUMP_FORWARD      '468'
    451 LOAD_CONST        'Status: %d guesses'
    455 LOAD_NAME         'count'
    458 BINARY_MODULO     ''
    459 PRINT_ITEM        ''
    464 PRINT_NEWLINE     ''
    468_0 COME_FROM         '448'
    468 LOAD_NAME         'error_input'
    474 LOAD_CONST        ''
    477 COMPARE_OP        '!='
    486 POP_JUMP_IF_FALSE '896'
    489 LOAD_CONST        ''
    492 LOAD_ATTR         'join'
    495 LOAD_GENEXPR      '<code_object <genexpr>&lgt;'
    498 MAKE_FUNCTION_0   ''
    504 LOAD_NAME         'error_input'
    511 GET_ITER          ''
    515 CALL_FUNCTION_1   ''
    518 CALL_FUNCTION_1   ''
    521 LOAD_ATTR         'encode'
    524 LOAD_CONST        'hex'
    530 CALL_FUNCTION_1   ''
    536 STORE_NAME        'tmp'
    544 LOAD_NAME         'tmp'
    549 LOAD_CONST        '312a232f272e27313162322e372548'
    552 COMPARE_OP        '!='
    557 POP_JUMP_IF_FALSE '590'
    560 LOAD_NAME         'sys'
    565 LOAD_ATTR         'exit'
    568 LOAD_CONST        ''
    571 CALL_FUNCTION_1   ''
    576 POP_TOP           ''
    587 JUMP_FORWARD      '590'
    590_0 COME_FROM         '587'
    590 LOAD_CONST        67
    593 LOAD_CONST        139
    598 LOAD_CONST        119
    602 LOAD_CONST        165
    608 LOAD_CONST        232
    611 LOAD_CONST        86
    614 LOAD_CONST        207
    618 LOAD_CONST        61
    624 LOAD_CONST        79
    628 LOAD_CONST        67
    631 LOAD_CONST        45
    637 LOAD_CONST        58
    640 LOAD_CONST        230
    647 LOAD_CONST        190
    650 LOAD_CONST        181
    654 LOAD_CONST        74
    657 LOAD_CONST        65
    660 LOAD_CONST        148
    663 LOAD_CONST        71
    669 LOAD_CONST        243
    676 LOAD_CONST        246
    683 LOAD_CONST        67
    686 LOAD_CONST        142
    690 LOAD_CONST        60
    693 LOAD_CONST        61
    702 LOAD_CONST        92
    711 LOAD_CONST        58
    722 LOAD_CONST        115
    725 LOAD_CONST        240
    728 LOAD_CONST        226
    733 LOAD_CONST        171
    738 BUILD_LIST_31     ''
    741 STORE_NAME        'stuffs'
    750 IMPORT_NAME       'hashlib'
    753 STORE_NAME        'hashlib'
    756 LOAD_NAME         'hashlib'
    762 LOAD_ATTR         'md5'
    765 LOAD_NAME         'win_msg'
    768 LOAD_NAME         'tmp'
    771 BINARY_ADD        ''
    772 CALL_FUNCTION_1   ''
    779 LOAD_ATTR         'digest'
    782 CALL_FUNCTION_0   ''
    785 STORE_NAME        'stuffer'
    788 SETUP_LOOP        '890'
    791 LOAD_NAME         'range'
    794 LOAD_NAME         'len'
    797 LOAD_NAME         'stuffs'
    800 CALL_FUNCTION_1   ''
    803 CALL_FUNCTION_1   ''
    808 GET_ITER          ''
    809 FOR_ITER          '889'
    816 STORE_NAME        'x'
    819 LOAD_NAME         'chr'
    822 LOAD_NAME         'stuffs'
    829 LOAD_NAME         'x'
    832 BINARY_SUBSCR     ''
    837 LOAD_NAME         'ord'
    840 LOAD_NAME         'stuffer'
    843 LOAD_NAME         'x'
    847 LOAD_NAME         'len'
    850 LOAD_NAME         'stuffer'
    853 CALL_FUNCTION_1   ''
    856 BINARY_MODULO     ''
    861 BINARY_SUBSCR     ''
    866 CALL_FUNCTION_1   ''
    876 BINARY_XOR        ''
    878 CALL_FUNCTION_1   ''
    885 PRINT_ITEM        ''
    886 JUMP_BACK         '809'
    889 POP_BLOCK         ''
    890_0 COME_FROM         '788'
    890 PRINT_NEWLINE     ''
    893 JUMP_FORWARD      '896'
    896_0 COME_FROM         '893'
    896 LOAD_CONST        ''
    899 RETURN_VALUE      ''


Filtering the unnecessary parts, we get the following desired control flow:
  1. Line 313 - Set win_msg to 'Wahoo, you guessed it with %d guesses\n'
    • where %d is a number between 1 and 26 (Lines 396 - 419)
  2. Lines 536 - 552 - tmp has to be equal to '312a232f272e27313162322e372548'
  3. Lines 590 - 741 - Set stuffs to [67, 139, ..., 226, 171]
  4. Lines 750 - 785 - Set stuffer to the MD5 of win_msg + tmp
  5. Lines 788 - 890 - XOR stuffs with stuffer as the key, and print each value

The only unknown variable is %d and can be easily brute-forced. Let's convert the above points into code which iterates over all possible values:

import hashlib
import sys

stuffs = [67, 139, 119, 165, 232, 86, 207, 61, 79, 67, 45, 58, 230, 190, 181, 74, 65, 148, 71, 243, 246, 67, 142, 60, 61, 92, 58, 115, 240, 226, 171]

tmp = "312a232f272e27313162322e372548"

for y in range (26):
    win_msg = "Wahoo, you guessed it with " + str(y) + " guesses\n"
    bin_add = win_msg + tmp
    stuffer = hashlib.md5(bin_add).digest()

    for x in range(len(stuffs)):
        sys.stdout.write((chr(stuffs[x] ^ ord(stuffer[x % len(stuffer)]))))

    print '\r'

Running the program:

root@kali: ~/Desktop
root@kali:~/Desktop# python solution.py [ .. snip .. ] 1mp0rt3d_pygu3ss3r@flare-on.com [ .. snip .. ] root@kali:~/Desktop#


No comments:

Post a Comment