terça-feira, 26 de abril de 2011

Palestra no GTS-17 dia 14/05 em SP

1 comentários
Terei o prazer de apresentar uma palestra na reunião do GTS-17 que faz parte do CGI.br – Comitê Gestor da Internet no Brasil.

O Grupo de Trabalho em Segurança de Redes, em sua 17ª Reunião a ser realizada nos dias 13 (GTER) e 14 (GTS) de maio de 2011 em São Paulo - SP, juntamente com a 31a. Reunião do GTER - Grupo de Trabalho em Engenharia de Redes, convida a comunidade de operadores de serviços internet no Brasil para as apresentações sobre Segurança, Engenharia e Operação de Redes Internet.

O evento é gratuito e ocorrerá em São Paulo dia 14/05/2011, os interessados podem fazer as inscrições e obter mais informações em gts.nic.br.

Haverá muitas palestras interessantes e ainda será um prazer conhecer pessoalmente vários colegas que apenas conheço pela Internet. Vejo vocês lá!

Segue um resumo da minha apresentação.

Dia: 14/05 - 09:00 - 09:40

Descriptografando Strings em Malwares

O número de artefatos maliciosos circulando na Internet é enorme, no Brasil a característica mais comum desses malwares é serem do tipo trojan-banker, utilizados para capturar dados bancários.

Para dificultar o rastreamento de seus golpes os fraudadores cibernéticos utilizam técnicas de criptografia de strings, principalmente nas mais relevantes como endereços de e-mails, URLs, servidores de banco de dados, etc.

Serão apresentadas algumas técnicas práticas para descriptografar essas strings, com destaque para uma que consegue realizar o processo de forma automática, ainda não comentada nesse blog.

Ronaldo P. Lima

segunda-feira, 18 de abril de 2011

Análise de código malicioso em Linux

3 comentários
Você é acionado para fazer a análise de um arquivo desconhecido, querem saber o que esse arquivo faz, não é revelado mais nenhum detalhe, apenas que ele está envolvido em um incidente de segurança. O que fazer?

Essa situação é baseada em um caso real.

Identificação

Assim como em outras análises o primeiro passo é identificar o arquivo, descobrir para qual sistema operacional ele foi compilado, qual arquitetura, a presença de proteções, etc. No Linux podemos utilizar o comando “file para descobrirmos o tipo do nosso arquivo suspeito:

:~#: file suspeito

suspeito: ELF 64-bit LSB executable, x86-64,
version 1 (SYSV), dynamically linked (uses shared libs),
for GNU/Linux 2.6.18, not stripped

O arquivo suspeito trata-se de um executável do Linux (ELF, Executable and Linkable Format), para a arquitetura x86 de 64 bits. Usa bibliotecas compartilhadas e foi compilado para o Kernel 2.6.18.

Strings

Agora vamos buscar as strings .

:~#: strings -a suspeito

Exibindo apenas as mais interessantes:

/lib64/ld-linux-x86-64.so.2
GLIBC_2.2.5
GCC: (Debian 4.4.5-8) 4.4.5
GCC: (Debian 4.4.5-10) 4.4.5
file.c
__libc_start_main@@GLIBC_2.2.5
fork@@GLIBC_2.2.5

Vemos que o arquivo usa a biblioteca compartilhada da linguagem C conhecida como GLIBC. Também há referências ao GCC e a um arquivo chamado file.c. Com isso já presumimos que a linguagem de programação utilizada é o C.

Vemos que foi compilado com o GCC 4.4.5 do sistema Debian, com o comando “file” vimos o kernel 2.6.18, assim poderíamos descobrir qual a versão do Debian que foi utilizada.

Por fim ainda há referência à função fork() da GLIBC.

Não vemos referência a mais nenhuma outra função da GLIBC, vamos continuar com a análise.

Sessões do arquivo

Agora vamos utilizar o utilitário “readelf” para obter mais informações sobre o arquivo, exibir seus headers.

:~# readelf -S suspeito

There are 31 section headers, starting at offset 0x9c0:

