# [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/