Você está na página 1de 16

Análise de Malwares

Aula 4: Análise Estática Avançada e Codificação – Parte II

Apresentação
Nesta quarta aula aprofundaremos a linguagem de máquina – Assembly, com exemplos mais elaborados e sua
correlação com estruturas comuns na linguagem em C.

Veremos a utilização de uma ferramenta poderosa na análise de malware, finalizando o básico da linguagem para
prosseguimento da disciplina.
Objetivo
Identificar chamadas de funções e passagem de parâmetros em assembly;

Identificar as funções básicas da ferramenta IDA Pro;

Distinguir Linguagem de alto e baixo nível.

Assembly
Na última aula introduzimos o básico da linguagem assembly, bem como realizamos alguns exercícios para fixar os
conhecimentos. É difícil fazer generalizações, pois toda regra possui uma exceção, mas todos os códigos complexos e bem
escritos possuem funções.

O que são funções?

As funções são partes do código dentro de um programa que executam uma tarefa específica, uma rotina, e que são
relativamente independentes do código restante. O código principal chama e transfere temporariamente a execução para
funções antes de retornar ao código principal.

Ressaltamos que a função pode ser implementada pelo autor do código ou


pode ser empregada para abrir uma conexão, um arquivo ou escrever algo
na tela.

Já mencionamos que todas as operações de um programa são realizadas na memória, e a pilha é o espaço reservado para que
isso aconteça. Todas as operações que precisam ser armazenadas temporariamente são armazenadas na pilha.

Na arquitetura x86, os parâmetros passados para uma função são passados para a pilha (Stack) para serem manipulados. A
instrução utilizada para armazenar dados na pilha é o push, lembrando que os dados são armazenados no tipo FIFO – primeiro
a entrar, último a sair –; pense numa pilha de pratos, o primeiro a ser colocado é o último a ser tirado.

Saiba mais
Existem outros pontos que podem ser necessários, mas que não teremos condições de abordar por falta de tempo. Alguns
pontos serão verificados nas próximas sessões, os demais podem ser pesquisados e aprofundados no manual disponível pela
própria Intel em: https://software.intel.com/content/www/us/en/develop/articles/intel-sdm.html

IDA Pro
O Interactive Disassembler Professional (IDA Pro) é um desmontador extremamente poderoso lançado pela empresa Hex-Rays.
Embora o IDA Pro não seja o único, é o desmontador mais utilizado na atualidade. Recentemente, a NSA – Agência de
Segurança Nacional dos EUA lançou uma ferramenta com os mesmos propósitos chamada Ghidra, que está fazendo muito
sucesso.

O IDA Pro é uma solução paga; existem duas versões comerciais e a diferença entre elas são os suportes aos processadores. A
versão avançada oferece suporte a muitos processadores, e suporta diversos formatos de arquivos, como o Portable
Executable (PE) para a plataforma Windows e o Executable and Linking Format (ELF) para a plataforma Linux.

Comentário

Concentraremos nossa discussão nas arquiteturas x86 e x64 e no formato de arquivo PE para Windows. Utilizaremos uma versão
gratuita do IDA Pro, o IDA Pro Free, disponível em http://www.hex-rays.com/idapro/idadownfreeware.htm.
Saiba, porém, que esta versão tem funcionalidades limitadas. Ela não é indicada para uso profissional, mas será mais do que
suficiente para nós. Não se limite ao que for ensinado aqui e procure explorar ainda mais; lembre-se de que a experiência vem
com a prática.

Basicamente, o IDA Pro processará o programa e realizará tarefas como, mas não limitadas a:

 Fonte: O autor.

Veremos, nessa aula, as técnicas básicas de como traçar um paralelo entre a análise feita pela ferramenta e o código-fonte da
aplicação. O IDA Pro possui uma base de dados de assinaturas de código que permitem reconhecer e rotular as funções
desmontadas.
A ferramenta foi criada para ser interativa; todo o processo de desmontagem pode ser modificado, manipulado, reorganizado
ou redefinido.

Um dos melhores aspectos do IDA Pro é a sua capacidade de salvar e documentar todo o progresso da análise, sendo
possível adicionar comentários, rótulos e nome de funções e, em seguida, salvar seu trabalho em um banco de dados
01 IDA Pro (conhecido como idb) para retorno posterior. O IDA Pro também oferece suporte para adição de extensões
próprias ou de terceiros para auxiliar na análise.