Section Headers:
[Nr] Name Type Address Offset
[ 0] NULL 0000000000000000 00000000
[ 1] .interp PROGBITS 0000000000400200 00000200
[ 2] .note.ABI-tag NOTE 000000000040021c 0000021c
[ 3] .note.gnu.build-i NOTE 000000000040023c 0000023c
[ 4] .hash HASH 0000000000400260 00000260
[ 5] .gnu.hash GNU_HASH 0000000000400288 00000288
[ 6] .dynsym DYNSYM 00000000004002a8 000002a8
[ 7] .dynstr STRTAB 0000000000400308 00000308
[ 8] .gnu.version VERSYM 0000000000400346 00000346
[ 9] .gnu.version_r VERNEED 0000000000400350 00000350
[10] .rela.dyn RELA 0000000000400370 00000370
[11] .rela.plt RELA 0000000000400388 00000388
[12] .init PROGBITS 00000000004003b8 000003b8
[13] .plt PROGBITS 00000000004003d0 000003d0
[14] .text PROGBITS 0000000000400400 00000400
[15] .fini PROGBITS 00000000004005c8 000005c8
[16] .rodata PROGBITS 00000000004005d8 000005d8
[17] .eh_frame_hdr PROGBITS 00000000004005dc 000005dc
[18] .eh_frame PROGBITS 0000000000400600 00000600
[19] .ctors PROGBITS 0000000000600680 00000680
[20] .dtors PROGBITS 0000000000600690 00000690
[21] .jcr PROGBITS 00000000006006a0 000006a0
[22] .dynamic DYNAMIC 00000000006006a8 000006a8
[23] .got PROGBITS 0000000000600848 00000848
[24] .got.plt PROGBITS 0000000000600850 00000850
[25] .data PROGBITS 0000000000600878 00000878
[26] .bss NOBITS 0000000000600888 00000888
[27] .comment PROGBITS 0000000000000000 00000888
[28] .shstrtab STRTAB 0000000000000000 000008c1
[29] .symtab SYMTAB 0000000000000000 00001180
[30] .strtab STRTAB 0000000000000000 00001798

Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)

Breve descrição de alguns headers:

.rodata: dados read-only
.dynsym: tabela de símbolos de linking dinâmico
.symtab: tabela de símbolos
.debug: informações para debugging
.dynstr: strings utilizadas pelo linking dinâmico
.comment: informações de controle de versão
.strtab: strings que representam nomes associados com entradas da tabela de símbolos
.text: instruções executáveis do programa.

O que são símbolos?

São nomes de variáveis e funções utilizadas no binário. Ao invés de deixar apenas os endereços hexadecimais o compilador mantém uma tabela com os nomes relativos a cada função. Essa informação pode ser retirada (stripped) do binário, mas no comando “file” vimos que no nosso arquivo não havia sido retirada, “not stripped”.

Vamos analisar conteúdo de algumas seções do nosso arquivo, vou retirar o hex do resultado para facilitar a visualização:

:~# readelf -x 1 suspeito

Hex dump of section '.interp':
/lib64/ld-linux-
x86-64.so.2.

:~# readelf -x 7 suspeito

Hex dump of section '.dynstr':
.__gmon_start__.
libc.so.6.fork._
_libc_start_main
.GLIBC_2.2.5.

:~# readelf -x 27 suspeito

Hex dump of section '.comment':
GCC: (Debian 4.4
.5-8) 4.4.5.GCC:
(Debian 4.4.5-1
0) 4.4.5.

:~# readelf -x 30 suspeito

Hex dump of section '.strtab':

.call_gmon_start
.crtstuff.c.__CT
OR_LIST__.__DTOR
LIST__.__JCR_LI
ST__.__do_global
_dtors_aux.compl
eted.6341.dtor_i
dx.6343.frame_du
mmy.__CTOR_END__
.__FRAME_END__._
_JCR_END__.__do_
global_ctors_aux
.file.c._GLOBAL_
OFFSET_TABLE_.__
init_array_end._
_init_array_star
t._DYNAMIC.data_
start.__libc_csu
_fini._start.__g
mon_start__._Jv_
RegisterClasses.
_fini.__libc_sta
rt_main@@GLIBC_2
.2.5._IO_stdin_u
sed.__data_start
.__dso_handle.__
DTOR_END__.__lib
c_csu_init.__bss
_start._end.fork
@@GLIBC_2.2.5._e
data.main._init.

Com isso apenas confirmamos o que já tínhamos encontrado, não vemos nenhuma função nova sendo utilizada, apenas a fork() que já mencionamos. Agora utilizaremos o comando “nm” que lista informações sobre símbolos de um arquivo objeto.

:~# nm -D suspeito

w __gmon_start__
U __libc_start_main
U fork

A opção “-D” lista os símbolos utilizados na biblioteca compartilhada GLIBC que o binário utiliza, ele só usa o fork() dela.

Disassembly

Com a ajuda do “objdump” vamos verificar o disassembly da função “main” do arquivo suspeito com o comando abaixo para exibir na síntaxe Intel:

:~# objdump -M intel -d suspeito |grep -A10 main

