python反编译

python反编译

  • .exe → .pyc

    pyinstxtractor

    https://github.com/extremecoders-re/pyinstxtractor

    https://github.com/countercept/python-exe-unpacker

    命令:

    pyinstxtractor-ng [filename]

    python pyinstxtractor.py [filename]

    转换出来的主程序格式不对,还需要对其进行手动修复。

    需要在该文件起始位置加上8个字节的pyc头,由4字节的magic和4字节的时间戳组成,其中magic会因为python版本的不同而不同,有个技巧就是,查看struct文件的magic,直接复制过去,保存为.pyc文件。

    Pyinstaller

    https://github.com/pyinstaller/pyinstaller

    命令:

    pyi-archive_viewer [filename]

    python archive_viewer.py [filename]

    两者对比文件头,添加12字节:

    ? x src

    ? x struct

    打开文件夹:

    ? o PYZ-00.pyz

pyc文件恢复(去混淆)

https://www.52pojie.cn/thread-912103-1-1.html

常见版本幻数

1
2
3
4
5
6
7
8
9
10
11
12
Python 2.7: 03 f3 0d 0a
Python 3.0: 3b 0c 0d 0a
Python 3.1: 4f 0c 0d 0a
Python 3.2: 6c 0c 0d 0a
Python 3.3: 9e 0c 0d 0a
Python 3.4: ee 0c 0d 0a
Python 3.5: 17 0d 0d 0a
Python 3.6: 33 0d 0d 0a
Python 3.7: 42 0d 0d 0a
Python 3.8: 55 0d 0d 0a
Python 3.9: 61 0d 0d 0a
Python 3.10: 6f 0d 0d 0a

恢复bytecode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#安装环境
import dis, marshal, sys

header_sizes = [
# (size, first version this applies to)
# pyc files were introduced in 0.9.2 way, way back in June 1991.
(8, (0, 9, 2)), # 2 bytes magic number, \r\n, 4 bytes UNIX timestamp
(12, (3, 6)), # added 4 bytes file size
# bytes 4-8 are flags, meaning of 9-16 depends on what flags are set
# bit 0 not set: 9-12 timestamp, 13-16 file size
# bit 0 set: 9-16 file hash (SipHash-2-4, k0 = 4 bytes of the file, k1 = 0)
(16, (3, 7)), # inserted 4 bytes bit flag field at 4-8
# future version may add more bytes still, at which point we can extend
# this table. It is correct for Python versions up to 3.9
]
header_size = next(s for s, v in reversed(header_sizes) if sys.version_info >= v)

with open('main.pyc', "rb") as f:
metadata = f.read(header_size) # first header_size bytes are metadata
code = marshal.load(f) # rest is a marshalled code object

dis.dis(code)

pyc文件解密

在反编译python生成可执行文件exe时,引用的类库文件经常遇到使用Crypto模块AES算法加密,解包生成的并不是pyc文件,而是加密的pyc.encrypted文件,它无法查看编译。

第一步,获取Crypto的key,这是打包时由开发者指定的。解包完成后将在根目录形成名为 pyimod00_crypto_key.pyc 的文件,将它转为py文件即可查看key文件。

第二步,编写解密处理的脚本代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import glob
import zlib
import tinyaes
from pathlib import Path

CRYPT_BLOCK_SIZE = 16

# key obtained from pyimod00_crypto_key
key = bytes('MySup3rS3cr3tK3y', 'utf-8')

for p in Path("PYZ-00.pyz_extracted").glob("**/*.pyc.encrypted"):
inf = open(p, 'rb') # encrypted file input
outf = open(p.with_name(p.stem), 'wb') # output file

# Initialization vector
iv = inf.read(CRYPT_BLOCK_SIZE)

cipher = tinyaes.AES(key, iv)

# Decrypt and decompress
plaintext = zlib.decompress(cipher.CTR_xcrypt_buffer(inf.read()))

# Write pyc header
# The header below is for Python 3.8
outf.write(b'\x55\x0d\x0d\x0a\0\0\0\0\0\0\0\0\0\0\0\0')

# Write decrypted data
outf.write(plaintext)

inf.close()
outf.close()

# Delete .pyc.encrypted file
p.unlink()

在前一步中获取的key是必须文件,否则无法进行解密;对于不同python版本头文件(header)也不相同,2.7~3.10如下:

1
2
3
4
5
6
7
8
9
10
11
12
Python 2.7: \x03\xf3\x0d\x0a\0\0\0\0
Python 3.0: \x3b\x0c\x0d\x0a\0\0\0\0
Python 3.1: \x4f\x0c\x0d\x0a\0\0\0\0
Python 3.2: \x6c\x0c\x0d\x0a\0\0\0\0
Python 3.3: \x9e\x0c\x0d\x0a\0\0\0\0\0\0\0\0
Python 3.4: \xee\x0c\x0d\x0a\0\0\0\0\0\0\0\0
Python 3.5: \x17\x0d\x0d\x0a\0\0\0\0\0\0\0\0
Python 3.6: \x33\x0d\x0d\x0a\0\0\0\0\0\0\0\0
Python 3.7: \x42\x0d\x0d\x0a\0\0\0\0\0\0\0\0\0\0\0\0
Python 3.8: \x55\x0d\x0d\x0a\0\0\0\0\0\0\0\0\0\0\0\0
Python 3.9: \x61\x0d\x0d\x0a\0\0\0\0\0\0\0\0\0\0\0\0
Python 3.10: \x6f\x0d\x0d\x0a\0\0\0\0\0\0\0\0\0\0\0\0

第三步,执行脚本文件,即可将加密的pyc.encrypted文件转成不加密的pyc文件。