# [NCtfU] pyc #### artis24106 ###### tags: `NCtfU` `Reverse` `python` --- ## What is .pyc? ---- ### .pyc = python bytecode ![](https://i.imgur.com/sK7kDZt.png =800x) ---- ### How to compile? test.py ```python= def hello_world(): print("Hello World =)") hello_world() ``` ```bash= python3 -m compileall test.py -b python3 test.pyc Hello World =) ``` ---- ### pyc format - Python 3.8.5 - [PEP 552](https://www.python.org/dev/peps/pep-0552/) - 16 bytes header + marshaled code object ![](https://i.imgur.com/Xt2BbQR.png) ---- ### magic number - 4 bytes - [magic 2 bytes](https://github.com/python/cpython/blob/5d1f32d33ba24d0aa87235ae40207bb57778388b/Lib/importlib/_bootstrap_external.py#L273) + `0d0a` - ex: - Python 3.8b4 -> `3413` -> `0d55` - little endian - `550d0d0a` ---- ### example test.pyc ![](https://i.imgur.com/2ZVgftW.png) --- ## Code object ---- ### code object - **byte-compiled** executable Python code - or **bytecode** ```python= # method1 def a(): print('b') a.__code__ # method2 co = compile(data, 'test.py', 'exec') # eval, exec, single ``` [`compile()` 的參數](https://www.programiz.com/python-programming/methods/built-in/compile) ---- ### read-only attributes - ex: - co_name: code object name - co_consts: constants - co_code: bytecode instruction - [more](https://docs.python.org/3.8/library/inspect.html)... ---- ### example ```python= def hello_world(a): b = 456 print("Hello World " + str(a) + str(b)) co = hello_world.__code__ print('co_argcount'.ljust(11), co.co_argcount) print('co_name'.ljust(11), co.co_name) print('co_code'.ljust(11), co.co_code) print('co_consts'.ljust(11), co.co_consts) print('co_varnames', co.co_varnames) print('co_names'.ljust(11), co.co_names) ``` ---- ### example cont. ```python= def hello_world(a): b = 456 print("Hello World " + str(a) + str(b)) ''' co_argcount 1 co_name hello_world co_code b'd\x01}\x01t\x00d\x02t... co_consts (None, 456, 'Hello World ') co_varnames ('a', 'b') co_names ('print', 'str') ''' ``` ---- ### bytecode ```python= def hello_world(): print("Hello world") co = hello_world.__code__ code = co.co_code print(code) # b't\x00d\x01\x83\x01\x01\x00d\x00S\x00' ``` ---- ### opcode - [`Lib/opcode.py`](https://github.com/python/cpython/blob/3.8/Lib/opcode.py) - (opcode \>= `HAVE_ARGUMENT`) -> have argument ![](https://i.imgur.com/eqAILY0.png) ---- ### instruction - 2 bytes each instruction - opcode (1 byte) + oparg (1 byte) - no argument -> oparg = 0 (default) - ex: `b't\x00d\x01'` ![](https://i.imgur.com/g1XWdhC.png) ```= LOAD_GLOBAL 0 LOAD_CONST 1 ``` ---- ### instruction cont. - https://docs.python.org/3.8/library/dis.html#python-bytecode-instructions - ![](https://i.imgur.com/BcCtb6E.png) - ![](https://i.imgur.com/mK9QgYs.png) ```= LOAD_GLOBAL 0 (co_names[0]) LOAD_CONST 1 (co_consts[1]) ``` ---- ### dis - python standard library - Disassembler for Python bytecode ```python= import dis def hello_world(): print("Hello world") dis.dis(hello_world.__code__) ``` ```= 4 0 LOAD_GLOBAL 0 (print) 2 LOAD_CONST 1 ('Hello world') 4 CALL_FUNCTION 1 6 POP_TOP 8 LOAD_CONST 0 (None) 10 RETURN_VALUE ``` ---- ### disassemble pyc ```python= import dis import marshal with open('./test.pyc', 'rb') as f: code = marshal.loads(f.read()[16:]) dis.dis(code) ``` ![](https://i.imgur.com/GMoGm5g.png) ---- ### decompile pyc to py - [uncompyle6](https://github.com/rocky/python-uncompyle6), [decompyle3](https://github.com/rocky/python-decompile3), ... ```bash= uncompyle6 test.pyc > source.py ``` ![](https://i.imgur.com/VOYjpxv.png) --- ## Anti decompile ---- ### method 1. change co_name 2. invalid instruction 3. remap opcode ---- ### method1: change co_name - uncompyle6 use `co_name` as function name - so... ![](https://i.imgur.com/XNijzcJ.png) - `'my_func'` can be replaced! ---- ### example patch `test.pyc` to `test_patched.pyc` ![](https://i.imgur.com/2BvpOlm.png =400x) ---- ### execute pyc ```bash= python3 test.pyc # my_func(87) is called python3 test_patched.pyc # my_func(87) is called ``` ---- ### decompile pyc and execute py file ```bash= uncompyle6 test_patched.pyc > test_patched.py python3 test_patched.py ``` ![](https://i.imgur.com/YUFtYk2.png) ---- ### view test_patched.py ![](https://i.imgur.com/JtEOHLD.png =700x) ---- ### disassemble patched pyc valid ! ![](https://i.imgur.com/qk4NZzW.png =600x) ---- ### method2: invalid instruction - insert invalid instruction in `co_code` ```bash= # assume len(co_consts) < 200 LOAD_CONST 200 ``` - use JUMP to skip them ![](https://i.imgur.com/e0dCdPB.png) ---- ### example - insert invalid instruction at the beginning of `co_code` ![](https://i.imgur.com/5GfO50u.png =400x) ---- ### execute pyc ```bash= python3 test.pyc # my_func(87) is called python3 test_patched2.pyc # my_func(87) is called ``` ---- ### decompile pyc ```bash= uncompyle6 test_patched2.pyc # IndexError: tuple index out of range ``` ---- ### disassemble pyc ![](https://i.imgur.com/NKz6MnE.png =450x) ---- ### method3: remap opcode - custom interpreter ---- ### example - download https://github.com/python/cpython/tree/3.8/Python ---- ### opcodes we want to swap ```= BINARY_ADD 23 BINARY_SUBTRACT 24 ``` ---- ### change opcodes - `Lib/opcode.py` - `Include/opcode.py` ![](https://i.imgur.com/v19LutA.png) ---- ### make && compile py ```bash= make -j ./python -m compileall test.py ``` ![](https://i.imgur.com/qpHf6PD.png) ---- ### decompile with uncompyle6 ```bash= uncompyle6 __pycache__/test.cpython-38.pyc > test_source.py ``` ![](https://i.imgur.com/9VQ4hht.png) ---- ### disassemble pyc && diff ```bash= vimdiff <(python3 dis_pyc.py ) <(./python dis_pyc.py ) ``` ![](https://i.imgur.com/Xa5a23Z.jpg) --- ## Little Demo - 2020 JustCTF - REmap - 2020 AIS3-EOF final - Cat Slayer ---- ### 2020 JustCTF - REmap - [challenge, source code](https://github.com/justcatthefish/justctf-2020/tree/master/challenges/re_REmap) - file: `backup_decryptor.exe` - ![](https://i.imgur.com/Vi3apiF.png) - [pyinstxtractor](https://github.com/extremecoders-re/pyinstxtractor) - `python38.dll` or check pyc header -> python3.8 - [real python3.8 opcode.py](https://github.com/python/cpython/blob/3.8/Lib/opcode.py) ---- ### 2020 AIS3-EOF final - Cat Slayer - [challenge, source code, write-up](https://github.com/splitline/My-CTF-Challenges/tree/master/ais3-eof/2020-final/cat-slayer) - file: `game.pyc`, `cat_slayer.data.meow.flag` --- ## Thanks <img src='https://i.imgur.com/MF5Huj7.png' style='width: 250px; border: none; background-color: transparent;'> <style> .hr { width: 100%; border: 2px solid black; text-align: center; padding-top: 7px; padding-bottom: 7px; background-color: #FFCE44; margin-top: 45px; margin-bottom: 15px; font-weight: 600; } hr { display: none; } </style>
{"metaMigratedAt":"2023-06-15T21:59:32.384Z","metaMigratedFrom":"YAML","title":"[NCtfU] pyc","breaks":"true","contributors":"[{\"id\":\"0b9aa881-9a57-4ba9-b469-e75c551389e8\",\"add\":9337,\"del\":2254}]"}
    1735 views