Skip to content

Back to the catalog

dfir.chwriteup

Analysis of Python's .pth files as a persistence mechanism

dfir.ch dissects a Linux persistence technique first spotted by Volexity in a backdoor (update.py) that drops a malicious .pth file into Python's site-packages/dist-packages directories; because Python executes any line in a .pth file starting with 'import', the attacker gets code execution every time any process on the box imports a module. The author reproduces the technique end-to-end, building a base64-encoded netcat bind-shell payload embedded in a .pth file across multiple site-packages locations (including user-site under /root/.local), and discusses detection gaps (most DFIR tooling doesn't check .pth files) plus a related PYTHONPATH-hijacking technique from Itzik Kotler's SafeBreach talk.

TL;DR Deep dive and PoC reproduction of the .pth-file Python persistence/backdoor technique (as used by a Volexity-discovered implant), with notes on detection blind spots.
Document

What it is

A DFIR.ch walkthrough of the Python .pth persistence technique used by the update.py backdoor (MD5 0c1554888ce9ed0da1583dbdf7b31651, per Volexity), plus reproduction and detection guidance the summary doesn't carry.

How it works

Since Python 3.5, any line in a .pth file (in a site-packages/dist-packages directory) that begins with import followed by a space or tab is executed at interpreter startup. The malicious file must begin with import, or Python ignores it. Beyond the base64 payload, update.py installs a self-healing mechanism: a signal handler that rewrites system.pth from an in-memory copy if the file is ever deleted, so removing the file alone does not evict the backdoor.

def stop(sig,frame):
    if not os.path.exists(systempth):
        with open(systempth,"w") as f:
            f.write(content)

Preconditions / Affected

Python ≥ 3.5. Attacker needs write access to any directory on sys.path. The author notes the hardcoded /usr/lib/python3.6/site-packages/ may not exist; use python3 -m site to enumerate real targets. On the test Ubuntu box only dist-packages existed, and the user-site path (/root/.local/lib/python3.12/site-packages) was writable and created on demand. Any python3 invocation — even python3 -c "print('x')" — triggers execution.

What to do

The author's stated detections:

  • Monitor for newly created .pth files — default security tooling does not inspect them.
  • Watch the process chain Python -> Bash -> nc (or even Python -> Bash), which an EDR would likely flag; default solutions will not detect execution inside .pth files.

To enumerate candidate paths an attacker (or you) would target:

python3 -m site

Reproduction payload (lab only) written to a .pth under dist-packages:

echo "import os; os.system('echo "<b64>" | base64 -d|bash')" > /usr/local/lib/python3.12/dist-packages/malmoeb.pth

Caveats

  • Volexity did not release the exact system.pth contents; the author's .pth payload (an nc bind shell on port 45555) is a lab-built reconstruction, not the original.
  • cpython issue #113659 discusses the risk; the maintainer comment quoted rates severity as "not very large" because activation requires a Python invocation, and proposes only to forbid hidden .pth files — non-hidden .pth execution remains intended behavior, so don't expect a fix to close this.

Organisations named here

  • Itzik Kotler
  • SafeBreach

Technologies named here

  • Netcat
  • Python
  • VirusTotal
  • python3 -m site
  • Ubuntu Server
  • Linux
  • Volexity

Techniques named here

  • .pth file code execution persistence
  • Self-healing/anti-deletion backdoor
  • Base64-encoded payload embedding
  • PYTHONPATH hijacking / module backdooring
  • Bind shell via mkfifo/nc