Dissecando Malware – Parte 1

Nesse artigo inicio a análise detalhada de um malware curioso, ao invés de executar de uma só vez, ele faz execuções recursivas para atingir seus objetivos. Em cada execução um novo bloco de código é alcançado até chegar na função principal. Essa estratégia pode ter sido utilizada para dificultar a análise ou mesmo ser apenas um estilo de codificação.

O objetivo desse artigo é mostrar um pouco mais sobre as estruturas internas dos malwares. O tópicos abordados nessa primeira parte serão:

- O Phishing
- Os Imports
- Caminhos de execução
- 1ª Execução
- Uso de Mutex
- Mais validações
- Preparação para a 2ª Execução
- Algumas Considerações

Qualquer dúvida, sugestão, correção, será muito bem-vinda. E não deixe de acompanhar as novidades do blog no twitter: @crimescibernet.

Let's start!

O Phishing

Tudo começou com um e-mail se passando pela Receita Federal comunicando que devido a um congestionamento nos sistemas, algumas declarações haviam sido danificadas e era necessário verificar com o CPF se houve problema ou não com a declaração. A velha fórmula de engenharia social envolvendo assunto do momento mais a ameaça ao usuário (medo).


O link apontava para essa URL:

hxxp://receita-federal.mail333.su/Declaracao 2011.rar

Fiz o donwload do RAR e descompactei, tratava-se do executável “Declaracao 2011.exe”. Estava compactado com o UPX e foi só utilizar o “upx -d” para remover o packer. No Brasil ainda utilizam bastante o UPX, será que só querem diminuir o tamanho do arquivo ou acham que ele realmente é bom para ofuscação de código?

Submetendo o arquivo descompactado ao Exeinfo PE e RDG Packer Detector a linguagem de programação identificada foi Borland Delphi 2009-2010. Fizeram um upgrade no Delphi, na maioria das vezes a versão é 6.0 ou 7.0.


Os Imports

Agora sim vamos iniciar a análise do código, as ferramentas que irei utilizar serão o IDA Pro para análise estática e o OllyDbg para debugging de alguns trechos do código.

Ao carregar o arquivo no IDA a primeira coisa que fiz foi abrir a aba Imports, ou seja, as funções que o malware importa da API do Windows. Eram muitos imports, mas alguns me chamaram atenção:

MessageBoxA
QueryPerformanceCounter
GetTickCount
WriteFile
IsDebuggerPresent
GetEnvironmentVariableA
GetEnvironmentVariableW
DeleteFileW
CreateMutexA
ShellExecuteA

Os imports de um programa dizem muito sobre suas funcionalidades. A partir dessa pequena lista podemos deduzir muitas coisas, o uso do MessageBoxA no diz que o malware em algum momento apresenta uma mensagem para o usuário.

Ao ver QueryPerformanceCounter e GetTickCount logo imaginei que eram utilizadas para anti-debugging. Geralmente elas são utilizadas na técnica de Time Check, onde o malware tira um timestamp, realiza algumas instruções, tira outro timestamp e compara os dois.

Se o tempo decorrido for maior que o normal ele supõe que está rodando dentro de um debugger, já que o debugger atrasa a execução do código. E ainda também há o uso da função IsDebuggerPresent, o nome já diz tudo. Estaríamos lidando com um malware com técnicas anti-debugging? Veremos mais adiante.

WriteFile e DeleteFile nos diz que o malware grava e apaga arquivo no disco. GetEnvironmentVariable no mostra que o malware manipula ou utiliza alguma variável de ambiente do SO.

CreateMutex é a função que cria objetos que são compartilhados por processos, isso para lidar com a concorrência, ou semáforo, entre os processos. Veremos mais sobre ela adiante. ShellExecuteA realiza operações em um arquivo, do tipo abrir, editar, imprimir, etc.

O “A” ou “W” que existe no final do nome das funções se referem a “ASCII” ou “UNICODE” (Wide), respectivamente. Para descobrir o que cada função faz, consulte o site da MSDN, pode-se buscar no Google: “NomedaFuncao msdn”.