00000000004004e4 <main>:
4004e4:  push rbp
4004e5:  mov rbp,rsp
4004e8:  call 4003f0 <fork@plt>
4004ed:  jmp 4004e8 <main+0x4>
4004ef:   nop

O main do arquivo é apenas isso, agora as coisas ficaram mais claras, veja a chamada para a função fork() e abaixo dela um jump incondicional para ela novamente. O que é isso? Um loop infinito.

Loop infinito para a função fork(), o que acontecerá?

A função fork() cria um processo filho que difere do processo pai apenas pelo seu PID e PPID. É duplicada toda a estrutura de memória do processo pai.

O processo vai sendo duplicado infinitamente e utilizando mais e mais memória esgotando todos os recursos do computador. Então nosso arquivo suspeito pode ser caracterizado como uma ferramenta de negação de serviço, Denial of Service - DoS.

Fiz o teste de execução em uma máquina virtual rodando Debian 6.0 64 bits, o computador host possui 3 GB RAM e para a VM reservei 1 GB. Ao executá-lo a VM ficou inoperante e visualizando o processo dela no Windows foi possível ver que estava consumindo praticamente a metade da memória RAM do host.

Conclusão

O atacante realizou um ataque DoS executando esse arquivo no servidor Linux alvo. Com as informações obtidas na análise foi possível dizer se o arquivo foi criado no próprio servidor ou em um computador externo.

O código-fonte para essa simples ferramenta de DoS poderia ser esse:

// suspeito.c
#include <sys/types.h>
#include <unistd.h>

main(){
    while(1){
       fork();
    }
}

Ronaldo P. Lima

sábado, 9 de abril de 2011

Decodificando Strings em Malwares

3 comentários

Todos os dias são criadas novas técnicas para dificultar a investigação de códigos maliciosos. A primeira barreira que encontramos são os packers de executáveis que dificultam bastante a análise estática.

Transcorrido esse obstáculo o caminho ainda não está livre, geralmente as strings mais relevantes do código como por exemplo endereços de e-mails, URLs, hosts e servidores de banco de dados, estão com uma espécie de criptografia, codificação ou obfuscação. Chamarei de criptografia.

Essa criptografia pode ser desde simples trocas de letras até complexas operações matemáticas. Nesse artigo farei um estudo de caso de um padrão de criptografia que encontrei em alguns malwares.

Nesse em questão havia mais de 2.200 strings criptografadas, conseguir revertê-las possibilitaria um entendimento significativo sobre o malware.

Uma característica da criptografia em malwares é que sempre a função de descriptografia estará dentro dele, se a string de um e-mail for algo do tipo “G5t64gKwiYJU8ht5shtXhzp72HtY” o malware só vai conseguir enviar o e-mail se essa string for transformada no endereço real antes.

Primeiramente farei a análise do código (dis)Assembly da função de descriptografia, depois com o entendimento da mesma mostrarei como poderia ser criado um programa para fazer o mesmo trabalho e descriptografar todas as strings de uma só vez.

Identificando o problema

Como em todas as análises o primeiro passo é identificar o executável do malware para descobrir a presença de packers e a linguagem de programação. Nesse caso o arquivo não possuía proteções e a linguagem utilizada foi MS Visual Basic 5.0 ou 6.0.


Ao abrir o arquivo no OllyDbg e fazer a busca por strings foi possível notar que muitas delas possuíam uma espécie de criptografia.


Ao analisar o código onde essas strings criptografadas eram utilizadas notei um padrão que se repetia, todas faziam uma chamada: CALL pernet.00444230. Então possivelmente essa seria a função de descriptografia, quando o malware precisava usar a string antes descriptografava para obter o texto verdadeiro dela.

Foi possível comprovar isso colocando um breakpoint após a chamada da função para pegar o retorno, ou seja, a string descriptografada.


Criptografada: "nDfL0+AGnDKL0+PLna5LnaAOnaKGJa02nPqG5aPdnDvL5602"
Descriptografada: "G_DB_USUARIO_AVISO"

A olho nu fica difícil identificar alguma lógica nesse processo, vamos partir para a escovação de bits para descobrir como isso é feito.

Baixando o nível

**************************************************
CHAMADA DA FUNÇÃO
**************************************************

OFFSET  INSTRUÇÃO

00453E3B MOV EDX,pernet.0040F6D8 ; "nDfL0+AGnDKL0+PLna5LnaAOnaKGJa02nPqG5a..."
00453E40 LEA ECX,DWORD PTR SS:[EBP-28]
00453E43 CALL ESI
00453E45 PUSH pernet.004EA1B0 ; "Zm7oWEOh6GLdu9Q4t1gCF"
00453E4A PUSH pernet.004EA1AC ; "w/sDbk2VKcUy5nJTA0paP8"
00453E4F LEA EDX,DWORD PTR SS:[EBP-28]
00453E52 PUSH pernet.004EA1A8 ; "xXqMIifSlBH3Z+vjYNRre"
00453E57 PUSH EDX
00453E58 CALL pernet.00444230

