
Em meados de junho, a luta contra o coronavírus no Cazaquistão estava a todo vapor. Alarmadas com o aumento do número de casos (até o ex-presidente Nursultan Nazarbayev foi infectado), as autoridades locais decidiram fechar novamente todos os centros comerciais e de entretenimento, cadeias de lojas, mercados e bazares. Naquele momento, os cibercriminosos aproveitaram-se da situação enviando correspondências maliciosas para empresas russas e internacionais.
Cartas perigosas, disfarçadas como um apelo do Ministro da Saúde da República do Cazaquistão, foram interceptadas pelo Grupo IB do Sistema de Detecção de Ameaças (TDS). O anexo continha documentos que, quando iniciados, instalavam um programa malicioso da família Loki PWS (Password Stealer), projetado para roubar logins e senhas de um computador infectado. No futuro, os invasores podem usá-los para obter acesso a contas de email para fraude financeira, espionagem ou vendê-los em fóruns de hackers.
Neste artigo, Nikita Karpov, analista do CERT-GIB , examina uma instância de um dos ladrões de dados mais populares da atualidade - Loki.
Hoje vamos considerar uma das versões de bot populares - 1.8. É vendido ativamente e o painel de administração pode até ser encontrado no domínio público: aqui .
Exemplo de painel de administração:

Loki é escrito em C ++ e é um dos malwares mais populares usados para roubar informações do usuário de um computador infectado. Como o flagelo do nosso tempo - vírus de ransomware - o Data Stealer, após ser atingido no computador da vítima, executa a tarefa em uma velocidade muito alta - não precisa se firmar e aumentar seus privilégios no sistema, quase não deixa tempo para se defender de um ataque. Portanto, nos eventos com malware que rouba dados do usuário, o papel principal é desempenhado pela investigação do incidente.
Descompactando e obtendo um despejo de malware viável
A distribuição na maioria dos casos ocorre por meio de anexos em listas de mala direta. O usuário, disfarçado como um arquivo legítimo, baixa e abre o anexo, iniciando o malware.
O marcador de injeção sugere a presença de um Loader.

Com a ajuda do DIE, obtemos informações de que o arquivo fonte está escrito em VB6.

O gráfico de entropia indica uma grande quantidade de dados criptografados.

Quando iniciado, o primeiro processo cria um processo filho, o injeta e sai. O segundo processo é responsável pelo trabalho do malware. Após um curto período de tempo, interrompemos o processo e salvamos o despejo de memória. Para confirmar se o Loki está dentro do dump, olhe dentro do url do centro de comando, que na maioria dos casos termina em fre.php .

Despejamos o fragmento de memória que contém o Loki e corrigimos o cabeçalho PE.
Verificaremos o desempenho do dump usando o sistema TDS Huntbox.

Funcionalidade do bot
No processo de exame do código do malware descompilado, encontramos uma parte contendo quatro funções que vão imediatamente após a inicialização das bibliotecas necessárias para a operação. Depois de desmontar cada um deles internamente, determinamos sua finalidade e funcionalidade de nosso malware.

