Ir para conteúdo
  • Cadastre-se
  • Entre para seguir isso  

    Montando sua máquina virtual para engenharia reversa em Linux

       (0 análises)

    Fernando Mercês

    No últmo artigo falei sobre como montar uma máquina virtual básica para ER em Windows. Agora chegou a vez do Linux, já que o mundo não é feito só de PE.:) A ideia aqui é montar um ambiente legal para reverter binários ELF. Dá uma olhada neste vídeo (em tela cheia de preferência) pra ter uma ideia do poder dessas ferramentas.

    Legal né? Então mãos à obra!

    Máquina virtual

    • 512 MB de memória
    • Placa de rede em modo NAT
    • Placa de rede em modo host-only

    Sistema operacional

    Utilizo a ISO netinstall do Debian 7 64-bits mas como sempre você é livre para utilizar a distribuição que quiser, só que este artigo é escrito com base nesta.;)

    Algumas dicas de instalação:

    • Não utilizar seu nome verdadeiro na criação da conta de usuário.
    • Não instalar as extensões do software virtualizador como VMware Tools ou VirtualBox Guest Additions.
    • Não configurar nenhuma regra de firewall ou qualquer software de proteção.

    /etc/apt/sources.list

    Eu deixo do seguinte jeito:

    deb http://ftp.br.debian.org/debian/ jessie main contrib non-free
    deb http://security.debian.org/ jessie/updates main
    deb http://ftp.br.debian.org/debian/ jessie-updates main
    deb http://ftp.br.debian.org/debian/ jessie-backports main

    O importante aqui é ter o “contrib non-free” após o main, infelizmente, pois alguns programas como o rar não são livres.:(

    Depois de configurado este arquivo, é bom atualizar os pacotes existentes:

    # apt-get update
    # apt-get upgrade -y

    Pacotes básicos

    # apt-get install zip unzip rar unrar p7zip file sudo git vim rcconf openssh-server

    Git para clonar repositórios de outros software que precisamos, servidor ssh para acessar a máquina de forma fácil, descompactadores etc.

    Software específico

    • wxHexEditor: Este é o melhor editor hexa que tem em minha opinião. Edita dispositivos e até processos em memória. Para compilá-lo, primeiro você vai ter que instalar suas dependências:
    # apt-get install autoconf libtool libwxgtk2.8-dev gettext

    Depois é só baixar o tarball, descomprimir, entrar no diretório e comandar os famosos:

    $ ./configure
    $ make
    # make install
    
    • Bokken: IDE muito boa para o pyew e radare2 (especialmente este útlimo). Com o Bokken é possível gerar inclusive um diagrama de fluxo das funções utilizadas pelo ELF. Na prática, ele server como disassembler, não como debugger. Ele é escrito em Python. Vamos à instalação das dependências então:
    # apt-get install python-radare2 pyew python-gtk2 python-gtksourceview2 graphviz python-rsvg python-utidylib

    Depois baixe o tarball (não use o pacote .deb disponível, ele está desatualizado com relação às dependências), descompacte e mova o diretório bokken-1.6 para um de sua prefência (/opt em nosso caso). Aí basta editar a varíavel BOKKEN_DIR no script /opt/bokken-1.6/bokken para refletir o novo diretório do programa (BOKKEN_DIR=/opt/bokken-1.6) e criar um link simbólico para este script em /usr/local/bin, para que este possa ser chamado a partir de qualquer diretório:

    # ln -s /opt/bokken-1.6/bokken /usr/local/bin/bokken 
    • Evan’s Debugger: Este projeto é simplesmente fenomenal. A ideia do Evan foi criar um debugger livre com base no OllyDbg. Eu apresentei este projeto na conferência H2HC de 2010 (fiz algumas contribuições) e e de lá pra cá o edb, como é chamado seu binário principal, só melhora. Dependências:
    # apt-get install libqt4-dev libboost-dev xterm yasm

    Após baixar a última versão do debugger e descomprimir, para compilar e instalar:

    $ qmake
    $ make
    # make install

    Agora em ~/.config/codef00.com/edb.conf você precisa configurar os diretórios utilizados pelo edb:

    [Directories]
    directory.symbol.path=/home/<usuario>/.config/codef00.com/symbols
    directory.plugin.path=/lib64/edb
    directory.session.path=/tmp

    E criar o diretório de symbols:

    $ mkdir -p ~/.config/codef00.com/symbols
    • bashacks: Valeria a pena só pelas funções asciitable e utf8table.:) Brincadeiras à parte, tem várias funções úteis para ER, como a hexcalc:
    $ hexcalc 4f + 2e
    0x7d

    Para instalar, é clonar o repositório com o git, mover para um diretório /opt por exemplo e adicionar no ~/.bashrc:

    $ git clone https://github.com/merces/bashacks.git
    # mv bashacks /opt
    $ echo 'source /opt/bashacks/bashacks.sh' >> ~/.bashrc

    Pronto. Ao relogar todas as funções estarão disponíveis. Se quiser saber um pouco mais sobre basta assistir ao nosso vídeo sobre esta ferramenta.

     

    Utilizo ainda os seguintes pacotes (apt-get install neles):

    • ht: instala o programa hte, um poderoso editor e disassembler de binários que utiliza a ncurses. Já me salvou onde não tinha X.
    • strace: rastreia chamadas de sistema feitas por binários
    • ltrace: rastreia chamadas de bibliotecas feitas por binários
    • foremost: ajuda nos casos em que há binários dentro de binários
    • unhide: mostra processos e conexões ocultas
     

    E há ainda os nativos do Linux que também são úteis:

    • readelf: mostra informações sobre binários ELF como symbols, cabeçalhos, seções etc
    • objdump: tipo o anterior, mas suporta outros formatos. Disassembla também. Um comando legal com ele é:
    $ objdump -M intel -d <arquivo>
    Observações
    • Após instalar todos os softwares, é de extrema utilidade criar um snapshot da máquina virtual, pra você voltar facilmente a este estado limpo após analisar um arquivo suspeito, por exemplo.
    • Não precisa instalar um ambiente gráfico. Pode conectar na máquina com ssh -X e rodar os programas gráficos.
    • Existe uma distribuição Linux chamada REMnux com foco em engenharia reversa. Eu sugeri ao autor que incluisse as ferramentas que menciono aqui.
    Entre para seguir isso  


    Feedback do Usuário

    Crie uma conta ou faça o login para deixar um comentário

    Você precisa ser membro para fazer uma análise

    Criar uma conta

    Crie uma nova conta em nossa comunidade. É fácil!

    Crie uma nova conta

    Entrar

    Já tem uma conta? Faça o login.

    Entrar Agora

    Não há análises para mostrar


  • Conteúdo Similar

    • Por Candeer
      Olá, neste artigo compartilharei um pouco da minha pesquisa no desenvolvimento de debuggers. No momento estou trabalhando em um protótipo de debugger para Linux, mas nada tão avançado quanto um gdb ou radare (muitas coisas são necessárias para chegar neste nível de maturidade de software).
      O desenvolvimento de debuggers é uma atividade muito interessante, já que, em sua forma mais básica, pode ser resumido em uma série de chamadas de sistema (syscalls) para que seja possível o controle do processo a ser depurado (muitas vezes chamado de debuggee) e de seus recursos, mas não vamos colocar a carroça na frente dos cavalos e vamos em partes.
      Antes de começarmos a discutir detalhes mais específicos acerca da depuração de processos, é necessário um entendimento básico de como os mesmos se comunicam na plataforma que vamos desenvolver o tal debugger, no nosso caso, UNIX-like.
      Inter-process communication (IPC)
      IPC é uma forma que processos podem utilizar para se comunicar dentro de um sistema operacional. Existem diversas maneiras de comunicação: via sinais (signals), sockets, etc, mas para a criação de um debugger é apenas necessário usar sinais para a execução.
      Sinais funcionam como uma notificação que pode ser enviada à um processo específico para avisar que algum evento ocorreu.
      É possível também programar um processo para reagir aos sinais de maneira não padrão. Se você já teve um uso razoável de Linux, você provavelmente já enviou sinais à um processo. Por exemplo, quando você aperta Ctrl+C para interromper a execução de um processo, é enviado um sinal do tipo SIGINT, que nada mais é que uma abreviação para Signal Interruption. Se o processo em questão não está preparado para reagir a este sinal, o mesmo é terminado. Por exemplo, considere o seguinte código:
      #include <stdio.h> int main(void) { while(1) printf("hi\n"); return 0; } Ao compilar e executar o código acima e apertar Ctrl+C, o mesmo encerra como esperado, porém podemos verificar que um SIGINT foi enviado usando a ferramenta ltrace, que além de listar chamadas a bibliotecas também mostra os sinais enviados ao processo:
      $ gcc -o hello hello.c $ ltrace ./hello Rode o comando acima e aperte Ctrl+C para verificar o sinal enviado!
      Programando reações a sinais
      A capacidade de enviar sinais a um processo nos dá a possibilidade de saber o que esta acontecendo com algum processo específico que estejamos depurando.
      Para programar reações a algum tipo de sinal, podemos incluir a biblioteca signal, para que possamos usar a função e estrutura (struct) sigaction:
      struct sigaction { void (*sa_handler)(int); void (*sa_sigaction)(int, siginfo_t *, void *); sigset_t sa_mask; int sa_flags; void (*sa_restorer)(void); };  
      int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); A struct sigaction nos permite adicionar handlers (tratadores) para nossos sinais, enviando o endereço de nossa função que realiza algum tipo de ação baseada no sinal enviado para o campo sa_handler(sigaction handler).
      Um handler neste contexto nada mais é que uma função que sempre vai ser chamada quando um dado sinal for enviado, dessa maneira podemos executar alguma ação quando recebermos um sinal.
      Já a função sigaction recebe o número do sinal, porém uma série de macros já são pré-definidas e podemos passar como argumento apenas o nome do sinal, como SIGINT por exemplo. A função recebe também a referência da struct previamente definida (struct sigaction) e, caso precise trocar um handler por outro, também recebe no último argumento (oldact) o handler anterior, para que possa ser feita a troca pelo novo. Como não é o nosso caso, vamos passar NULL neste último argumento.
      O código abaixo simula um uso de handlers de sinais, que imprime uma mensagem quando um sinal é enviado:
      #include <stdio.h> #include <signal.h> #include <unistd.h> // sleep void simple_handler(int sig) { printf("Hello SIGINT\n"); } int main() { struct sigaction sig_handler = { simple_handler }; sigaction(SIGINT, &sig_handler, NULL); sleep(1000); return 0; } Ao executar o código acima, aperte Ctrl+C e veja que será imprimido a mensagem do nosso handler!
      O manual da signal contém uma tabela com todos os sinais usados por sistemas POSIX.
      Para enviarmos sinais facilmente em sistemas UNIX podemos usar o comando kill:
      $ kill -l O comando acima mostra todos os sinais e seus respectivos números, com isso podemos fazer algo interessante. Por exemplo, rode o código acima em um terminal separado e use o kill para se comunicar com o seu processo, assim:
      $ ps ax | grep simple_signal $ kill -2 <pid> Primeiro buscamos o PID do nosso processo então usamos o kill que espera como primeiro argumento numero do sinal (listado em kill -l) e o segundo o PID do processo alvo.
      Ao enviar o sinal, podemos ver que o nosso código reage aos sinais que foram associados a um handler especifico! Tente criar handlers para vários sinais e teste usando o comando kill. 😃
      Abaixo um código para demonstrar um uso real de um software que escreve dados aleatórios nos arquivos temporários e antes de uma finalização abrupta, é deletado o que foi usado:
      #include <stdio.h> #include <signal.h> #include <unistd.h> // Log errors void fatal(const char* err_msg) { fprintf(stderr, "Error: %s\n", err_msg); } // Escreve algo random em um arquivo void random_work() { FILE* temp_files = fopen("/tmp/foo", "w"); if (!temp_files) { fatal("Cant open foo!"); } else { fprintf(temp_files, "%s", "Random random random!\n"); fclose(temp_files); } } // Handler para deleta arquivos criados void handler_termination(int sig) { // Verifica se existe usando a function access // Caso existe usa a syscall unlink para remover o arquivo if (access("/tmp/foo", R_OK) < 0) return; unlink("/tmp/foo"); printf("All clean! closing...\n"); } int main() { //struct sigaction que recebe a function handler_termination como valor do seu handler struct sigaction interruption_handler; interruption_handler.sa_handler = handler_termination; // Syscall sigaction que associa o nosso handler para um sinal especifico // O ultimo campo NULL, espera o handler anterior para que posso tornar o novo handler o default sigaction(SIGINT, &interruption_handler, NULL); random_work(); sleep(1000); handler_termination(0); return 0; } Dica: Dê uma olhada na tabela de sinais e crie handlers para o mesmo código acima!
      Para a construção do nosso debugger iremos focar mais no signal SIGTRAP, para que seja possível detectar se o nosso processo sofreu uma "trap" da CPU. Uma trap ocorre quando acontece alguma interrupção síncrona na execução, que faz o processo ficar parado até que o sistema operacional execute alguma ação. Isto será usado para implementar e interpretar breakpoints. Veremos tudo isso com mais detalhes em breve!
      Sinta-se livre para comentar e sugerir correções e melhorias. Até o próximo artigo!
      Links úteis:
      Syscall IPC CERO 11 – Linux Syscalls Syscalls, Kernel mode vs User mode Programação em C
    • Por R3n4to
      Olá,
      Gostaria de sugestões de tema para TCC na área segurança. Segurança da informação me atrai bastante, mas estou muito sem ideia em relação ao tema para TCC. O que vocês podem me sugerir? Com o que dá para fazer um bom trabalho?  Muito obrigado!
       
    • Por Fabiano Furtado
      Pessoal,
      bom dia. Estou estudando algumas técnicas de proteção de binários, me baseando no video do Fernando Mercês do Roadsec 2017 https://www.youtube.com/watch?v=cpU9U0sqzh4
      Mais especificamente, em 27'29", o Fernando mostra como substituir algumas instruções por outras equivalentes, para dificultar a análise do binário.
      Fiz exatamente isso, mas o programa da um Segmentation Fault após a alteração, e eu não tenho idéia do que pode ser. Alguma ajuda?
      Segue o programa exemplo em C que fiz para alterar o JMP:
      #include <stdio.h>
      int main(void) {
        int c = 0;
        c++;
        
        if ( c == 1 ) {
          __asm__("nop");
          __asm__("nop");
          __asm__("nop");
          __asm__("nop");
          goto end;
        }
        do {
          puts("Dentro do while");
          goto end;    
        } while (1);
        puts("antes do Fim");
        
      end:
        puts("Fim");
        
        return 0;
      }
      Compilei ele com: gcc -Wall -m32 -O0 salto.c -o salto
      Seguem as linhas originais:
          11b5:       83 7d f4 01             cmp    DWORD PTR [ebp-0xc],0x1                                                    
          11b9:       75 06                   jne    11c1 <main+0x34>                                                           
          11bb:       90                      nop                                                                               
          11bc:       90                      nop                                                                               
          11bd:       90                      nop                                                                               
          11be:       90                      nop                                                                               
          11bf:       eb 13                   jmp    11d4 <main+0x47>                                                           
          11c1:       83 ec 0c                sub    esp,0xc                                                                    
          11c4:       8d 83 08 e0 ff ff       lea    eax,[ebx-0x1ff8]                                                           
          11ca:       50                      push   eax                                                                        
          11cb:       e8 60 fe ff ff          call   1030 <puts@plt>                                                            
          11d0:       83 c4 10                add    esp,0x10                                                                   
          11d3:       90                      nop                                                                               
          11d4:       83 ec 0c                sub    esp,0xc                                                                    
          11d7:       8d 83 18 e0 ff ff       lea    eax,[ebx-0x1fe8]
      Seguem as linhas alteradas:
          11b9:       75 06                   jne    11c1 <main+0x34>                                                           
          11bb:       68 d4 11 00 00          push   0x11d4                                                                     
          11c0:       c3                      ret                                                                               
          11c1:       83 ec 0c                sub    esp,0xc                                                                    
          11c4:       8d 83 08 e0 ff ff       lea    eax,[ebx-0x1ff8]                                                           
          11ca:       50                      push   eax                                                                        
          11cb:       e8 60 fe ff ff          call   1030 <puts@plt>                                                            
          11d0:       83 c4 10                add    esp,0x10                                                                   
          11d3:       90                      nop                                                                               
          11d4:       83 ec 0c                sub    esp,0xc                                                                    
          11d7:       8d 83 18 e0 ff ff       lea    eax,[ebx-0x1fe8]
      Alguma ajuda?
      Desde já, agradeço.
    • Por Fernando Mercês
      Saudações, leitores do Mente Binária! Hoje me deu vontade de falar sobre uma tarefa que eventualmente preciso fazer na empresa onde trabalho, que é a de verificar as diferenças entre arquivos executáveis, normalmente de Windows, também conhecidos por executáveis PE.
      Há vários usos ao comparar binários. É possível avaliar o que mudou na versão atual de um software em relação à anterior, descobrir o que muda em cada sample diferente de uma mesma família de malware, etc. Esses dias mesmo me foi pedido que verificasse a diferença entre 6 arquivos maliciosos, que compartilho abaixo como fiz.
      Reconhecimento básico
      Os arquivos que recebi tinham seu hash SHA-256 como nome. A primeira coisa que fiz foi checar seu tipo (usando comandos do macOS, mas o Linux tem comandos similares):
      $ file * fdba340bb35635934aa43b4bddd11df31f2204e73394b59756931aa2f7f59e04: PE32 executable (GUI) Intel 80386, for MS Windows fdf3060eb9c39b1a2be168b1ac52c2f80171394e73fe03c4e0c57911cb9358a9: PE32 executable (GUI) Intel 80386, for MS Windows fedf9d9815b3d0ad28e62f99d5dcf92ec0f5fcb90135b4bdc30bb5709ab9ff05: PE32 executable (GUI) Intel 80386, for MS Windows ff2f1be6f64c91fa0a144cbc3c49f1970ba8107599d5c66d494ffb5550b0f7fd: PE32 executable (GUI) Intel 80386, for MS Windows ff53c7ba285ffdc2c29683bb79bb239ea59b3532f8b146523adf24d6d61fc640: PE32 executable (GUI) Intel 80386, for MS Windows ffee504e292a9f3ae6c439736881ebb314c05eac8b73d8b9c7a5a33605be658e: PE32 executable (GUI) Intel 80386, for MS Windows Só para garantir, também chequei o SHA-256 deles e realmente bateu com o nome, o que era esperado:
      $ shasum -a256 * fdba340bb35635934aa43b4bddd11df31f2204e73394b59756931aa2f7f59e04 fdba340bb35635934aa43b4bddd11df31f2204e73394b59756931aa2f7f59e04 fdf3060eb9c39b1a2be168b1ac52c2f80171394e73fe03c4e0c57911cb9358a9 fdf3060eb9c39b1a2be168b1ac52c2f80171394e73fe03c4e0c57911cb9358a9 fedf9d9815b3d0ad28e62f99d5dcf92ec0f5fcb90135b4bdc30bb5709ab9ff05 fedf9d9815b3d0ad28e62f99d5dcf92ec0f5fcb90135b4bdc30bb5709ab9ff05 ff2f1be6f64c91fa0a144cbc3c49f1970ba8107599d5c66d494ffb5550b0f7fd ff2f1be6f64c91fa0a144cbc3c49f1970ba8107599d5c66d494ffb5550b0f7fd ff53c7ba285ffdc2c29683bb79bb239ea59b3532f8b146523adf24d6d61fc640 ff53c7ba285ffdc2c29683bb79bb239ea59b3532f8b146523adf24d6d61fc640 ffee504e292a9f3ae6c439736881ebb314c05eac8b73d8b9c7a5a33605be658e ffee504e292a9f3ae6c439736881ebb314c05eac8b73d8b9c7a5a33605be658e PS.: No Linux o comando seria sha256sum ao invés de shasum -a256.
      O próximo passo foi checar o tamanho deles:
      $ wc -c * 396973 fdba340bb35635934aa43b4bddd11df31f2204e73394b59756931aa2f7f59e04 396973 fdf3060eb9c39b1a2be168b1ac52c2f80171394e73fe03c4e0c57911cb9358a9 396973 fedf9d9815b3d0ad28e62f99d5dcf92ec0f5fcb90135b4bdc30bb5709ab9ff05 396973 ff2f1be6f64c91fa0a144cbc3c49f1970ba8107599d5c66d494ffb5550b0f7fd 396973 ff53c7ba285ffdc2c29683bb79bb239ea59b3532f8b146523adf24d6d61fc640 396973 ffee504e292a9f3ae6c439736881ebb314c05eac8b73d8b9c7a5a33605be658e 2381838 total Aqui apresentou-se um caso atípico: os binários possuem exatamente o mesmo tamanho! Já pensei que havia grandes chances de as diferenças entre eles serem mínimas: provavelmente algo usado pelo autor do malware só para "mudar o hash" na tentativa de evitar que os antivírus detectem os arquivos idênticos, por exemplo. Essa tentativa é na verdade frustrada visto que, ao contrário do que muitos pensam, os antivírus não detectam malware por hash normalmente, já que isso seria muito custoso do ponto de vista do desempenho (seria preciso ler todos os bytes do arquivo!) e também seria muito fácil tornar um novo arquivo indetectável - bastaria alterar um único byte para um hash final completamente diferente.
      Comparação de estrutura
      Se estivéssemos tratando arquivos de texto, poderia simplesmente usar o comando diff, mas o assunto aqui é PE, então algo interessante de verificar é sua estrutura, que consiste basicamente em cabeçalhos, localizados antes das seções. Se você não sabe do que estou falando, recomendo os seguintes recursos:
      Posts do @Leandro Fróes sobre o formato PE e suas referências. Capítulo sobre PE do livro Fundamentos de Engenharia Reversa. Aulas 5 e 6 do CERO, nosso Curso de Engenharia Reversa Online em vídeo. Digitar "PE executable" no Google ler o que curtir. Depois dessa imersão no mundo dos executáveis PE, não tenho dúvidas de que você vai se apaixonar por eles também! 😍
      Voltando à comparação, o que eu quero dizer com estrutura? Bem, os valores dos campos dos cabeçalhos. Por exemplo, para ver o cabeçalho COFF de um arquivo PE, usei o readpe, parte do kit de ferramentas pev:
      $ readpe -h coff fdba340bb35635934aa43b4bddd11df31f2204e73394b59756931aa2f7f59e04 COFF/File header Machine: 0x14c IMAGE_FILE_MACHINE_I386 Number of sections: 5 Date/time stamp: 1401620468 (Sun, 01 Jun 2014 11:01:08 UTC) Symbol Table offset: 0 Number of symbols: 0 Size of optional header: 0xe0 Characteristics: 0x102 Characteristics names IMAGE_FILE_EXECUTABLE_IMAGE IMAGE_FILE_32BIT_MACHINE Mas não, não usei o pev por saudosismo! A ideia de ter uma saída em texto da estrutura desses binários é depois usar o comando diff para compará-las. A primeira coisa que precisei então foi gerar um .txt contendo toda a estrutura, e não só o cabeçalho COFF, para cada um dos arquivos. Uma repetição em bash dá conta do recado:
      $ ls -1 readpe_output_* readpe_output_fdba340bb35635934aa43b4bddd11df31f2204e73394b59756931aa2f7f59e04.txt readpe_output_fdf3060eb9c39b1a2be168b1ac52c2f80171394e73fe03c4e0c57911cb9358a9.txt readpe_output_fedf9d9815b3d0ad28e62f99d5dcf92ec0f5fcb90135b4bdc30bb5709ab9ff05.txt readpe_output_ff2f1be6f64c91fa0a144cbc3c49f1970ba8107599d5c66d494ffb5550b0f7fd.txt readpe_output_ff53c7ba285ffdc2c29683bb79bb239ea59b3532f8b146523adf24d6d61fc640.txt readpe_output_ffee504e292a9f3ae6c439736881ebb314c05eac8b73d8b9c7a5a33605be658e.txt Eu usei o readpe sem nenhuma opção, assim ele imprime todos os cabeçalhos, incluindo os de seções. Só pra começar fiz um diff do primeiro para o segundo e não houve qualquer saída, ou seja, a estrutura dos arquivos eram idênticas! E eram mesmo:
      $ wc -c readpe_output_* 21627 readpe_output_fdba340bb35635934aa43b4bddd11df31f2204e73394b59756931aa2f7f59e04.txt 21627 readpe_output_fdf3060eb9c39b1a2be168b1ac52c2f80171394e73fe03c4e0c57911cb9358a9.txt 21627 readpe_output_fedf9d9815b3d0ad28e62f99d5dcf92ec0f5fcb90135b4bdc30bb5709ab9ff05.txt 21627 readpe_output_ff2f1be6f64c91fa0a144cbc3c49f1970ba8107599d5c66d494ffb5550b0f7fd.txt 21627 readpe_output_ff53c7ba285ffdc2c29683bb79bb239ea59b3532f8b146523adf24d6d61fc640.txt 21627 readpe_output_ffee504e292a9f3ae6c439736881ebb314c05eac8b73d8b9c7a5a33605be658e.txt 129762 total $ md5 !$ md5 readpe_output_* MD5 (readpe_output_fdba340bb35635934aa43b4bddd11df31f2204e73394b59756931aa2f7f59e04.txt) = 05b36b89b1165b3d619bee16f8a1d7f7 MD5 (readpe_output_fdf3060eb9c39b1a2be168b1ac52c2f80171394e73fe03c4e0c57911cb9358a9.txt) = 05b36b89b1165b3d619bee16f8a1d7f7 MD5 (readpe_output_fedf9d9815b3d0ad28e62f99d5dcf92ec0f5fcb90135b4bdc30bb5709ab9ff05.txt) = 05b36b89b1165b3d619bee16f8a1d7f7 MD5 (readpe_output_ff2f1be6f64c91fa0a144cbc3c49f1970ba8107599d5c66d494ffb5550b0f7fd.txt) = 05b36b89b1165b3d619bee16f8a1d7f7 MD5 (readpe_output_ff53c7ba285ffdc2c29683bb79bb239ea59b3532f8b146523adf24d6d61fc640.txt) = 05b36b89b1165b3d619bee16f8a1d7f7 MD5 (readpe_output_ffee504e292a9f3ae6c439736881ebb314c05eac8b73d8b9c7a5a33605be658e.txt) = 05b36b89b1165b3d619bee16f8a1d7f7
      Os hashes MD5 da saída em texto da estrutura de todos os arquivos batem. Eles são mesmo iguais estruturalmente!
      Passado o choque, percebi que teria que comparar o conteúdo das seções (código, dados, talvez resources, etc). Aí fui obrigado a inicializar minha VM do Janelas mesmo...
      Comparação do conteúdo das seções
      Existem alguns softwares que trabalham com PE e possuem funções de comparação de dois executáveis. Eu costumava usar o Cold Fusion (um antigo gerador de patch) pra isso, mas ele tem alguns bugs que me impediram. Achei a mesma função no Stud_PE, mas ele localiza arquivos por extensão na janela de comparação, então renomeei o primeiro e o segundo arquivo que tinha para a.exe e b.exe respectivamente.
      Ao abrir o a.exe no Stud_PE, usei o botão "File Compare", selecionei o método "Binary", setei o "Starting from" pra "Raw" e cliquei em "Compare":

      Se você não entendeu por que fiz isso, volte uma casa ou leia os tutorias de PE que indiquei. Ou pergunte que eu falo. 😍
      Bem, entre esses dois caras então havia 9 bytes que o diferenciavam e eu já tinha os offsets a partir do início do arquivo. Agora é descobrir em que seção eles estavam no PE, o que são, o que comem e como eles vivem. 😎
      Descobrindo como as diferenças são usadas
      Abri o executável no x64dbg (na verdade no x32dbg, já que este binário é de 32-bits) mas percebi que o entrypoint estava no endereço 013706AA. Como o ImageBase deste binário é 00400000, percebi que o ASLR estava habilitado e, antes de continuar , desabilitei-o com o DIE, como mostro neste vídeo rápido no canal Papo Binário:
      Antes de reabrir o binário no x32dbg, convém lembrar que eu tinha um offset e precisava convertê-lo para endereço virtual (VA). Isso é feito com o que alguns analisadores de PE chamam de FLC (File Location Calculator). O DIE tem, o Stud_PE tem e o pev também tem, com a ferramenta ofs2rva:
      $ ofs2rva 0x4c451 a.exe 0x4dc51 Mas pra não você não me acusar de saudosismo de novo, vou mostrar no Stud_PE 😄

      Percebe que o Stud_PE já diz que este byte pertence à seção .rdata, o que à esta altura você já sabe, caso tenha feito o trabalho de casa de estudo do PE, que é provavelmente uma seção de dados somente-leitura, então há grandes chances de nossa sequência diferentona pertencer à uma string constante, por exemplo. Fui ver no debugger como é que tava a festa. Abri o a.exe lá e dei um Ctrl+G no Dump pra ir pro endereço 44DC51:

      De fato tinha uma string lá: zuk0KRrGrP, mas ela na verdade começava em 44DC44 e pra saber quando ela era usada no malware, coloquei um breakpoint de hardware no acesso ao byte, que é o primeiro da string e cheguei à conclusão de que, como o nome sugere, é realmente uma string de identificação da campanha do malware, sempre no mesmo offset (calculei de novo usando FLC).  Agora foi só ver a dos outros e novamente recorri à uma ferramenta do pev (💚), a pestr:
      $ for i in *; do echo $i; pestr -so $i | grep 0x4c444; echo; done fdba340bb35635934aa43b4bddd11df31f2204e73394b59756931aa2f7f59e04 0x4c444 .rdata identifierStrzuk0KRrGrP fdf3060eb9c39b1a2be168b1ac52c2f80171394e73fe03c4e0c57911cb9358a9 0x4c444 .rdata identifierStrAR0U4hr1wW fedf9d9815b3d0ad28e62f99d5dcf92ec0f5fcb90135b4bdc30bb5709ab9ff05 0x4c444 .rdata identifierStrswEYVkFWeg ff2f1be6f64c91fa0a144cbc3c49f1970ba8107599d5c66d494ffb5550b0f7fd 0x4c444 .rdata identifierStrKXaUzlBDIj ff53c7ba285ffdc2c29683bb79bb239ea59b3532f8b146523adf24d6d61fc640 0x4c444 .rdata identifierStrv91TJ5c3Lr ffee504e292a9f3ae6c439736881ebb314c05eac8b73d8b9c7a5a33605be658e 0x4c444 .rdata identifierStrOzJnvFQy2U Bom, daí o céu é o limite. Dá pra criar assinatura, criar um script pra extrair esse ID da campanha, enfim, missão cumprida.

      FAQ
      1. Por que você não utilizou só um comparador de arquivos qualquer, que compara os bytes em hexadecimal?
      Eu queria saber exatamente onde estavam as diferenças entre os arquivos, se na estrutura ou não. Em caso negativo, é código? Se sim, que código? Que faz o que? São dados? Usados onde? Em qual seção? Um editor hexadecimal ignorantão não me daria isso. Além disso, se os arquivos fossem diferente estruturalmente, ou em tamanho, eu queria saber antes, pra não perder tempo analisando diferenças de bytes hexa que eu não sei o que é.
      2. Existem softwares para comparar binários PE muito mais poderosos, como o BinDiff. Por que caralhas você não o usou?
      O BinDiff é pra comparar código. Minha diferença estava nos dados. Além disso, o BinDiff e seus amigos traduzem o Assembly original do binário para uma linguagem intermediária própria e comparam lógica, não instruções. É bem fodão, mas não me atendia neste caso, afinal eu já sabia que os binários eram idênticos em funcionalidade. Só queria saber onde estava a diferença exata.
      3. Percebi pela screenshot do Stud_PE que ele também compara a estrutura dos arquivos PE, então todo aquele processo com o readpe foi à toa?
      Sim, foi só pra Inglês ver. Não, brincadeira! O Stud_PE compara os cabeçalhos COFF, Optional e os diretórios de dados somente. O readpe imprime todos os cabeçalhos, incluindo todas as seções mais os imports. É outro nível, moleque! 😏

      4. E quanto à executáveis ELF?
      O título não fala somente de PE propositalmente, já que a mesma técnica pode ser usada para arquivos ELF, só mudando os programas (readelf, etc).
      Por hora é só. Se você deixar sua análise abaixo ou quiser fazer um comentário/pergunta, ficarei muito grato. Considera apoiar a gente também vai. 💚
×