Ao carregar um executável no IDA Pro, ele tenta reconhecer o formato do arquivo e a arquitetura do processador. No
exemplo da figura a seguir (Figura 1), o arquivo é automaticamente reconhecido como formato PE e com a arquitetura
02 Intel x86. Outra característica da ferramenta é o mapeamento do arquivo em memória como se tivesse sido carregado
pelo sistema operacional.

Uma opção útil é carregar o arquivo como um binário bruto, pois o mesmo pode possuir mecanismos de proteção e
não carregar dados adicionais, parâmetros de criptografia e até mesmos outros executáveis na memória quando o
03 malware for executado pelo Windows ou carregado no IDA Pro.

Figura 1: Carregamento de arquivo no IDA Pro

 Fonte: O autor.

Por padrão, o IDA Pro não inclui o cabeçalho PE ou as seções de recursos em sua desmontagem; esses locais podem ser
utilizados para esconder códigos maliciosos pelo artefato.

Basta selecionar a opção de carregamento manual (Manual Load) que a ferramenta perguntará se você deseja carregar cada
seção, uma por uma, incluindo o cabeçalho do arquivo PE. Isso será importante para que essas seções não escapem da
análise.

É possível analisar o código em modo gráfico e em modo de texto. No modo gráfico, a cor e a direção das setas ajudam a
mostrar o fluxo do programa durante a análise. A cor da seta informa se o caminho é baseado em uma decisão específica que
foi tomada:
Vermelho, se determinada Verde se for atendida. Azul para um salto incondicional.
condição não for atendida.

Exemplo
Por exemplo, digamos que determinado trecho de código verifica se existe conexão com a internet; caso haja, o programa
executará determinada ação – seta verde;em caso contrário, executará outras ações – seta vermelha.

A direção da seta mostra o fluxo do programa; setas para cima normalmente denotam uma situação de loop. É possível
também destacar um texto; assim, todas as instâncias do mesmo texto são destacadas, auxiliando a encontrar padrões e
repetições.

Figura 2: Modo gráfico do IDA Pro

 Fonte: O autor.

Já o modo de texto é uma visão mais tradicional da análise do binário e pode ser usado para análises mais profundas,
verificando, por exemplo, outras seções do mesmo. A Figura a seguir exibe a visualização do modo de texto de um binário; o
endereço de memória (0040105B) em vermelho, o nome da seção (seg002) e os opcodes em verde, e a instrução
correspondente, ao lado direito, em amarelo.

Figura 3: Modo texto do IDA Pro

 Fonte: O autor.

Várias outras janelas do IDA Pro destacam itens específicos em um executável. Os itens a seguir são os mais significativos
para nossos propósitos:

Janela de funções

Lista todas as funções no executável e
mostra o tamanho de cada uma. Você pode
classificar por comprimento de função e
filtrar por funções grandes e complicadas que
provavelmente sejam interessante.

Janela de nomes

Lista todos os endereços com um nome,
incluindo funções, código, dados e strings.

Janela Strings

Mostra todas as strings. Por padrão, essa
lista mostra apenas strings ASCII com mais
de cinco caracteres. Você pode alterar isso
clicando com o botão direito na janela e
selecionando Setup.

Janela de importações

Lista todas as importações de um arquivo.

Janela de exportação

Lista todas as funções exportadas para um
arquivo. Esta janela é bastante útil quando
analisamos DLLs.

Janela de estruturas

Lista o layout de todas as estruturas de
dados ativas.

Essas janelas também oferecem um recurso de referência cruzada que é particularmente útil para localizar códigos
interessantes.
Exemplo

Por exemplo, para encontrar todos os locais de código que chamam uma determinada função, você pode usar a janela de
importação, clicar duas vezes na função importada de interesse e usar o recurso de referência cruzada para localizar a chamada
de importação na listagem de código.

É possível realizar buscas por determinados textos dentro da janela atual no IDA Pro; basta selecionar o menu Search e a opção
texto (Text). Outra busca bastante útil é a busca por sequências de bytes, caso seja necessário fazer uma busca binária dentro
do contexto. Pode-se buscar, também, por combinações de opcodes e por dados específicos.

Outra possibilidade do IDA Pro é a referência cruzada, citada como xref no IDA Pro, que informa onde uma função é chamada
ou onde uma string é usada, como pode ser visto na Figura 3.

Vejamos um caso de uso.

Você identifica uma função útil e precisa saber os parâmetros com os


quais ela é chamada; você pode usar uma referência cruzada para
navegar rapidamente até o local em que os parâmetros são colocados
na pilha através da referência cruzada. Gráficos interessantes também
podem ser gerados com base em referências cruzadas, que são úteis
para realizar análises.