Os nomes das funções foram renomeados para serem mais descritivos por conveniência.
A funcionalidade do bot é determinada por duas funções principais:
- O Data Stealer é a primeira função responsável por roubar dados de 101 aplicativos e enviá-los ao servidor.
- Downloader - uma solicitação de comandos CnC (Comando e Controle) para execução.
Por conveniência, a tabela abaixo lista todos os aplicativos dos quais a instância do Loki sendo examinada tenta roubar dados.
ID da função | inscrição | ID da função | inscrição | ID da função | inscrição |
---|---|---|---|---|---|
1 | Mozilla Firefox | 35 | FTPInfo | 69 | ClassicFTP |
2 | Comodo IceDragon | 36 | LinasFTP | 70 | PuTTY / KiTTY |
3 | Apple Safari | 37 | FileZilla | 71 | Thunderbird |
4 | K-Meleon | 38 | Equipe-FTP | 72 | Foxmail |
cinco | SeaMonkey | 39 | BlazeFtp | 73 | Pocomail |
6 | Rebanho | 40 | NETFile | 74 | IncrediMail |
7 | NETGATE BlackHawk | 41 | GoFTP | 75 | Notificador pro Gmail |
8 | Lunascape | 42 | ALFTP | 76 | Checkmail |
nove | Google Chrome | 43 | DeluxeFTP | 77 | WinFtp |
dez | Ópera | 44 | Comando total | 78 | Martin Prikryl |
onze | Navegador QTWeb | 45 | FTPGetter | 79 | 32BitFtp |
12 | QupZilla | 46 | WS_FTP | 80 | Navegador FTP |
treze | Internet Explorer | 47 | Arquivos de configuração do cliente de email | 81 | Mailing
(softwarenetz) |
quatorze | Opera 2 | 48 | Full tilt poker | 82 | Opera Mail |
quinze | Cyberfox | 49 | Pokerstars | 83 | Caixa postal |
dezesseis | Lua pálida | 50 | ExpanDrive | 84 | FossaMail |
17 | Waterfox | 51 | Corcel | 85 | Becky! |
dezoito | Pidgin | 52 | FlashFXP | 86 | POP3 |
19 | SuperPutty | 53 | NovaFTP | 87 | Outlook |
20 | FTPShell | 54 | NetDrive | 88 | Ymail2 |
21 | NppFTP | 55 | Total Commander 2 | 89 | Trojitá |
22 | MyFTP | 56 | SmartFTP | 90 | TrulyMail |
23 | FTPBox | 57 | FAR Manager | 91 | Arquivos .spn |
24 | sherrod FTP | 58 | Bitvise | 92 | Lista de tarefas para fazer |
25 | FTP agora | 59 | RealVNC
TightVNC |
93 | Stickies |
26 | NexusFile | 60 | Carteira mSecure | 94 | NoteFly |
27 | Xftp | 61 | Syncovery | 95 | NoteZilla |
28 | EasyFTP | 62 | FreshFTP | 96 | Post-it |
29 | SftpNetDrive | 63 | BitKinex | 97 | KeePass |
trinta | AbleFTP | 64 | UltraFXP | 98 | Enpass |
31 | JaSFtp | 65 | FTP agora 2 | 99 | Meu RoboForm |
32 | Automize | 66 | Vandyk SecureFX | 100 | 1 senha |
33 | Cyberduck | 67 | Odin Secure FTP Expert | 101 | Mikrotik WinBox |
34 | Fullsync | 68 | Arremesso |
Networking
Existem dois problemas que precisam ser resolvidos para registrar as interações de rede:
- A Central de Comando está disponível apenas no momento do ataque.
- O Wireshark não grava comunicações de bot no loopback, então você precisa usar outros meios.
A solução mais simples é encaminhar o endereço CnC com o qual o Loki se comunicará para o localhost. Para o bot, o servidor agora está disponível a qualquer momento, embora não responda, mas não é necessário registrar as comunicações do bot. Para resolver o segundo problema, usaremos o utilitário RawCap, que nos permite escrever as comunicações de que precisamos para pcap. A seguir, analisaremos o pcap gravado no Wireshark.

