# [Note] Utilization of Python decompiler uncompyle6 ###### tags: `python decompiler` , `python`, `decompiler`, `reverse engineer` [toc] ## Goal To retrieve the python source code by decompiling **.pyc* and **.pyo* to **.py.* ## Introduction | Abbr. | Description | | -------- | -------- | | .py | This is normally the input source code that you've written. | | .pyc | This is the compiled bytecode. If you import a module, python will build a *.pyc file that contains the bytecode to make importing it again later easier (and faster). | | .pyo | This was a file format used before Python 3.5 for *.pyc files that were created with optimizations (-O) flag. | | .pyd | This is a dynamic link library that contains a Python module, or set of modules, to be called by other Python code. It could be a *.so file in Linux or a *.dll like file in Windows. **"Uncompyle6"** is written in python and it handles opcodes introduced in Python 3.5. ## Environment ```shell Tomas# uname -a 5.4.0-74-generic #83~18.04.1-Ubuntu SMP Tue May 11 16:01:00 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux Tomas# python --version Python 2.7.18 ``` ## Setup ```shell Tomas# pip install uncompyle6 Collecting uncompyle6 Using cached https://files.pythonhosted.org/packages/65/24/04e4e3eeb1d39c2a910f552bf0a69185a4d618924be2a98fad8e048e7cdf/uncompyle6-3.7.4-py3-nol Collecting spark-parser<1.9.0,>=1.8.9 (from uncompyle6) Using cached https://files.pythonhosted.org/packages/e1/c3/745adc57618998882a6e120cedebfba6ebf76aa9052c8b89e49c0fe47c2e/spark_parser-1.8.9-py3-l Collecting xdis<5.1.0,>=5.0.4 (from uncompyle6) Using cached https://files.pythonhosted.org/packages/57/63/eea3354f4590476131c493a1c6aa6ea724b5d87166d271746e10df4b20d5/xdis-5.0.9-py2.py3-nonel Collecting click (from spark-parser<1.9.0,>=1.8.9->uncompyle6) Using cached https://files.pythonhosted.org/packages/76/0a/b6c5f311e32aeb3b406e03c079ade51e905ea630fc19d1262a46249c1c86/click-8.0.1-py3-none-anl Collecting six>=1.10.0 (from xdis<5.1.0,>=5.0.4->uncompyle6) Using cached https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-nonel Collecting importlib-metadata; python_version < "3.8" (from click->spark-parser<1.9.0,>=1.8.9->uncompyle6) Using cached https://files.pythonhosted.org/packages/23/5d/f2151217058e7d5c5b4b584bc6052c2ae478c5a36b58a056364351613bfb/importlib_metadata-4.5.l Collecting zipp>=0.5 (from importlib-metadata; python_version < "3.8"->click->spark-parser<1.9.0,>=1.8.9->uncompyle6) Using cached https://files.pythonhosted.org/packages/0f/8c/715c54e9e34c0c4820f616a913a7de3337d0cd79074dd1bed4dd840f16ae/zipp-3.4.1-py3-none-anyl Collecting typing-extensions>=3.6.4; python_version < "3.8" (from importlib-metadata; python_version < "3.8"->click->spark-parser<1.9.0,>=1.8.9->) Using cached https://files.pythonhosted.org/packages/2e/35/6c4fff5ab443b57116cb1aad46421fb719bed2825664e8fe77d66d99bcbc/typing_extensions-3.10.l Installing collected packages: zipp, typing-extensions, importlib-metadata, click, spark-parser, six, xdis, uncompyle6 Successfully installed click-8.0.1 importlib-metadata-4.5.0 six-1.16.0 spark-parser-1.8.9 typing-extensions-3.10.0.0 uncompyle6-3.7.4 xdis-5.0.91 Tomas# whereis uncompyle6 uncompyle6: /usr/local/bin/uncompyle6 ``` ## Generate the python bytecode Here is my sample code *test.py*. ```python # File name: test.py # This program prints Hello, world! print('Hello, world!') ``` To generate the bytecode ```shell Tomas# python -m test.py Hello, world! /usr/local/bin/python: No module named test.py Tomas# ls -al -rw-rw-r-- 1 tomas tomas 60 六 12 15:12 test.py -rw-rw-r-- 1 tomas tomas 117 六 12 15:13 test.pyc Tomas# python -O -m test.py Hello, world! /usr/local/bin/python: No module named test.py Tomas# ls -al -rw-rw-r-- 1 tomas tomas 60 六 12 15:12 test.py -rw-rw-r-- 1 tomas tomas 117 六 12 15:13 test.pyc -rw-rw-r-- 1 tomas tomas 117 六 12 15:13 test.pyo ``` ## Record the use of uncompyle6 ### Decompile the pyc and pyo ```shell tomas# ls */ test/: test.py test.pyc test.pyo out/: tomas# uncompyle6 -o out test/*.pyc tomas# ls -al out total 12 drwxrwxr-x 2 tomas tomas 4096 六 12 15:32 . drwxr-xr-x 19 tomas tomas 4096 六 12 15:33 .. -rw-rw-r-- 1 tomas tomas 223 六 12 15:32 test.py tomas# uncompyle6 -o out test/*.pyo test/test.pyo -- # Successfully decompiled file tomas# ls -al out total 16 drwxrwxr-x 2 tomas tomas 4096 六 12 15:33 . drwxr-xr-x 19 tomas tomas 4096 六 12 15:33 .. -rw-rw-r-- 1 tomas tomas 223 六 12 15:32 test.py -rw-rw-r-- 1 tomas tomas 223 六 12 15:33 test.pyo_dis ``` ### Check the decompiled source code ```python tomas# cat out » cat test.py # uncompyle6 version 3.7.4 # Python bytecode 2.7 (62211) # Decompiled from: Python 3.6.9 (default, Jan 26 2021, 15:33:00) # [GCC 8.4.0] # Embedded file name: test.py # Compiled at: 2021-06-12 15:12:26 print 'Hello, world!'% tomas# cat test.pyo_dis # uncompyle6 version 3.7.4 # Python bytecode 2.7 (62211) # Decompiled from: Python 3.6.9 (default, Jan 26 2021, 15:33:00) # [GCC 8.4.0] # Embedded file name: test.py # Compiled at: 2021-06-12 15:12:26 print 'Hello, world!'% ``` ## uncompyle6 Usage ```shell tomas# uncompyle6 -h Usage: uncompyle6 [OPTIONS]... [ FILE | DIR]... uncompyle6 [--help | -h | --V | --version] Examples: uncompyle6 foo.pyc bar.pyc # decompile foo.pyc, bar.pyc to stdout uncompyle6 -o . foo.pyc bar.pyc # decompile to ./foo.pyc_dis and ./bar.pyc_dis uncompyle6 -o /tmp /usr/lib/python1.5 # decompile whole library Options: -o <path> output decompiled files to this path: if multiple input files are decompiled, the common prefix is stripped from these names and the remainder appended to <path> uncompyle6 -o /tmp bla/fasel.pyc bla/foo.pyc -> /tmp/fasel.pyc_dis, /tmp/foo.pyc_dis uncompyle6 -o /tmp bla/fasel.pyc bar/foo.pyc -> /tmp/bla/fasel.pyc_dis, /tmp/bar/foo.pyc_dis uncompyle6 -o /tmp /usr/lib/python1.5 -> /tmp/smtplib.pyc_dis ... /tmp/lib-tk/FixTk.pyc_dis --compile | -c <python-file> attempts a decompilation after compiling <python-file> -d print timestamps -p <integer> use <integer> number of processes -r recurse directories looking for .pyc and .pyo files --fragments use fragments deparser --verify compare generated source with input byte-code --verify-run compile generated source, run it and check exit code --syntax-verify compile generated source --linemaps generated line number correspondencies between byte-code and generated source output --encoding <encoding> use <encoding> in generated source according to pep-0263 --help show this message Debugging Options: --asm | -a include byte-code (disables --verify) --grammar | -g show matching grammar --tree={before|after} -t {before|after} include syntax before (or after) tree transformation (disables --verify) --tree++ | -T add template rules to --tree=before when possible Extensions of generated files: '.pyc_dis' '.pyo_dis' successfully decompiled (and verified if --verify) + '_unverified' successfully decompile but --verify failed + '_failed' decompile failed (contact author for enhancement) ``` ## Summary Except for the tool uncompyle6, **"pycdc"** is also utilized to do the same work here. Besides, it is worthy noticing that these tools cannot reverse ****.pyd*** file! ## Refereonce https://pypi.org/project/uncompyle6/ https://www.itread01.com/content/1547579727.html https://stackabuse.com/differences-between-pyc-pyd-and-pyo-python-files https://stackoverflow.com/questions/8822335/what-do-the-python-file-extensions-pyc-pyd-pyo-stand-for https://ephrain.net/linux-%E4%BD%BF%E7%94%A8-decompile-pycdc-%E5%8F%8D%E7%B5%84%E8%AD%AF-pyc-%E6%AA%94%E6%A1%88/