题目给了exe或者直接给了pyc文件
处理exe(解包为 pyc 文件)
有的题直接给 .exe 文件,这通常是 PyInstaller 打包的 Python 程序。
我们需要先将 .exe 提取出 .pyc 文件夹。
使用工具:pyinstxtractor.py
使用
1 2
| python pyinstxtractor.py target.exe
|
运行后会生成 target.exe_extracted/ 文件夹。
在里面可以看到 .pyc 文件和 PYZ-00.pyz_extracted/ 目录。
注意事项
- 反编译出的
.pyc 文件魔数要正确:
- 每个 Python 版本的魔数(magic number)不同;
- 可以从同版本的
struct.pyc 或官方魔数表复制。
| Python版本 |
魔数字节 |
| Py2.x |
8字节(magic + timestamp) |
| Py3.0–3.2 |
12字节(magic + timestamp + size) |
| Py3.3+ |
16字节(magic + flags + timestamp/hash) |
- 一定要用与原程序一致的 Python 版本运行
pyinstxtractor.py,否则解包后的 PYZ-00.pyz_extracted 可能是空的。
处理 pyc 文件
解包后获得 .pyc 文件,我们需要将其反编译成 .py 源码。
工具一:uncompyle6
工具二:pycdc
1
| pycdc.exe yourfile.pyc > yourfile.py
|
工具三(针对高版本)PyLingual
注意事项
.pyc 反编译出来的 .py 文件可能:
while、if 分支混乱;
- 列表、条件逻辑丢失;
- 要人工修复逻辑结构。
- 若
.pyc 文件加入了花指令(junk bytecode),需手动修复。
pyc 字节码(Bytecode)分析
当题目直接给了 pyc 字节码或源码无法反编译时,可以手动分析。
工具:Python 内置 dis 模块
1 2
| import dis dis.dis(your_function)
|
1
| python -m dis yourfile.pyc
|
手动还原思路
- 参考 u-tools 的「程序员手册」搜索
dis 指令说明;
- 每个 Python 版本字节码结构略不同:
- Python 2:每条指令 3 字节/1字节
- Python 3:每条指令 2 字节/1字节
通过分析 opcode,可手动还原出 Python 逻辑。
pyc字节码花指令处理
花指令即插入无效或干扰反编译的字节码。
步骤流程
识别花指令
定位花指令
修改花指令
修改code object总长度
pyc文件结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| typedef struct { PyObject_HEAD int co_argcount; int co_nlocals; int co_stacksize; int co_flags; PyObject *co_code; PyObject *co_consts; PyObject *co_names; PyObject *co_varnames; PyObject *co_freevars; PyObject *co_cellvars; PyObject *co_filename; PyObject *co_name; int co_firstlineno; PyObject *co_lnotab; } PyCodeObject;
|
其中我们关注的就是co_code字段
1 2 3
| import marshal code = marshal.load(open("1.pyc", "rb")) print(len(code.co_code))
|
根据长度在winhex搜索这个数值,找到的第一个,就是长度,code object长度用四个字节存储,这四个字节前是marshal中byte对象的标识,一个’s’(0x73),这四个字节后就是code object内容
PyArmor 加壳与加密分析
PyArmor 是常见的 Python 加密与授权保护工具。
PyArmor是把源码/字节码用加密包装并在运行时由专有 runtime(如 pyarmor_runtime.pyd)解密后执行。
因此你看到的 .py 文件通常只包含一个启动壳(stub),它调用 runtime,并把加密的二进制 blob 交给 runtime 解密并加载——这就是你看到的 __pyarmor__(__name__, __file__, b'...')。
原理图:
![pyarmor原理]()
工具:PyArmor
1 2 3 4 5 6 7 8 9
| pyarmor init --src=src --entry=main.py my_project
cd my_project pyarmor gen
pyarmor gen main.py
|
常见特征
解题:
GitHub - Lil-House/Pyarmor-Static-Unpack-1shot: ✅ No execution ✅ Pyarmor 8.0 - latest 9.1.x ✅ Universal ✅ Statically convert obfuscated scripts to disassembly and (experimentally) source code.
下载release
打开文件夹
![pyarmor]()
如果runtime不用显式指定,在这个目录下用
1
| python shot.py 被混淆程序所在文件路径
|
如果题目也把runtime给你了,那就用下面的包含.pyd的命令
1
| python shot.py -r /path/to/pyarmor_runtime.pyd /path/to/obfuscated_scripts
|
有的程序为了防御这种攻击,会把PyArmor 加密文件的标识符去掉,我们给它在开头加上就行
比如:
1 2 3 4 5
| from pyarmor_runtime_000000 import __pyarmor__ __pyarmor__(__name__, __file__, b'\x00\x03\r\x00\xf3\r\r\n\x80\x00\x01\x00\x08\x00\x00\x00\x04\x00\x00\x00@\x00\x00\x00d\n\x00\x00\x12\t\x04\x00y"\x04"\xc0\xa4\t\xad2\\\x17\x13D\x0c\x81\xeb\x00\x00\x00\x00\x00\x00\x00\x00[\xc1%I\xfe56\xaa\x1cc\xc9\xf2E\xa3\x86\xc1\x88=sB\xf08\x14\'U\xfc\t\x10\xc7%\x1a\xb6\xc4\xa6QvBDK\xfd\xf5\xb0&NZ&n\xe41@VC\x11&j\x8fs\x18\xd6\xe3iQ\xf8_\x9e\xdb\xfa\x92~\xfb\xe7f\xdf#\x00\xa3fv\xb5\x9b\xa13\x99 \x83\xc3\x93\xf2\x1d=\xe2-\x93zb\xb0\x10&\n\xc4\xb8\xb9\x83\x99^\xd0\x94G9\xb9\xa0D\xedX\xff\x01\xbd>\xa9\xe8\xf03]\xdd7\x87\x84\x13;%\xae\xd5\xe8\xcc\xd1\xa8\xe0\xda\xc2\x9f0\x02\xb88J\xac\xe5pS-\x14\x86\xdb\xc4/\xea\xa8\xd3\x05\x04V\x94\xfd_\xf3\xd7\xf1\x7f#\x8d\x9e\xd71\xb1\xfd\xb4\xb9\xd9\xf7>y\xfb\x12\x8f\x9a\xe1\x8d\xe2X\xf9\xcb\xe2\xe3\xa2\xd7\xfc\xfa\xa0\x91n\xb9\xbc\xc0\xf0#\xfd\x83\xad\xb2\\K\xee\x88R\x80\xff\xcf\x93\x8f\x13\xe5\x01`\xbe$)\xad\xa6\xdc;\x8e\\\x0c\xae\x87yJ\xd9\xedF/P\x89\xe8\xb6\xebI\xb1u\x9b"pt\xc3v\x19 \x83\xae\x89\x07\x1fmR\xd9AX4C\x05fa\x81\xf5\xab\xf5\x9a\xfe\xe2\xa2\x88HX\'\x9c\x9f\xea\x1d\x7f\xeb\n-\xa3\tFDsM\x99MF\xb6\xa9.\x1a\x86n\x95\xc4\x85f\xf8\x90\x076\xb5_\xeb.\xddn\x94f\x12\xb7\x04\xbb_\xd1\x87XO\x054<\x9a\\1%H\xe4eBo)\xf2\xf5\xc6\xe1\xbd\x10i\xe2\xefD\x98\xdc\xea\xf0\xc0\xd1\xdc\x8c&J\x7fX\x87\xc5\xfb\x05d\xb2\xc4\xc5\x0b\xfc\xf8\x9e0R\xb2{em\xb2\x98\\\x05\xd3\xbe_\xf3\x07\x04\x05\x82p\x1b\xa7\x19\xf2\x02\x8e~\x12\xa3;\xb1\xfeb\xdd\x83\x04\xf4VM\xba\xe1}\x8ff\x91\xa0\x94}\xef\x12(\xdf\xa9j\xd4gA\xaa\tB\xf0\\=m%\xa4B\xbd\x1b\xe2AD\xfb\x98\xdf\xfd\xf6\xe5\x80-B\x90a\xf4E_\xa7\xec\x8eBx\xb8\x80\xef\x08X]\x0b\x18\xa3\xab\xf4\x8b\xe8\t\xfd\xd9o\'\x96<\x1d\xf6\xdb\xe1\xe4\xb5\x8ec5o\x85\x92\xf4\x1e<g\xecJI\xf3\x01\x90\x1e\xb7\x00\xda\t:}\xc7I\x8c\xa5\x01\x13b!\xf2\xcd6JUy\xc5\xfc\xd8Q\x8f\x13\xdb\x0f\xfe\xbf\xd7\xe8\x992\xe4\xdf\x17\xd0\xef\xa5QC\xa6przcd\x9f\x8aS\x963\xa7\x0c\xc4\x97\x11j\xa8\xcf\x19\x1a\xc6\xff\xb1\x1d|\xe9n\x129\x19\xad\x08\x80\xc9\x98=q6\xc7U\xc8\x12\xe2\x0bSt\xc9,\xb4\x07\xd5X\xad\r}\xb1 \xe4\xf6\xfb\xb6m\x11\xf9\x1e\x8c|\xd7\xf5\xd0\x99\x01t\xa39\xd0&X\xa8\'^\xa5\xd1\x98{\xed\xca\xe1\x95E\x08&\xd1\x0e\xecF^\xc6\xad\x15h\xadV\xfe~\xb2<Xe\xa2$\xf6\x82\xa8Wk\xd1%1\x9d\xcc\x08\x88\xfe\xbck\xb6t\xe4\xc0\xc8F\xf7\xa3\xee\xd8UMb(\xeb\xe9#\xfa( \x97Kp\xe4\xe6\x13>\xbcZ\xab\x89\x99Uy\xe5\xf7s\xcda\x8a\xcb`\x0f\x02]e\xc6:\x84\xf4J\xda3n\x1a\x01?\x83\x98L\xf2P\xae\x10\x80A\xf7\x86\x17F7\xb3 7\xe7\xfb\xed\xed\x0c"v\'\xf0W\x97\xc7\xdaMe=\xc1\x1c\xf9\xc3\xe2\xa3\x08\x0e\xe0\x0c\xd9\x10\xd7\x0e\x7fJ\xf5\x12\x94\x19\xbf\xf3\xc4K\xe1\xb2b\x07\xa6:\xa6a\x9eW\xf9B\x0e\xab\x9c5\xd4B\xdeN!\xeb\xa9\xb5K\xf4\xcan\xed\x04\x7fp\x87\x95\x14\x18\r-\xa9R\x95H\x9f++\x8f!!\xb5\xc8W\xbeG\x1a\xb0\x1c\x1b\xcdb\xb2y\x89\x16\xab\xbe\xdb)0\xe6\x07\x8a\x1fv\x02n\x83\xfd\x88q\xf3\xa8\xaf4\x84t\x0e\x95I\xf7\x11\x02Q\t.K\x10_\x88\x89\xf4\x98\xaf\xeb\x9d\xc6\x02"u\xb6\x1dbOv\xc6\xfed%\x83-3\x8e\xde\x0ed"\x16\x95j@\x07R\xd5PumB@"\xa4`\xa5\xe7*V\x12\xf2\x90A\x95\xd9\xaal\xb8\x1a\xe6\xcc\x0b1\\\xca\x03m\xa1\xaf;c{\x14\'\x14\xb4\xb2\x13\xf2\x9e\xe6\xaf\xcf<\x86\x16\xc6>\x86\x01?\xf6J\xa4\x9b/\xce\xd4\xec+\xc9\xf6Y\x9f\xef\x08\x88\x05\xe1B\x12\xc4\xca4\x03?\x9b\x9e\xa9r\x99\xeb\x08\xc9Tfm\xb9,\x8e"\xfcK\xd3\x9dx\x97\x0ff\x15\x08\xfb\x89\xdd\xd0Z\x85W?\x9c\xb9\xd3\x10\xaf\x9dG\x96f\xae ed\tu\x0e\x8e\xccX\xe1\xe4\x82\xbc\xe9\xeb\xa2\xd6\xa44\xf4\xeb]p\xad\xa0k\t\x84\x0f\xba\xed\xc8\x18\xbc\xd3\xc02\x97\x8a\x97B\xd69\xc4\x8c\x11\xca\xeb*\xbed\xefv\x95\x96H\xf7\xb4\xb6\xbd\xfb\xc6q\xc7\x90,eCkf\xc5\xb5\x91\xb1J\x85\xab\xc4\x06\xff\xdb\xae\x03]Cd\xa1\xe4\xdf\x80\xddk2\xb4\xe7\xac\xac\x05i\\6\xf8:\x87Ps\xbcM\x06\n\xce\xdc\xafo\xc1\xd8\xf4\xd5p\x8d\xe1\xa3P\x99e\xc6Z4t\xc5\xdc\xc02.\xa8\x08\x97\xbcJ\xc4ga\xd1\xe6\xc5t\xf1\xf9\x1a\t\xc3\x95\x8a\x05\xea\x99\xe3^\xe9\x05r\xda\xb5\x97\xd4\xbd\x01\x9f\x86\xae!?\x13\xf3\xeb\x93\xc5\x80v\x01F\x96\xd9\xb6\xf6C\xb0w\x9ciH\xbc\xd3\x1c\xa5R\xd0\x08;d\xd8\xac\xbf\x1c\xff\xfca\xf2Ey\xac\x00rY\xf2A\xeb\r\x9f\x8a\xcf\xb6\xa97\xf4\x82\x12\xee,<\xaa\xec\xa7RY`\xb1\x1e\xe3\xac,\xb8\xee\xe2\xc0\xc47\x9e\xf4B\\\xb1?\xd7[I\xf8\x19\xd5\xbe\x87(S\x9e$\xffq\xb0p\x8e2\x85XCE\x84\x8c\x97\xebw\xa3\x00\\_\x13\xf5Jw5\xfeK\x95\xb2\xbb\xd4\x92F\xa0\x9a\xb54\x97\x03\xaa\xe0\x98q\x0b\xdd\xbd\xf3\xe3\xfc\xfdU\x1aH\xbc\x13\x00gP\xe6\xe7\xb3\xa6\x91hP\xd3\'r\x8c\xab\xa39\xe6\x14\x0f\x15;\tt\x9bf\xa6\x7f\xc8\xe5\xf7\x16\x19\xf8\x9a\xc0n\x7fa\x1c\xf5I\xefU\xf5O\xfb\xfc\xad\x05\xadYB\x17\xb3~xL\x8d\x9f\x05\xc0\xcaWp\xf5x\xd4\xcf\xddX\x96\xad]P[`\x9ei\xfdap\xe2\xf4\xc2w\xf2\xbc\xd1 \xff\x13\xccz\xd0&+\xc8\xa00\xe2\x11\xdb\xa2.\x961\xa2\x0fs(R8\xdd\x898Ve}\x8f>S\x83\x14\x83P\x8bA@s\xe5\x8d\xda\xb2\xf9\xba\x00\xb3\xd0Q\x1d\xaaZ\xae\x03\x9e\x92=.\x13|\xb1\x8dK4\x96A\xa5\xdeM\xbc\xd9dOxB_\xc5\x10\xbdp\xccD1\xc1$d\xdc8\xc5\x18\xd3e\x1a)[s\xecn\xd2htl\x8c\x1a0y\xd8a&Q\xb5\xd7\xcd\xb2>\x1b\xe1\xfdKA\xeb^v{#\xba\x0eo\xef\xf5\x1c,\xe5X\x05\xc7s\xf6\xa7\xfa\xd9\xed\x87\x18\xa0{C\\\xc4\xef6^\xcf%\xc9[\xb7\x87\xb0\xac\tN\xd1\x04\xaa\xa2\x10\xa4kg\xa1Pn\xcb\xfd\xfflL]\x92h}<\xa2\xfej1\x83\xe6\xfb\x97\x9c\xae\xe8\x88\xf1\xbdN\x0f\xde\xfd\x8d\xe6\x87^S\x94\x0c\xad\xee%\t\xb5\xd6\xfb\xac\xb7d0\\\xe8\xb9\xfe\x04\xc2\x92"@\xd7\x08GY%\xdf%\xba\x83\x18\x17\x7f\x00\'\x1c\'/\xa7\x7f\xe2\xf7\xc8\x9b 2(\x9cO\xa7\x7fI\xc4\xe7l\x0b\x0f\xda\xe9uR%\xaeb\x9dm\x85\xab\xbf\xe7\x95\x88\xc9\xff\xe0\xd2\x85\xaah\x0e\xd3\xf8\xe2\x89\xd1\x96ix\xe7aic\xb8\x10\x08\x9cJ,\xc6o\xbb\xe0(\x10}\x0c\xb9\x9a\x11\xdf\rR\x9c\x00\x95\x88xO\xeb\xba\xbe+Qi[`u\x86\xce\xf4\xe2C\xd0\xb7\x00_#j\x19.{\xeb4\xecc\xb8N^K"\xab\x0b\x9e\xd7u\xe1\x1c\xe8\x1dL\xfcC\xf2\xbfS\xf5\x95\x00\xe7>\x0e\x9ew\xf6\x83\xee\n\xd1\x1e\xb5\xceV\x93\x9clg\xeaV\xab\xf9\xec\xc7\x01\x9f\x0c\xe5\xd1h0\xafbMC\x13\xd8zJ\xab\x81\x7f\x88|\x0b\x95<(\x15d\x90\xb7\xd7\x9e,\xb5\x14\n\xfe4\xe3\x1a\xa5\x9b}\xf1\xa0\r\x96.5\x99\xce\xc9E\xed\x97\xa3\n4O\xb5\xdb\xc8\xbf\xb4\x88\xdb\xd5V\x0f\xfd\xda\r\x1d9x\xe1\xa0\xc5\xc6L\x08\xda\xc8\x88\x87Z?\x9d\xa8\xd5\xcdI\x16\xb5Q`>\xb8\x1fSbN\xd9\xf2F\x01\xeb\x07\xf2.9\x87\x8e\xab\xb6\x8e\x1e\x8b\x04\xc3\xcb?\xd3F\x18g6r\xc4\x1dh$\xc9|w\xe5+/\xb4X\x94\x1ax\xf5a\x05::\x1c^\xa1\x8e\x08E\x84\xde)2m\x9cD\xb7UBsE\x82\x01\xf6\xa8\xd6E\'\xe4\x7f^\xdd\xf7\xbb4\xb0*}\x12=\x1f3\x1b9\x9d\xd1\xfc\x8c\xbd\x9bC\x83Z+\xdb\x06\'_\xf2\xb9\xd6\x8d\x8e\xa0M\xc8\x8a\x13\xfeDC\xc8\xa0\x96?h\xba\xb7"\xb7\x15x\xd2\xb0P\xc9\xc3c~\xdd`\x81\xb8JKJ\xe0\xcd\x80\xe6/}\xce&\x01h\xce\xa6\x8e\xfa*\xf4(l]\xc5\xb1a{"\xbe\xc9\xf3\xd3\xdc\xad\xe5!\xd3\xfd"hR\'&\xaf\xad\xf6\xb5\x04\xb5\xe1\x98g8\xcc\xe3f\xa9\x8d\'\xd0\xc4\x94\xf2\x865\x82>U\xdba\x98:\x02V\xad\xfb\x81\xb4PDy<\xdd\xeb\x01\xa9\x99^)\x8bK\xc8E\x04AM\x88\xd4\x99H\x9a\\\x8c\x13OIxmZ\xc5\x19\xe8\xc1\x19\x82E\xf7\x01SNr\xf70\xa4\xe1\x92\xa6\xed\x1d\tZ\xbfD0\xc6\x80\xe5\xf8\xdd\xa9\xd8\xe3\xa2\xb1y\xc5;\x9d\xd6c\x8as\x07\x0fBa"\xc2Mw\x9f,w\x1b\xc7\x98\x8a\xd3\x93\xb0\'\xdb\xact\x99\x11Vq<\x8d$c\x8bX\xd3\x04\x9d\x92\x17\xa9\x91}\x9fc\xc5x8\xc3\xea\xb2><\x88krU\x055\x1cN\x8d\xf7\'S>\x14\xc9pV\xbe\x1fj\xed\xa5\t\xbc\xbfD\xed$\x88\x00n7<\xf5\xd6\xe9h\xaf@\x94\xfb\x1am\x9a\xa4\x1a\xf5Z\xcb\xf9\x80\xb6\x9d\xd6\xf1\x9f\xce\x1e\xaf\x97=\xdcrj\xcfh\xc2\x1dS\xdd\x18\x15\xb5\xc3O=\x0e\xba\x07\x0b\xb1J\x01A\x7fl\xcf?\'\xb0e\x9aO\xed2\xf7\xf4\xa1\xd3\xdf\x11_H\x8f\x8f\x03\x7f\xeej\x954\r\x1aA\xdanM\x8f\xef\x9dq\xc6\xf5\x16E3\x10\x80\x88\xa0\xd0Q\xfc\xf1\x8b\xf1\xcf\xb5\xed\xa54P\x18\xb4\xc9\x02:}\xc6\xf8\x17\xaa\x8a\xcd\xb88\x03\xecI\xe7\x9a\xef\xab9\x1e\x7f9\x1ah\xef\x1c\xef\x13m\x9bfqb\x1a\x8a\xe1\x02\x8e\xf9\xa1\x9d\x90\xb2$\xcbS\x10QS\x96\xa8\xea\x8bx\xc11\x93\x1c\xb9\x0b\x80~\xde\x9b\xc4\xa2N\xe0N~Q\xaf\xc0@\x01\x9d=[@\r1T\xec\xb9\xd3\xe0q\x94\xd1\xf7\x983\x10*\xad\x1a\xd3hs\x98}\xd8\xfd\x05\n\xd1u\xf7d\xd2\x9a\xc6\x95\xba\x0cJ\\GSC\xbb\'\x18\x1f\xaa\x12/|q\x0b\x81\xb4)\x15}\xa0\xf9S\xb3\xcb\x86\xdcQ)2OH\x11\xde.\xcb\xe1Z!DA\xd3Q<N>\xa0k\n-Z)r5\xa3k\x9f\x91&\xc7\xb8\xfe\x1814\x16xh\x97^\xe3\x03:\xb0f\x14\xce\\\x85\xe4k_\xadf2\xe5\xb1`\x8f8\xfc\xf6#x\x1f\x98\xdex7+\x98=G\xb1M')
|
开头没有标识符,根据运行环境可知特征码是PY000000,我们帮它加上PY000000,工具才能正确识别这个pyarmor加密
1 2 3
| from pyarmor_runtime_000000 import __pyarmor__ __pyarmor__(__name__, __file__, b'PY000000\x00\x03\r\x00\xf3\r\r\n\x80\x00\x01\x00\x08\x00\x00\x00\x04\x00 后面的省略
|
![pyarmor成功]()
成功后会出现这种图形化界面,现在我们去到被混淆的文件发现多了很多文件,后缀是.py的文件就是解密后的py源码
总结
| 阶段 |
工具 |
命令 |
作用 |
| exe 解包 |
pyinstxtractor.py |
python pyinstxtractor.py target.exe |
提取 pyc |
| pyc 反编译 |
uncompyle6 |
uncompyle6 -o ./out ./file.pyc |
还原 py |
| pyc 反编译 |
pycdc |
pycdc file.pyc > file.py |
还原 py |
| 高版本 pyc |
pylingual |
python pylingual.py file.pyc |
分析 py312 |
| 字节码分析 |
dis |
python -m dis file.pyc |
查看汇编 |
| 修复花指令 |
WinHex |
手动修补 |
去花 |
| 加壳识别 |
pyarmor |
python shot.py -r /path/to/pyarmor_runtime.pyd /path/to/obfuscated_scripts |
加密分析 |