Apesar dos muitos imports, algo me chamou atenção, não havia nenhum import de API relacionada a funções de rede. Muito estranho para um malware já que praticamente todos utilizam a Internet para se comunicarem com um servidor C&C, baixarem mais pragas, enviarem dados, etc. Somente havia imports das DLLs: kernel32, advapi32, oleaut32, shell32 e user32.


Porém, ao buscar as strings do malware, encontrei referências a DLLs com funções de rede: WS2_32.DLL, MSWSOCK.DLL, Wship6.dll e FwpucInt.dll. Então, essas DLLs devem ser carregadas pelo malware em tempo de execução através da função LoadLibrary. Veremos também sobre isso adiante.



Ainda falando sobre as strings, encontrei algumas criptografadas, no decorrer da análise veremos o uso delas.

Já temos algumas informações sobre as funcionalidades do malware, hora de seguir em frente e encarar o (dis)assembly!

Caminhos de execução

Esse malware funciona de maneira peculiar, ele é recursivo. Na verdade, após a primeira execução originada pelo usuário ele se auto-executa mais duas vezes. Em cada execução ele passa por algumas validações e é direcionado para um caminho diferente de código, até atingir o objetivo final.

Vamos seguir as funções principais do código para entender melhor com isso funciona. Abaixo o trecho inicial do código. Use essa imagem para acompanhar o que está sendo explicado, esse é o fluxo principal do programa, o Main Code.


1ª Execução

Assim que o malware é executado pelo usuário, é chamada uma função que denominei FilePathInfo. Essa função é complexa, dentro dela há chamadas para umas outras 20 funções, nesses casos para não nos perdermos devemos abstrair e tentar entender o propósito geral da função, para isso o debugger ajuda muito.

Descobri que basicamente essa função busca o caminho da variável de ambiente TEMP através da GetEnvironmentVariableA, como podemos ver no código abaixo.


A string “TEMP” está criptografada mas logo abaixo há a função de descriptografia, com o debugger foi fácil obter o retorno. Esse tipo de criptografia de strings já é bem conhecido do blog.

E depois ela obtém o caminho do executável com o uso da GetCommandLineW. Feito isso ela compara os dois caminhos e retorna:

0 se forem diferentes
1 se forem iguais

Após a chamada da função FilePathInfo há o teste do retorno no Main Code:

call FilePathInfo
test al, al
jnz short Continue

Jump if Not Zero (jnz), se forem iguais pule para o bloco de código que denominei “Continue”. Em outras palavras, testou se o malware está sendo executado dentro do diretório TEMP. Como não está, vai executar o bloco de código logo abaixo, apontado pela seta vermelha.

Uso de Mutex

A próxima instrução é a chamada para a função TestMutex (nomeada por mim). Aqui teremos os uso do mutex. Mutex é um objeto criado no kernel do Windows, esse objeto pode-se entender como uma string. Essa string é utilizada por um programa (processo) para sinalizar a outro (programa) alguma coisa.

Sabemos que cada processo em user-mode tem seu espaço de memória reservado e isolado dos demais, justamente para evitar que um interfira no outro. Criar o mutex no kernel permite que todos os processos tenham acesso a ele.

Essa técnica é muito utilizada por malwares para garantir que somente uma instância do processo esteja em execução, para evitar erros de execução por exemplo. O processo antes de executar completamente sempre verifica se o mutex já existe e se existir finaliza o processo, sinal que já há outra instância do malware em execução.

Isso é exatamente o que nosso artefato faz, dentro da função TestMutex há o trecho de código abaixo.


Há o uso de outra string criptografada que em texto simples significa "SFIheg72". Esse será o nome do mutex. Depois há a chamada à função CreateMutex da API do Windows. Se consultarmos na MSDN o que essa função faz, diz o seguinte:

Creates or opens a named or unnamed mutex object.

Então é passado o nome do mutex para a função, se o mutex "SFIheg72" não existir é criado um mutex com esse nome, caso contrário é aberto o mutex já existente.

Como o programa sabe se foi criado um novo ou se já existia? Buscando o código de erro gerado pela função através da GetLastError. Há uma tabela padrão no Windows com todos os códigos de erros. Nessa tabela há:

