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...
Ronaldo,
ResponderExcluirMuito bom o seu post e vamos ver como será a Parte II.
Abraços,
Vitor Nakano
Parabéns, muito bom, ótimo.
ResponderExcluirÓtimo artigo, que Deus te abençoe.
Muito bom seu blog, fiquei fã. Parabéns.
ResponderExcluirCara, Excelente post. Continue postando, estou aguardando a parte 2.
ResponderExcluirAbraços
Esperando a parte 2 e esperando as imagens dos post serem restauradas!
ResponderExcluirMuito bom o post!
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