Uvod
Obravnavali smo zanimiv primer napada, pri katerem se napadalci lažno predstavljajo kot iskalci zaposlitve ali pa želijo kakšno drugo sodelovanje z neko organizacijo. Prvi kontakt praviloma vzpostavijo preko omrežja LinkedIn, nato pa poskušajo zavesti žrtev v zagon zlonamerne kode.
Njihov cilj so predvsem podjetja in posamezniki, ki se ukvarjajo z Web3 tehnologijo (pametne pogodbe, kriptovalute, tehnologija veriženja blokov…).

Tekom komunikacije napadalec pošlje povezavo do nekega predstavitvenega projekta, ki na prvi pogled zgleda povsem običajen in neškodljiv, vendar pa ob zagonu izvrši tudi zlonamerno kodo, ki ukrade podatke in vzpostavi stranska vrata do okuženega sistema.

Sama zlonamerna koda je razdeljena na dva glavna dela, ki sta znana po imenih BeaverTail in InvisibleFerret. Razlikujeta se po namenu in programskem jeziku, v katerem sta modula spisana: BeaverTail – Node.js, InvisibleFerret – Python.
Ta tehnični zapis se ne ukvarja z atribucijo napadalcev, raziskave in poročila nekaterih drugih varnostnih strokovnjakov pa povezujejo tovrstno prakso in zlonamerno kodo z aktivnostmi državno sponzoriranih hekerjev iz Severne Koreje.
Analiza projekta

Analiza deljenega projekta je pokazala, da gre za Node.js Package Manager (NPM) paket. Projekt vsebuje tudi navodila za gradnjo in zagon programa, katera izvedejo tudi zlonamerno kodo.

Konfiguracijska datoteka package.json vsebuje podatke o NPM paketu in definira tudi glavno (main) datoteko, v kateri se začne izvajanje Node.js kode.

Datoteka server.js sama ne vsebuje zlonamerne kode, vendar pa s funkcijo require naloži dodatne module. Večina teh modulov je standardnih in legitimnih, modul app je pa lokalen in se nahaja v datoteki app.js.

Datoteka app.js tudi direktno ne vsebuje zlonamerne kode, naloži pa dodatne lokalne module. Večina teh modulov vsebuje le odvečno kodo, ki ni preveč zanimiva, v modulu userRoutes se pa skriva začetek zlonamerne kode.
userRoutes.js

Datoteka userRoutes.js na koncu vsebuje prikrito kodo. Vsa prikrita koda je v eni sami vrstici in ima pred njo veliko presledkov. Na ta način se skrije v tekstovnih urejevalnikih brez možnosti preloma besed (word wrap). Koda pa je bila verjetno zamaskirana oz. obfuskirana z uporabo odprto-kodnega obfuskatorja javascript-obfuscator.

S pomočjo orodja synchrony in nekaj ročnega dela smo pridobili bolj berljivo oz. deobfuskirano obliko kode. Analiza kode je pokazala, da najprej poskusi poslati nekaj osnovnih informacij o sistemu na t.i. C2 strežnik, ki je pod nadzorom napadalcev, zatem pa z njega prenese in izvrši nadaljnji tovor. Ta postopek skripta izvrši 3x, in sicer ob zagonu in nato še dvakrat z zamikom 10 minut, nato pa zaključi izvajanje.

V zgornjem izseku kode vidimo funkcijo, ki pošlje podatke na C2 strežnik. To naredi s HTTP POST zahtevo na sledeč naslov:
hxxp://23[.]106.253.221:1244/keys
Opomba: vsi C2 strežniki iz tega zapisa v času objave niso bili več dosegljivi
Med podatki, ki jih pošlje v zahtevi, so timestamp, type/fingerprint, host ID (hostname), data type, in data.
Prenos in zagon tovora