Por padrão, o IDA Pro mostra apenas algumas referências cruzadas para as funções; normalmente existem muitas chamadas
a uma função durante a execução de um programa.

Caso queira ver todas as referências cruzadas para uma função, basta clicar no nome da função e pressionar a tecla ‘x’; surgirá
uma janela que conterá uma lista com todos os locais em queesta função é chamada. Basta dar um clique duplo no item da
lista para ir ao local da memória referenciado.

Conforme falamos nas aulas anteriores, a análise estática de strings pode frequentemente ser usada como ponto de partida
para sua análise. Se você vir uma string interessante, use o recurso de referência cruzada do IDA Pro para saber exatamente
onde e como essa string é usada no código.

Basta realizar o mesmo procedimento da função: Selecionar a cadeia de caracteres a ser verificada e pressionar a tecla ‘x’.

Outra característica muito utilizada é a edição de nomes. Ao carregar um binário dentro do IDA Pro, as funções e variáveis
recebem nomes sem muito sentido. Durante a análise, ao encontrar o significado da função, você pode renomear o nome da
mesma.

Basta modificar o nome desta função ou variável em apenas um local; a ferramenta propagará o nome em todas as referências
cruzadas e ficará muito mais simples identificá-la no código.

Comentário
A capacidade do IDA Pro de realizar a desmontagem do binário é apenas uma das partes de sua funcionalidade; sua capacidade
interativa é o seu maior fator de facilitação.

Códigos em C x Assembly
Na aula anterior, revisamos a arquitetura x86 e suas instruções mais comuns. Entretanto, analistas de malware experientes não
avaliam cada instrução individualmente, a menos que seja estritamente necessário. O processo é muito tedioso e as instruções
para um programa inteiro desmontado podem chegar a milhares ou até milhões.

Como analista de malware, você deve ser capaz de obter uma imagem de alto nível da funcionalidade do código, analisando as
instruções como grupos, focando em instruções individuais apenas quando necessário. Essa habilidade leva tempo para ser
desenvolvida.

Para começar a desenvolver essa habilidade, vamos pensar em como um autor de malware desenvolve o seu código. O
artefato é normalmente desenvolvido usando uma linguagem de alto nível; para nosso estudo utilizaremos a linguagem C.

A estrutura do código é um nível de abstração que define uma propriedade funcional, mas não os detalhes de sua
implementação. Exemplos de estruturas de controle de código incluem: Laços de repetição, instruções de decisão, listas
encadeadas, instruções do tipo escolhas múltiplas e outras.

Os programas podem ser divididos em construções individuais que, quando combinadas, implementam a funcionalidade geral
do programa.

Vamos analisar algumas construções em linguagem de alto nível e seu respectivo código em assembly. O analista
normalmente faz o caminho inverso, porém aprender na direção reversa é geralmente mais fácil. Por isso, vamos nos
concentrar em como as construções mais comuns, como laços de repetições e instruções condicionais, são compiladas.

Comentário

Lembre-se de que seu objetivo é compreender a funcionalidade geral de um programa, não analisar todas as instruções.
Mantenha isso em mente e não se prenda a minúcias. Concentre-se na maneira como os programas funcionam em geral, não em
como eles fazem cada coisa em particular.

Iniciaremos com simples operações matemáticas e sua correspondente instrução em assembly. Mas, antes de iniciarmos,
devemos preparar o ambiente para testes. Além de todas as recomendações já mencionadas, utilizaremos uma ferramenta
para programar e compilar os códigos criados.

Dica
Usaremos o programa Dev-C++ disponível em: https://orwelldevcpp.blogspot.com/ DI ou web, site de fonte não acadêmica (blog).
Por favor, verificar se é para manter ou não.

Figura 4: Aritmética Simples

 Fonte: O autor.

Na Figura 4 podemos verificar algumas aritméticas simples. Nesse exemplo, x e y são variáveis locais porque são referenciadas
pela pilha: x = esp+0c e y = esp+8 – lembrando que o registrador ESP aponta para o topo da pilha. Por questões de
performance, as variáveis foram tratadas apenas na memória, não sendo rotuladas pelo IDA Pro.

Primeiro, a variável é inicializada com os valores 4 e 5, respectivamente, nas instruções 1 e 2. Depois, acrescentamos 12
unidades (0x0C em hexadecimal) na terceira instrução. Então, a variável y é movida (copiada) para eax e subtrai-se de x o valor
contido em eax, instruções 4 e 5.

