|

W4SP continues to nest in PyPI: Same supply chain attack, different distribution method

Karlo Zanki
Blog Author

Karlo Zanki, Reverse Engineer at ReversingLabs. Read More...

pypi-w4sp-karlo-zanki

Days after researchers for Phylum and Checkmarx revealed an ongoing software supply chain attack spreading the W4SP Stealer malware through malicious packages on the Python Package Index (PyPI), ReversingLabs researchers discovered 10 additional PyPI packages pushing modified versions of W4SP that were overlooked.

The newly discovered packages appear to be part of the same campaign but are using slightly modified versions of the W4SP Stealer malware and different command and control infrastructure.

Here's our discoveries, indicators of compromise (IOCs), and ReversingLabs YARA rules that can be used to detect the malicious packages in your environment. 

Introduction

In the beginning of November several malicious python packages distributing the W4SP malware were found in the Python Package Index (PyPI) open source repository. These packages contain malicious code, hidden inside __init__.py or setup.py scripts, which downloads a stage 2 payload from a remote location. Stage 2 payload is W4SP stealer capable of stealing a wide range of sensitive data including stored passwords, cookies, Discord tokens, crypto wallets, telegram data and a long list of files related to different web services. This Python malware contains several layers of obfuscation in order to avoid detection. More details can be found in Phylum and Checkmarx reports. 

Despite the disclosure by Phylum and Checkmarx, this supply chain attack is ongoing. Further investigation by ReversingLabs has uncovered 10 previously undisclosed Python packages that appear to be part of the same campaign, but that are pushing a slightly modified version of the W4SP Stealer and relying on a different command and control (C2) infrastructure.

Same attack, different downloader 

The architecture of the attack uncovered by ReversingLabs is the same as the attack documented by Phylum and Checkmarx. PyPI packages are infected with downloader code and published to the PyPI repository. The malicious code executes upon package installation and downloads an obfuscated stage 2 payload from a remote location and loads it on the infected machine. W4SP Stealer, the stage 2 malware, is capable of stealing sensitive data and exfiltrating it to a remote location.  

The versions of the malicious modules ReversingLabs discovered differ from those identified by Phylum and Checkmarx in a number of ways. Previous downloaders were embedded in PyPI package __init__.py scripts and in some cases setup.py scripts and were downloading stage 2 payloads from urls similar to the following: 

hxxp://wasp.plague.fun/inject/<random_string>

Transfer.sh: Malware distribution made easy

However, samples discovered during our research were using the public file sharing service transfer.sh to deliver W4SP Stealer. This service provides “easy file sharing from the command line.” It is convenient from an attacker's perspective because it is very friendly for command line scripting and allows uploading up to 10GB of data which can be stored for up to 14 days, for free. What is even more convenient is the ability to define a maximum number of downloads. Such a feature can be used to assure malware gets downloaded only once, ideally by the targeted victim, before it gets removed from the remote location, preventing security researchers from getting their hands on malicious samples. The sample download and execution is performed in a large powershell one-line command. (Figure 1.)

Figure 1: Stage 2 downloader code

In the observed samples, files hosted on transfer.sh service were typically named Updater.zip or transfer.zip. This is the YARA rule that can be used to hunt such downloader python scripts:

rule transfer_sh_url

{

    strings:

        $updater_url =  { 3A 2F 2F 74 72 61 6E 73 66 65 72

                          2E 73 68 2F ?? ?? ?? ?? ?? ?? 2F 

                          55 70 64 61 74 65 72 2E 7A 69 70 }

        $transfer_url = { 3A 2F 2F 74 72 61 6E 73 66 65 72

                          2E 73 68 2F ?? ?? ?? ?? ?? ?? 2F 

                          74 72 61 6E 73 66 65 72 2E 7A 69 70 }


    condition:

        $updater_url or $transfer_url

}

W4SP reloaded: expanded data stealing options

In all samples, the entrypoint for the W4SP Stealer payload is in server.pyw file. This file contains a large LZMA compressed and Base64 encoded file which is a minify-ed version of the original payload created using the pyminifier application. 

Figure 2: Stage 2 entrypoint before decoding and decompression

After decompression and decoding are performed, the original code is executed. The main function sets up the persistence and threads responsible for keylogging and data stealing.  

Figure 3: Main function

An interesting feature of the W4SP Stealer is that there are two mechanisms for data exfiltration. The first uploads stolen data to the tranfer.sh service and sends the download url to the threat actor controlled C2 server together with the information about the infected machine. You can see this part of code in Figure 4, together with the calls to data stealing functions to get a sense about the type of data that gets stolen.

Figure 4: Data exfiltration using transfer.sh service

The second data exfiltration mechanism is based on Cloudflare’s reverse tunnels. The malware first downloads Cloudflare Tunnel client (formerly Argo Tunnel) from their GitHub repository. Then it creates a reverse tunnel and sends the generated tunnel’s URL to the same threat actor-controlled C2 server mentioned in the first case.    

 Figure 5: Data exfiltration using reverse cloudflare tunnel

The Flask micro web framework is then used to create a local web server which responds to the requests received through the created tunnel. URL paths are created in a modular way, so each functionality could be executed on its own. This activity can be seen in Figure 6, where the paths are the strings inside the @app.route() annotations. This is another example of malicious actors abusing commercial services to further attacks and data exfiltration with tools like reverse tunnels, an increasingly popular technique among malware authors.

Figure 6: Url paths defined using Flask framework

A new RUST downloader

