Jump to content
  • Comparando executáveis

       (2 reviews)

    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

    shocked-queen-sm.jpg.ce229f2f14bf9f1612c9250af8f45976.jpg

    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":

    stud_pe_binary.png.f43593b53d37207b41c525fdcf7a39a7.png

    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 😄

    stud_pe_flc.png.efbe2b4eb269814e9623419d9d8db653.png

    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:

    x32dbg_dump.png.934f997d258d268b85cf23e99677272d.png

    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.

    yes-baby-sm.jpg.e8177feec593858c2d9ce5484a87e6dc.jpg

    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! 😏

    stud_pe_structure.png.b43dc07b3e0aa18ad7948610542c360b.png

    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. 💚

    Edited by Fernando Mercês



    User Feedback

    Join the conversation

    You can post now and register later. If you have an account, sign in now to post with your account.

    Guest

    Lincoln Arantes

      

    Mandou bem!

    Share this review


    Link to review
    Guest GuzMan

      

    O Mercês é fodástico!

    Share this review


    Link to review

  • Similar Content

    • By ncaio
      ====== Bem-vindo a bordo ======

      Este é um repositório/espaço aberto/livre de conteúdo referente a hardware hacking em geral. Sinta-se a vontade para contribuir e retirar suas dúvidas. Assim como em outros espaços de conhecimento compartilhado na Internet, este Fórum tem regras. Algumas delas, são:
        * Seja educado(a) e respeitoso(a);
        * Pesquise antes;
        * Seja claro(a) e descritivo(a);
        * Esteja preparado(a) para compartilhar informações relevantes a sua dúvida;
        * Não fuja do foco;
        * Referencie autores;
        * E etc.
    • By Fabiano Furtado
      Pessoal...
      Ontem achei um artigo na Internet bem escrito, interessante e detalhado sobre Engenharia Reversa em ELF.
      É um reversing básico, mas não tããããão básico assim. Acho que vale a pena conferir.
      http://manoharvanga.com/hackme/
      Valeu!
    • By Ciro Moises Seixas Dornelles
      Olá a todos, existe alguma maneira de se extrair o conteúdo do livro de engenharia reversa para que eu posso lê-lo em um dispositivo kindle?

       
    • By Candeer
      Olá, já faz um bom tempo desde do ultimo artigo sobre a construção de debuggers mas, sem mais delongas, vamos dar continuidade a esta série! 😀 
      Neste artigo iremos falar um pouco sobre uma chamada de sistema que é capaz de controlar quase todos os aspectos de um processo: a syscall PTRACE (process trace). Antes de continuarmos, vale ressaltar que todo o código utilizado neste artigo está disponível no repositório do Github.
      De acordo com o manual do Linux (man ptrace), a syscall ptrace é definida assim:
      "A syscall ptrace provê meios para que um processo (denominado "tracer") possa observar, controlar a execução de um outro processo (denominado "tracee"), examinar e modificar a memória e registradores do "tracee". É primariamente utilizado para a implementação de 'breakpoint debugging' e para rastreamento de syscalls".
      Em outras palavras, podemos utilizar a ptrace para controlar um outro processo sobre o qual termos permissões sobre!
      Por exemplo, execute:
      strace /bin/ls O comando "strace" acima, é utilizado para que se possa rastrear todas as syscalls que um programa realiza. Vale lembrar que toda a técnica utilizada para o rastreamento de syscalls envolve o conteúdo abordado nos artigos anteriores, então é de suma importância que você tenha lido (ou saiba) o primeiro artigo sobre Sinais e o segundo sobre Forks.
      Antes de começar a rastrear um dado comando, o strace precisa ter controle total sobre a execução do processo alvo, para isso é feito um fork do processo em questão e o mesmo é "traceado". Voltaremos neste assunto em breve.
      A wrapper da ptrace é definida em <sys/ptrace.h> e tem o seguinte protótipo:
      #include <sys/ptrace.h> long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data); Onde o primeiro argumento request é um enum onde cada valor define uma ação em cima do "tracee", tais como TRACEME, GETEREGS, SETREGS e etc. O segundo argumento, pid, é o PID (Process Identification) do processo que queremos "tracear", o terceiro argumento addr é um endereço para alguma interação que a ser realizada da memória do processo "traceado" e o quarto e último argumento data é algum tipo de dado passado para o processo.
      Agora que você ja conhece o formato desta syscall, vamos fazer um pequeno breakdown do comando "strace".
      Execute:
      strace strace /bin/ls 2>&1 | grep -A2 clone Por mais bizarro que o comando acima pareça, o que vamos fazer aqui é rastrear todas as syscalls que o strace faz usando o próprio strace! Como a saída padrão do strace não é o stdout (dê uma lida em standart streams, caso esteja confuso) então é primeiro redirecionar a saída de erro para a saída padrão, para que seja possível rodar o grep no que queremos.
      Estamos buscando aqui, alguma chamada a syscall clone, que é sempre chamada quando é feito um fork. A chamada à ptrace vem logo em seguida:
      clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f7c4aa8ea10) = 16203 ptrace(PTRACE_SEIZE, 16203, NULL, 0) = 0 Nesse caso, o strace cria um processo filho e em seguida usa o ptrace com o argumento SEIZE para iniciar o rastreamento (tracing) de um processo sem interrompê-lo, como analisaremos em seguida. Dessa maneira o strace é capaz de interceptar cada chamada de sistema feita pelo processo!
      Dê uma olhada no comando ltrace, que diferente do strace, rastreia todas as chamadas à bibliotecas (libraries trace) e tente fazer o mesmo que fizemos acima!
      Algumas ações notáveis que podemos fazer com a ptrace:
      PTRACE_PEEKTEXT, PTRACE_PEEKDATA Ler uma word em um dado endereço. PTRACE_POKETEXT, PTRACE_POKEDATA Copiar uma word para um determinado endereço (injete dados na memória). PTRACE_GETREGS Ler os registradores de um processo, que será guardado na struct user_regs_struct em <sys/user.h>. PTRACE_SETREGS Escrever nos registradores de um processo (também no formato da struct acima). Execute "man ptrace" para uma abordagem mais detalhadas de todos os valores disponíveis. 👍
       
      Implementando um simples tracer
      Agora que já temos uma base de forks e uma ideia de como o ptrace funciona, podemos unificar os dois e tenho certeza que o ptrace irá ficar mais claro. A partir de agora ele é fundamental para a implementação do nosso debugger.
      O primeiro passo é definir o escopo de como será feito o nosso "tracer": vamos rastrear um processo que já esta sendo executado ou vamos criar um novo? Para o nosso debugger, iremos apenas criar um fork e trocar sua imagem de execução para a do programa que queremos debugar, usando uma das funções da família exec.
      Primeiro vamos usar a função execl, que faz parte do leque de funções exec (man 3 exec) que trocam a imagem do nosso processo por outra, ou seja, o nosso programa é realmente trocado por outro em uma execução.
      A função execl é definida como:
      #include <unistd.h> int execl(const char *pathname, const char *arg, ... /* (char *) NULL */); Onde o primeiro argumento pathname é caminho completo do nosso executável alvo e os demais argumentos, que podem ser vários, são os argumentos para o programa que será executado.
      Para seguir um padrão, o primeiro argumento que geralmente colocamos é o caminho do programa em questão (lembrem que no array argv a posição 0 guarda o nome do programa em si), o resto dos argumentos são opcionais e seguem no modelo de lista de argumentos que são delimitados por um argumento NULL, que geralmente usamos para finalizar a lista.
      Agora considere o seguinte exemplo:
      #include <unistd.h> #include <stdio.h> int main(int argc, char* const* argv) { if (argc < 3) { printf("Usage: %s <command> <args>\n", argv[0]); return 1; } const char* command = argv[1]; char* const* args = &argv[1]; printf("First arg => %s\n", args[0]); execv(command, args); puts("Continua?\n"); return 0; } Compile com
      $ gcc -o exec exec.c $ ./exec /bin/ls -lah Este programa bem simples demonstra como a exec funciona.
      O que acabamos de criar aqui foi uma espécie de wrapper para qualquer comando: ele irá pegar o nome do comando e os seus respectivos argumentos e trocar sua execução atual pela a que você especificou.
      Note também a string "Continue?" que deveria ser impressa na tela. Esta nunca será impressa pois o nosso programa virou de fato, outro.
      Interessante, não? Usando um pouco de criatividade, podemos criar novos processos filhos combinando forks + exec, ou seja, criamos um fork do nosso processo e trocamos sua imagem por outra! Dessa maneira, por exemplo, temos total controle sobre o comando ls.
      Modificando um pouco o código acima e seguindo a ideia de forks, temos:
      #include <stdio.h> #include <sys/types.h> #include <sys/ptrace.h> #include <unistd.h> int main(int argc, char* const* argv) { if (argc < 3) { printf("Usage: %s <command> <args>\n", argv[0]); return 1; } const char* command = argv[1]; char* const* args = &argv[1]; pid_t child_pid = fork(); // Neste ponto, todas as variaveis sao copiadas para o nosso fork // o fork NAO recebe as mesmas variaveis, apenas uma cópia ;) if (!child_pid) { // Hora de transformar nosso fork em outro programa ptrace(PTRACE_TRACEME, NULL, NULL, NULL); execv(command, args); } char in; do { puts("Iniciar processo ? [y/n]: "); in = getchar(); } while (in != 'y'); ptrace(PTRACE_CONT, child_pid, NULL, NULL); return 0; } Compile
      $ gcc -o fork_exec fork_exec. $ ./fork_exec /bin/ls O programa acima realiza os primeiros passos do nosso tracer: é passado o caminho de um programa e os argumentos para o mesmo. Com isso criamos um fork e usamos o ptrace no própio fork com o argumento TRACEME. Este parâmetro indica que o este processo será "traced" pelo seu processo pai. Em seguida trocamos a nossa execução para o nosso programa alvo. Neste momento temos total controle sobre a execução, no exemplo acima, do comando ls.
      Quando um processo inicia sua execução com TRACEME + exec, o mesmo recebe um sinal de interrupção (SIGTRAP) até que o seu processo pai indique que ele deve continuar sua execução. Por isso, o nosso processo pai, que retém o PID do processo filho, usa o ptrace com o argumento CONT para que seja enviado o signal para dar continuidade de execução.
      E depois?
      Agora toda a comunicação entre os processos pai e o filho se dará via sinais e usaremos a syscall wait constantemente.
      Lembra que definimos acima algumas funções que podemos usar em conjunto com a ptrace? Para já irmos adiantando alguns artigos, vamos fazer um programa que mostra o estado dos registradores para um processo, passo a passo. Vamos usar dois parâmetros para a ptrace: GETREGS e STEP. Segue o código:
      #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/ptrace.h> #include <sys/user.h> #include <sys/wait.h> void display_regs(struct user_regs_struct* regs) {     printf("RIP: 0x%x\n", regs->rip);     printf("RBP: 0x%x\n", regs->rbp);     printf("RSP: 0x%x\n", regs->rsp); } int main(int argc, char* const* argv) {     if (argc < 2) {         fprintf(stderr, "Usage: %s <program_path>\n", argv[0]);         return 1;     }     const char* progName = argv[1];          pid_t child = fork();     if (!child) {         ptrace(PTRACE_TRACEME, NULL, NULL, NULL);         execl(progName, progName, NULL);     }          int status;     int options = 0;     int signal;     // Estrutura que mantem os registradores     struct user_regs_struct regs;     /// Capta primeiro sinal de parada do filho     waitpid(child, &status, 0);     signal = WSTOPSIG(status);     if (signal == SIGTRAP) {         printf("Processo alvo %s esperando pronto para iniciar\n\n", progName);     }          printf("Executando 10 instruções\n");     for (int i = 0; i < 10; ++i) {         printf("Passo: %d\n", i+1);         // Executa uma instrução         ptrace(PTRACE_SINGLESTEP, child, NULL, NULL);         // Espera sinal do filho         waitpid(child, &status, 0);         // Copia o estado atual dos registradores         ptrace(PTRACE_GETREGS, child, NULL, &regs);         // Função local para imprimir os principais registradores         display_regs(&regs);         puts("\n\n");     }     puts("Continuando...\n");     /// Continua execução     ptrace(PTRACE_CONT, child, NULL, NULL);     waitpid(child, &status, 0);     printf("Filho saiu com %d\n", WIFEXITED(status));     return 0; }  
      Compile:
      $ gcc -o tracer tracer.c $ ./tracer /bin/ls O código acima, além de criar e rastrear o processo, executa as primeiras 10 instruções e copia os estados dos registradores em cada passo. Logo após, continua a execução do programa normalmente.
      A estrutura user_reg_struct, definida em <sys/user.h>, contém todos os registradores que estão disponíveis na sua arquitetura. O código foi escrito considerando um ambiente x86-64.
      Com o estudo da ptrace, fechamos toda a introdução para construirmos o nosso debugger de fato, que vamos começar a desenvolver no próximo artigo, incialmente com capacidade de por breakpoints, imprimir o atual estado dos registrados e executar instrução por instrução do processo.
      Qualquer dúvida ou correção sinta-se livre de por nos comentários!  😁
      Links úteis:
      Process control Process relationship Code injection with ptrace Sinais Fork Até a próxima!
    • By Fernando Mercês
      Dia 02/04/2019 (terça) tivemos o lançamento oficial do Visual Studio 2019, com o anúncio de inúmeras novidades envolvendo o desenvolvimento de soluções baseadas em tecnologias como Azure DevOps, .NET Core, ASP.NET Core, C# e PowerShell.

      Assim como aconteceu em outras ocasiões, a Microsoft novamente fará uma parceria com comunidades técnicas através da realização de eventos locais.

      O DevOps Professionals em conjunto com a FC Nuvem também participa desta iniciativa, com um EVENTO PRESENCIAL e GRATUITO 

      Programação prevista (grade sujeita a alterações):

      - Novos Recursos para Debugging no Visual Studio 2019 + Suporte a Docker no .NET Core 3.0 - Renato Groffe (Microsoft MVP)

      - Dicas e truques com Azure e Azure DevOps no Visual Studio 2019 - Vinicius Moura (Microsoft MVP)

      - Colaboração Contínua com o Visual Studio Live Share - Milton Câmara Gomes (Microsoft MVP)

      - Indo além de ambientes Windows com PowerShell Core, Linux e Visual Studio Code - Ewerton Jordão (.NET SP, SampaDevs)

      Acompanhe e apoie esta iniciativa, divulgando e indicando o Visual Studio 2019 Launch para amigos e colegas de trabalho!
      Mais informações: https://www.sympla.com.br/visual-studio-2019---lancamento---devops-professionals--fc-nuvem__525409
×
×
  • Create New...