# [SECCON CTF 2021] dis-me writeup (Rev, 15 solves) @moratorium08 ## Overview of the challenge The concept of this challenge is to introduce an packer-like technique to pyc object. The given pyc file contains a packed code in the middle of code bytes so python's dis cannot disassemble this program. ``` >>> dis.dis(x) 0 0 EXTENDED_ARG 3 2 JUMP_ABSOLUTE 898 4 ROT_THREE Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/home/vagrant/.pyenv/versions/3.6.9/lib/python3.6/dis.py", line 60, in dis disassemble(x, file=file) File "/home/vagrant/.pyenv/versions/3.6.9/lib/python3.6/dis.py", line 335, in disassemble co.co_consts, cell_names, linestarts, file=file) File "/home/vagrant/.pyenv/versions/3.6.9/lib/python3.6/dis.py", line 346, in _disassemble_bytes line_offset=line_offset): File "/home/vagrant/.pyenv/versions/3.6.9/lib/python3.6/dis.py", line 308, in _get_instructions_bytes argval, argrepr = _get_name_info(arg, names) File "/home/vagrant/.pyenv/versions/3.6.9/lib/python3.6/dis.py", line 272, in _get_name_info argval = name_list[name_index] IndexError: tuple index out of range ``` (In fact, pycdas can) You may notice the first byte code is to just jump to addr `898`. Then you may want to extract the byte code from addr 898. So let's do that. ``` >>> dis.dis(c) 0 0 LOAD_CONST 0 (0) 2 LOAD_CONST 1 (None) 4 IMPORT_NAME 0 (marshal) 6 STORE_NAME 0 (marshal) 8 LOAD_CONST 0 (0) 10 LOAD_CONST 1 (None) 12 IMPORT_NAME 1 (base64) 14 STORE_NAME 1 (base64) 16 LOAD_CONST 0 (0) 18 LOAD_CONST 1 (None) 20 IMPORT_NAME 2 (types) 22 STORE_NAME 2 (types) 24 LOAD_NAME 3 (open) 26 LOAD_NAME 4 (__file__) 28 LOAD_CONST 2 ('rb') 30 CALL_FUNCTION 2 32 STORE_NAME 5 (f) ... omitted ... ``` It looks good. By reading this file, you can see how the code can be unpacked: ```python= f = open(__file__, "rb") f.seek(12) s = marshal.loads(f.read()).co_code[4:] marshal.loads(base64.b64decode( bytes([(x- 7 *i -45)%256 for i, x in enumerate(s[2:2+s[0]*256+s[1]])]) )) ``` So, let's do that. ```python= import base64 f = open(F, "rb") f.seek(12) s = marshal.loads(f.read()).co_code[4:] code_dump = base64.b64decode(bytes([(x - 7 * i - 45) % 256 for i, x in enumerate(s[2:2 + s[0] * 256 + s[1]])])) with open("fixed-func.pyc", "wb") as f: f.write(header) f.write(code_dump) ``` then ``` $ uncompyle6 fixed-func.pyc # uncompyle6 version 3.8.0 # Python bytecode 3.6 (3379) # Decompiled from: Python 3.9.6 (default, Sep 4 2021, 04:11:19) # [GCC 9.3.0] # Embedded file name: run.py # Compiled at: 2021-11-21 18:11:20 # Size of source mod 2**32: 1169 bytes f = lambda n: n if n <= 1 else (f(n - 1) + f(n - 2)) % 10 flag = input('Input the flag > ') s = 1 if flag.startswith('SECCON{'): if flag.endswith('}'): if len(flag) == 40: s = sum(abs(ord(c) - ord(str(f(i)))) for i, c in enumerate(flag[7:-1])) s or print('Correct! The flag is', flag) else: print('Wrong :(') # okay decompiling fixed-func.pyc ``` Now you can see what is the flag clearly :)