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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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