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.
- Instalando o Decompyle++:
1) Primeiro precisamos instalar o 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) Faça o download do Decompile++ e compile:
$ git clone git@github.com:zrax/pycdc.git
$ cd pycdc
$ /usr/local/bin/cmake ../pycdc/
$ make
- Recuperando o arquivo .py original:
- Entendendo os bytecodes e opcodes:
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}" > dicionario.py
$ python -m dis dicionario.py
1 0 BUILD_MAP 1
3 LOAD_CONST 0 (1)
6 LOAD_CONST 1 ('a')
9 STORE_MAP
10 STORE_NAME 0 (dicionario)
13 LOAD_CONST 2 (None)
16 RETURN_VALUE
>>> opcode.opmap['BUILD_MAP']
105
>>> opcode.opmap['LOAD_CONST']
100
>>> chr(opcode.opmap['BUILD_MAP'])
'i'
* 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
Por exemplo, revertendo a criação de um dicionário temos:
$ echo "dicionario={'a':1}" > dicionario.py
$ python -m dis dicionario.py
1 0 BUILD_MAP 1
3 LOAD_CONST 0 (1)
6 LOAD_CONST 1 ('a')
9 STORE_MAP
10 STORE_NAME 0 (dicionario)
13 LOAD_CONST 2 (None)
16 RETURN_VALUE
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. 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:
>>> import opcode>>> opcode.opmap['BUILD_MAP']
105
>>> opcode.opmap['LOAD_CONST']
100
>>> chr(opcode.opmap['BUILD_MAP'])
'i'
>>> chr(opcode.opmap['LOAD_CONST'])
'd'
>>> hex(opcode.opmap['BUILD_MAP'])
'0x69'
>>> hex(opcode.opmap['BUILD_MAP'])
'0x69'
>>> hex(opcode.opmap['LOAD_CONST'])
'0x64'
>>> import py_compile
>>> py_compile.compile('dicionario.py')
Verificando os opcodes:
$ hexdump -C dicionario.pyc
00000000 03 f3 0d 0a 07 f0 fe 57 63 00 00 00 00 00 00 00 |.......Wc.......|
00000010 00 03 00 00 00 40 00 00 00 73 11 00 00 00 69 01 |.....@...s....i.|
00000020 00 64 00 00 64 01 00 36 5a 00 00 64 02 00 53 28 |.d..d..6Z..d..S(|
00000030 03 00 00 00 69 01 00 00 00 74 01 00 00 00 61 4e |....i....t....aN|
00000040 28 01 00 00 00 74 0a 00 00 00 64 69 63 69 6f 6e |(....t....dicion|
00000050 61 72 69 6f 28 00 00 00 00 28 00 00 00 00 28 00 |ario(....(....(.|
00000060 00 00 00 73 0d 00 00 00 64 69 63 69 6f 6e 61 72 |...s....dicionar|
00000070 69 6f 2e 70 79 74 08 00 00 00 3c 6d 6f 64 75 6c |io.pyt....<modul|
00000080 65 3e 01 00 00 00 73 00 00 00 00 |e>....s....|
0000008b
Podemos confirmar a presença dos caracteres ascii "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, como vimos antes com o dis. Assim, caso precise arrumar um arquivo corrompido .pyc, 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
0 comentários:
Postar um comentário