Drugi del skripte je namenjen prenosu in izvršitvi tovora. Vidimo, da funkcija poskusi kreirati novo mapo z imenom .vscode v domači mapi trenutnega uporabnika (Windows – %USERPROFILE%, Linux/Darwin – $HOME, ~/) in nato vanjo prenese datoteki test.js (hxxp://23[.]106.253.221:1244/j/ZU1RINz7) in package.json (hxxp://23[.]106.253.221:1244/p). Zatem v isti mapi požene ukaz npm install, ki prenese potrebne knjižnice (dependencies), nato pa še node test.js, ki izvrši prenešen tovor.
Preprečevanje izvedbe
Skripta vsebuje tudi zanimiv način preprečevanja izvedbe deobfuskirane kode, ki lahko oteži izvajanje dinamične analize:

Notranja funkcija v spremenljivki H pridobi niz, ki vsebuje kodo zunanje funkcije (rezultat funkcije F), in nad njim požene regex (((.+)+)+)+$. V primerih, kjer niz vsebuje novo vrstico, čas izvajanja tega regularnega izraza eksponentno narašča glede na število znakov v vrstici. Tovrstno izvajanje ima tudi ime catastrophic backtrace ali runaway regular expression. To pomeni, da se bo za deobfuskirano kodo, ki vsebuje presledke, ta izraz predolgo izvajal in vrnil neko napako. Ker pa je obfuskirana koda v eni sami vrstici, se izraz hitro zaključi in nato se lahko izvede še nadaljnja zlonamerna koda.
Glavni Node.js modul – BeaverTail
Če se vrnemo malo nazaj in se osredotočimo na prenešen tovor, ugotovimo, da gre tudi tu za NPM paket.

Konfiguracijska datoteka (package.json) je zelo preprosta in vsebuje le nekaj knjižnic, ki se prenesejo z npm install ukazom.

Nadaljnja koda se pa nahaja v datoteki test.js. Tako kot prejšnji del je tudi ta koda obfuskirana in vsebuje enak način preprečevanja izvedbe z uporabo regularnih izrazov. Kodo smo deobfuskirali na podoben način kot v prejšnjem delu in dodatno smiselno preimenovali funkcije ter nekatere spremenljivke. Pridobljena koda je malo bolj kompleksna in vsebuje dva dela, s katerima najprej ukrade podatke in nato prenese ter izvede kodo nadaljnje stopnje.
Kraja podatkov

Zgoraj je vidna glavna funkcija, ki pridobi in pošlje podatke iz različnih spletnih brskalnikov. Najprej poskusi pridobiti podatke iz razširitev/dodatkov brskalnikov, na Linux in Darwin sistemih pa nato še druge podatke, shranjenih v brskalnikih (gesla, seje, zgodovina, itd.). Pridobljene podatke pošlje nadzornemu strežniku preko HTTP POST zahtev na naslov:
hxxp://23[.]106.253.221:1244/uploads
Spodaj je naveden seznam razširitev iz katerih program krade podatke:
ID razširitve | Ime | Vir |
nkbihfbeogaeaoehlefnkodbefgpgknn | Metamask | Chrome store |
ejbalbakoplchlghecdalmeeeajnimhm | Metamask | Microsoft store |
ibnejdfjmmkpcnlpebklmnkoeoihofec | TronLink | Chrome store |
fhbohimaelbohpjbbldcngcnapndodjp | BNB Chain Wallet | Chrome store |
hnfanknocfeofbddgcijnmhnfnkdnaad | Coinbase Wallet | Chrome store |
bfnaelmomeimhlpmgjnjophhpkkoljpa | Phantom | Chrome store |
aeachknmefphepccionboohckonoeemg | Coin98 Wallet | Chrome store |
egjidjbpglichdcondbcbdnbeeppgdph | Trust Wallet | Chrome store |
hifafgmccdpekplomjjkcfgodnhcellj | Crypto.com | Wallet Extension | Chrome store |
aholpfdialjgjfhomihkjbmgjidlcdno | Exodus Web3 Wallet | Chrome store |
mcohilncbfahbmgdjkbpemcciiolgcge | OKX Wallet | Chrome store |
pdliaogehgdbhbnmkklieghmmjkpigpa | Bybit Wallet | Chrome store |
bhghoamapcdpbohphigoooaddinpkbai | Authenticator | Chrome store |
Prenos in izvršitev naslednje stopnje

Drugi del kode na Windows sistemih najprej namesti Python 3.11 okolje. To naredi tako, da z orodjem curl prenese ZIP arhiv iz naslova hxxp://23[.]106.253.221:1244/pdown, ki vsebuje celotno Python okolje za Windows OS in ga shrani pod imenom p2.zip v mapi z začasnimi datotekami. Ta arhiv nato z orodjem tar razširi v domačo mapo uporabnika. Zanimivost pri tem je uporaba orodji curl in tar (bsdtar – omogoča razširitev ZIP datotek), ki sta bolj značilni za operacijske sisteme podobne Unixu, vendar sta orodji vsebovani tudi v Windows OS-ih novejših od verzije 1803 (build 17063). Za ostale operacijske sisteme program predvideva, da je Python okolje že nameščeno.
Zatem program s HTTP GET zahtevo pridobi kodo iz naslova:
hxxp://23[.]106.253.221:1244/client/ZU1RINz7
Odgovor shrani v datoteko .npl v domači mapi uporabnika in jo nato izvrši s Python interpretorjem. Zatem se zlonamerna koda nadaljuje v različnih Python skriptah. Node.js koda v tej sekciji je znana po imenu BeaverTail in se jo lahko prepozna po formatu URL-jev, s katerimi komunicira, imenu prenešenih datotek (.npl) in po sami funkciji prenosa Python okolja in zagonu tovora.
Python – InvisibleFerret

Koda v datoteki .npl je šifrirana po zelo preprostem postopku. Spremenljivka qt vsebuje ključ (prvih 8 znakov ) in zašifrirane podatke (preostali znaki). Podatke najprej dekodira (base64) in dešifrira (XOR), nato pa dobljeno kodo izvrši s pomočjo funkcije exec.

Poleg enostavnega šifriranja koda ne vsebuje drugih metod obfuskacije, kar močno olajša analizo. Namen kode je prenos in izvršitev dveh dodatnih Python skript oz. modulov:
Ime modula | URL | Mesto prenosa |
Payload | hxxp://23[.]106.253.221:1244/payload/ZU1RINz7 | $HOME/.n2/pay |
Browser | hxxp://23[.]106.253.221:1244/brow/ZU1RINz7 | $HOME/.n2/bow |
Program na Darwin sistemih izvrši samo prvi modul (Payload) in zatem, s klicem funkcije sys.exit, zaključi izvajanje. Začetek programa vsebuje tudi zanimiv način, s katerim dinamično namesti vse potrebne knjižnice (dependencies).
Payload modul

Podobno kot prejšnji del je tudi tu koda prekrita, vendar po nekoliko drugačnem postopku. V tem primeru je koda stisnjena (zlib), kodirana (base64) in na koncu je rezultat kodiranja še obrnjen. Razširjena koda je nato izvedena s funkcijo exec. Vendar po le eni iteraciji tega postopka še ne dobimo povrnjene kode, saj je v tem primeru koda večkrat stisnjena. Pridobljena koda po prvi iteraciji:

Za popolno povrnitev kode smo spisali preprosto Python skripto:

Po 50 iteracijah dobimo dokončno razširjeno kodo. Tudi ta ne vsebuje kakšnih drugih metod obfuskacije. Njen glavni namen je izvajanje prejetih ukazov iz C2 strežnika.

Zgornji izsek kode vsebuje razred Client, ki izvede povezavo na C2 strežnik in začne komunikacijo, ki poteka preko TCP protokola. V tem primeru je naslov C2 strežnika sledeč:
173[.]211.106.101:1244
Po povezavi na C2 strežnik začne program izvajati prejete ukaze. Struktura paketa, ki vsebuje ukaz, je zelo preprosta:
4 bajti – big endian | … |
Velikost podatkov | Podatki |
Sam ukaz (podatki) je v JSON formatu in je sestavljen iz ID ukaza (število) in argumentov. Razred, ki bere in izvrši ukaze, je Shell:

Tabela možnih ukazov:
ID ukaza | Ime funkcije | Opis |
1 | ssh_obj | Izvrši ukaz v ukazni lupini in vrne izhod. |
2 | ssh_cmd | Izvrši posebne ukaze. V tem primeru je implementiran samo en ukaz, ki zapre celotno povezavo. |
3 | ssh_clip | Vrne zabeležene vnose (keylogger). Keylogger je aktiven samo na Windows OS. |
4 | ssh_run | Prenese in izvrši »Browser« modul (opisan v nadaljevanju). |
5 | ssh_upload | Omogoča iskanje datotek po sistemu in odlaganje datotek na nek FTP strežnik (podatki FTP strežnika so podani v argumentih ukaza). |
6 | ssh_kill | Ubije procese brsklanikov Chrome in Brave |
7 | ssh_any | Prenese in izvrši dodatno skripto, ki namesti in konfigurira program za oddaljen dostop AnyDesk |
8 | ssh_env | Pridobi datoteke iz zunanjih diskov in map za dokumente ter prenose in jih odloži na FTP strežnik |
9 | ssh_zcp | Ukrade podatke brskalnikov, razširitev, upravljalcev gesel, kripto denarnic in nekaterih drugih aplikacij ter jih shrani v zašifriran arhiv. Arhiv nato odloži na FTP strežnik ali pa jih pošlje v Telegram kanal (preko API-ja) |
Ukaz ssh_zcp krade podatke iz aplikacij Chrome, Chromium, Opera, Brave, Edge, Vivaldi, 1Password, Exodus, Atomic, Electrum, WinAuth, Proxifier v4, Dashlane, ter iz razširitev iz spodnjega seznama:
ID razširitve | Ime |
aeachknmefphepccionboohckonoeemg | Coin98 |
aholpfdialjgjfhomihkjbmgjidlcdno | Exodus |
bfnaelmomeimhlpmgjnjophhpkkoljpa | Phantom |
ejbalbakoplchlghecdalmeeeajnimhm | MetaMask |
ejjladinnckdgjemekebdpeokbikhfci | PetraAptos |
egjidjbpglichdcondbcbdnbeeppgdph | Trust |
fhbohimaelbohpjbbldcngcnapndodjp | Binance |
gjdfdfnbillbflbkmldbclkihgajchbg | Termux |
hifafgmccdpekplomjjkcfgodnhcellj | Crypto |
hnfanknocfeofbddgcijnmhnfnkdnaad | CoinBase |
ibnejdfjmmkpcnlpebklmnkoeoihofec | TronLink |
lgmpcpglpngdoalbgeoldeajfclnhafa | Safepal |
mcohilncbfahbmgdjkbpemcciiolgcge | OKX |
nkbihfbeogaeaoehlefnkodbefgpgknn | MetaMask |
nphplpgoakhhjchkkhmiggakijnkhfnd | Ton |
pdliaogehgdbhbnmkklieghmmjkpigpa | ByBit |
phkbamefinggmakgklpkljjmgibohnba | Pontem |
kkpllkodjeloidieedojogacfhpaihoh | Enkrypt |
agoakfejjabomempkjlepdflaleeobhb | Core |
jiidiaalihmmhddjgbnbgdfflelocpak | Bitget |
kgdijkcfiglijhaglibaidbipiejjfdp | Cirus |
kkpehldckknjffeakihjajcjccmcjflh | HBAR |
idnnbdplmphpflfnlkomgpfbpcgelopg | Xverse |
fccgmnglbhajioalokbcidhcaikhlcpm | Zapit |
fijngjgcjhjmmpcmkeiomlglpeiijkld | Talisman |
enabgbdfcbaehmbigakijjabdpdnimlg | Manta |
onhogfjeacnfoofkfgppdlbmlmnplgbn | Sub |
amkmjjmmflddogmhpjloimipbofnfjih | Wombat |
glmhbknppefdmpemdmjnjlinpbclokhn | Orange |
hmeobnfnfcmdkdcmlblgagmfpfboieaf | XDEFI |
acmacodkjbdgmoleebolmdjonilkdbch | Rabby |
fcfcfllfndlomdhbehjjcoimbgofdncg | LeapCosmos |
anokgmphncpekkhclmingpimjmcooifb | Compass |
epapihdplajcdnnkdeiahlgigofloibg | Sender |
efbglgofoippbgcjepnhiblaibcnclgk | Martian |
ldinpeekobnhjjdofggfgjlcehhmanlj | Leather |
lccbohhgfkdikahanoclbdmaolidjdfl | Wigwam |
abkahkcbhngaebpcgfmhkoioedceoigp | Casper |
bhhhlbepdkbapadjdnnojkbgioiodbic | Solflare |
klghhnkeealcohjjanjjdaeeggmfmlpl | Zerion |
lnnnmfcpbkafcpgdilckhmhbkkbpkmid | Koala |
ibljocddagjghmlpgihahamcghfggcjc | Virgo |
ppbibelpcjmhbdihakflkdcoccbgbkpo | UniSat |
afbcbjpbpfadlkmhmclhkeeodmamcflc | Math |
ebfidpplhabeedpnhjnobghokpiioolj | Fewcha |
fopmedgnkfpebgllppeddmmochcookhc | Suku |
gjagmgiddbbciopjhllkdnddhcglnemk | Hashpack |
jnlgamecbpmbajjfhmmmlhejkemejdma | Braavos |
pgiaagfkgcbnmiiolekcfmljdagdhlcm | Stargazer |
khpkpbbcccdmmclmpigdgddabeilkdpd | Suiet |
kilnpioakcdndlodeeceffgjdpojajlo | Aurox |
bopcbmipnjdcdfflfgjdgdjejmgpoaab | Block |
kmhcihpebfmpgmihbkipmjlmmioameka | Eternl |
aflkmfhebedbjioipglgcbcmnbpgliof | Backpack |
ajkifnllfhikkjbjopkhmjoieikeihjb | Moso |
pfccjkejcgoppjnllalolplgogenfojk | Tomo |
jaooiolkmfcmloonphpiiogkfckgciom | Twetch |
kmphdnilpmdejikjdnlbcnmnabepfgkh | OsmWallet |
hbbgbephgojikajhfbomhlmmollphcad | Rise |
nbdhibgjnjpnkajaghbffjbkcgljfgdi | Ramper |
fldfpgipfncgndfolcbkdeeknbbbnhcc | MyTon |
jnmbobjmhlngoefaiojfljckilhhlhcj | OneKey |
fcckkdbjnoikooededlapcalpionmalo | MOBOX |
gadbifgblmedliakbceidegloehmffic | Paragon |
ebaeifdbcjklcmoigppnpkcghndhpbbm | SenSui |
opfgelmcmbiajamepnmloijbpoleiama | Rainbow |
jfflgdhkeohhkelibbefdcgjijppkdeb | OrdPay |
kfecffoibanimcnjeajlcnbablfeafho | Libonomy |
opcgpfmipidbgpenhmajoajpbobppdil | Sui |
penjlddjkjgpnkllboccdgccekpkcbin | OpenMask |
kbdcddcmgoplfockflacnnefaehaiocb | Shell |
abogmiocnneedmmepnohnhlijcjpcifd | Blade |
omaabbefbmiijedngplfjmnooppbclkk | Tonkeeper |
cnncmdhjacpkmjmkcafchppbnpnhdmon | HAVAH |
eokbbaidfgdndnljmffldfgjklpjkdoi | Fluent |
fnjhmkhhmkbjkkabndcnnogagogbneec | Ronin |
dmkamcknogkgcdfhhbddcghachkejeap | Keplr |
dlcobpjiigpikoobohmabehhmhfoodbb | ArgentX |
aiifbnbfobpmeekipheeijimdpnlpgpp | Station |
eajafomhmkipbjmfmhebemolkcicgfmd | Taho |
mkpegjkblkkefacfnmkajcjmabijhclg | MagicEden |
ffbceckpkpbcmgiaehlloocglmijnpmp | Initia |
lpfcbjknijpeeillifnkikgncikgfhdo | Nami |
fpkhgmpbidmiogeglndfbkegfdlnajnf | Cosmostation |
kppfdiipphfccemcignhifpjkapfbihd | Frontier |
fdjamakpfbbddfjaooikfcpapjohcfmg | Dashalane |
hdokiejnpimakedhajhdlcegeplioahd | LastPass |
bhghoamapcdpbohphigoooaddinpkbai | GoogleAuth |
Browser modul
Tudi ta modul je kodiran in stisnjen po enakem postopku kot prejšnji. Njegov namen je kraja gesel in kreditnih kartic shranjenih v spletnih brskalnikih. Gre za bolj specializirano različico ssh _zcp ukaza iz prejšnjega modula in kradljivca v BeaverTail kodi.
Razlika je v tem, da ostala dva kradljivca preneseta celotne mape s shranjenimi podatki spletnih brskalnikov, ta se pa osredotoči samo na shranjena gesla in kreditne kartice.

Zgornja funkcija pridobi podatke kreditnih kartic, shranjenih v nekem brskalniku. Najprej iz SQLite baze webdata.db pridobi zašifrirane podatke in jih zatem še dešifrira.

Pridobitev ključa in dešifriranje se razlikuje glede na operacijski sistem, saj se tudi sama implementacija v brskalnikih nekoliko razlikuje. V Windows OS-u program za dešifriranje podatkov uporabi funkcijo dec_win_pwd, na ostalih OS-ih pa dec_unx_pwd. Na Windows OS-u je ključ dodatno zašifriran in shranjen v JSON datoteki Local State, za dešifriranje tega ključa se uporablja Windows API funkcija CyptUnprotectData, ki s posebnim ključem trenutnega uporabnika dešifrira podatke. Ostali operacijski sistemi pa imajo ključ shranjen v t.i. keychain/keyring sistemskih shrambah.

V zgornjem izseku kode vidimo, da podatke krade samo iz spletnih brskalnikov Chrome, Opera, Brave, Yandex in Edge (baziranih na Chromium), kar je najverjetneje posledica višje specializiranosti. Pridobljene podatke pošlje z metodo save, kjer se tudi vidi, da uporabi HTTP POST zahtevo na fiksni naslov hxxp://23[.]106.253.221:1244/keys.
Povzetek
V tem delu smo pogledali delovanje dveh zlonamernih Python skript, ki sta prenešeni in izvedeni preko t.i. loaderja ali dropperja (.npl) z imenom InvisibleFerret. Podobno kot za BeaverTail lahko tudi tega prepoznamo po obliki URL-jev s katerimi komunicira, imenih modulov (pay, bow, adc) ali pa imenu skripte .npl.
Seznam indikatorjev zlorabe (IoC)
Ime | SHA256 |
userRoutes.js | 4f632429fedb39fa2addaeff3ba900679ebff99e45353d523776831e2558df80 |
test.js | 354e7014103783c2096b9f29e4eed11f79d19b13bda112b6fed4ffbf3ad438b9 |
p2.zip | 6a104f07ab6c5711b6bc8bf6ff956ab8cd597a388002a966e980c5ec9678b5b0 |
.npl | f46b47e859f321b6676289f3637538b60e1d7d97ee8d79b8ba9d12248858a75a |
pay | 95e3cbaac2749928598928c3b8ca80358c136e01bc429d2c2da77cc788566e1e |
bow | 6b331ab212a839ad1b1b673ca74a3c50c6dae383c19661e0ae71853f615aa891 |
adc | 335ad22f143ff050bb405cd48d0011a9eb9f4c7501b18781a42d3956718827c8 |
URL |
hxxps://github[.]com/0xcestlaview/addingtoken |
hxxp://23[.]106.253.221:1244/j/ZU1RINz7 |
hxxp://23[.]106.253.221:1244/p |
hxxp://23[.]106.253.221:1244/keys |
hxxp://23[.]106.253.221:1244/pdown |
hxxp://23[.]106.253.221:1244/uploads |
hxxp://23[.]106.253.221:1244/client/ZU1RINz7 |
hxxp://23[.]106.253.221:1244/payload/ZU1RINz7 |
hxxp://23[.]106.253.221:1244/brow/ZU1RINz7 |
hxxp://23[.]106.253.221:1244/adc/ZU1RINz7 |
hxxp://23[.]106.253.221:1244/any |
IP | Vrata |
173[.]211.106.101 | 1244 |