Código
Constante
Descrição
0 (0x0)
ERROR_SUCCESS
A operação foi realizada com sucesso.
183 (0xB7)
ERROR_ALREADY_EXISTS
Não é possível criar o arquivo quando ele já existe.

No código vemos que é feita uma comparação com o valor 0xB7, isso para descobrir se o mutex já existe ou não. Baseado nessa comparação, a função TestMutex irá retornar:

0 se o mutex não existir (e criará o mutex)
1 se o mutex existir (e retornará um handle para o mutex)

Note que após a chamada da função no Main Code, é feita uma comparação com o retorno no registrador AL. Jump if Zero (jz) “Continue”. E se o valor não for zero, ou seja, o mutex já existe, o que quer dizer que outra instância do malware já está sendo executada. Nesse caso é chamada uma função que finaliza o processo com ExitProcess.

Prosseguindo com a execução, agora no bloco “Continue”, há novamente a chamada para a função TestMutex. Dessa vez ela retornará 1 pois o mutex já existe, acabou de ser criado como vimos.

Mais validações

Novamente é chamada a função FilePathInfo e ela retornará 0, já que o arquivo não está dentro do diretório TEMP. Não identifiquei a necessidade de chamar a TestMutex e a FilePathInfo nesse trecho de código.

Prosseguindo no Main Code, é chamada a função que denominei GetToken. Nessa função há uma série de chamadas a funções relacionadas com o SID (Security Identifier) do processo em execução. Ficou meio obscura a funcionalidade dela pra mim, mas de forma geral tem a ver com as permissões de acesso do usuário que está executando o processo, é feita algumas comparações relacionadas a isso.

Parece que se o usuário não tiver privilégios suficientes o processo é finalizado. Nos meus testes essa função sempre retornou 1, mas vemos que se ela retornar 0 é chamado o código que prepara a finalização do processo.

Abaixo a continuação do Main Code para prosseguirmos com a análise.


Como a GetToken retornou 1, a execução prosseguirá para a chamada da FilePathInfo novamente. Como sabemos ela verifica se o executável está dentro do TEMP e retorna 0 caso não esteja, que é o valor retornado aqui.

O Jump redirecionará para o bloco de código “ExecFile”. Esse por sua vez chama a função rotulada por mim como ExecuteFile.

Preparação para a 2ª Execução

A função ExecuteFile possui diferentes caminhos com diferentes validações, vamos passar pelas instruções mais interessantes.

Pouco depois de armazenar espaço na pilha para as variáveis locais, a função chama a FilePathInfo para testar se o executável está no TEMP. Não estando lá é feito um jump para o bloco de código que denominei “NotInTEMP”. Abaixo seguem as instruções desse bloco.


Pelo nome que coloquei nas funções já dá pra ter uma ideia do que elas fazem. A GetTEMPVar busca o caminho do diretório %TEMP%, ReturnPath busca o caminho completo do executável, ReturnFileName retorna somente o nome do arquivo, RemoveExtension retira do nome do arquivo a extensão, o “.exe” (poderia ser ponto qualquer coisa).

Depois é utilizada uma string criptografada que equivale a “.exe”. Por fim, é chamada a função MakeNewPath que faz a concatenação de tudo:

%TEMP% + FileName + “.exe”

E em seguida a GetNewPath retorna esse caminho:

C:\DOCUME~1\ADMINI~1\CONFIG~1\Temp\Declaracao 2011.exe

Após construir o caminho é hora de salvar o arquivo em disco, isto é, fazer uma cópia do arquivo no diretório TEMP. A função que faz isso está logo abaixo, chamada de WriteMZFile.


Após salvar em disco há uma chamada para a função GetOSVersion, ela é responsável por verificar a versão do Windows, não sei dizer se essa verificação foi adicionada pelo programador ou pelo próprio compilador, o código dela está a seguir.


A função GetVersionExW da API do Windows tem a seguinte definição:

Retrieves information about the current operating system. If the function succeeds, the return value is a nonzero value. If the function fails, the return value is zero.

