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.
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
.pthfiles — default security tooling does not inspect them. - Watch the process chain
Python -> Bash -> nc(or evenPython -> Bash), which an EDR would likely flag; default solutions will not detect execution inside.pthfiles.
To enumerate candidate paths an attacker (or you) would target:
python3 -m siteReproduction 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.pthCaveats
- Volexity did not release the exact
system.pthcontents; the author's.pthpayload (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
.pthfiles — non-hidden.pthexecution 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
Related in Offensive Security