Antes de cada comunicação, o bot verifica a disponibilidade do CnC e, se disponível, abre um soquete. Todas as comunicações de rede ocorrem no nível de transporte usando o protocolo TCP e, no nível do aplicativo, o HTTP é usado.
A tabela abaixo mostra os cabeçalhos dos pacotes que o Loki usa como padrão.
Campo | Valor | Descrição |
---|---|---|
Agente de usuário | Mozilla / 4.08 (Charon; Inferno) | Um agente de usuário típico para Loki |
Aceitar | * / * | |
Tipo de conteúdo | aplicação / fluxo de octeto | |
Codificação de conteúdo | binário | |
Content-Key | 7DE968CC | Resultado de hash dos cabeçalhos anteriores (o hash é feito por um algoritmo CRC personalizado com polinômio 0xE8677835) |
Conexão | perto |
- A estrutura dos dados gravados depende da versão do bot, e nas versões anteriores não há campos responsáveis pelas opções de criptografia e compactação.
- O servidor determina pelo tipo de solicitação como processar os dados recebidos. Existem 7 tipos de dados que o servidor pode ler:
- 0x26 Dados de carteira roubados
- 0x27 Dados de aplicativo roubados
- 0x28 Pedido de comando do servidor
- 0x29 Descarregando um arquivo roubado
- 0x2A POS
- Dados do keylogger 0x2B
- 0x2C Screenshot
- Na instância examinada, apenas 0x27, 0x28 e 0x2B estavam presentes.
- Cada solicitação contém informações gerais sobre o bot e o sistema infectado, de acordo com as quais o servidor identifica todos os relatórios de uma máquina, e depois há informações que dependem do tipo de solicitação.
- Na versão mais recente do bot, apenas a compactação de dados é implementada e os campos criptografados são preparados para o futuro e não são processados pelo servidor.
- A biblioteca APLib de software livre é usada para compactar dados.
Ao formar uma solicitação com dados roubados, o bot aloca um buffer de tamanho 0x1388 (5000 bytes). A estrutura das solicitações 0x27 é mostrada na tabela abaixo:
Viés | O tamanho | Valor | Descrição |
---|---|---|---|
0 x 0 | 0x2 | 0x0012 | Versão do bot |
0x2 | 0x2 | 0x0027 | Tipo de solicitação (enviar dados roubados) |
0x4 | 0xD | ckav.ru | ID binário (XXXXX11111 também ocorre) |
0x11 | 0x10 | - | Nome do usuário |
0x21 | 0x12 | - | Nome do computador |
0x33 | 0x12 | - | Nome de domínio do computador |
0x45 | 0x4 | - | Resolução da tela (largura e altura)
|
0x49 | 0x4 | - | |
0x4D | 0x2 | 0x0001 | Sinalizador de direitos do usuário (1 se administrador) |
0x4F | 0x2 | 0x0001 | Sinalizador SID (1 se definido) |
0x51 | 0x2 | 0x0001 | Sinalizador de bitness do sistema (1 se x64) |
0x53 | 0x2 | 0x0006 | Versão do Windows (número da versão principal) |
0x55 | 0x2 | 0x0001 | Versão do Windows (número da versão secundária) |
0x57 | 0x2 | 0x0001 | Informações adicionais do sistema (1 = VER_NT_WORKSTATION) |
0x59 | 0x2 | - | |
0x5B | 0x2 | 0x0000 | Os dados roubados foram enviados |
0x5D | 0x2 | 0x0001 | Foi usada compressão de dados |
0x5F | 0x2 | 0x0000 | Tipo de compressão |
0x61 | 0x2 | 0x0000 | Foi usada criptografia de dados |
0x63 | 0x2 | 0x0000 | Tipo de encriptação |
0x65 | 0x36 | - | MD5 do valor de registro MachineGuid |
0x9B | - | - | Dados roubados compactados |
Tamanho do buffer: 0x2BC (700 bytes)
Viés | O tamanho | Valor | Descrição |
---|---|---|---|
0 x 0 | 0x2 | 0x0012 | Versão do bot |
0x2 | 0x2 | 0x0028 | Tipo de solicitação (solicitação de comando do centro de comando) |
0x4 | 0xD | ckav.ru | ID binário (XXXXX11111 também ocorre) |
0x11 | 0x10 | - | Nome do usuário |
0x21 | 0x12 | - | Nome do computador |
0x33 | 0x12 | - | Nome de domínio do computador |
0x45 | 0x4 | - | Resolução da tela (largura e altura) |
0x49 | 0x4 | - | |
0x4D | 0x2 | 0x0001 | Sinalizador de direitos do usuário (1 se administrador) |
0x4F | 0x2 | 0x0001 | Sinalizador SID (1 se definido) |
0x51 | 0x2 | 0x0001 | Sinalizador de bitness do sistema (1 se x64) |
0x53 | 0x2 | 0x0006 | Versão do Windows (número da versão principal) |
0x55 | 0x2 | 0x0001 | Versão do Windows (número da versão secundária) |
0x57 | 0x2 | 0x0001 | Informações adicionais do sistema (1 = VER_NT_WORKSTATION) |
0x59 | 0x2 | 0xFED0 | |
0x5B | 0x36 | - | MD5 do valor de registro MachineGuid |
Tamanho do buffer: 0x10 (16 bytes) + 0x10 (16 bytes) para cada comando no pacote.
Cabeçalho HTTP (início dos dados) | \ r \ n \ r \ n | [0D 0A 0D 0A] | 4 bytes | ||
- | - | 4 | |||
2 | [00 00 00 02] | 4 | |||
4 |
4 |
4 |
4 |
() |
|
---|---|---|---|---|---|
#0
EXE- |
[00 00 00 00] | [00 00 00 00] | [00 00 00 00] | [00 00 00 23] | www.notsogood.site/malicious.exe |
#1
DLL |
[00 00 00 00] | [00 00 00 01] | [00 00 00 00] | [00 00 00 23] | www.notsogood.site/malicious.dll |
#2
EXE- |
[00 00 00 00] | [00 00 00 02] | [00 00 00 00] | [00 00 00 23] | www.notsogood.site/malicious.exe |
#8
(HDB file) |
[00 00 00 00] | [00 00 00 08] | [00 00 00 00] | [00 00 00 00] | - |
#9
|
[00 00 00 00] | [00 00 00 09] | [00 00 00 00] | [00 00 00 00] | - |
#10
|
[00 00 00 00] | [00 00 00 0A] | [00 00 00 00] | [00 00 00 00] | - |
#14
Loki |
[00 00 00 00] | [00 00 00 0E] | [00 00 00 00] | [00 00 00 00] | - |
#15
Loki |
[00 00 00 00] | [00 00 00 0F] | [00 00 00 00] | [00 00 00 23] | www.notsogood.site/malicious.exe |
# 16
Altere a frequência de verificação da resposta do servidor |
[00 00 00 00] | [00 00 00 10] | [00 00 00 00] | [00 00 00 01] | cinco |
# 17
Remova Loki e saia |
[00 00 00 00] | [00 00 00 11] | [00 00 00 00] | [00 00 00 00] | - |
Analisador de tráfego de rede
Graças a essa análise, temos todas as informações de que precisamos para analisar as interações de rede de Loki.
O analisador é implementado em Python, recebe um arquivo pcap como entrada e encontra todas as comunicações pertencentes a Loki nele.
Primeiro, vamos usar a biblioteca dkpt para encontrar todos os pacotes TCP. Para receber apenas pacotes http, vamos colocar um filtro na porta usada. Entre os pacotes http recebidos, selecionamos aqueles que contêm os cabeçalhos Loki bem conhecidos e obtemos as comunicações que precisam ser analisadas para extrair informações deles de forma legível.
for ts, buf in pcap:
eth = dpkt.ethernet.Ethernet(buf)
if not isinstance(eth.data, dpkt.ip.IP):
ip = dpkt.ip.IP(buf)
else:
ip = eth.data
if isinstance(ip.data, dpkt.tcp.TCP):
tcp = ip.data
try:
if tcp.dport == 80 and len(tcp.data) > 0: # HTTP REQUEST
if str(tcp.data).find('POST') != -1:
http += 1
httpheader = tcp.data
continue
else:
if httpheader != "":
print('Request information:')
pkt = httpheader + tcp.data
httpheader = ""
if debug:
print(pkt)
req += 1
request = dpkt.http.Request(pkt)
uri = request.headers['host'] + request.uri
parsed_payload['Network']['Source IP'] = socket.inet_ntoa(ip.src)
parsed_payload['Network']['Destination IP'] = socket.inet_ntoa(ip.dst)
parsed_payload_same['Network']['CnC'] = uri
parsed_payload['Network']['HTTP Method'] = request.method
if uri.find("fre.php"):
print("Loki detected!")
pt = parseLokicontent(tcp.data, debug)
parsed_payload_same['Malware Artifacts/IOCs']['User-Agent String'] = request.headers['user-agent']
print(json.dumps(parsed_payload, ensure_ascii=False, sort_keys=False, indent=4))
parsed_payload['Network'].clear()
parsed_payload['Compromised Host/User Data'].clear()
parsed_payload['Malware Artifacts/IOCs'].clear()
print("----------------------")
if tcp.sport == 80 and len(tcp.data) > 0: # HTTP RESPONCE
resp += 1
if pt == 40:
print('Responce information:')
parseC2commands(tcp.data, debug)
print("----------------------")
pt = 0
except(dpkt.dpkt.NeedData, dpkt.dpkt.UnpackError):
continue
Em todas as solicitações Loki, os primeiros 4 bytes são responsáveis pela versão do bot e pelo tipo de solicitação. Usando esses dois parâmetros, determinamos como processaremos os dados.
def parseLokicontent(data, debug):
index = 0
botV = int.from_bytes(data[0:2], byteorder=sys.byteorder)
parsed_payload_same['Malware Artifacts/IOCs']['Loki-Bot Version'] = botV
payloadtype = int.from_bytes(data[2:4], byteorder=sys.byteorder)
index = 4
print("Payload type: : %s" % payloadtype)
if payloadtype == 39:
parsed_payload['Network']['Traffic Purpose'] = "Exfiltrate Application/Credential Data"
parse_type27(data, debug)
elif payloadtype == 40:
parsed_payload['Network']['Traffic Purpose'] = "Get C2 Commands"
parse_type28(data, debug)
elif payloadtype == 43:
parsed_payload['Network']['Traffic Purpose'] = "Exfiltrate Keylogger Data"
parse_type2b(lb_payload)
elif payloadtype == 38:
parsed_payload['Network']['Traffic Purpose'] = "Exfiltrate Cryptocurrency Wallet"
elif payloadtype == 41:
parsed_payload['Network']['Traffic Purpose'] = "Exfiltrate Files"
elif payloadtype == 42:
parsed_payload['Network'].['Traffic Purpose'] = "Exfiltrate POS Data"
elif payloadtype == 44:
parsed_payload['Network']['Traffic Purpose'] = "Exfiltrate Screenshots"
return payloadtype
O próximo passo será analisar a resposta do servidor. Para ler apenas informações úteis, procure a seqüência \ r \ n \ r \ n , que define o final dos cabeçalhos dos pacotes e o início dos comandos do servidor.
def parseC2commands(data, debug):
word = 2
dword = 4
end = data.find(b'\r\n\r\n')
if end != -1:
index = end + 4
if (str(data).find('<html>')) == -1:
if debug:
print(data)
fullsize = getDWord(data, index)
print("Body size: : %s" % fullsize)
index += dword
count = getDWord(data, index)
print("Commands: : %s" % count)
if count == 0:
print('No commands received')
else:
index += dword
for i in range(count):
print("Command: %s" % (i + 1))
id = getDWord(data, index)
print("Command ID: %s" % id)
index += dword
type = getDWord(data, index)
print("Command type: %s" % type)
index += dword
timelimit = getDWord(data, index)
print("Command timelimit: %s" % timelimit)
index += dword
datalen = getDWord(data, index)
index += dword
command_data = getString(data, index, datalen)
print("Command data: %s" % command_data)
index += datalen
else:
print('No commands received')
return None
Isso conclui a análise da parte principal do algoritmo do analisador e segue para o resultado que obtemos na saída. Todas as informações são exibidas no formato json.
Abaixo estão imagens do resultado do trabalho do parser, obtidas a partir das comunicações de vários bots, com diferentes CnCs e gravadas em diferentes ambientes.
Request information:
Loki detected!
Payload type: 39
Decompressed data:
{'Module': {'Mozilla Firefox'}, 'Version': {0}, 'Data': {'domain': {'https://accounts.google.com'}, 'username': {'none@gmail.com'}, 'password': {'test'}}}
{'Module': {'NppFTP'}, 'Version': {0}, 'Data': {b'<?xml version="1.0" encoding="UTF-8" ?>\r\n<NppFTP defaultCache="%CONFIGDIR%\\Cache\\%USERNAME%@%HOSTNAME%" outputShown="0" windowRatio="0.5" clearCache="0" clearCachePermanent="0">\r\n <Profiles />\r\n</NppFTP>\r\n'}}
{
"Network": {
"Source IP": "-",
"Destination IP": "185.141.27.187",
"HTTP Method": "POST",
"Traffic Purpose": "Exfiltrate Application/Credential Data",
"First Transmission": true
},
"Compromised Host/User Data": {},
"Malware Artifacts/IOCs": {}
}
Acima está um exemplo de uma solicitação ao servidor 0x27 (carregando dados do aplicativo). Para teste, contas foram criadas em três aplicativos: Mozilla Firefox, NppFTP e FileZilla. O Loki tem três opções para registrar os dados do aplicativo:
- Na forma de um banco de dados SQL (o analisador salva o banco de dados e exibe todas as linhas disponíveis nele).
- Na forma aberta, como o Firefox no exemplo.
- Como um arquivo xml, como NppFTP e FileZilla.
Request information:
Loki detected!
Payload type: 39
No data stolen
{
"Network": {
"Source IP": "-",
"Destination IP": "185.141.27.187",
"HTTP Method": "POST",
"Traffic Purpose": "Exfiltrate Application/Credential Data",
"First Transmission": false
},
"Compromised Host/User Data": {},
"Malware Artifacts/IOCs": {}
}
A segunda solicitação é do tipo 0x28 e solicita comandos do servidor.
Responce information:
Body size: 26
Commands: 1
Command: 1
Command ID: 0
Command type: 9
Command timelimit: 0
Command data: 35
Um exemplo de resposta do CnC, que enviou um comando em resposta para iniciar o keylogger. E o posterior descarregamento de dados do keylogger.
Request information:
Loki detected!
Payload type: : 43
{
"Network": {
"Source IP": "-",
"Destination IP": "185.141.27.187",
"HTTP Method": "POST",
"Traffic Purpose": "Exfiltrate Keylogger Data"
},
"Compromised Host/User Data": {},
"Malware Artifacts/IOCs": {}
}
No final do trabalho, o analisador imprime as informações contidas em cada solicitação do bot (informações sobre o bot e o sistema) e o número de solicitações e respostas associadas ao Loki no arquivo pcap.
General information:
{
"Network": {
"CnC": "nganyin-my.com/chief6/five/fre.php"
},
"Compromised Host/User Description": {
"User Name": "-",
"Hostname": "-",
"Domain Hostname": "-",
"Screen Resolution": "1024x768",
"Local Admin": true,
"Built-In Admin": true,
"64bit OS": false,
"Operating System": "Windows 7 Workstation"
},
"Malware Artifacts/IOCs": {
"Loki-Bot Version": 18,
"Binary ID": "ckav.ru",
"MD5 from GUID": "-",
"User-Agent String": "Mozilla/4.08 (Charon; Inferno)"
}
}
Requests: 3
Responces: 3
O código do analisador completo está disponível em: github.com/Group-IB/LokiParser
Conclusão
Neste artigo, examinamos mais de perto o malware Loki, desmontamos sua funcionalidade e implementamos um analisador de tráfego de rede que simplificará muito o processo de análise de incidentes e nos ajudará a entender o que exatamente foi roubado do computador infectado. Embora o desenvolvimento do Loki ainda esteja em andamento, apenas a versão 1.8 (e anteriores) vazou, que é a versão que os profissionais de segurança encontram todos os dias.
No próximo artigo, analisaremos outro ladrão de dados popular, o Pony, e compararemos esses malware.
Indicador de compromisso (IOCs):
Urls:
- nganyin-my.com/chief6/five/fre.php
- wardia.com.pe/wp-includes/texts/five/fre.php
- broken2.cf/Work2/fre.php
- 185.141.27.187/danielsden/ver.php
- Hash MD5: B0C33B1EF30110C424BABD66126017E5
- User-Agent String: «Mozilla/4.08 (Charon; Inferno)»
- Binary ID: «ckav.ru»