Essa é a chamada da função, os 4 Offsets destacados são os parâmetros que são passados através da pilha, eles são passados na ordem inversa, do 4º para o 1º, serão acessados na função como:

4º [EBP+14] – "Zm7oWEOh6GLdu9Q4t1gCF"
3º [EBP+10] – "w/sDbk2VKcUy5nJTA0paP8"
2º [EBP+C] – "xXqMIifSlBH3Z+vjYNRre"
1º [EBP+8] – "nDfL0+AGnDKL0+PLna5LnaAOnaKGJa02nPqG5aPdnDvL5602"

O 1º parâmetro é a string criptografada, o 2º, 3º e 4º parâmetros são 3 strings que juntas formarão um chave criptográfica que chamarei de KEY, através dela a função consegue descriptografar as strings.

**************************************************
INÍCIO DA FUNÇÃO
**************************************************

00444230 PUSH EBP
00444231 MOV EBP,ESP
00444233 SUB ESP,0C
00444236 PUSH <JMP.&MSVBVM60.__vbaExceptHandler> ; SE handler installation
0044423B MOV EAX,DWORD PTR FS:[0]
00444241 PUSH EAX
00444242 MOV DWORD PTR FS:[0],ESP

Aqui temos o prólogo da função onde o ponteiro da pilha (ESP) é copiado para o EBP que será usado como o ponteiro base, através dele a função acessa as variáveis locais e os parâmetros. As outras instruções criam o manipular de exceções para controlar os erros da função.

00444249 SUB ESP,0B0

Cria espaço na pilha de 176 bytes (0xB0) para variáveis locais.

0044424F PUSH EBX
00444250 PUSH ESI
00444251 PUSH EDI
00444252 MOV DWORD PTR SS:[EBP-C],ESP
00444255 MOV DWORD PTR SS:[EBP-8],pernet.00401828

0044425C MOV EAX,DWORD PTR SS:[EBP+C]
0044425F MOV EDX,DWORD PTR SS:[EBP+10]

Copia o 2º e 3º parâmetro para EAX e EBX respectivamente.

00444262 MOV ESI,DWORD PTR DS:[<&MSVBVM60.__vbaStrCat>]
00444268 XOR EBX,EBX ; zerou o EBX
0044426A MOV ECX,DWORD PTR DS:[EAX]
0044426C MOV EAX,DWORD PTR DS:[EDX]
0044426E PUSH ECX
0044426F PUSH EAX

Copia o 2º e 3º parâmetro para a pilha

00444270 MOV DWORD PTR SS:[EBP-1C],EBX
00444273 MOV DWORD PTR SS:[EBP-20],EBX
00444276 MOV DWORD PTR SS:[EBP-24],EBX
00444279 MOV DWORD PTR SS:[EBP-2C],EBX
0044427C MOV DWORD PTR SS:[EBP-34],EBX
0044427F MOV DWORD PTR SS:[EBP-44],EBX
00444282 MOV DWORD PTR SS:[EBP-54],EBX
00444285 MOV DWORD PTR SS:[EBP-64],EBX
00444288 MOV DWORD PTR SS:[EBP-74],EBX
0044428B MOV DWORD PTR SS:[EBP-84],EBX
00444291 MOV DWORD PTR SS:[EBP-A4],EBX
00444297 MOV DWORD PTR SS:[EBP-B4],EBX

Zera várias posições na pilha para armazenar variáveis locais.

0044429D CALL ESI ; <&MSVBVM60.__vbaStrCat>

Chama a função vbaStrCat tendo como parâmetros o ECX e EAX que foram colocados na pilha. Essa sequência de instruções mostra bem o papel do compilador na geração do código, vemos que não seguiu uma sequência lógica de colocar os parâmetros na pilha e já chamar a vbaStrCat, antes realizou outras tarefas (zerar a memória), isso é feito pelo compilador para otimizar o código e melhor aproveitar a CPU.

0044429F MOV EDI,DWORD PTR DS:[<&MSVBVM60.__vbaStrMove>]
004442A5 MOV EDX,EAX

Geralmente o resultado de uma função vem no EAX, aqui o EAX tem a string concatenada com o 2º e 3º parâmetro.

004442A7 LEA ECX,DWORD PTR SS:[EBP-34]
004442AA CALL EDI ; <&MSVBVM60.__vbaStrMove>

