ESET Research has discovered a cluster of malicious Python projects being distributed in PyPI, the official Python package repository. The threat targets both Windows and Linux systems and usually delivers a custom backdoor. In some cases, the final payload is a variant of the infamous W4SP Stealer, or a simple clipboard monitor to steal cryptocurrency, or both. In May 2023, we reported on another cluster of packages we found on PyPI that delivers password and cryptocurrency stealing malware, but the two clusters appear to be different campaigns.

Key points of this blogpost:

  • ESET Research discovered 116 malicious packages in PyPI, the official repository of software for the Python programming language, uploaded in 53 projects.
  • Victims have downloaded these packages over 10,000 times.
  • Since May 2023, the download rate is more or less 80 per day.
  • The malware delivers a backdoor capable of remote command execution, exfiltration, and taking screenshots.
  • The backdoor component is implemented for both Windows, in Python, and Linux, in Go.
  • In some cases, the W4SP Stealer or a clipboard monitor that steals cryptocurrency, or both, is delivered instead.

PyPI is popular among Python programmers for sharing and downloading code. Since anyone can contribute to the repository, malware – sometimes posing as legitimate, popular code libraries – can appear there. We found 116 files (source distributions and wheels) from 53 projects containing malware. Some package names do look similar to other, legitimate packages, but we believe the main way they are installed by potential victims isn’t via typosquatting, but social engineering, where victims are walked through running pip install {package-name} to be able to use the “interesting” package for whatever reason.

Over the past year, victims downloaded these files more than 10,000 times; see Figure 1.

Figure 1 Malicious package downloads from PyPI using pip
Figure 1. Malicious package downloads over the past year from PyPI using pip

Infesting PyPI

PyPI packages can take two forms: source packages, which contain all project source code and are built upon installation, and prebuilt packages (called wheels), which may contain compiled modules for a specific operating system or Python version. Interestingly, in some cases the Python code in the source distribution differs from the built distribution. The former is clean, while the latter contains the malicious code. Python’s package manager, pip, favors a wheel when it’s available rather than a source distribution. As a result, the malicious one gets installed unless explicitly requested otherwise.

We have observed the operators behind this campaign using three techniques to bundle malicious code into Python packages.

Malicious test.py module

The first technique is to place a “test” module with lightly obfuscated code inside the package. Figure 2 shows a test.py file with a function called graby being defined and then called. Notice that the function handles both Windows and Linux systems.

Figure 2 Lightly obfuscated code inside test py
Figure 2. Lightly obfuscated code inside test.py

This test module is imported in the middle of the source code of the package’s main module (__init__.py), so that the malicious code runs whenever the package is imported. Figure 3 shows a module that masquerades as a screenshotter and imports the malicious test.py.

Figure 3 main module importing malicious code
Figure 3. In some packages, the main module imports the malicious code

PowerShell in setup.py

The second technique is to embed PowerShell code in the setup.py file, which is typically run automatically by package managers such as pip to help install Python projects.

Figure 4 shows a PowerShell script that downloads and executes the next stage.

Figure 4 malicious PowerShell script embedded in setup py file
Figure 4. In some packages, a malicious PowerShell script is embedded in the setup.py file

This PowerShell script downloads transfer[.]sh/eyRyPT/Updater.zip into a temporary directory as update.zip. The script then decompresses the ZIP file into C:\ProgramData and deletes it from the temporary directory. Next, the script runs the pip program to install dependencies. Finally, it runs the Python code in C:\ProgramData\Updater\server.pyw.

This technique only works on Windows and will fail to infest Linux systems.

In the package metadata from Figure 4 , you may have noticed that the author of the package is billythegoat356. There have been numerous reports associating this nickname with malicious activities, including an article from Phylum, where they reveal Billy’s potential link to W4SP Stealer.

Just malware…

In the third technique, the operators make no effort to include legitimate code in the package, so that only the malicious code is present, in a lightly obfuscated form.  Figure 5 shows two pieces of malicious code for Windows being written into temporary files and then run with pythonw.exe, which is used instead of python.exe so that the code executes without opening a console window.

Figure 5 only lightly obfuscated code
Figure 5. In some packages, only lightly obfuscated code is present

The next stages are Python packages, scripts, or binary files downloaded from either Dropbox or transfer.sh.

Persistence

On Windows, persistence is achieved most of the time via a VBScript Encoded (VBE) file, which is an encoded VBScript file, written to %APPDATA%/Pythonenv/pythenenv.vbe.  Figure 6 shows cmd.exe hiding the directory %APPDATA%/Pythonenv, running pythenenv.vbe, and then scheduling the VBE file to be run every five minutes under the task MicrosoftWinRaRUtilityTaskB.

Figure 6 Persistence on Windows systems achieved with scheduled task
Figure 6. Persistence on Windows systems is achieved with a scheduled task

On Linux, persistence is achieved by placing a malicious desktop entry, mate-user-share.desktop, in the ~/.config/autostart/ directory, as seen in Figure 7 . Files located in the autostart directory are executed on each system startup. The desktop entry uses the name of a MATE subproject for its filename, but it’s only to reduce suspicion because it has nothing to do with the desktop environment.

Figure 7 Persistence on Linux systems achieved via autostart directory
Figure 7. Persistence on Linux systems is achieved via the autostart directory

