Python - Revertendo bytecodes
Autor: zeldani | Publicado: 2016-10-13T01:05:00-03:00


Caso você precise recuperar um arquivo .py de um .pyc uma boa opção é usar o Decompyle++, que reverte os bytecodes compilados à um código python legível por humanos.&nbsp;



Instalando o Decompyle++:




1)&nbsp;Primeiro precisamos instalar o&nbsp;cmake:


$ wget http://www.cmake.org/files/v2.8/cmake-2.8.12.2.tar.gz

$ tar xzvf cmake-2.8.12.2.tar.gz

$ cd cmake-2.8.10.2

$ ./bootstrap

$ make

# make install


2)&nbsp;Faça o download do&nbsp;Decompile++&nbsp;e compile:
$ git clone git@github.com:zrax/pycdc.git
$ cd pycdc
$ /usr/local/bin/cmake ../pycdc/
$ make


Recuperando o arquivo .py original:

$ ./pycdc dicionario.pyc &gt; dicionario.py




Entendendo os bytecodes e opcodes:

Os bytecodes são a representação interna (.pyc) de um script python no compilador. O interpretador Cpython lê o arquivo binário e executa as intruções (opcodes) uma de cada vez. O script python .py resultante é nada mais do que uma sequencia de chamadas de diferentes funções.

O módulo dis inclui funções para lidar com os bytecodes. A função dis.dis() mostra a representação do código python (módulo, classe, método, função, ou objeto de código).

Por exemplo, revertendo a criação de um dicionário temos:
$ echo "dicionario={'a':1}" &gt; dicionario.py&nbsp;
$ python -m dis dicionario.py&nbsp;
&nbsp; 1 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 0 BUILD_MAP &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;1
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 3 LOAD_CONST &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 0 (1)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 6 LOAD_CONST &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 1 ('a')
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 9 STORE_MAP &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;10 STORE_NAME &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 0 (dicionario)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;13 LOAD_CONST &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 2 (None)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;16 RETURN_VALUE &nbsp; &nbsp; &nbsp; 



A saída é organizada em colunas contendo a linha original, o endereço da instrução, o nome do opcode e os argumentos passados para o opcode.&nbsp;Nesse exemplo, o código usa 5 operações diferentes para criar, popular um dicionário e salvar os resultados em uma variável local.




Para ver o código opcode e a representação do caractere de cada operação, podemos usar o módulo opcode:
&gt;&gt;&gt; import opcode
&gt;&gt;&gt; opcode.opmap['BUILD_MAP']
105
&gt;&gt;&gt; opcode.opmap['LOAD_CONST']
100
&gt;&gt;&gt; chr(opcode.opmap['BUILD_MAP'])
'i'


&gt;&gt;&gt; chr(opcode.opmap['LOAD_CONST'])

'd'
&gt;&gt;&gt; hex(opcode.opmap['BUILD_MAP'])
'0x69'




&gt;&gt;&gt; hex(opcode.opmap['LOAD_CONST'])

'0x64'






Para criar um arquivo .pyc podemos usar o py_compile:


&gt;&gt;&gt; import py_compile

&gt;&gt;&gt; py_compile.compile('dicionario.py')




Verificando os opcodes:


$ hexdump -C dicionario.pyc&nbsp;

00000000 &nbsp;03 f3 0d 0a 07 f0 fe 57 &nbsp;63 00 00 00 00 00 00 00 &nbsp;|.......Wc.......|

00000010 &nbsp;00 03 00 00 00 40 00 00 &nbsp;00 73 11 00 00 00 69 01 &nbsp;|.....@...s....i.|

00000020 &nbsp;00 64 00 00 64 01 00 36 &nbsp;5a 00 00 64 02 00 53 28 &nbsp;|.d..d..6Z..d..S(|

00000030 &nbsp;03 00 00 00 69 01 00 00 &nbsp;00 74 01 00 00 00 61 4e &nbsp;|....i....t....aN|

00000040 &nbsp;28 01 00 00 00 74 0a 00 &nbsp;00 00 64 69 63 69 6f 6e &nbsp;|(....t....dicion|

00000050 &nbsp;61 72 69 6f 28 00 00 00 &nbsp;00 28 00 00 00 00 28 00 &nbsp;|ario(....(....(.|

00000060 &nbsp;00 00 00 73 0d 00 00 00 &nbsp;64 69 63 69 6f 6e 61 72 &nbsp;|...s....dicionar|

00000070 &nbsp;69 6f 2e 70 79 74 08 00 &nbsp;00 00 3c 6d 6f 64 75 6c &nbsp;|io.pyt....&lt;modul|

00000080 &nbsp;65 3e 01 00 00 00 73 00 &nbsp;00 00 00 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |e&gt;....s....|

0000008b




Podemos confirmar a presença dos caracteres &nbsp;ascii&nbsp;"i" e "d" e os hexadecimais 69 01 00 64 00 00 64 01 00 36 5a 00 00 64, indicando que temos os 2 opcodes BUILD_MAP e 3 LOAD_CONST, &nbsp;como vimos antes com o dis. Assim, caso precise arrumar um arquivo corrompido&nbsp;.pyc,&nbsp; conhecer os opcodes pode facilitar a tarefa. :)

* Fontes:
http://multigrad.blogspot.com.br/2014/06/fun-with-python-bytecode.html
http://turtle-philosophy.blogspot.com/2014/06/decompiling-python-bytecode-with-pycdc.html
https://pymotw.com/2/dis/
http://akaptur.com/blog/2013/08/14/python-bytecode-fun-with-dis/
http://vgel.me/posts/patching_function_bytecode_with_python/
ftp://ftp.ntua.gr/mirror/python/peps/pep-0339.html
https://docs.python.org/3/library/dis.html