Moveu a string concatenada para a posição [EBP-34] da pilha.

004442AC MOV ECX,DWORD PTR SS:[EBP+14]
004442AF PUSH EAX
004442B0 MOV EDX,DWORD PTR DS:[ECX]
004442B2 PUSH EDX
004442B3 CALL ESI
004442B5 MOV EDX,EAX

O ESI ainda aponta para vbaStrCat e aqui é concatenado o 4º parâmetro com as outras duas strings. Agora o EAX aponta para a string final que será a KEY:

"w/sDbk2VKcUy5nJTA0paP8xXqMIifSlBH3Z+vjYNRrezm7oWEOh6GLdu9Q4t1gCF"
3º + 2º + 4º parâmetros

004442B7 LEA ECX,DWORD PTR SS:[EBP-2C]
004442BA CALL EDI

Move a string concatenada para a posição [EBP-2C] na pilha.

004442BC LEA ECX,DWORD PTR SS:[EBP-34]
004442BF CALL DWORD PTR DS:[<&MSVBVM60.__vbaFreeStr>]
004442C5 MOV EAX,DWORD PTR SS:[EBP+8]

Copia para EAX o primeiro parâmetro que é a string criptografada.

004442C8 MOV DWORD PTR SS:[EBP-24],EBX
004442CB XOR EDI,EDI
004442CD MOV ECX,DWORD PTR DS:[EAX]
004442CF PUSH ECX
004442D0 CALL DWORD PTR DS:[<&MSVBVM60.__vbaLenBstr>]
004442D6 MOV ECX,EAX

ECX recebe o resultado da função vbaLenBstr que calculou o comprimento da string criptografada.

004442D8 CALL DWORD PTR DS:[<&MSVBVM60.__vbaI2I4>]
004442DE MOV DWORD PTR SS:[EBP-BC],EAX

Copia para a posição [EBP-BC] o tamanho da string criptografada.

004442E4 MOV EAX,1
004442E9 MOV DWORD PTR SS:[EBP-18],EAX

EAX recebe 1 e foi copiado para a posição [EBP-18] que servirá como um contador.

**************************************************
INÍCIO DO LOOP
**************************************************

004442EC CMP AX,WORD PTR SS:[EBP-BC]
004442F3 JG pernet.00444492

Aqui vemos o início de um loop que vai percorrer toda a string criptografada caractere por caractere. É equivalente a esse pseudocódigo:

for(i=1; i<= length(cryptstring); i++)