Por fim, as últimas duas instruções incrementam e decrementam em uma unidade o valor de y e x, respectivamente. Apesar de
ser um exemplo simples, começamos a entender como as coisas acontecem dentro do processador.

Atenção! Aqui existe uma videoaula, acesso pelo conteúdo online

Figura 5: Estrutura condicional

 Fonte: O autor.

As estruturas de controle são bastante utilizadas para alterar a execução do programa com base em certas condições. A figura
acima exibe uma instrução em C e com o assembly correspondente. Em uma estrutura condicional deve haver um salto
condicional, mas nem todos os saltos condicionais correspondem a uma estrutura condicional.
Como você pode ver na Figura 5, uma decisão deve ser tomada para execução de dois códigos diferentes. Essa decisão
corresponde ao salto condicional (jnz) e é feita com base na comparação (cmp), que verifica se x é igual a y (esp+0C e esp+18,
respectivamente).

Se os valores não forem iguais, o salto ocorre e o código imprime na tela "x diferente de y."; caso contrário, o código continua o
caminho de execução e imprime "x igual a y".

Atenção! Aqui existe uma videoaula, acesso pelo conteúdo online

Atenção

É importante que você reconheça que apenas um desses dois caminhos de código pode ser seguido.

Figura 6: Estrutura de repetição

 Fonte: O autor.

As estruturas de repetição são mecanismos muito utilizados e sempre possuem quatro componentes básicos:

Inicialização Comparação Instruções de execução

Incremento ou decremento

Nesse exemplo, a inicialização define i como 0 (zero), a comparação verifica se i é menor que 10, a instrução printf será
executada caso a condição seja atendida e, por fim, o incremento adicionará 1 a i. O ciclo ocorrerá até que i seja maior ou igual
a 10.

Em assembly:

A primeira instrução corresponde à etapa de inicialização.


A instrução 7 corresponde ao incremento.

A comparação ocorre na instrução 8.

Na instrução 9, a decisão é feita pelo salto condicional. Se o salto for executado, a instrução printf será executada
e,em caso contrário, o programa seguirá o fluxo normal.

Atenção! Aqui existe uma videoaula, acesso pelo conteúdo online

Chamadas de função
No início da aula, discutimos como a pilha é usada para chamadas de função. As chamadas de função podem aparecer de
formas diferentes no código assembly e as convenções de chamada definem a maneira como uma chamada de função ocorre.

Essas convenções incluem dois aspectos:

1 2

A ordem em que os parâmetros são colocados na pilha ou Se o chamador ou a função chamada é responsável por
nos registradores. limpar a pilha quando a função for concluída.

A convenção de chamada usada depende do compilador, entre outros fatores. Frequentemente, existem diferenças sutis em
como os compiladores implementam essas convenções; portanto, pode ser difícil fazer a interface do código compilado por
diferentes compiladores.

No entanto, você precisa seguir certas convenções ao usar chamadas de funções do sistema operacional Windows; elas são
implementadas de maneira uniforme para compatibilidade.

Figura 7: Definição e chamada de Funções

 Fonte: O autor.

Como podemos ver na Figura 7, a função é definida em outra parte da memória.

Nesse exemplo, o IDA Pro nomeou a função como __Z4somaii. Inicialmente, a função executa o prólogo (pushebp e
movesp, ebp), alocando espaço para variáveis locais.   Em nosso exemplo, a função recebe dois argumentos (arg_0 e
01 arg_4), que são movidos para o registrador edx e eax, respectivamente.

Por fim, o registrador edx é adicionado ao eax e a função é finalizada com o seu epílogo   (pop ebp e retn).Lembre-se
de que, por convenção, os valores retornados de uma função sempre estarão no registrador eax. Portanto, a função
02 não deve se preocupar em reservar uma área da memória para realizar qualquer operação, basta deixar sua operação
no valor em eax.

Após verificarmos as operações realizadas pela função, vamos analisar agora como a mesma é chamada.
Inicialmente, os valores da variável x e y são armazenados em esp+1C e esp+18, respectivamente. Depois, o
03 registrador eax é utilizado para mover o valor de y e x para esp+4 e esp, respectivamente.

Lembre-se de que esp aponta para o topo da pilha, portanto os


valores estão sendo colocados ali. A instrução push existe para esse
fim, mas por questões de performance o mov também pode ser
usado. Depois,a função soma é chamada, passando os dois
parâmetros através da pilha. A próxima instrução move para esp+4 o
valor contido em eax.