Figure 7 also shows the module downloads dl.dropbox[.]com/s/u3yn2g7rewly4nc/proclean to ~/.config/.kde/.kdepath. This is probably an effort to impersonate a configuration directory for the KDE Plasma GUI for Linux.

Launching the mate-user-share.desktop file in turn executes the downloaded .kdepath file, which is the Linux executable file containing the backdoor component.

Final payload

Typically, the final payload is a custom backdoor that allows remote command execution, file exfiltration, and sometimes includes the ability to take screenshots. On Windows the backdoor is implemented in Python.

Figure 8 shows the backdoor creating a TCP socket connection to blazywound.ignorelist[.]com on port 6001. After sending the hostname, MAC address, and username to the C&C server, the backdoor will directly handle some commands or run any other command in a separate process and send back the command output and any error information to the server.

Figure 8 Python implementation of backdoor
Figure 8. The Python implementation of the backdoor

On Linux, the backdoor is implemented in Go; see Figure 9 .

Figure 9 The Go implementation of the backdoor
Figure 9. The Go implementation of the backdoor

In some cases, instead of the backdoor the payload is a variant of the infamous W4SP Stealer, or a simple clipboard monitor that steals cryptocurrency, or both. Figure 10 shows a clipboard monitor targeting Bitcoin, Ethereum, Monero, and Litecoin cryptocurrencies. The malware uses the legitimate pyperclip package to check clipboard content for wallet addresses. If found, the malware copies an attacker-controlled address to the clipboard in the hope that the victim pastes this address instead in a future cryptocurrency transaction.

Figure 10 Simple clipboard monitor
Figure 10. A simple clipboard monitor implemented in Python

ESET products detect the malicious Python packages as variants of Python/Agent and Python/TrojanDownloader, and the backdoor as Python/Agent.AOY or Linux/Spy.Agent.BB.

Most of the packages were already taken down by PyPI at the time of this research. ESET communicated with PyPI to take action against the remaining ones and all of the known malicious packages are now offline. The full list of 116 packages can be found in our GitHub repository.

It’s worth noting that malware in a PyPI project repository isn’t a security issue with PyPI itself. In fact, the software running PyPI was recently audited by an external firm that assessed that PyPl “conformed to widely accepted best practices”.

Conclusion

PyPI continues to be abused by cyberattackers to compromise Python programmers’ devices. This campaign displays a variety of techniques being used to include malware in Python packages. Python developers should thoroughly vet the code they download, especially checking for these techniques, before installing it on their systems. As well as continuing to abuse the open-source W4SP Stealer, the operators have also deployed a simple, but effective, backdoor. We expect that such abuse of PyPI will continue and advise caution when installing code from any public software repository.

For any inquiries about our research published on WeLiveSecurity, please contact us at threatintel@eset.com.
ESET Research offers private APT intelligence reports and data feeds. For any inquiries about this service, visit the ESET Threat Intelligence page.

IoCs

Files

SHA-1

Filename

Detection

Description

439A5F553E4EE15EDCA1CFB77B96B02C77C5C388

cache.py

Python/Agent.AGL

Linux backdoor downloader..

B94E493579CC1B7864C70FAFB43E15D2ED14A16B

coloramma-0.5.4-py3-none-any.whl

Python/Agent.AGU

Package with Linux backdoor installer.

AE3072A72F8C54596DCBCDE9CFE74A4146A4EF52

coloramma-4.5-py3-none-any.zip

Python/Agent.AOY

Package with Windows backdoor.

70C271F79837B8CC42BD456A22EC51D1261ED0CA

junk.py

Python/Agent.AGM

Windows persistence installer.

B0C8D6BEEE80813C8181F3038E42ADACC3848E68

proclean

Linux/Spy.Agent.BB

Linux backdoor.

07204BA8D39B20F5FCDB9C0242B112FADFFA1BB4

prov.py

Python/Agent.AGL

Linux backdoor downloader.

EF59C159D3FD668C3963E5ADE3C726B8771E6F54

tmp

Linux/Spy.Agent.BB

Linux backdoor.

For a full list of malicious packages, see our GitHub malware-ioc repository.

Network

Domain name

IP Address

First seen

Description

blazywound.ignorelist[.]com

204.152.203[.]78

2022-11-21

C&C server for backdoor component.

MITRE ATT&CK techniques

This table was built using version 14 of the MITRE ATT&CK framework.

Tactic

ID

Name

Description

Initial Access

T1195.001

Supply Chain Compromise: Compromise Software Dependencies and Development Tools

Malware is distributed using Python’s PyPl package management service.

Persistence

T1053.005

Scheduled Task/Job: Scheduled Task

On Windows, persistence is achieved using a scheduled task.

T1547.013

Boot or Logon Autostart Execution: XDG Autostart Entries

On Linux, an autostart entry is created to launch the backdoor when the user logs in.

Defense Evasion

T1036.005

Masquerading: Match Legitimate Name or Location

On Linux, persistent files have names similar to legitimate software

Credential Access

T1555.003

Credentials from Password Stores: Credentials from Web Browsers

W4SP steals passwords from the installed web browsers.

Collection

T1115

Clipboard Data

To steal funds during a cryptocurrency transaction, clipboard data is replaced.

Command and Control

T1095

Non-Application Layer Protocol

The backdoor uses an unencrypted binary protocol over TCP.