As part of our research, we also employed the ReversingLabs Titanium Platform’s  RetroHunt feature, which can test YARA rules against recent file samples submitted to us. The results of that scan included several Python scripts which were expected since the campaign was targeting PyPI users, but also an unexpected Windows PE (portable executable). That sample executes a Powershell block very similar to the one documented above and seen in the PyPI downloader packages. (Figure 7.)

Figure 7: Powershell code snippet responsible for downloading Stage 2 payload

Strings from the executable lead us to the conclusion that this sample was written in Rust. This, again, is consistent with recent trends among malware authors, who are turning to rust to create ransomware and other malicious applications. 

The compilation timestamp on the Rust downloader also helps to confirm the timeline for this PyPi campaign. Specifically: the sample we observed was created on September 25th 2022. That is the same date on which the first PyPI package observed as a part of this research — pygradient — was published. 

The malicious packages ReversingLabs discovered predate those discovered by Phylum by weeks. According to that company’s blog, the supply chain attack started “around October 12, 2022” but intensified in the third week in October, before it was detected. Phylum did mention, as well, a small set of malicious PyPI packages with similar IOCs it discovered that date to July, 2022. 

That suggests the September packages were part of a larger and longer running campaign. One possible explanation is that the packages we discovered were used to test functionality before the actual launch of the malicious campaign in October. Another possibility is that the packages we discovered are artifacts of a real malicious compromise that has not been disclosed. Unfortunately typical pivoting attempts like searching on file similarity and section hashes didn’t have any results so no additional samples were discovered, so there are many questions that remain unanswered at this time.  

Conclusion

Our discovery of an earlier tranche of malicious PyPI packages expands our understanding of this latest software supply chain attack on the PyPI platform. It is also a reminder to organizations that the mere discovery and disclosure of supply chain attacks and compromises is not enough to end such attacks, or prevent malicious actors from continuing their work. 

Based on our findings, the W4SP Stealer malware campaign exposed by Phylum and Checkmarx in October started on September 25th and had its climax a month later, in late October. Based on Phylum data, the campaign may stretch back even further, to July, 2022. Still, it is alive and active today, with new PyPI packages published on a daily basis. 

This is similar to the behavior we have seen with earlier supply chain attacks, such as IconBurst, where new, malicious packages consistent with the IconBurst campaign have continued to turn up in the months following initial discovery of that campaign on the npm platform. 

As this incident and incidents like IconBurst reveal: Disclosure of supply chain attacks might fail to capture the full breadth of malicious campaigns. At the same time, malicious actors may continue to publish new malicious packages during and even after the campaign has been exposed. To counter these threats, development organizations and their customers have to remain vigilant: scanning open source packages for malicious functionality and being on the lookout for other supply chain attacks including dependency confusion, typosquatting and more. 

Indicators of Compromise (IOC) list

C2 IP address:

206.189.80.30

185.112.83.115

PyPI packages:

 

package_name

version

SHA1

pygradient

2.3

7ba6cbb93ad96f7f4e8a0e04b8fc6a317579e933

pygradient

2.5

c71b137da681681507379205aaa91b7b5ff95457

clistyling

2.0

723dfe8bdb6ca2ce1d41a3dc36177357300714bb

styling

1.9

c6a1b89578f75b8a7208f2d65eb8485301e2b74b

styling

2.0

a829c65a2fbf63fe7e19d42bb8715feaaf6614bf

styler

2.0

ceb389fe35a02b23bc57417c121f13a18c5bca6c

paintpy

2.0

745f78c9fa96c4133ac5fe8b580b28c93d6bdd1e

devicespoofer

2.0

25a4146e81147ba0b3043e3f90c775d15ba134a7

devicespoofer

2.2

f3d2639b15bf1ef9233787db092e2868554c64f3

devicespoof

2.0

2ea977718fb9d131799a61a1d9ce872076d43628

1inch

8.6

94376c20c5e65419dde80e6125fb6e03e9bf4698

1inch

8.7

d5056548377c32149ab814d658395842bf64d93d

1inch

8.9

dcfd849cfe2a14137858db35201427af99170555

ethereum2

8.4

01c7251610510bd0f122e7b11cb05d6a07baa317

ethereum2

8.6

26f0844b44f1ca02d9724da894e7d1ca66111b79

ethereum2

8.9

b57d662cc5814b7e6ab2f7bccd6e0b7e5d778610

ethereum0etl

8.6

5ada51df30f972ea27bfa55799e8bf4d2dcdc39c

 

Stage 2 zip archives SHA1:

 

f44cf80ce3a162f2354d5b60f8d48eb09760edb9

750b1ba531e59ddc51d75b1fe48025f27f8157dc

4742cfe83d8e9ebf6590b1ed553d804e343d2b72

b6d0a1dce731563abbf1bce0c6c229edcb6da9e0

42fff691ca7c67144ef25084ac7c262606b963ff

8f7b0902135f172a11a869acb02ebed32a3d9459

56932c92f9d389252f9a84e128696bdb298263d0

acf42993259db2998c4c7960f3529412b740baaf

0717a5fe9dd8ca6cb20d2bc1e78108e4ae8fd057

4461e37aaf15ae350b5b3068babdf758dee6fe87

8b83dc33acc228f321e18f7e2b8722123ee19611

a2840950dcc021012414c5cadfc07e111ccbf27b

589dd13059f63c09ff281d9106122f29c59fa4c9

aacc3639677d6f7c307de37c57b68ed15187bde4

07b64e16e08605da5b16d592ed0a954b75136e48

 

Executable downloader SHA1:

f6849ddf8b5f043b7499e239092ffeee91da2e47

[ Open-source protection: ReversingLabs YARA Rules GitHub page ]