Você se lembra de onde vem esse valor?

Exato, da própria função soma.

Por fim, move-se para esp um ponteiro do tipo dword, de 4 bytes, que aponta para a área da memória que possui a string: “Valor
retornado: %d”. Por fim, chama-se a função printf, que entende que o valor a ser substituído em %d está em esp+4. Com isso,
fecha-se a execução do programa.

Switch-case
Para finalizar a aula de hoje, vamos analisar mais um exemplo. Outra estrutura de controle bastante utilizada, chamada de
switch-case, está demonstrada na figura a seguir. Essas estruturas são usadas para fazer uma decisão baseada em alguma
variável.

Um Backdoor, por exemplo, pode se basear em um determinado valor de byte;caso receba o valor correto, ele executa
determinada ação que facilitará a entrada do atacante; caso receba outro valor, ele pode hibernar por um determinado tempo.
Esse tipo de estrutura é útil, e reconhecer suas instruções é vital para um bom analista.

Figura 8: Switch-case
 Fonte: O autor.

Como podemos ver na Figura 8, o compilador compara o valor de “i” e faz um salto condicional para o local da execução
daquele caso. O último salto é incondicional, ou seja, se o valor não corresponder a nenhum dos casos criados, ele saltará para
o loc_40144C que, no caso do código em C, é o valor default, que exibirá uma mensagem na tela de “Valor incorreto”.

Pode parecer não natural em um primeiro momento, mas com o tempo e experiência a linguagem de baixo nível começará a
fazer sentido. Lembre-se de que existem diversas outras estruturas em linguagens de alto nível; algumas delas poderão ser
abordadas no decorrer do curso, as demais você deverá percorrer sozinho.

Atividade
1. As funções são partes do código dentro de um programa que executam uma tarefa específica, uma rotina, e que são
relativamente independentes do código restante. Em relação às funções, classifique a ordem em que o código em baixo nível
aparece na memória:

1. A função realiza seu trabalho.

2. Por meio do uso de um epílogo de função, a pilha é restaurada.

3. Por meio do uso de um prólogo de função, espaço é alocado na pilha para variáveis locais.

4. Os argumentos são colocados na pilha usando instruções push ou mov.

5. Uma função é chamada usando a instrução: call <local da memória>

6. A função retorna chamando a instrução ret.

a) 1 – 2 – 3 – 4 – 5 – 6
b) 4 – 5 – 3 – 1 – 2 – 6
c) 3 – 5 – 2 – 1 – 4 – 6
d) 4 – 3 – 5 – 2 – 1 – 6
e) 3 – 5 – 4 – 1 – 2 – 6
2. Qual dos tipos de arquivos abaixo não podem ser carregados na ferramenta IDA Pro?

a) exe
b) dll
c) COFF
d) ELF
e) doc

3. Analise a figura a seguir e responda:

 Fonte: O autor.

Qual das afirmações abaixo está correta?

a) A instrução acima trata-se de uma estrutura de repetição.


b) O valor de eax na hora da comparação é 4.
c) O salto dessa instrução é um salto incondicional.
d) O fim desse fluxo é a área da memória referenciada por loc_40154A.
e) Os valores comparados são idênticos.

Notas
Referências

ANLEY, C. The shellcoder’sHandbook: discoveringandexploringsecurityholes. 2.ed. [S.1.]: WileyPublishing, Inc., 2007.

SIKORSKI,M.;HONIG, A. PracticalMalwareAnalysis: The Hands-On Guide to Dissecting Malicious Software (1.ed.). No Starch
Press,2012.

TANENBAUM, A.S. Organização Estruturada de Computadores. 5.ed. São Paulo: Pearson Prentice Hall, 2010.

Próxima aula

Depuração de arquivos;

Outras ferramentas para análise de código assembly;


Outras ferramentas para análise de código assembly;

Depuração do Kernel com WinDbg.

Explore mais

Hoje prosseguimos na construção de um conhecimento um pouco mais além do que já havíamos ido, mas ainda é muito
pouco. Por fim, acrescentamos os diversos materiais que fazem referência e ensinam a manusear o IDA Pro, carregue
programas dentro dele e tente fazer algumas análises mesmo que sejam superficiais, você aumentará seu conhecimento. Por
fim, se ainda não tem o domínio ou um conhecimento aprofundado em linguagens de programação, essa é a hora, escolha
uma linguagem e aprenda a desenvolver algumas tarefas, verá que isso o ajudará muito nessa área.

Você também pode gostar