Python Crypto Library Updated to Steal Private Keys
Yesterday, Phylum's automated risk detection platform discovered that the PyPI package aiocpa was updated to include malicious code that steals private keys by exfiltrating them through Telegram when users initialize the crypto library. While the attacker published this malicious update to PyPI, they deliberately kept the package's GitHub repository clean of the malicious code to evade detection.
The Attack
According to its README:
aiocpa is a syncronous & asynchronous Crypto Pay API client.
And to its credit, this does appear to be true…or at least it was true until version 0.1.13 was released. This package was first published in August of 2024 and has received seemingly appropriate updates, as one would expect, since then. However, yesterday (20 November 2024), the author published an update with the following diff to the file cryptopay/utils/sync.py:
A highly obfuscated blob is inserted at the top level of the utils.sync module in a crypto library? You only need one guess where this is going. Before we deobfuscate to see precisely what it’s doing, let’s first take a look at how and when this blob is executed. Since this is at the top level of this module, we only need to find where it’s imported. It turns out the package's top-level __init__.py does exactly that:
That blob is executed as soon as you install this package and then run from crypto pay import CryptoPay. Okay, let’s now figure out exactly what it does.
This particular blob is recursively encoded and compressed 50 times! Undoing that is trivial and after doing so gives us the following:
Interesting! The attacker overwrites the __init__ method of the CryptoPay class. Actually, it’s acting more like a wrapper around the originality functionality of the method. They’re saving the original method via init = CryptoPay.__init__ and then calling it as per usual with init(*args, **kwargs) and then sending a Telegram message to, presumably, the attacker’s Telegram bot call with args[1:] as the message. Take a look at the original constructor:
We can see that args[1:] will send all arguments after the self argument—the most important, of course, being the token. The additional arguments are also exfiltrated, potentially giving the attacker more context.
Just to recap, we’re seeing a crypto library that dynamically alters the class’s constructor upon module import to exfiltrate the victim’s private keys when calling the class’s constructor!
Another interesting aspect we discovered in our investigation is that its PyPI homepage points to a GitHub repo.
However, if you look at the same file in the GitHub repo, you’ll notice that the obfuscated payload is missing! This means the attacker updated a local copy of the repo with the malicious payload and then published that package to PyPI, leaving the GitHub repo with the same version numbers malware-free — a clear attempt at evasion.
This library's popularity - with 17 GitHub stars and (according to pypistats.org before the package was removed) nearly 4K downloads in the last month–makes this incident particularly concerning. The attack highlights two critical security lessons: First, it demonstrates the importance of scanning the actual code sent to open source ecosystems, that is the code that actually runs when you pip install or node -i a package, rather than just reviewing source repositories alone. As evidenced here, attackers can deliberately maintain clean source repos while distributing malicious packages to the ecosystems. Second, it serves as a reminder that a package's previous safety record doesn't guarantee its continued security.
While the identity of who published the malicious versions is unknown—whether it was, in fact, the original developer or someone who compromised their credentials—what's clear is that this package was deliberately weaponized to steal private keys. This incident again underscores the need for constant vigilance and proper security measures when dealing with open source dependencies.