004442F9 MOV EDX,DWORD PTR SS:[EBP-2C]
004442FC MOV ECX,DWORD PTR SS:[EBP+8]
004442FF MOV DWORD PTR SS:[EBP-9C],EDX
00444305 LEA EDX,DWORD PTR SS:[EBP-44]
00444308 MOVSX EAX,AX
0044430B MOV DWORD PTR SS:[EBP-7C],ECX
0044430E PUSH EDX
0044430F LEA ECX,DWORD PTR SS:[EBP-84]
00444315 PUSH EAX
00444316 LEA EDX,DWORD PTR SS:[EBP-54]
00444319 MOV ESI,2
0044431E PUSH ECX
0044431F PUSH EDX
00444320 MOV DWORD PTR SS:[EBP-A4],8
0044432A MOV DWORD PTR SS:[EBP-3C],1
00444331 MOV DWORD PTR SS:[EBP-44],ESI
00444334 MOV DWORD PTR SS:[EBP-84],4008
0044433E CALL DWORD PTR DS:[<&MSVBVM60.#632>] ; MSVBVM60.rtcMidCharVar

Basicamente o que todas essas linhas fazem é retornar o caractere que está na posição "i" (contador do loop) na string criptografada. No primeiro loop seria o “n”, no segundo o “D” e assim por diante. Um pseudocódigo para isso:

caractere = cryptstring[i]

00444344 LEA EAX,DWORD PTR SS:[EBP-A4]
0044434A PUSH 1
0044434C LEA ECX,DWORD PTR SS:[EBP-54]
0044434F PUSH EAX
00444350 PUSH ECX
00444351 LEA EDX,DWORD PTR SS:[EBP-64]
00444354 PUSH EBX
00444355 PUSH EDX
00444356 MOV DWORD PTR SS:[EBP-AC],1
00444360 MOV DWORD PTR SS:[EBP-B4],ESI
00444366 CALL DWORD PTR DS:[<&MSVBVM60.__vbaInStrVar>]
0044436C PUSH EAX
0044436D LEA EAX,DWORD PTR SS:[EBP-B4]
00444373 LEA ECX,DWORD PTR SS:[EBP-74]
00444376 PUSH EAX
00444377 PUSH ECX
00444378 CALL DWORD PTR DS:[<&MSVBVM60.__vbaVarSub>]
0044437E PUSH EAX
0044437F CALL DWORD PTR DS:[<&MSVBVM60.__vbaI2Var>]

Esse bloco de instruções recebe o caractere encontrado e faz uma busca na KEY para descobrir em qual posição da chave está o caractere, lembrando que começa a contar da posição zero.

Exemplos:
1º loop, “n” é o 13º caractere da KEY, em hexadecimal “0xD”
2º loop, “D” é o 3º caractere da KEY, em hexadecimal “0x3”
3º loop, “f” é o 28º caractere da KEY, em hexadecimal “0x1C”

00444385 MOV ESI,EAX
00444387 LEA EDX,DWORD PTR SS:[EBP-64]
0044438A LEA EAX,DWORD PTR SS:[EBP-54]
0044438D PUSH EDX
0044438E LEA ECX,DWORD PTR SS:[EBP-44]
00444391 PUSH EAX
00444392 PUSH ECX
00444393 PUSH 3
00444395 CALL DWORD PTR DS:[<&MSVBVM60.__vbaFreeVarList>]

Libera alguns espaços na pilha.

0044439B ADD ESP,10
0044439E CMP SI,BX
004443A1 JL pernet.00444492

Controle de erro.

004443A7 IMUL DI,DI,40

Os registradores (E)DI e (E)SI tem um papel fundamental na função, são eles que realizam vários cálculos para chegar no resultado final, ou seja a string descriptografada. Aqui o DI é multiplicado por 0x40, um valor “hard-coded” na função.

004443AB MOV AX,WORD PTR SS:[EBP-24]
004443AF JO pernet.00444508

O JO é para controle de erro das operações, Jump if Overflow.

004443B5 ADD DI,SI

Aqui o DI e SI sendo usados.

004443B8 JO pernet.00444508
004443BE ADD AX,6
004443C2 JO pernet.00444508
004443C8 CMP AX,8
004443CC MOV DWORD PTR SS:[EBP-24],EAX
004443CF JL pernet.0044447F

Esse é um trecho muito importante, o AX serve como um “controlador” para permitir que o caractere da string criptografada seja utilizado nos cálculos da descriptografia ou não. Na verdade a cada 3 caracteres um é descartado.

Se o AX for menor que 8 é descartado e inicia um novo loop. No primeiro loop o JL (Jump if Lower) é satisfeito já que o AX tem o valor 6.

004443D5 MOVSX ESI,DI
004443D8 LEA EDX,DWORD PTR SS:[EBP-24]
004443DB SUB AX,8

Instruções importantes envolvendo o ESI, EDI e AX.

004443DF PUSH EDX
004443E0 PUSH ESI
004443E1 JO pernet.00444508
004443E7 MOV DWORD PTR SS:[EBP-24],EAX
004443EA CALL pernet.00445610
004443EF MOV ECX,EAX
004443F1 CALL DWORD PTR DS:[<&MSVBVM60.__vbaI2I4>]
004443F7 MOV EBX,EAX
004443F9 LEA EAX,DWORD PTR SS:[EBP-24]
004443FC PUSH EAX
004443FD PUSH 1
004443FF CALL pernet.00445490

00444404 MOV ECX,EAX
00444406 MOV EAX,ESI
00444408 CDQ
00444409 IDIV ECX

Esse é o trecho mais importante e mais obscuro da função. Chama duas funções externas nos offsets destacados, ao invés de tentar entender o que essas funções fazem resolvi estudar os retornos delas e descobri que segue um padrão. O resultado sempre vai ser 0x10, 0x04 ou 0x01, e depois recomeça a sequência.

A lógica por trás desse bloco é: o ESI é utilizado como o QUOCIENTE de uma divisão e o resultado dessas funções obscuras é o DIVISOR dessa divisão. Então no último trecho é feita uma divisão:

EAX = EAX (ESI) / ECX (0x10, 0x04 ou 0x01)

Ainda, o resto da divisão também é utilizado nos cálculos e é atribuído ao EDX que poderia ser representado pela operação de módulo:

EDX = EAX (ESI) módulo ECX (0x10, 0x04 ou 0x01)

0044440B MOV ECX,EDX
0044440D CALL DWORD PTR DS:[<&MSVBVM60.__vbaI2I4>]
00444413 MOV EDX,DWORD PTR SS:[EBP-20]
00444416 AND BX,0FF
0044441B MOV EDI,EAX
0044441D MOV DWORD PTR SS:[EBP-7C],EDX
00444420 MOV DWORD PTR SS:[EBP-84],8
0044442A JNS SHORT pernet.00444435
0044442C DEC BX
0044442E OR BX,0FF00
00444433 INC BX
00444435 MOVSX EAX,BX
00444438 LEA ECX,DWORD PTR SS:[EBP-44]
0044443B PUSH EAX
0044443C PUSH ECX
0044443D CALL DWORD PTR DS:[<&MSVBVM60.#608>] ; MSVBVM60.rtcVarBstrFromAnsi

Esse trecho pega o resultado da divisão e transforma ele no caractere ASCII equivalente, por exemplo no segundo loop o resultado da divisão foi 0x34, se procurar na tabela ASCII o hexadecimal 0x34 equivale ao caractere “4”. No terceiro loop o resultado da divisão foi 0x37 equivalente ao “7”.

00444443 LEA EDX,DWORD PTR SS:[EBP-84]
00444449 LEA EAX,DWORD PTR SS:[EBP-44]
0044444C PUSH EDX
0044444D LEA ECX,DWORD PTR SS:[EBP-54]
00444450 PUSH EAX
00444451 PUSH ECX
00444452 CALL DWORD PTR DS:[<&MSVBVM60.__vbaVarCat>]
00444458 PUSH EAX
00444459 CALL DWORD PTR DS:[<&MSVBVM60.__vbaStrVarMove>]
0044445F MOV EDX,EAX
00444461 LEA ECX,DWORD PTR SS:[EBP-20]
00444464 CALL DWORD PTR DS:[<&MSVBVM60.__vbaStrMove>]
0044446A LEA EDX,DWORD PTR SS:[EBP-54]
0044446D LEA EAX,DWORD PTR SS:[EBP-44]

Nesse bloco é feita a concatenação dos caracteres ASCII que são obtidos como resultado e colocados em um local da pilha. Nos exemplos do “4” e “7”, a string concatenada já estaria “47...”.

00444470 PUSH EDX
00444471 PUSH EAX
00444472 PUSH 2
00444474 CALL DWORD PTR DS:[<&MSVBVM60.__vbaFreeVarList>]
0044447A ADD ESP,0C
0044447D XOR EBX,EBX
0044447F MOV EAX,1
00444484 ADD AX,WORD PTR SS:[EBP-18]
00444488 JO SHORT pernet.00444508
0044448A MOV DWORD PTR SS:[EBP-18],EAX
0044448D JMP pernet.004442EC

Libera a memória, incrementa o contador e faz um Jump incondicional para o início do loop, agora vai pegar o segundo caractere da string criptografada.

**************************************************
FIM DO LOOP
**************************************************

00444492 MOV ECX,DWORD PTR SS:[EBP-20]
00444495 PUSH ECX
00444496 CALL pernet.004457A0
0044449B MOV EDX,EAX
0044449D LEA ECX,DWORD PTR SS:[EBP-1C]
004444A0 CALL DWORD PTR DS:[<&MSVBVM60.__vbaStrMove>]
004444A6 PUSH pernet.004444F2
004444AB JMP SHORT pernet.004444E1
004444AD TEST BYTE PTR SS:[EBP-4],4
004444B1 JE SHORT pernet.004444BC
004444B3 LEA ECX,DWORD PTR SS:[EBP-1C]
004444B6 CALL DWORD PTR DS:[<&MSVBVM60.__vbaFreeStr>]
004444BC LEA ECX,DWORD PTR SS:[EBP-34]
004444BF CALL DWORD PTR DS:[<&MSVBVM60.__vbaFreeStr>]
004444C5 LEA EDX,DWORD PTR SS:[EBP-74]
004444C8 LEA EAX,DWORD PTR SS:[EBP-64]
004444CB PUSH EDX
004444CC LEA ECX,DWORD PTR SS:[EBP-54]
004444CF PUSH EAX
004444D0 LEA EDX,DWORD PTR SS:[EBP-44]
004444D3 PUSH ECX
004444D4 PUSH EDX
004444D5 PUSH 4
004444D7 CALL DWORD PTR DS:[<&MSVBVM60.__vbaFreeVarList>]
004444DD ADD ESP,14
004444E0 RETN

Após o loop haverá uma string concatenada com todos os resultados, no nosso exemplo seria:

"475F44425F5553554152494F5F415649534F"

Nesse trecho final é feita a conversão dessa string novamente para ASCII pegando de dois em dois os caracteres e transformando-os no equivalente em ASCII:

0x47 = G
0x5F = _
0x44 = D
0x42 = B
0x5F = _
0x55 = U
0x53 = S
0x55 = U
0x41 = A
0x52 = R
0x49 = I
0x4F = O
0x5F = _
0x41 = A
0x56 = V
0x49 = I
0x53 = S
0x4F = O

A string é concatenada e retornada através do EAX:

"G_DB_USUARIO_AVISO"

**************************************************
FIM DA FUNÇÃO
**************************************************

Um pouco mais de código

Agora que já “entendemos” o que a função faz, ficou fácil (ou não) escrever um programa que realiza a descriptografia de forma automática.

Peguei todos as strings criptografadas do malware e copiei em um TXT. Como fiz isso? No OllyDbg na opção “Search for – all referenced text strings” copiei tudo para a área de transferência e salvei no TXT, mas nem todas eram criptografadas, fiz uma triagem com ajuda do Excel definindo colunas e linhas. Esse processo levou cerca de 15 minutos.

Criei o código abaixo em Python que lê linha a linha o TXT e imprime a string descriptografada.

import sys

key = "w/sDbk2VKcUy5nJTA0paP8xXqMIifSlBH3Z+vjYNRrezm7oWEOh6GLdu9Q4t1gCF"

def decode(enc):
  i = 0
  loop = 1
  edi = 0x0
  esi = 0x0
  control = 0x0
  dec = ""
  final = ""
  
  while(i<len(enc)):
    esi = key.find(enc[i])
    edi = edi * 0x40
    edi = edi + esi
    control = control + 0x06

    if(control >= 0x08):     
      esi = edi
      control = control - 0x08

      if loop==1:
        divisor = 0x10;
        loop = loop + 1
      elif loop==2:
        divisor = 0x04
        loop = loop + 1
      else:
        divisor = 0x01
        loop = 1
        
      result = esi / divisor
      edi = esi % divisor

      if result>0: dec += chr(result)

    i = i + 1

  i = 0
  
  while(i<len(dec)):
    try:
      if i+1 < len(dec):
        aux = dec[i]+dec[i+1]
      else:
        aux = dec[i]

      aux = chr(int(aux,16))
      final += aux
    except:
      pass

    i = i + 2
  
  return final

def main():
  try:
    f = open(sys.argv[1],"r")
  except:
    print "File not found! Usage: python decrypt.py <file.txt>"
    sys.exit(2)

  for line in f:
    print decode(line)
  f.close()

if __name__ == "__main__":
  main()


A função "decode" faz a mesma coisa que as linhas que vimos em assembly, recebe um string criptografada e retorna ela descriptografada fazendo os mesmos cálculos.

Salvei como “decrypt.py” e para executá-lo redirecionei a saída para um TXT com esse comando:

python decrypt.py encrypted.txt > decrypted.txt


Algumas das strings descriptografadas que foram encontradas:

https://www2.bancobrasil.com.br/aapf/
HKEY_CURRENT_USER\Software\Microsoft\
C:\avenger.txt
\GbPlugin\bb.gpc
http://vivaxmotos.com/data/c_c_s.gif
https://internetbanking.caixa.gov.br/
numeroContratoOrigem
titular
senhaConta
onkeydown
SQLOLEDB
Data Source
Initial Catalog
User ID
Password
Cadastro_Computador Travou Browser


Como eu disse são mais de 2.200 strings criptografadas, infelizmente não terei tempo de analisar o malware com mais cuidado mas se você quiser poderá baixar todos os arquivos envolvidos no caso (senha: crimesciberneticos.com) e continuar a análise.

Conclusão

Esse não é o método mais fácil nem mais rápido para realizar esse tipo de trabalho mas talvez seja o mais desafiante. Uma das dificuldades que tive foi com as funções importadas da biblioteca do Visual Basic MSVBVM60.DLL.

Praticamente não existe documentação dela na Internet e as vezes as funções utilizam um padrão diferente para retornar os resultados, ao invés de ser no EAX é no EAX+8. Caso fosse a biblioteca do C seria bem mais fácil o processo.

O debugger é o melhor amigo nesses casos, poder executar o programa linha a linha e acompanhar os resultados nos registradores e na pilha ajudam muito no entendimento das funções.

Quando estamos analisando um código assembly é muito importante que pensemos de forma mais abrangente, não devemos focar em o que cada linha faz mas sim qual a função de cada bloco de código, como ele seria representado em uma linguagem de alto-nível. 

Geralmente são necessárias várias linhas de assembly para executar uma instrução de apenas uma linha em linguagem de alto-nível. Conseguir fazer essa abstração é essencial na engenharia reversa.

* Baixe todos os arquivos do post aqui - senha: crimesciberneticos.com

Ronaldo P. Lima
Related Posts Plugin for WordPress, Blogger...