Como parâmetro a função recebe uma estrutura de dados chamada de VersionInformation que receberá as informações do sistema operacional caso tenha sucesso. Essa estrutura possui o campo dwMajorVersion que é o versão numérica principal do SO. Na MSDN há uma tabela com os nomes e versões possíveis para esse campo.

Basicamente, a versão será:

6 – para Windows 7, Server 2008 e Vista.
5 – para Windows 2000, XP, Server 2003.

Vemos no código que há uma comparação com o 6 que determinará o valor do EAX (AL). Se a versão for 6 o AL (EAX) receberá 1, caso contrário receberá 0 (xor eax, eax).

E o EAX será o retorno da GetOSVersion, que após a chamada da função é utilizado em um “test”. No meu caso, como estou utilizando o Windows XP, a versão retornada foi a 5 e consequentemente o EAX recebeu 0.

No “test al, al” fui redirecionado pelo “JZ” para o bloco de código “loc_4BC2D3”.


Vemos no código o uso de uma string criptografada que em texto simples equivale a “open”. E depois é obtido o novo caminho do arquivo através da GetNewPath.

Enfim vamos chegar na segunda execução do malware. Isso é feito com a função ShellExecuteA. Essa função executa uma operação e um arquivo específico e possui 6 parâmetros:

HINSTANCE ShellExecute(
__in_opt HWND hwnd,
__in_opt LPCTSTR lpOperation,
__in LPCTSTR lpFile,
__in_opt LPCTSTR lpParameters,
__in_opt LPCTSTR lpDirectory,
__in INT nShowCmd
);

Os mais interessantes para nós são o lpOperation (qual operação vai ser executada no arquivo), lpFile (o arquivo utilizado) e lpParameters (parâmetros passado para o arquivo, como em linha de comando). Para saber mais sobre a função consulte a MSDN.

No código a seguir vemos a chamada da função com a passagem de parâmetros através do 6 PUSHs.


Com o OllyDbg conseguimos ver a chamada da função, e como é possível imaginar, com os parâmetros ela ficará assim:


Isso vai abrir (executar) o arquivo recém-criado no TEMP. Trata-se do mesmo arquivo, mesmo MD5. Lembro que todo esse código está dentro da ExecuteFile. Após o retorno dessa função, o processo original (Main Code) é finalizado.

Algumas considerações

Vimos que em determinados trechos do código há a verificação se o arquivo está no TEMP ou não, por causa disso nessa segunda execução o malware percorrerá caminhos diferentes, o que fará com que ele execute outras funções.

Outro ponto a ser observado, imaginemos que estamos analisando o malware com o debugger, linha a linha. O que acontecerá quando chegar na função ShellExecute e dermos um step over? O malware será executado sem que possamos debugá-lo já que será criado um novo processo.

Por isso, é importante entendermos seu funcionamento. Agora que sabemos o que ele fez, se quisermos debugar essa segunda execução é só abrir o executável a partir do TEMP.

Normalmente não precisamos fazer uma análise detalhada assim, basta sabermos o que o malware faz, mas quanto maior o conhecimento do baixo-nível mais fácil será para obter o alto-nível.

Continua...

6 comentários:

  1. Ronaldo,
    Muito bom o seu post e vamos ver como será a Parte II.

    Abraços,
    Vitor Nakano

    ResponderExcluir
  2. Parabéns, muito bom, ótimo.
    Ótimo artigo, que Deus te abençoe.

    ResponderExcluir
  3. Muito bom seu blog, fiquei fã. Parabéns.

    ResponderExcluir
  4. Cara, Excelente post. Continue postando, estou aguardando a parte 2.
    Abraços

    ResponderExcluir
  5. Esperando a parte 2 e esperando as imagens dos post serem restauradas!
    Muito bom o post!

    ResponderExcluir
  6. ola exeinfo ip nao se encontra mais para baixar em lugar nenhum nem mesmo no sit oficial que por algum motivo deixou de funciona porem gostei da ferramenta se alguem souber localizaçao dela para min baixa kk vlw

    ResponderExcluir

Related Posts Plugin for WordPress, Blogger...