Jump to content

Search the Community

Showing results for tags 'engenharia reversa'.



More search options

  • Search By Tags

    Type tags separated by commas.
  • Search By Author

Content Type


Forums

  • Supporter area
    • Tools of the Trade
    • Finance transparency
  • MBConf
    • MBConf v1
    • MBConf v2
    • MBConf v3
  • Mente Binária
    • General
    • Arquitetura de Computadores
    • Certifications
    • Quantum computing
    • Cryptography
    • Challenges and CTF
    • Hardware Hacking
    • Electronics
    • Conferences
    • Forensics
    • Games
    • Data privacy and laws
    • Code breaking
    • Networking
    • Pentest
    • Speak to us!
  • Career
    • Study and profession
    • Oportunidades
  • Reverse Engineering
    • General
    • Malware Analysis
    • Firmware
    • Linux and UNIX-like
    • Windows
  • Programming
    • Assembly
    • C/C++
    • Python
    • Other languages
  • Operating Systems
    • GNU/Linux and UNIX-like
    • Windows
  • Segurança na Internet's Discussão

Calendars

Categories

  • Trainings

Categories

  • News

Categories

  • Articles

Categories

  • Contribuindo

Categories

  • Crackmes
  • Documentação
  • Debuggers
  • PE tools
  • Books
  • Util
  • Packers
  • Unpackers

Find results in...

Find results that contain...


Date Created

  • Start

    End


Last Updated

  • Start

    End


Filter by number of...

Joined

  • Start

    End


Group


Full name


Como veio parar aqui?


Website


Github/Gitlab


LinkedIn

Found 30 results

  1. Em todos os sistemas operacionais existem arquivos estruturados. Imagine um bloco segmentado em diversas partes e cada uma sendo uma área que armazena um tipo de dado específico (ex.: cabeçalho, área de código, área de dado inicializado, área de dado estático, área de dado não inicializado, área de referência de definições externas/outros objetos) servindo de referência para resguardar determinada classe de dado do respectivo arquivo binário para serem usados durante a execução do software ou até mesmo para fornecer informações que ajudam no processo de debugging. O conceito dessa formatação do arquivo (file format) é presente em todos os sistemas operacionais populares como Windows e Unix-like – isso inclui o macOS. Sabendo que o macOS é um sistema operacional do Unix é de se esperar que seus arquivos binários também tenham um “formato”, e estes são conhecidos como “arquivos de objeto do Mac” ou simplesmente Mach-O. Com esse entendimento o propósito deste artigo é dar uma visão técnica geral sobre a estrutura de arquivos construídos com este formato. Por que é importante conhecer o formato Mach-O? Algumas pessoas acreditam que o sistema operacional macOS (atualmente na versão denominada Catalina) é mais seguro que outros sistemas operacionais existentes pelo fato de não ser afetado por malware. Grande engano! Atualmente, vemos muitas publicações de vulnerabilidades relacionadas ao macOS, o que demonstra que este sistema operacional é, sim, um alvo em potencial. A grande pergunta que sempre faço é: “O que é mais interessante para um criminoso?”. Neste contexto, por “criminoso” me refiro à qualquer indivíduo que se utiliza dos meios eletrônicos para cometer fraudes. Deixando dispositivos móveis de lado, minha opinião é que hajam duas alternativas principais: Infectar o maior número de hosts possível (Windows ou Linux); Infectar um número mais restrito de hosts, porém algo mais direcionado a usuários, em geral, de cargos executivos, por exemplo: Diretores, CSO, etc. ou usuários domésticos, que muitas vezes permitem que softwares de fonte desconhecida sejam executados livremente em seu sistema operacional, ao desativar mecanismos de segurança como o gatekeeper; Se eu fosse um criminoso, optaria pela segunda opção; pois, atualmente o MacBook está se tornando cada vez mais popular. A imagem abaixo nos mostra a grande quantidade de arquivos Mach-O que foram analisados no VirusTotal nos últimos 7 dias desde a escrita deste artigo: Estes são os tipos de arquivos submetidos ao VirusTotal nos últimos 7 dias, obtidos em 25/julho/2020. Repare que a imagem não reporta arquivos infectados, mas sim os binários de cada tipo analisados. Bom, é perceptível que Mach-O está ganhando uma certa predominância hoje em dia, embora ainda seja bem inferior ao número do arquivo executável do Windows (Win32.exe). Apenas a título de curiosidade, o Mach-O tem um formato multi arquitetura, também conhecido como “fat binary” (conforme podemos ver na imagem abaixo) aonde ele suporta 3 tipos de arquiteturas diferentes: x86_64, i386 e ppc7400: Aqui temos uma tabela com todos os “Magic Number” (valor numérico de texto usado para identificar um formato de arquivo) referentes à binários do tipo Mach-O: Ainda nesta linha de pesquisa, a técnica utilizada para gerar um binário suportado com várias plataformas (cross-compiling) é demonstrada na imagem abaixo utilizando o compilador gcc: Usando o comando file do macOS vemos o tipo do arquivo e a arquitetura da plataforma que é suportado: O formato Mach-O de 64-bits Conforme observado anteriormente os binários Mach-O tem três regiões principais: Cabeçalho (Header); Comandos de carregamento (Load Commands); e, Dados (Data). A imagem abaixo representa a estrutura básica dos arquivos Mach-O 64-bit: No Header, encontram-se especificações gerais do binário, como seu magic number e a arquitetura alvo. Podemos encontrar este header em /usr/include/mach-o/loader.h: Conhecendo um pouco mais a estrutura do mach header podemos notar que ela é composta por 8 membros, cada um possuindo 4 bytes, ou seja: 4 * 8 = 32. Podemos ver os primeiros 32 bytes do binário, isto é, os valores do header abaixo: A região Load Commands especifica a estrutura lógica do arquivo e informações para que o binário possa ser carregado em memória e utilizado pelo sistema. Ela é composta por uma sequência de diversos modelos de commands numa tupla, por exemplo: “[load_command, specific_command_headers]” -- definindo as diferentes “seções lógicas” (commands) do binário. Cada command necessita de um ou mais cabeçalhos específicos, por isso, o segundo membro da tupla (specific_command_headers) pode variar de acordo com o tipo de command da mesma em questão: A título de exemplo, podemos ver também o command LC_SEGMENT_64 do cabeçalho do binário Mach-O: Neste mesmo contexto, podemos ver que as bibliotecas dinâmicas (dylib) "libncurses" e "libSystem" foram carregadas nos commands 12 e 13, que pertencem ao cabeçalho LC_LOAD_DYLIB. Deste jeito, o kernel consegue mapear as informações do executável para um espaço de memória que pode ser acessado simultaneamente por múltiplos programas na finalidade de prover comunicação entre eles ou para evitar compartilhamento de dados supérfluos – tal conceito é conhecido como memória compartilhada: Podemos ver também que a section __text contém o segmento __TEXT: E por fim temos a Data, onde temos instruções armazenadas logo após a região LOAD_Commands. Na região Data é que são definidas as permissões de leitura e gravação. Dependendo do tipo de Mach-O a maneira como essa região é usada varia. Quando analisamos um binário um dos primeiros pontos para o início dos testes é a inspeção do binário em um debugger a partir de seu entrypoint. No caso do deste Mach-O que estamos analisando percebemos que o código é colocado na seção __TEXT, as bibliotecas são carregadas no cabeçalho LC_LOAD_DYLIB e o LC_MAIN é o cabeçalho que aponta para o ponto de entrada (entrypoint) : Por enquanto já temos uma noção básica da estrutura dos binários Mach-O. Em um próximo artigo, iremos detalhar melhor este binário com foco em engenharia reversa para identificar ações de software malicioso. Para ajudar, recomendo a você artigos da H2HC Magazine sobre pilhas, registradores etc., dos colegas Fernando Mercês, Ygor da Rocha Parreira, Gabriel Negreiros, Filipe Balestra e Raphael Campos nas edições 7, 8, 9, 10 e 11. Outra referência para auxiliar nesta análise é o artigo "Montando sua máquina virtual para engenharia reversa em macOS"[11]. Até lá! Referências Palestra H2HC University Vídeo Demo Malware Keranger Mach-O Vídeo Demo Crackme Mach-O Calling Conventions OS X ABI Mach-O File Format Revista H2HC ed7 Revista H2HC ed8 Revista H2HC ed9 Revista H2HC ed10 Revista H2HC ed11 Montando sua máquina virtual para engenharia reversa em macOS
  2. until
  3. Já faz um bom tempo (quase 1 ano!) desde o último artigo da série de desenvolvimento de debuggers. Este é o último artigo da série e iremos finalmente criar nosso primeiro prototipo de debugger. A ideia aqui, é compilar tudo que foi ensinado nos artigos anteriores sobre Sinais, Forks e ptrace . Com isso, criaremos um simples tracer em C que irá receber um endereço como argumento e colocar um breakpoint no mesmo. Diagrama Antes vamos definir um pouco o escopo do nosso software: O nosso tracer irá criar um fork e nesse fork será feita a chamada para a execv, que por sua vez irá trocar a imagem do atual processo (seu conteúdo) pela de outro processo, fazendo com que de fato vire outro processo. Já o nosso debugger, dentro de um loop, irá se comunicar via sinais com o processo filho. Breakpoints Existem dois tipos de breakpoints: software breakpoints e hardware breakpoints. Ambos servem para interromper a execução do nosso software em determinada instrução. Para que isso seja possível é necessário que a execução do processo seja interrompida na nossa CPU. Interrupções Quando ocorre algum evento no computador que precisa de um tratamento imediato, a CPU invoca uma interrupção. Cada evento desse contém uma ação especifica que nosso kernel irá lidar de alguma maneira e a estrutura responsável por salvar os valores e significados das mesmas é a Interrupt Descriptor Table. A imagem acima representa visualmente uma implementação desse vetor, onde cada posição (offset) contém uma struct associada e nela os valores necessários para lidar com isso. Você pode ter uma explicação mais detalhada aqui. Mas por que eu estou falando de tudo isso? Porque breakpoints nada mais são do que uma interrupção em um dado endereço que faz com que o processador pare a execução do seu programa. O valor que interrompe a CPU para um breakpoint é o 0x03. Vamos testar isto nesse pequeno bloco de código: main() { int x = 4; // Iniciando qualquer coisa __asm__( "int $0x03" ); } A macro __asm__ permite que seja colocado o código direto em assembly, nesse caso, foi colocado o mnémonico INT, que cuida das interrupções com o valor 3 (offset comentado acima na IDT). Se você compilar e executar esse programa: ~ ./code zsh: trace trap (core dumped) ./code Nesse momento o trabalho de fazer o handle dessa interrupção é do nosso software. O que fizemos aqui foi implementar um software breakpoint. Agora vamos executar esse programa no gdb e não por breakpoint algum (dentro do gdb) e só executar: (gdb) r Starting program: /home/zlad/code Program received signal SIGTRAP, Trace/breakpoint trap. 0x000055555555515f in main () (gdb) disas Dump of assembler code for function main: 0x0000555555555139 <+0>: push %rbp 0x000055555555513a <+1>: mov %rsp,%rbp 0x000055555555513d <+4>: sub $0x10,%rsp 0x0000555555555141 <+8>: movl $0x2,-0x4(%rbp) 0x0000555555555148 <+15>: mov -0x4(%rbp),%eax 0x000055555555514b <+18>: mov %eax,%esi 0x000055555555514d <+20>: lea 0xeb0(%rip),%rdi 0x0000555555555154 <+27>: mov $0x0,%eax 0x0000555555555159 <+32>: callq 0x555555555030 <printf@plt> 0x000055555555515e <+37>: int3 => 0x000055555555515f <+38>: mov $0x0,%eax 0x0000555555555164 <+43>: leaveq 0x0000555555555165 <+44>: retq End of assembler dump. (gdb) Veja que a nossa interrupção foi capturada pelo GDB, pois ele detectou um breakpoint trap e é exatamente isso que iremos fazer. Nosso tracer será capaz de detectar quando irá ocorrer um SIGTRAP, ou seja, um sinal que deve ser tratado por nosso sistema operacional. Finalmente implementando Vamos finalmente começar o nosso pequeno tracer, que será capaz colocar breakpoints, executar instrução por instrução e imprimir os registradores na tela! Para inserir a interrupção de breakpoint (int 3) não precisamos de muito, pois já existe um mnemónico para isso que é o int3 e que tem como valor 0xCC. Para inserir breakpoints precisamos de um endereço (que vá ser executado) e uma maneira de escrever nesse local na memória virtual do nosso processo. Já vimos anteriormente o ptracer e nele sabemos que temos alguns enums que podem ser passados como seu primeiro argumento. São eles o PEEK_DATA e o POKE_DATA, que servem para buscar algo na memória de um processo e escrever algo na memória de um processo, respectivamente. Segue a função que vamos usar para adicionar breakpoints no nosso tracer: uint64_t add_breakpoint(pid_t pid, uint64_t address) { uint64_t break_opcode = 0xCC; uint64_t code_at = ptrace(PTRACE_PEEKDATA, pid, address, NULL); uint64_t breakpoint_code = (code_at & ~0xFF) | break_opcode; ptrace(PTRACE_POKEDATA, pid, address, breakpoint_code); return code_at; } Respire fundo e vamos em partes, a ideia aqui é a seguinte: Dado o pid do nosso processo filho e um endereço de memória, vamos buscar o código que estava naquele local (code_at), salvar esse código (não só queremos adicionar um novo opcode, mas podemos futuramente querer executá-lo) e então vamos adicionar nossa instrução nos bytes menos significativos, ou seja, vamos executar ela primeiro. Usamos aqui uma variável de 64 bits por conta da arquitetura do meu sistema. Se você quiser tornar isto portável, é possível criar uma variável genérica baseada na arquitetura: #ifdef __i386__ #define myvar uint32_t #else #define myvar uint64_t #endif Isso é opcional, mas caso você queira criar algo mais genérico, esse é o caminho. A operação bitwise que fizemos aqui também pode ser “nebulosa” para alguns, mas segue o equivalente de maneira mais “verbosa” e em python: >>> hex(0xdeadbeef & ~0xFF) # Mascarando byte menos significativo '0xdeadbe00' >>> hex(0xdeadbeef & ~0xFF | 0xCC) # Mascarando byte e adicionado opcode int3(0xCC) '0xdeadbecc' O que é feito aqui é uma jogada lógica. Vamos quebrar isso em passos: Fazemos um AND com algo negado (0xFFFFFF00); Fazemos um OR com o resultado que irá "preencher" o espaço vazio, visto que um valor OR 0 será sempre o valor com conteúdo; No final mascaramos o último byte e colocamos nosso opcode; O nosso loop precisa executar enquanto nosso processo filho estiver sendo debugado. Em termos de estrutura de códigos vamos usar um laço que irá receber uma flag para sua execução: while (!WIFEXITED(status)) { // Our code } Caso você esteja perdido nessa função WIFEXITED, vale a pena dar uma olhada no artigo desta série sobre Forks. Agora é puramente uma questão de jogar com sinais e estruturar nosso código da maneira mais coesa possível, resumindo, pura programação 🙂 Após nosso breakpoint ser definido em memória precisamos fazer o handling disso. Para isso usamos a função WSTOPSIG, que irá receber o status do nosso processo (que é atribuído na função wait) e irá nos dizer qual tipo de interrupção ocorreu: while (!WIFEXITED(status)) { wait(&status); signal = WSTPOPSIG(status); switch(signal) { case SIGTRAP: puts("We just hit a breakpoint!\n"); display_process_info(pid); break; } } No momento que uma sigtrap for enviada para a gente podemos considerar que caímos no nosso breakpoint. Nesse momento, nosso processo filho está block (pois sofreu uma interrupção), esperando algum tipo de ação para continuar. A função display_process_info(pid) irá mostrar o atual estado dos nossos registrados, usando o enum PTRACE_GETREGS que recebe a struct regs (também já visto no artigo passado): void display_process_info(pid_t pid) { struct user_regs_struct regs; ptrace(PTRACE_GETREGS, pid, NULL, &regs); printf("Child %d Registers:\n", pid); printf("R15: 0x%x\n", regs.r15); printf("R14: 0x%x\n", regs.r14); printf("R12: 0x%x\n", regs.r12); printf("R11: 0x%x\n", regs.r11); printf("R10: 0x%x\n", regs.r10); printf("RBP: 0x%x\n", regs.rbp); printf("RAX: 0x%x\n", regs.rax); printf("RCX: 0x%x\n", regs.rcx); printf("RDX: 0x%x\n", regs.rdx); printf("RSI: 0x%x\n", regs.rsi); printf("RDI: 0x%x\n", regs.rdi); printf("RIP: 0x%x\n", regs.rip); printf("CS: 0x%x\n", regs.cs); printf("EFGLAS: 0x%x\n", regs.eflags); } O código do nosso loop final fica da seguinte forma: while (!WIFEXITED(status)) { signal = WSTOPSIG(status); switch(signal) { case SIGTRAP: puts("We just hit a breakpoint!\n"); break; } printf("> "); fgets(input, 100, stdin); if (!strcmp(input, "infor\n")) { display_process_info(pid); } else if (!strcmp(input, "continue\n")) { ptrace(PTRACE_CONT, pid, NULL, NULL); wait(&status); } } printf("Child %d finished...\n", pid); return 0; } Não iremos focar em implementação pela parte da interação do úsuario pois não é o foco dessa série de artigos. Tentei ser o mais “verboso” possível no quesito UX 😃. No projeto original usei a lib linenoise para criar uma shell interativa, mas isso fica para sua imaginação. Vamos executar: ~/.../mentebinaria/artigos >>> ./tracer hello 0x401122 #<== Endereco da main [130] Forking... Adding breakpoint on 0x401122 We just hit a breakpoint! > infor Child 705594 Registers: R15: 0x0 R14: 0x0 R12: 0x401050 R11: 0x2 R10: 0x7 RBP: 0x0 RAX: 0x401122 RCX: 0x225d7578 RDX: 0x19a402c8 RSI: 0x19a402b8 RDI: 0x1 RIP: 0x401123 CS: 0x33 EFGLAS: 0x246 We just hit a breakpoint! > continue Hello world Child 705594 finished... A ideia aqui não é criar tudo para você. A partir de agora, com o conhecimento básico dessa série de artigos, é possível criar o seu próprio debugger ou ferramenta semelhante. Deixo aqui o meu projeto, sdebugger, que foi fruto do meu estudo sobre este tema. Todo conhecimento base que eu passei aqui foi o necessário para criar este projetinho. Agradeço a toda turma do Mente Binária pelo apoio e desculpa à todos pela demora para finalizar essa série de artigos. Tenho várias ideias para artigos futuros, então vamos nos ver em breve! Links úteis: ELF Interruptions Breakpoints Interrupt Descriptor Table Qualquer problema/erro por favor me chame 🙂
  4. 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
  5. Olá! No artigo anterior falamos sobre Signals, que é de suma importância para a comunicação entre processos, mas para construir o nosso debugger precisamos muito mais do que apenas isso, precisamos de fato ter total controle sobre um dado processo e se possível controlar até o seu própio início. Neste artigo será explicado o que são forks e seu uso em desenvolvimento de aplicações em sistemas UNIX. Sem mais delongas, vamos prosseguir!!!😀 Resumidamente a syscall fork é usada para a duplicação e criação de um processo. Quando um dado processo chama a função fork(), é criada uma cópia idêntinca de seus dados. Note que apenas uma cópia é feita, o processo filho não compartilha o mesmo espaço de memória do pai. A syscall fork retorna um PID que é usado para indetificar em qual processos estamos e também dar acesso ao ID do processo filho. Caso o PID seja 0 estamos executando no filho, caso seja qualquer outro somos o processo pai, isso ocorre pois o pai precisa saber o PID do filho, mas o filho não necessariamente precisa saber o seu própio (da mesma maneira que o seu processo não sabe o própio PID ao menos que o mesmo peça). Algo interessante de se notar é que os Init System usados para subir e gerenciar serviços de sua máquina trabalham dessa mesma maneira, você pode checar sua árvore de processo usando comando pstree: $ pstree Dessa maneira você tem uma representação bem visual de como está dividida a sua estrutura de processos 😀. Note que todos os processos são filhos do seu Init system (seja ele SystemV, Systemd, etc). Aconselho você explorar o comando pstree para uma visão bem mais detalhada do seu sistema! Outra abordagem é usar o própio comando ps: $ ps -ef Rode o comando acima (dependendo da quantidade de processos use um pipe para o less 😉) e com ele teremos uma visão mais detalhada. A coluna PID representa o ID do processo em si e a coluna PPID representa o "Parent Process ID", que nada mais é que o ID do processo pai. Note que o PID 1 é o seu Init System e os seus processos rodam como filho dele! Vale notar que o processo Pai do própio init é o PID 0, que é conhecido como "swapper" ou "scheduler", que é o processo responsavel para realização de paging. Paging é o sistema de gerenciamento de memória que salva os dados da RAM em uma memória secundária (HD, SSD e etc) e recupera em formato de páginas (outros PID também são filhos do propio PID 0 como PID 2 que gerencia todas as threads que rodam em Kernel Land(KThread) etc). Programando Forks A syscall fork está na lib <unistd.h> (Unix Standard library) e tem a seguinte construção: #include <sys/types.h> #include <unistd.h> pid_t fork(void); Precisamos incluir a lib <sys/types.h> para que seja possivel acessar o tipo pid_t. A função fork não espera nenhum parâmetro para a sua construção e o código abaixo demonstra o quão simples é cria um fork. #include <stdio.h> // Acesso a syscall #include <unistd.h> // Acesso ao tipo variavel pid_t #include <sys/types.h> int main(void) { int x; printf("Processo normal...\n"); printf("Forking...\n"); sleep(5); pid_t pid = fork(); x = 40; if (pid == 0) { printf("Eu sou o processo filho meu PID: %d\n", pid); } else { printf("Eu sou o processo pai de %d\n", pid); } sleep(5); return 0; } Compile o código acima da seguinte forma: $ gcc -o fork fork.c $ ./fork Note que o código se "divide" a partir da chamada fork e um if é usado para saber se estamos executando no pai ou no filho, note também que o pai sabe o PID e o filho não. Para melhor visualização o código acima roda por 10 segundos (por conta da chamada ao sleep com esse tempo de espera). Abra um outro terminal e rode o comando: $ watch -n1 pstree O comando acima vai executar o pstree a cada 1 segundo, desta forma você verá o exato momento da criação do fork. Comunicando-se com o processo fork Agora imagine que um processo precisa esperar o seu filho terminar algum trabalho e dependendo do seu sinal o processo pai realiza alguma ação. A comunicação entre o processo pai e o filho se da por signals. O pai pode saber exatamente o estado do seu processo filho usando a syscall wait e waitpid, ambas na lib <sys/wait.h>: #include <sys/types.h> #include <sys/wait.h> pid_t wait(int *status); pid_t waitpid(pid_t pid, int *status, int options); A syscall wait espera que ao menos 1 de seus processos filho troque de estado, já a waitpid espera por um processo específico. Como sabemos exatamente qual processo queremos rastrear iremos usar esta call 😀: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> int main(void) { printf("Spliting work...\n"); pid_t pid = fork(); if (!pid) { int a = 0; for(int i = 0; i < 100000000; i++ ) { a += i*2 + 10 *i; } return 9; } int status; int signal; printf("Waiting child finish work...\n"); waitpid(pid, &status, 0); if (WIFEXITED(status)) { signal = WEXITSTATUS(status); printf("Child exited, status = %s\n", strsignal(signal)); } return 1; } Compile o código acima e execute: $ gcc -o work work.c $ ./work Spliting work... Waiting child finish work... Child exited, status = Killed Veja que após a chamada de fork nosso processo filho executa várias iterações e realiza um cálculo (um cálculo totalmente randômico) e após isso retorna 9. Este retorno em questão é apenas por motivos educativos (no artigo anterior falamos de sinais e como eles funcionam). O processo pai usa a syscall waitpid para esperar que qualquer signal seja enviada do pid especificado. Após receber um status é verificado se o fork saiu (WIFEXITED) e se sim, pegamos o signal enviado usando WEXITSTATUS(status da saída) e usamos a chamada strsignal(provida pela string.h) para recuperar uma versão em texto do signal. Nesse caso iremos recuperar o signal "KILLED", pois colocamos 9 apenas por razões educativas. Normalmente se tudo ocorreu bem colocamos 0 (inclusive é dessa maneira que sua shell avalia se o programa rodou certo). $./work && echo "Filho saiu com 0, tudo certo..." || echo "Filho saiu com 1, algo errado..." No caso acima a nossa shell irá criar um fork do nosso work, executar o nosso programa (que por sua vez também executa um fork mas não entra em questão aqui) e se o signal retornado pelo fork for 0 ele imprime uma mensagem, caso contrario ele imprime uma mensagem de erro, dessa maneira você pode orquestrar um shell scripting usando o própio retorno do processo 😉 Tente mudar o retorno do fork acima e verifique seu status usando funções providas pela <sys/wait.h>. No exemplo acima usamos apenas a call WIFEXITED e WEXITSTATUS, mas existem várias outras. Forks são de extrema importância para criação e gerenciamento de processos e iremos usar forks para que seja possível executar o programa que queremos debugar, dessa maneira o software em questão vai ser filho do nosso debugger, o que nós da total controle sobre o mesmo. Comentarios são todos bem vindos e todos os códigos usados estão disponíveis no github! 😀 Links úteis: Process Control fork wait Process State Fork Bomb - Cuidado com isso
  6. 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. 💚
  7. 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!
  8. Vou começar agradecendo ao @Fernando Mercês pela oportunidade e por ter sugerido este artigo, que também me motivou bastante a escrevê-lo! Introdução Não sou conhecido internet a dentro, apenas acompanho alguns canais no Discord (tal como o do Mente Binária). Meu nível de programação e engenharia reversa não é algo admirável ainda. Em um grupo especifico intitulado "Terra do 1337", que é um grupo fechado de amigos com finalidade de estudar engenharia reversa, programação e descontrair, eu surgi com uma idéia de escrever uma ferramenta que iria facilitar a vida de muitos nesta área de engenharia reversa e achei de API Inspector. A seguir um spoiler de como foi o início do projeto, para quem se interessar. 😉 O que é o API Inspector É uma ferramenta de código-aberto voltada para área de engenharia reversa, que irá auxiliar na análise de funções correspondentes a certas API's do Windows, retornando informações obtidas dos argumentos caso a função seja chamada pela aplicação. O que ele faz Ele faz um hook (do Inglês "gancho"), que consiste num desvio na função original da API solicitada para nossa própria função e com isso podemos obter os dados (argumentos/parâmetros) que foram passados para tal função. Como ele funciona O princípio de um hook é simples: você insere no inicio da função um salto que irá levar para a sua função (que é uma cópia da função original) e depois de efetuar o que quiser, irá retornar para a função original prosseguir. Talvez mais fácil visualizar o que expliquei com código: //Aqui está a função //ZwWriteVirtualMemory | NtWriteVirtualMemory, originada do binário: ntdll.dll //créditos ao https://undocumented.ntinternals.net/ NTSYSAPI NTSTATUS NTAPI //WINAPI NtWriteVirtualMemory( IN HANDLE ProcessHandle, IN PVOID BaseAddress, IN PVOID Buffer, IN ULONG NumberOfBytesToWrite, OUT PULONG NumberOfBytesWritten OPTIONAL ); //Sua versão assembly 777F2110 mov eax,0x3A 777F2115 mov edx,ntdll.77808D30 777F211A call edx 777F211C ret 0x14 //O que nós vamos fazer é criar uma função similar á ela com o nome que decidirmos //Então vamos inserir um jmp no início da função original para nossa função, ficando assim: 777F2110 jmp api inspector.573523EC 777F2115 mov edx,ntdll.77808D30 777F211A call edx 777F211C ret 0x14 //Usei como exemplo minha próprio ferramenta! //Então quando ocorrer a chamada desta função ela será jogada em nossa função! Depois de nós fazermos que desejar vamos retorna-la, porém para uma região que aloquei onde contém //Um buffer dos bytes que foram sobrescritos da função original: 03610000 mov eax,0x3A 03610005 jmp ntdll.777F2115 //Ela irá retornar depois do jmp que existe na função original e continuar o código.... Vantagens de se utilizar o API Inspector ao invés de um debugger Imagine que você está visualizando as chamadas intermodulares (para bibliotecas externas, no caso) que um programa faz, utilizando um debugger (o x64dbg por exemplo) e notou que uma certa função que deseja inspecionar é chamada em diversos pontos do programa. Vejo duas opções neste caso: colocar vários breakpoints, um em cada chamada à função, no código do programa ou colocar um único breakpoint função em si, no código dela, na DLL. Em ambos os casos, você vai precisar analisar chamada por chamada, parâmetro por parâmetro. E se a função for chamada 20 vezes consecutivamente? O tempo que você levaria para visualizar apenas o primeiro parâmetro da chamada é o tempo que a ferramenta iria levar para exibir todas as 20 chamadas, com os argumentos formatados bonitinhos ao seu dispor. Entende a vantagem? 🙂 E as desvantagens? Por hora, uma desvantagem é a quantidade de funções e API's suportadas. De fato, a primeira release não possui uma quantidade significativa que vá fazer você utilizar a ferramenta e nem uma quantidade de recursos interessantes na ferramenta. Mas é ai que vem o ponto chave, o fato de deixar ela pública remete ao próprio crescimento da mesma, no primeiro momento é necessário uma orientação da parte de vocês para me ajudar a melhorar o código visual. O segundo passo é eu e vocês começarem a fornecerem mais recursos para ela. Eu irei adicionar todo ou qualquer recurso que seja significativo para a mesma, e para isso eu já tenho mais funcionalidades para implementar na ferramenta que são excelentes. Interface gráfica Na imagem abaixo, utilizei o API Inspector para hookar a função MessageBoxW() da USER32.DLL. Depois disso, escrevi um texto num novo arquivo no Notepad++ e tentei fechar o programa. Ao fazer isso, o Notepad++ perguntou se eu queria salvar o arquivo e ele faz isso através de uma chamada à MessageBoxW(), que o API Inspector interceptou prontamente. Na imagem acima, a janela à esquerda mostra o que está atualmente passando pelas funções hookadas. Na janela a direita, temos um log. Como utilizar o API Inspector A única coisa que você precisa fazer é anexar a DLL do API Inspector ao processo desejado e para isso existem os softwares chamados "Injetores de DLL" que podem ser achados na internet. Você também pode criar o seu próprio injetor. Uma dica é pesquisar sobre injeção com a função LoadLibrary(), mas no exemplo a seguir eu vou mostrar como utilizar o Process Hacker para fazer a injeção. 1 - Abra o Process Hacker e identifique no mesmo o processo no qual você quer injectar a DLL do API Inspector. No exemplo, usei o processo do Notepad++. 2 - Clique com o botão direito sobre o processo e escolha Miscellaneous > Inject DLL. 3 - Selecione a DLL API-Inspector.dll e clique em Abrir. 4 - Se o Process Hacker possuir privilégios suficientes a ferramenta irá ser carregada, caso contrário, não. Após isso você precisa selecionar a API desejada, a função desejada e clicar em GO Hook! O step call é uma funcionalidade que vai fazer a ferramenta aguardar o pressionamento da tecla ENTER para retornar para a função original. Pronto, o seu hook está feito e você já poderá inspecionar a função desejada. Download e código No repositório do API Inspector no Github você pode baixar a versão compilada e ter acesso ao código-fonte também. Contribuições são muito bem vindas! Bom, eu nunca tinha escrito um artigo. Se faltou informação ou coloquei informação demais me desculpe. Estou aberto pra ler os comentários. Ah, e participem deste projeto! Eu quero fazer ele crescer muito. Caso precise de referências de como cheguei a este projeto, tem tudo na página inicial do projeto no Github. Agradecimentos Obrigado novamente ao Fernando Mercês, ao pessoal do Terra 1337 que me incentiva cada vez mais e em especial para o iPower e Luan que são colaboradores do projeto. Referências Dear ImGui Programming reference for the Win32 API NTAPI Undocumented Functions C++ 3D DirectX Programming
  9. Comecei a estudar a linguagem Go há alguns dias e fiquei muito impressionado com seus recursos. A facilidade para programação paralela, o fato de ter ponteiros, funções que retornam mais de um valor, código enxuto (se você declarar uma variável e não usar, o programa nem compila!) e outros realmente me encantaram. Recentemente precisei disassemblar um trecho de código de um binário PE para um projeto que está escrito em Go. Vi que existem algumas bibliotecas prontas para serem usadas, como gapstone (bindings da Capstone) e go-zydis (bindings da Zydis) mas não encontrei uma nativa. No entanto, vi que existe uma ferramenta nativa no toolset da linguagem similar ao objdump do GNU binutils: $ go doc cmd/objdump Objdump disassembles executable files. Usage: go tool objdump [-s symregexp] binary Objdump prints a disassembly of all text symbols (code) in the binary. If the -s option is present, objdump only disassembles symbols with names matching the regular expression. Compilei um "hello, world" em Go só pra ver: ~/hello $ cat main.go package main import "fmt" func main() { fmt.Println("menteb.in") } ~/hello $ go build E de fato o objdump da Go funciona: ~/hello $ go tool objdump hello | head TEXT go.buildid(SB) :-134217728 0x1001000 ff20 JMP 0(AX) :-134217728 0x1001002 476f OUTSD DS:0(SI), DX :-134217728 0x1001004 206275 ANDB AH, 0x75(DX) :-134217728 0x1001007 696c642049443a20 IMULL $0x203a4449, 0x20(SP), BP :-1 0x100100f 226d35 ANDB 0x35(BP), CH :-1 0x1001012 4c6f OUTSD DS:0(SI), DX :-1 0x1001014 6a52 PUSHL $0x52 :-1 0x1001016 436e OUTSB DS:0(SI), DX :-1 0x1001018 4a31794f XORQ DI, 0x4f(CX) Mas ao tentar com o um PE compilado pra 64-bits, descobri que só funciona com binários feito em Go. 😩 $ go tool objdump putty.exe objdump: disassemble putty.exe: no runtime.pclntab symbol found De qualquer forma, resolvi olhar o código-fonte deste objdump interno da linguagem pra ver qual é dessa mandinga. Na linha 43 do main.go do objdump tem um import pra uma biblioteca chamada objfile. Pensei: Wow, deve ser uma biblioteca de disassembly, talvez eu possa alterar ! E na hora já criei um projeto tentando usá-la mas fui surpreendido com um errão! kkkk ~hello $ cat main.go package main import "fmt" import "cmd/internal/objfile" func main() { fmt.Println("menteb.in") } ~hello $ go build main.go:4:8: use of internal package cmd/internal/objfile not allowed Não pesquisei muito sobre essa história sobre eu não poder usar um pacote interno (por quê o objdump pode e eu não posso?!), mas fui olhar esta objfile e terminei encontrando seu fonte. Para minha alegria, neste arquivos disasm.go vi os seguintes imports: "golang.org/x/arch/arm/armasm" "golang.org/x/arch/arm64/arm64asm" "golang.org/x/arch/ppc64/ppc64asm" "golang.org/x/arch/x86/x86asm" Agora sim, carái! É tudo público e posso usar. Desculpe o desabafo.. hehe o artigo na verdade começa aqui mas quis contar como cheguei porque né. 😁 Cada uma dessas bibliotecas possui uma função Decode() justamente pra decodificar uma instrução (tipo Inst). Testei com um NOP em 64-bits, só pra ver: package main import ( "fmt" "log" "golang.org/x/arch/x86/x86asm" ) func main() { dados := []byte{0x90} ins, err := x86asm.Decode(dados, 64) if err != nil { log.Fatalln(err) } fmt.Println(ins) } A saída foi exatamente a esperada: $ ./hello NOP Show. Agora é abrir um PE, ler de onde quero e daí disassemblar usado essa x86asm.Decode() num loop, mas vou deixar esse exercício aí pra quem quiser treinar Go. Ou se acharem útil posso postar um aqui mais tarde. Aqui já funcionou mas precisa de uma polida. 🙂 Perceba também que há bibliotecas para ARM e PowerPC. Achei bem maneiro. Talvez em breve o time da Go adicione suporte a mais arquiteturas. Amém! 🙏
  10. Ano passado eu assisti à uma palestra sobre esse novo utilitário da suíte GNU chamado poke. Ele é um editor de dados binários de linha de comando bem diferente dos que costumo usar (HT Editor, Hiew, etc). Hoje decidi testá-lo e curti bastante. Tá em mega beta, então não tá nem perto de ter pacote disponível nos repositórios oficiais das distros Linux, mas consegui compilar e neste artigo vou dar as instruções, que podem variar em cada ambiente, até porque o poke está em constante desenvolvimento. Usei um ambiente Debian testing aqui. Instalando as dependências A dependência mais chatinha de instalar foi a gettext, porque o pacote pronto dela não foi suficiente. Então tive que clonar e compilar: $ sudo apt install perf fp-compiler fp-units-fcl groff build-essential git $ git clone https://git.savannah.gnu.org/git/gettext.git $ cd gettext $ ./gitsub.sh pull $ ./autogen.sh $ ./configure $ make $ sudo make install Com a gettext instalada, agora podemos partir para as demais dependências do poke: $ sudo apt install build-essential libgc-dev libreadline-dev flex libnbd-dev help2man texinfo Só então podemos seguir para a compilação do poke. Compilando o poke $ git clone git://git.savannah.gnu.org/poke.git $ cd poke $ ./bootstrap $ ./configure $ make $ sudo make install Criando links para as bibliotecas Como instalei as bibliotecas do poke em /usr/local e o meu sistema não tinha este diretório configurado para que o loader busque as bibliotecas, precisei criar dois links para elas em /usr/lib: $ sudo ln -s /usr/local/lib/libpoke.so.0 /usr/lib/libpoke.so.0 $ sudo ln -s /usr/local/lib/libtextstyle.so.0 /usr/lib/libtextstyle.so.0 Sei que há outras maneiras de resolver isso, mas fiz assim pra acelerar, afinal eu queria mexer no poke logo! 🤪 Abrindo um binário PE no poke Baixei o executável do PuTTY para brincar um pouco e abri assim: $ poke putty.exe _____ ---' __\_______ ______) GNU poke 0.1-beta __) __) ---._______) Copyright (C) 2019, 2020 Jose E. Marchesi. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>. This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Powered by Jitter 0.9.212. Perpetrated by Jose E. Marchesi. hserver listening in port 47209. For help, type ".help". Type ".exit" to leave the program. (poke) Gerenciando os arquivos abertos O poke permite trabalhar com múltiplos arquivos de uma vez. Você pode ver a lista de arquivos abertos com o seguinte comando: (poke) .info ios Id Mode Size Name * #0 rw 0x0010b990#B ./putty.exe ios signifca "IO Spaces". Não tem nada a ver com o SO da Cisco ou com o da Apple. hehe Se quiser abrir outro arquivo, pode usar o comando .file <arquivo> e aí pode selecionar em qual você quer trabalhar com o comando .ios #n onde n é o número que identifica o arquivo, mas vou seguir o artigo com somente um arquivo aberto mesmo, então só teremos a tag #0. Dumpando dados Um dos principais comandos do poke é o dump (perceba este não começa com um ponto) que basicamente visualiza o conteúdo do arquivo, mas este tem várias opções. Vamos à mais básica: A primeira linha na saída acima é só uma régua pra te ajudar a encontrar os bytes. Fiz questão de colar uma captura de tela aí acima pra você ver que o poke colore a saída, mas nos exemplos seguintes vou colar a saída em texto pelo bem da sua largura de banda. 🙂 Por padrão, o dump exibe 128 bytes do arquivo, começando do seu primeiro byte. O número de bytes pode ser alterado na própria linha de comando: (poke) dump :size 64#B 76543210 0011 2233 4455 6677 8899 aabb ccdd eeff 0123456789ABCDEF 00000000: 4d5a 7800 0100 0000 0400 0000 0000 0000 MZx............. 00000010: 0000 0000 0000 0000 4000 0000 0000 0000 ........@....... 00000020: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00000030: 0000 0000 0000 0000 0000 0000 7800 0000 ............x... A sintaxe pode parecer um pouco estranha no início, mas você acostuma rápido. O sufixo #B diz que a unidade usada é bytes. Você pode testar outros valores como 2#KB ou 1#MB por exemplo. 😉 Dumpando a partir de posições específicas Para dumpar a partir de uma posição específica, podemos usar a opção :from do comando dump: (poke) dump :from 0x30#B :size 32#B 76543210 0011 2233 4455 6677 8899 aabb ccdd eeff 0123456789ABCDEF 00000030: 0000 0000 0000 0000 0000 0000 7800 0000 ............x... 00000040: 0e1f ba0e 00b4 09cd 21b8 014c cd21 7468 ........!..L.!th No comando acima eu pedi para o poke me mostrar 32 bytes a partir da posição 0x30. Seria o equivalente a fazer hd -n 32 -s 0x30 <arquivo>. O poke mantém um ponteiro de leitura no arquivo, por isso se você comandar somente dump novamente, o dump ocorrerá a partir da última posição lida (no caso, 0x30). Se quiser voltar o ponteiro para a posição zero, é a mesma sintaxe: dump :from 0#B. Interpretando dados O dump sempre te entrega uma saída em hexadecimal, mas e se quisermos interpretar os dados e exibi-los de maneiras diferentes? Para isso a gente larga de mão o comando dump e começa a operar com o jeito do poke de ler e interpretar especificamente, assim: (poke) byte @ 0#B 77UB O sufixo UB significa Unsigned Byte. Se eu quiser a saída em hexa por exemplo, basta eu setar a variável obase (output base): (poke) .set obase 16 (poke) byte @ 0#B 0x4dUB Eu poderia querer ler 2 bytes. Tranquilo: (poke) byte[2] @ 0#B [0x4dUB,0x5aUB] Posso interpretar o conteúdo como número também: (poke) uint16 @ 0#B 0x4d5aUH O prefixo UH significa Unsigned Half (Integer). Perceba que o poke sabe que um uint16 tem 2 bytes e por isso os lê sem a necessidade que especifiquemos o número de bytes a serem lidos. À essa altura você já sacou que equivalentes aos tipos padrão da linguagem C (da inttypes.h na real) estão disponíveis para uso né? Fique à vontade pra testar off64, int64, int32, etc. Lendo strings Além dos tipos numéricos, o poke tem o tipo string, onde ele lê até encontrar um nullbyte: (poke) dump 76543210 0011 2233 4455 6677 8899 aabb ccdd eeff 0123456789ABCDEF 00000000: 4d5a 7800 0100 0000 0400 0000 0000 0000 MZx............. 00000010: 0000 0000 0000 0000 4000 0000 0000 0000 ........@....... 00000020: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00000030: 0000 0000 0000 0000 0000 0000 7800 0000 ............x... 00000040: 0e1f ba0e 00b4 09cd 21b8 014c cd21 5468 ........!..L.!Th 00000050: 6973 2070 726f 6772 616d 2063 616e 6e6f is program canno 00000060: 7420 6265 2072 756e 2069 6e20 444f 5320 t be run in DOS 00000070: 6d6f 6465 2e24 0000 5045 0000 4c01 0700 mode.$..PE..L... (poke) string @ 0x4d#B "!This program cannot be run in DOS mode.$" Patch simples Vamos fazer um patch simples: alterar o "T" desta string acima de maiúsculo para minúsculo. Basicamente é só colocar à esquerda o jeito que acessamos uma determinada posição do arquivo e igualar ao que a gente quer. Sabendo que para converter maiúsculo para minúsculo na tabela ASCII basta somar 32 (0x20), podemos fazer: (poke) byte @ 0x4e#B = 0x74 Perceba que fui na posição 0x4e, porque na 0x4d temos o '!' e não o 'T'. Só pra checar se funcionou: (poke) string @ 0x4d#B "!this program cannot be run in DOS mode.$" (poke) Legal né? Mas dá pra ficar melhor. O poke suporta char, então podemos meter direto: (poke) char @ 0x4e#B = 't' (poke) string @ 0x4d#B "!this program cannot be run in DOS mode.$" Por hora é só. Fica ligado aí que postarei a parte 2 em breve, onde vou mostrar mais recursos do poke que tô achando bem úteis para engenharia reversa. Até lá! 😎
  11. ====== 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.
  12. 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!
  13. 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?
  14. Bom dia Galera, Sou bem novo aqui no Fórum, procurei as regras mas não encontrei. Então usarei apenas do bom senso! 🙂 Eu estou a mais ou menos dois meses cutucando um CTF do hack the box, minha experiência com reversing não é muito grande, mas estou procurando aprimora-la cada vez mais. Terminei semana passada o CERO que o Fernando ministrou no Papo Binário, consegui certos progressos mas ainda assim não consigo retornar a flag. Segue o enunciado do CTF: Find the secret flag and get the name of the creators of this challenge! o arquivo do CTF esta em anexo, mas tambem pode ser baixado em https://www.hackthebox.eu/home/challenges/Reversing na opção Find The Secret Flag Senha do arquivo ZIP: hackthebox Identifiquei alguns pontos sobre o desafio: As 4 primeiras strings ascii são parte do programa normal, e não são produtivas pra nada, pelo que eu vi, as duas ultimas dentro do data são stirngs encodadas: Mesmo sabendo das strings, não encontrei referencias dela em nenhuma parte do código. A unica referencia a string encodada é na função sub.printf_400a5b, e o fluxo do código tabém não cai na área dela por padrão Eu fiz todo os tipos de direcionamentos que aprendi, procurei fazer com que as entradas do que as funções necessitam fossem sempre válidas, mas sempre que entro nestas funções recebo um seg fault, ou uma saida aleatória que não é útil pra nada. As funções que destaquei abaixo são as que não identifiquei como sendo chamadas de forma nennhuma na execução do binário. Alguém mais experiente poderia me auxiliar com este desafio? Honestamente estou a tanto tempo nele que nem estou mais preocupado com a pontuação em si, mas quero entender que pontos estou errando para melhorar minhas habilidades. tenho certeza que isso me ajudará a reverter strings ofuscadas em malwares!! Obrigado desde já! secret_flag.zip
  15. 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.
  16. Version 0.22

    293 downloads

    Mais uma ferramenta do hacker horsicq (http://ntinfo.biz), o XVolkolak é um unpacker para vários packers livres e comerciais. Vale a pena ver o vídeo que a gente fez explicando seu funcionamento:
  17. paulosgf

    FSG packer

    Galera, Onde baixo esse packer, de preferência na versão mais atual, que é a 2.0, acho! Aqui no portal não achei em downloads. Abraços!
  18. No artigo anterior, discutimos como encontrar informações importantes para se comunicar com seu dispositivo. Neste, vamos falar sobre uma abordagem genérica antes de reverter o código do firmware de fato. Objetivos A coisa mais importante durante o processo a partir de agora é saber quais perguntas você quer responder. Se você começa a querer entender tudo, sem focar, no final acaba perdido numa montanha de informações sem sentido. Dê uma de Jack, O Estripador clássico: entenda tudo, mas por partes. Eu normalmente começo procurando o protocolo de comunicação no caso dele não estar documentado. Após isso quero entender geralmente o algoritmo usado para autenticação ou o gerador de senhas ou algo que me dê acesso a dados interessantes que possam ser usados em outros $hardware iguais. Normalmente a segurança de sistemas embarcados não é muito boa nisso: todos os hardware precisam de alguma forma de identificação única presente no firmware. Como você solucionaria o problema pensando em larga escala? Conversando com o seu $sistema A melhor parte e o principal diferencial da análise de hardware é ter o bare metal (o equipamento físico) nas suas mãos. Ter acesso aos sinais eletrônicos, poder medir frequências e VER como o sistema trabalha (adicionar LEDs em todos os circuitos possíveis e adaptar a frequência por exemplo, sempre lindo!) são coisas que fazem o coração palpitar bem mais do que algumas linhas de código. Acredite 😉 Sem muita informação prévia e com alguns equipamentos baratos, é possível obter dados interessantes para a análise. Poderíamos começar a controlar o tráfego de dados usando o analisadores lógicos simples e ao mesmo tempo podemos usar equipamentos mais avançados para medir o consumo de energia no processo de inicialização do hardware com tanta precisão, que poderíamos deduzir possíveis valores de chaves privadas - é pura ciência, física 💚 - e claro que funciona em condições ideais, como aprendemos na escola. Fluxo de dados na PCB Não adianta muito ter dados se você não pode ler, escrever ou transferi-los de alguma maneira. Olhar a placa deve ser suficiente para entender como o fluxo de dados ocorre, simplesmente pensando na posição do CI (Circuito Integrado) e das marcas na PCB, ou seja, na placa. Outra coisa importante: quase todas as plaquinhas têm uma datasheet voando na !nternetz e acessível publicamente, contendo toda a informação técnica (da pinagem, voltagem e o PROTOCOLO DE COMUNICAÇÃO). Sempre vale a pena usar o DuckDuckGo antes de começar a ter dores de cabeça por algo que esta documentado, certo? Vamos começar sem gastar muito $$$: Procure a fonte! Quem esta começando agora pode não ter todas as ferramentas disponíveis ou não ter acesso a uma oficina / laboratório. Por isso, vamos dump o código direto do hardware e abrir mao do contexto - que teríamos no caso da leitura dos dados no hardware - mas economizaremos dinheiro e teremos acesso a toda informação possível do $device. Acesso ao firmware e memória Dumpe o código do CI e o descomprima. Essa é a parte mais fácil e mais difícil de todo o processo, mas para isso você não precisa de nenhum equipamento e pode usar várias ferramentas de graça (algumas inclusive de código aberto). Como eu falei anteriormente, é possível achar na Internet os datasheets (documentação completa, normalmente em PDF) de quase todos os dispositivos. Ache a datasheet para o seu CI e assim não terá necessidade nem de identificar a pinagem nem de reverter o conjunto de instruções necessárias para a comunicação. Algo também importante é saber como parar a comunicação entre o seu CI e os outros pontos de comunicação da placa, pois isso pode interferir na leitura dos dados. Como interromper o fluxo de dados depende bastante do circuito que você esta analizando. Mas eu preciso desoldar? Esse seria o caminho mais óbvio certo? Não quer interferência, desconecte seu CI da placa e vai para o abraço. Esse método te dá controle total sobre o seu CI 🙂 O problema aqui é que esse processo requer experiencia e TEMPO. A ideia deste artigo é para ser algo mais simples e barato, então tente evitar essa situação e pense em ideias mais "criativas". Normalmente é possível conectar os dispositivos num osciloscópio ou voltímetro para monitorar e ter certeza que não há interferência dos sinais de cada pino do CI. Fique atento nos outros componentes, se algum tráfego de dados está ocorrendo (instale por exemplo um monitor na outra porta UART). Se algum tráfego ocorrer, daí não tem outro jeito a não ser desconectar o seu CI - use um fim de semana. 🙂 Assim que o seu CI estiver isolado, conecte ele a qualquer aparelho que "fale" o mesmo protocolo e comece a ler a memória bloco por bloco. Isso pode demorar, por isso aconselho quando começar a leitura, vá a cozinha e faça um café! 🙂 Dumping os dados Criar suas próprias ferramentas pode ser super divertido, mas custa tempo e é bem mais fácil quando você sabe o que está procurando. Por enquanto, podemos usar vários projetos de código aberto disponíveis: Para fazer o dump, flashrom é uma das ferramentas populares. É facil de usar, cheia de bugs mas tem suporte para várias arquiteturas diferentes. Funciona muito bem no Rasperry Pi. 🙂 Normalmente vale a pena usar o comando file para ter uma ideia do tipo de arquivo que você esta lidando. Se ele não te ajudar em nada, tem o famoso binwalk. Os logs do binwalk the darão os endereços importantes. Então agora podemos usar o dd e dividir o seu binário em segmentos específicos. O comando dd precisa dos parâmetros bs (block size), do skip (offset) e o count (que aqui signifca quantos blocos você tem/quer copiar). Normalmente você terá pelo menos 3 blocos: O bootloader.bin: geralmente não esta criptografado pelo fato do microcontrolador não poder descriptografar. O mainkernel.bin: se você tiver sorte será algum kernel Linux. Esse é o firmware que controla o bare metal. 🙂 Geralmente o mais divertido de ler e várias vezes comprimido - use o file novamente para saber como descomprimir. O mainrootfs.bin: para quem entende um pouco de Linux e sistemas BSD, esse é o sistema de arquivos, contendo todos os arquivos com configurações, os binários do sistema, etc. Use novamente o file para verificar se está comprimido. No caso da imagem estar criptografada, é possível quebrá-la utilizando o fcrackzip. No próximo artigo eu vou tentar entrar em detalhes desses três binários - vamos ver se eu acho algum hardware interessante. 🙂
  19. Complementando os artigos criados sobre máquina virtual para ambiente Windows e Linux, este tutorial tem como finalidade auxiliar na criação de uma máquina virtual para análise de binários, possivelmente maliciosos, em ambiente macOS. Configurações da Máquina Virtual 2 processadores (cores). 2GB de RAM. Placa de rede em modo NAT (em casos aonde você realmente precisa de comunicação com um C&C). Placa de rede em modo Host-Only. Compartilhamento de Pastas desativado ou Host-Only (com host local). Aqui vem um ponto interessante: como tenho receio de malwares que detectam o ambiente de virtualização (ex: VMware Fusion) e tentam escapar do guest pro host, rodo sempre o SO guest num SO host diferente. No caso, rodo a máquina virtual com macOS mas o SO host é Linux. Sistema Operacional virtual 10.10.1 (Yosemite) publicado em 2014 10.13.4 (High Sierra) Versão Atual Obs.: As duas versões do macOS mencionados acima são para demonstrar a tela de configuração do Gatekeeper de cada versão. A importância das versões está nos diferentes tipos de malware que podem se propagar em versões específicas. No entanto, basta escolher uma. O Gatekeeper é um componente de proteção para o macOS existente desde a edição Mountain Lion. A responsabilidade deste constituinte é encontrar a identificação do desenvolvedor (Developer ID, também conhecido como “assinatura de autenticidade”), que é fornecido pela própria Apple. Quando em conformidade, o Gatekeeper mantém-se adormecido até o momento do cujo arquivo executável ou instalador ser flagrado com a assinatura de autenticidade ausente ou por ser reconhecido pela semelhança de algum tipo de malware. Uma vez que você utiliza softwares baixados através da App Store e ou assinados pela Apple você já possui uma certa segurança. Tendo consciência que boa parte dos últimos malwares para macOS dependiam que este recurso estivesse desativado, consequentemente, permitindo o download e instalação de qualquer software não identificado. Desativando o Gatekeeper Imagem 1: versão Yosemite No macOS Sierra e posterior a opção “Anywhere” não aparece mais, agora o sistema operacional perguntará para o usuário se ele deseja permitir que o software realmente seja instalado / executado no sistema. Porém há maneira de desabilitar o Gatekeeper e voltar com a opção como mostra na Imagem 1, usando o spctl (SecAssessment system policy security), via Terminal: $ sudo spctl --master-disable Imagem 2: versão high-sierra Outro sistema de segurança é o SIP (System Integrity Protection). Eu ainda não vi nenhuma necessidade de desativar para rodar malware porem caso precisem: SIP (proteção de integridade do sistema) Clique no menu Selecione Reiniciar ... Mantenha pressionado o ⌘comando-R para inicializar no sistema de recuperação. Clique no menu Utilitários e selecione Terminal. Digite csrutil disable e pressione Enter. Feche o aplicativo Terminal. Clique no menu e selecione Reiniciar .... Ferramentas No macOS além da própria Apple Store (que com certeza neste caso não terá as principais ferramentas que precisamos), também temos algumas boas fontes de ferramentas. O MacPorts, sistema de pacotes muito utilizado, e também o Homebrew suprem muito bem nossas necessidades quanto aos pacotes. Abaixo deixei um lista de ferramentas tanto para análise estática quanto dinâmica, claro que em alguns casos a mesma ferramenta pode ser utilizada em ambos os tipos de análise. Analise Estática xxd -> Cria um dump a partir de um binário, parecido com o hexdump. strip -> Remove e ou modifica a tabela de símbolos de um binario. hexEdit -> Editor hexadecimal. lipo -> Cria ou modifica arquivos multi-arquitetura, na imagem 6 tempos um exemplo da sua funcionalidade. otool -> Exibe informações sobre binários Mach-O (tipo um objdump). jtool -> Versão melhoradas do otool. nm -> Exibe a tabela de símbolos. codesign -> Usado para criar, verificar e exibir assinaturas de código. machOView -> Interface visual para edição de binários mach-o. class-dump -> Usado para examinar informações em tempo de execução do Objective-C armazenadas em arquivos Mach-O. dtrace -> Ferramenta usada para analisar comportamento do Sistema Operacional e dos programas em execução. fs_usage -> Exibe informações sobre chamadas de sistemas, executa rastreamento do kernel e processos, tudo em real-time. xattr -> Usado para exibir, modificar e ou remover atributos(metadados) de arquivos, diretórios e links simbólicos. Analise Dinâmica xcode -> IDE de desenvolvimento de software oficial da apple, possui recursos internos para testes de perfomance de sistema. hopper -> Ferramenta usada para disassemble e decompile de arquivos mach-o 32/64bits. lldb -> Debugger utilizado para depurar programas C, C ++, Objective-C, Objective-C ++ e Swift. fseventer -> Ferramenta gráfica usada para verificar atividades em disco e execução de processos de forma visual. open snoop -> Usado para rastrear acessos de arquivos, aplicativos, processos e também monitoramento do filesystem, você pode utilizar ele em conjunto com o Dtrace. activity Monitor -> Exibe processos que estão sendo executados no macOS. procexp -> Ferramenta exibe informações acessíveis pelo proc_info para exibição de processos, parecido com o top e htop. lsock -> Baseado no PF_SYSTEM o lsock e usado para visualização em real time das conexões (Sockets) no sistema, similar ao netstat. Por se tratar de máquina virtual para pesquisas em macOS, não poderia deixar de mencionar as ferramentas do pessoal da Objective-See. Vale a pena testar e acompanhar os artigos deles. Imagem 3: ferramentas objective-see Destaco três ferramentas especificas para quem estiver analisando binários do tipo Mach-O: Imagem 4: ferramenta otool exibindo o magic number do binário. Imagem 5: ferramenta jtool exibindo o endereço da função main() do binário Mach-O Imagem 6: ferramenta lipo extraindo o suporte a uma arquitetura especifica em binários do tipo fat. Considerações finais Criar um snapshot da instalação default; Ficar atento: Anti-Disasssembly Anti-Debugging Sistemas de Ofuscação Anti-VM Ficar atento às falhas publicadas
  20. Mês passado eu postei no blog uma análise básica de um malware brasileiro que recebi por e-mail. Algumas pessoas se ofereceram para continuar a análise mas não o fizeram, por motivos diversos. Eu tomei vergonha e achei uma máquina virtual com o XP e alguns programas velhos que ajudariam na análise. Este artigo é o meu relato sobre esse malware. No post do blog cheguei a identificar que o malware tinha UPX [1], então neste artigo vamos partir da remoção do packer: $ wc -c IMG2005M.exe 58880 IMG2005M.exe $ upx -dqq IMG2005M.exe 102400 <- 58880 57.50% win32/pe IMG2005M.exe $ wc -c IMG2005M.exe 102400 IMG2005M.exe Ainda no Linux, fui atrás do entry point do binário com o pev [2]: $ pev -o IMG2005M.exe | grep -iA3 entry Entry point: 0x1758 Address of CODE section: 0x1000 Address of DATA section: 0x10000 Imagebase: 0x400000 Soma-se o EP à ImageBase e tem-se o offset do EP: 0x401758. Daqui pra frente resta muito pouco a se fazer com ferramentas nativas do Linux já que o binário é de Windows e o pev ainda não disassembla. Em seguida eu abri uma VM com Windows XP para ver a cara do inimigo: Brincalhão esse coder hein? Detalhe para o ícone de fotos no EXE – usuário leigo cai fácil. Depois usei o RDG Packer Detector [3] para tentar descobrir o compilador utilizado no arquivo, se há alguma criptografia conhecida envolvida, hashes etc: O RDG detectou que é um binário compilado em Visual Basic 6.0, em código nativo. Pelo visto era só o UPX mesmo pois o RDG não detectou mais nada. No entanto, são suposições… Usando o OllyDbg [4], abri o malandro, e sempre a primeira coisa que vem em minha mente binária é buscar pelas strings hardcoded no executável. As strings dentro de um executável podem dizer *muito* sobre ele. Vale sempre a pena “passar o olho” nelas. No Olly é bem simples buscá-las: basta clicar com o botão direito e ir em “Search for -> All referenced strings”. Várias strings foram encontradas (referenced-strings.txt). Destaco algumas: UNICODE “*AC:Documents and SettingsMasterDesktopProfessor PardalSummer_TRSSummer_TRS.vbp” ASCII “GetLocaleInfoA” ASCII “GetUserDefaultLCID” UNICODE “6655475C45405C58521B606F53535577515F505640625441” UNICODE “625F5B06076B7A465047524C58585F6B4A46415059” UNICODE “695B5E545F470707584D1D4C5C46” A primeira string, bem, acho que dispensa comentários né? Em seguida duas funções da API do Windows para identificação do idioma. Seria particularmente interessante um malware brasileiro ter versões do Windows em português como alvo? As outras três eu escolhi aleatoriamente. Acontece que criadores de malware sabem que precisam proteger suas strings, do contrário, muita coisa ficaria exposta a qualquer curioso que souber utilizar o comando strings do *n?x ou funções como essa do OllyDbg. Como há várias strings similares a essas três no binário, aposto que são strings encriptadas pelo programador do malware. Você não? =P De volta à tela do disassembly (ALT+C), parado bem no EP: 00401758 PUSH 00401850 0040175D CALL <JMP.&MSVBVM60.#100> ;Jump to MSVBVM60.ThunRTMain O jeito como o compilador constrói o executável é importante. No caso do VB é desse jeito acima: coloca-se um endereço na pilha e chama uma função da biblioteca. Esse endereço PUSHado é o offset de uma estrutura que define todo o programa em VB. Olhando a estrutura em 0x401850 no dump, dá pra identificar o offset do ProjectStruct (vem após esse 0x409): 00401850 56 42 35 21|F0 1F 56 42|36 45 53 2E|44 4C 4C 00| VB5!ðVB6ES.DLL. 00401860 00 00 00 00|2A 00 00 00|00 00 00 00|00 00 00 00| ….*……….. 00401870 00 00 0A 00|0A 0C 00 00|09 04 00 00|D0 AE 40 00| ………..Ю@. 00401880 D8 1B 40 00|00 F8 30 00|00 FF FF FF|08 00 00 00| Ø@..ø0..ÿÿÿ… Então fui para o endereço 0x40AED0 no disassembler (CTRL+G) e coloquei um breakpoint (F2) logo no início da função. Aí foi só seguir com o F8 até chegar numa call que parecia interessante pois era uma chamada de função interna em 0x40AF1C. 0040AF16 MOV EAX, DWORD PTR SS:[LOCAL.5] 0040AF19 PUSH EAX 0040AF1A MOV ECX, DWORD PTR DS:[EAX] 0040AF1C CALL DWORD PTR DS:[ECX+1C] (0x40B450) 0040AF1F TEST EAX, EAX 0040AF21 FCLEX Ao entrar nessa call, caí em 0x40B450. Nessa função dá pra ver uma daquelas strings doidas sendo operada em 0x40B4B7. Foi ali mesmo que coloquei um breakpoint e mandei rodar (F9). 0040B4B0 MOV DWORD PTR SS:[LOCAL.1], 3 0040B4B7 MOV EDX, 00402664 ; UNICODE “6655475C45405C58521B606F53535577515F505640625441” 0040B4BC LEA ECX, [LOCAL.11] 0040B4BF CALL DWORD PTR DS:[<&MSVBVM60.__vbaStrCopy>] 0040B4C5 LEA EAX, [LOCAL.11] 0040B4C8 PUSH EAX 0040B4C9 LEA ECX, [LOCAL.17] 0040B4CC PUSH ECX 0040B4CD CALL 0040AF60 0040B4D2 PUSH 0 0040B4D4 LEA EDX, [LOCAL.17] 0040B4D7 PUSH EDX 0040B4D8 LEA EAX, [LOCAL.12] 0040B4DB PUSH EAX 0040B4DC CALL DWORD PTR DS:[<&MSVBVM60.__vbaStrVarVal>] 0040B4E2 PUSH EAX 0040B4E3 LEA ECX, [LOCAL.21] 0040B4E6 PUSH ECX Fui seguindo com F8 e quando passei da chamada para __vbaStrVarVal em 0x40B4DC não é que a string se transformou?! O que era “6655475C45405C58521B606F53535577515F505640625441” virou “Scripting.SWbemObjectSet”, mas como? Os olhos aqui têm que estar afiados. Se liga: $ echo -n 6655475C45405C58521B606F53535577515F505640625441 | wc -c 48 $ echo -n Scripting.SWbemObjectSet | wc -c 24 A string encriptada tem o dobro do tamanho. Além disso, parecem ser bytes hexa já que todos os dígitos vão de 0 a F. Mas se você achou que o Professor Pardal simplesmente usou os bytes hexa de cada caracter da string original para encriptá-la, se enganou. É um pouquinho mais trabalhado. hehehe Repara que antes da string ser desencriptada, o fluxo de execução só passou por uma função local, uma call em 0x40B4CD para 0x40AF60. As outras são funções da MSVBVM60.DLL. Portanto pude supor que é nesta função que está a rotina de desencriptação, coloquei um breakpoint lá e comecei tudo de novo… Essa função que desencripta (0x40AF60) é relativamente grande. Vou resumir o que ela faz: O primeiro loop (de 0x40B08F a 0x40B123) cria a seguinte string fixa gigante que será usada mais à frente: “5655545655381688355541151683736525351505152535455336435363716816838424040414195431239696123125941259” Por que usar um loop para criar uma string fixa ao invés de declará-la hardcoded? Essa string é a chave para descriptografar todas as outras. O Professor Pardal não a deixaria tão solta. Se tiver curiosidade de descobrir como ela é gerada vai ser um ótimo treino, principalmente pra quem está começando. Qualquer coisa tô pelo Twitter. O segundo loop pega dois caracteres da string encriptada e os interpreta de uma vez como um byte hexadecimal (um número). Depois disso, XOReia (tem gente que fala mIgUxOO) este número com o equivalente númerico do caractere da string chave na mesma posição. Complicou? Vamos a um exemplo… String encriptada: 665547 1. Pegam-se dois caracteres da string encriptada e os interpreta como um número hexa: “66” -> 0x66 2. Pega-se o equivalente numérico de um caractere da string chave: “5” -> 0x35 3. Faz-se o xor entre esses números 0x66 ^ 0x35 = 0x53 4. Converte o resultado para o equivalente em ASCII. $ printf “x53n” 5. Volta para o passo 1 até que todos os caracteres da string encriptada tenham sido pegos, sempre dois a dois. O próximo cálculo será 0x55 ^ 0x36 e o outro, 0x47 ^ 0x35, certo? Pra você não se perder no disassembly, o xor fica em 0x40B309 – é uma chamada à função __vbaVarXor da MSVBVM60.DLL. Nem bitwise programa em VB faz, rapaiz! Escrevi um programa em C para fazer este trabalho sujo de desencriptar as strings (pardal_decrypt.c) : $ gcc -o pardal_decrypt pardal_decrypt.c $ ./pardal_decrypt 6655475C45405C58521B606F53535577515F505640625441 Scripting.SWbemObjectSet Em 0x40B4E7, depois de desencriptar a primeira string, o malware chama a função rtcCreateObject2 da biblioteca. Conseguimos saber o nome dela graças à análise do OllyDbg. Dá pra imaginar o que essa função faz pelo nome e pela string que foi descriptografada antes dela. Não tenho intenção aqui de reescrever o código do malawre, então não vou me focar em como ele faz e sim o que ele faz. Logo, meus chutes de código não refletem necessariamente a realidade. Daqui pra frente seria bom acompanhar olhando o disassembly do malware (cuidado – não vá rodar o cara!) Na sequência, mais strings são desencriptadas: “Scripting.SWbemObjectSet” e “Scripting.SWbemObject”. Objetos destas classes também são criados. Em seguida mais duas strings: “Win32_OperatingSystem” e “winmgmts:{impersonationLevel=impersonate}”. Não é difícil deduzir que será feita uma query WMI. Em 0x40B672 há uma chamada para a rtcGetObject. É claro que ter alguma experiência com programação ajuda aqui. No caso de executáveis feitos em VB, ter programado em VBScript me ajuda. Entrando na call em 0x40B70D, cheguei na função 0x40E210, que pega o idioma da instalação do Windows. Ainda nesta função, em 0x40E38F, o malware verifica se a string “uguês (Brasil)” faz parte da string de retorno do idioma. É uma chamada à função InStr() do VB. De volta ao fluxo anterior, em 0x40B726 rola um “for each” que vai iterar através da coleção de objetos que a query “SELECT * FROM Win32_OperatingSystem”, montada algumas linhas antes, retorna. Nesse loop duas coisas são feitas: o atributo Caption do objeto retornado é guardado e o idioma é checado. Em 0x40B87F há uma call interna para 0x4019ED. Esta é um call bem legal de acompanhar de perto. Ela desencripta a string “TMP” e em 0x40BB58 chama a rtcEnvironBstr, que vai retornar o valor de uma variável de ambiente. No caso, o valor da variável de ambiente TMP que é o caminho completo do diretório temporário do usuário logado: C:> echo %TMP% C:DOCUME~1xpclientCONFIG~1Temp O malware desperta a curiosidade: o que ele quer no temp? Baixar algo? Gravar algo? Em 0x40BF57 o corno chama a rtcFileCopy e se copia para o diretório temporário do usuário. Em seguida testa se está no Windows Vista ou 7 para usar o runas, porque ele vai querer executar a sua cópia com privilégios administrativos. Em 0x40C101 ele manda executar sua cópia com uma chamada a rtcShell e morre. O trabalho sujo mesmo será feito pela cópia agora. Abri a cópia no debugger e vi que em 0x40C12A o malware verifica se seu caminho atual de execução é o diretório temporário. O problema é que pelo menos no Windows XP, essa checagem foi feita de maneira errada (pois é, bug no malware). O caminho atual pego vem no formato 8.3 do DOS. Veja: 0040C12A MOV EAX, DWORD PTR SS:[LOCAL.10] ; UNICODE “C:\DOCUME~1\xpclient\CONFIG~1\Temp\IMG2005M.exe” Quando essa string é comparada com o conteúdo da variável ambiente TMP, o resultado é falso e o malware tenta se copiar novamente para o temp, mas já existe um executável dele lá (que é ele mesmo) e não consegue se copiar. Aí entra em loop. =P Zerei, em tempo de execução, o valor de EAX, que é o registrador que armazena o retorno da __vbaStrCmp, para que o salto em 0x40C132 aconteça. Seguindo a análise, entrei numa call para 0x40C640 que busca o valor “EnableLUA” na chave “HKLMSOFTWAREMicrosoftWindowsCurrentVersionPoliciesSystem”. Caso não exista ou não seja zero, o malware seta para zero, desabilitando esta proteção do Windows. Mais a frente, em 0x40C939 ele exibe essa mensagem: Isso não é um erro causado pelo malware – é uma mensagem inofensiva, exibida de propósito. O texto é fixo e essas strings fazem parte das strings criptografadas. Elas estão em 0x40C8CF e 0x40C8EA. Agora o processo fica residente aguardando acesso à internet. Para isso ele tenta acessar a página do Google. Quando conseguir, vai para a última função do mal em 0x40C9D0. À esta altura do campeonato, basta olhar a função que você vai perceber todo este fluxo. Logo no início dela, um arquivo mkajs21mx.tmp é criado no diretório temp do usuário. Em seguida o malware faz uma conexão com um serivodr MS-SQL remoto, na URLia8eaatjyur0gqzaslrlqw2n8k.zlg.br na porta 9321. Nome de usuário, senha e nome do banco também são desencriptados pela mesmíssima rotina 0x40AF60. Após conectar, o malare faz a seguinte query SQL: SELECT TOP 1 * FROM Professor_Carregador ORDER BY Professor_Carregador_ID DESC O retorno dessa consulta tem ~70k e começa com 0x4d e 0x5a. O que é o que é? =D O malware salva o binário pego via SQL em %WINDIR%System32MakeObject.dll e depois registra essa DLL com o regsvr32.exe como um complemento do IE: Aí vem outra query: INSERT INTO Professor_Informa(Professor_Informa_WithEvents,Professor_Informa_Control) VALUES(‘0’, ‘0’) Fiz um script em VBScript pra pegar alguns registros deste banco (pardal_sql.vbs) e descobri que há mais de 6000 registros já. Ou seja, um número próximo desse de computadores brasileiros foram infectados. Impressionante. Já em relação aos registros, não tem nada demais. Por fim, só pra não dizer que não perturbou, o malware tenta apagar o arquivo WAV que é o somzinho de inicialização do Windows, mas pelo menos no XP SP3 PT-BR ele errou o nome e tentou apagar o Início do Windows XP.wav quando o nome real é Inicialização do Windows XP.wav. Segundo bug. E o que faz a MakeObject.dll? Eu dei uma pesquisada e achei alguns usuários reportando sintomas de envio automático de e-mail via Hotmail. Por coincidência ou não, todos tinham essa DLL em logs do HiJackThis. Mas no momento estou cansado de ver funções do VB na minha frente. Então quem quiser analisar, eu envio a DLL. Os usuários de Windows têm então mais duas coisas para se preocupar: a falta do somzinho de incialização e um MakeObjects como complemento no IE. Sem falar na DLL em si, que não sabemos o que faz ainda. Boa sorte! hehe Referências: [1] upx.sourceforge.net [2] https://github.com/merces/pev [3] rdgsoft.net [4] www.ollydbg.de
  21. Resolvi montar este tutorial depois do grande trabalho que tive pra atualizar a máquina virtual que utilizo para analisar malware. Perdi horas procurando e instalando programas. Minha vontade era de distribuir um OVA com a máquina já pronta e talvez faça isso no futuro mas por hora vou discutir aqui os programas que utilizo para uma análise básica de malware ou engenharia reversa de algum software. Máquina virtual 512 MB de memória Placa de rede em modo NAT Sistema operacional Utilizo o Windows 7 64-bits em Português do Brasil, para facilitar na análise de malware brasileiro que teste o idioma do SO. Claro que em alguns casos vale a pena você ter máquinas virtuais de outras versões do Windows e em diferentes idiomas para ver se o comportamento do seu alvo muda. “Alvo” aqui significa o arquivo que você vai analisar, seja porque ele é suspeito seja porque você quer conhecer seu funcionamento. Algums dicas durante de instalação: Não utilizar seu nome verdadeiro na criação da conta de usuário do Windows durante a instalação. Não há necessidade. Não instalar as extensões do software virtualizador como VMware Tools ou VirtualBox Guest Additions. Não instalar nenhum antivírus, firewall ou qualquer software de proteção. Desabilitar todos os itens na “Central de Segurança” do Windows, incluindo as atualizações automáticas. Arquivos diversos Costumo espalhar uns arquivos .doc, .xls etc pela máquina, a fim de parecer que é uma máquina real. Algumas ameaças (principalmente ransomware) tentam encriptar ou mesmo destruir arquivos das vítimas. Software geral 7-Zip: Descomprime qualquer formato! E é livre. Firefox: Browser livre. Flash: Caso precise analisar SWF ou visualizar algum site que exija, mas cuidado pra não baixar tralha da McAfee junto. Java: Somente a JRE (Java Runtime Environment), para analisar classes Java. .NET Framework: Para permitir que ferramentas e alvos escritos nesta linguagem rodem. Notepad++: o único editor de arquivos que você precisa no Windows! Software específico API Monitor: Pode monitorar qualquer chamada a uma função específica da API do Windows ou de uma DLL específica. Muito bom. CFF Explorer: Analisador de PE. DeDe: Descompilador para Delphi. Extremamente útil para os malwares brasileiros! DIE (Detect It Easy): Detector de packer novo e promissor com suporte a regras personalizadas. Dis#: Descompilador para .NET. E2A (Event to Address): Somente para alvos escritos em Borland C++, dá os endereços dos eventos, como o clique de um botão por exemplo. Impressionante. FileGrab: Monitora o filesystem e copia para um diretório qualquer arquivo criado nele. Útil pra monitorar comportamento de malware que se deleta. Hex Workshop: Editor hexa animal, mas pago (não se preocupe, há um livre no final da lista). IDA Free: Disassembler e debugger. Tem uma função graph muito útil, além de suporte a vários tipos de binários e muitos recursos. ImpRec (Import Reconstructor): Útil para unpacking manual, para reconstruir a IAT (Import Address Table) de binários. JD-GUI: Descompilador Java (gera código Java a partir dos .class). .NET Reflector: Outro descompilador pra .NET. Assim como o Dis#, gera MSIL (Microsoft Intermeddiate Language) como saída, não Assembly. OllyDbg: Debugger e disassembler. O principal programa para análise. PeStudio: Analisador de arquivos PE. pev: Toolkit para análise de binários. ProcessExplorer: Monitora o que os processos estão fazendo no sitema (chaves de registro que estão abrindo, outros arquivos etc). RawCap: Sniffer de pacotes que usa raw sockets. É muito rápido e simples de usar. Gera o PCAP pra ser analisado no Wireshark futuramente, o que pode ser feito fora da máquina virtual. RDG Packer Detector: Melhor detector de packer que conheço. RegShot: Usado para tirar uma “foto” do sistema antes e outra depois da execução de um aplicativo, a fim de compará-las para investigar o que foi alterado. ResHack (Resource Hacker): Editor de recursos (ícones, telas e afins dentro do binário). wxHexEditor: Editor hexadecimal livre, cheio de recursos. Tem a capacidade de editar discos inclusive. Software opcional, caso precise programar Orwell Dev-C++: Versão em constante atualização do Dev-C++, para quem precisar codar em C/C++ para ver o comportamento de uma função da API do Windows por exemplo. Visual Studio Express: Para desenvolver em .NET, a Microsoft oferece uma versão gratuita (Express) do Visual Studio. IMPORTANTE: 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. Se você é iniciante, recomendo baixar e testar um programa de cada vez, até entender o motivo pelo qual ele pode ser útil. Alguns programas da lista acima são redundantes entre si pois possuem o mesmo objetivo, mas às vez um é mais preciso que outro dependendo do alvo, por isso é interessante mantê-los. Recomendo colocar as ferramentas que não possuem programa de instalação, como o E2A, num diretório tipo C:tools e criar atalhos para os executáveis principais na área de trabalho. O artigo é curto mas o trabalho que o leitor terá é grande.
  22. 3mm4

    NoREpls

    Desafio feito pelo dtm. Categoria: Easy! O desafio é conseguir usar a versão "completa" do software removendo tudo que te impede. Chall: http://www.mediafire.com/file/h789ga3v3gxv9vr/NoREpls.exe VirusTotal: https://www.virustotal.com/en/file/6adb6cd98c6da46ffe39ff8b5fe10634358cdc48c432369edd06f939be596bd2/analysis/1500375245/
  23. Caro leitor, você gosta de desafios? Neste artigo vou contar como resolvi um desafio de engenharia reversa do Shellterlabs, mas sem usar um disassembly! Para quem não é acostumado com o termo, de acordo com o grupo CTF-BR!, um CTF (Capture The Flag) nada mais é do que uma competição que envolve diversas áreas mas principalmente as áreas ligadas à segurança da informação. No Papo Binário também há um vídeo sobre o assunto. O desafio em questão é o Shellter Hacking Express Acidentalmente. Em sua descrição, há a seguinte frase: Acidentalmente codificamos a chave. Isso não diz muita coisa mas ao baixar o binário, percebemos que há dois arquivos: tar tf ~/Downloads/e74a74b5-86cf-4cb3-a5bb-18a36ef067cf.tgz RevEng400/ RevEng400/encoder RevEng400/key.enc Usando o comando file, verifiquei de que tipo são os arquivos extraídos: cd RevEng400/ $ file * encoder: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.2.5, stripped key.enc: data Ao ver que o encoder é um binário ELF, fui direto analisar seu código num disassembler usando objdump e gdb, mas percebi que o binário não continha os símbolos, o que torna sua análise um pouco mais difícil. Sendo iniciante em engenharia reversa e depois de horas analisando a função de cifragem, confesso que fiquei sem saber para onde ir (já viu algum apressadinho tentando aprender a tocar guitarra? Pois é, já quer ir lá tocar aquele solo, e na velocidade Steve Vai, aí não dá né? rs) e desisti, mas não por muito tempo (ei crianças, nunca desistam dos seus sonhos viu! rs), e procurei o nosso querido prof. @Fernando Mercês lá no servidor do Discord, que me deu umas dicas. Segue trecho da conversa: > @fernandom @gzn sei que vc ta treinando ER, mas nem precisa disassemblar esse binario pra esse desafio nego > se vc olhar bem, vai ver que a saída do encoder tem o tamanho da string de entrada + o byte 0x03 no final > olhando a chave encodada (key.enc), é razoável admitir que ela tenha 16 caracteres então > você só precisa encontrar qual deles é o 0xef .. um loop com bash mata > supondo que seja o 'A'... então 'A' -> 0xef, aí você vai precisar da letra que gera o 0xf9 e assim sucessivamente, até chegar em 16 Já ouviu a expressão "pensar fora da caixa"? Pois é! Por que eu fui direto disassemblar? Esse é um dos problemas quando nós estamos começando: às vezes a gente acha que o método mais difícil deve ser o único ou o melhor para se resolver problemas, mas nem sempre é assim. Daí pensei: se o Mercês falou que não é muito difícil, vamos ao menos tentar não é? Bem, a primeira coisa que fiz foi ver uma maneira de imprimir o conteúdo do binário em hexadecimal. Para isso criei um pequeno script que usa o hexdump para me dar uma saída somente com os bytes em hexadecimal do parâmetro que receber. Chamei o script de hexdump.sh e depois dei permissão de execução nele (chmod +x). Seu conteúdo é o seguinte: #!/bin/sh hexdump -v -e '/1 "%02X "' $1 Então comecei os testes: for letra in 0 A a; do echo -n "$letra "; ./encoder $letra | ./hexdump.sh; echo; done 0 7F 01 A F7 02 a F7 03 Parece que nem sempre o final é 0x03... Bem, fui verificar o conteúdo do arquivo key.enc e encontrei isso: ./hexdump.sh < key.enc EF F9 42 09 A3 1A 43 F7 8C 8B BB 22 2A C2 A3 14 03 Pela lógica, já que essa é a chave codificada, se eu passar a chave correta original em texto como parâmetro para o binário encode ele terá que gerar a sequência acima. Seguindo a dica do Mercês, usei o próprio bash para tentar quebrar o desafio, primeiro mostrando o conteúdo em hexadecimal da chave codificada, depois iterando pelos caracteres possíveis e filtrando pelo primeiro byte dela: hexdump.sh < key.enc; echo for ((i=32;i<=126;i++)); do > l=$(printf "\x$(printf "%x" $i)") > echo -n "$l " > ./encoder "$l" | ./hexdump.sh > echo done | grep 'EF' Este código basicamente faz: Mostra os bytes em hexadecimal da chave a cada vez que executarmos esse comando (só pra saber qual byte é o próximo). Itera por todos caracteres imprimíveis da tabela ASCII (faixa de 32 à 126 em decimal). Imprime o caractere na tela sem a quebra de linha. Passa essa letra para como argumento para o binário encode e imprime a saída dele em hexadecimal. Por fim, usa o grep para encontrar uma combinação que tenha o próximo byte da chave. Partindo para um exemplo prático, fui tentar encontrar a primeira letra dessa chave, sabendo que sua versão codificada deve resultar no byte 0xEF: ./hexdump.sh < key.enc; echo EF F9 42 09 A3 1A 43 F7 8C 8B BB 22 2A C2 A3 14 03 for ((i=32;i<=126;i++)); do > caractere=$(printf "\x$(printf "%x" $i)") > echo -n "$caractere ";./encoder "$l" | ./hexdump > echo done | grep 'EF' " EF 01 B EF 02 b EF 03 Conforme pode ver acima, encontrei três caracteres diferentes que, quando encodados pelo encoder, geram o byte 0xEF: ", B, e b. Escolhi seguir com o B, prefixando-o na chave para dar sequência ao script e ver se encontramos o caractere que resulta no próximo byte da chave codificada (0xF9): ./hexdump.sh < key.enc; echo EF F9 42 09 A3 1A 43 F7 8C 8B BB 22 2A C2 A3 14 03 for ((i=32;i<=126;i++)); do > caractere=$(printf "\x$(printf "%x" $i)") > echo -n "B${caractere} " > ./encoder "B$l" | ./hexdump > echo done | grep 'EF F9' % EF F9 01 E EF F9 02 e EF F9 03 Mais uma vez encontrei três opções. Foi só continuar este processo até encontrar a chave que gera os exatos 16 bytes do arquivo key.enc. Aproveitei e automatizei um brute forcer com Python: #!/usr/bin/env python3 # -*- coding: utf-8 -*- import subprocess def encode(arg): result = subprocess.run(['./encoder', arg], stdout=subprocess.PIPE) return result.stdout def loadKey(): key_enc = [] with open('./key.enc', 'rb') as file: while True: byte = file.read(1) if byte: # a ordem dos bytes aqui não importa (porque trata-se apenas de 1 byte), mas é necessário especificar key_enc.append(int.from_bytes(byte, byteorder='little')) else: break return key_enc def permutations(key='', key_enc=loadKey(), key_i=0): if key_i == len(key_enc) - 1: print(key) return for char in (chr(i) for i in range(32, 127)): result = encode(key + char) if result[key_i] == key_enc[key_i] and key_i < len(key_enc): permutations(key=key + char, key_enc=key_enc, key_i=key_i + 1) def main(): permutations() if __name__ == "__main__": main() Saída codificada em base64 (pra não estragar a brincadeira de quem vai tentar resolver o desafio por conta própria): QmV3aXRjaGluZyBTZXh0LwpCZXdpdGNoaW5nIFNleHRPCkJld2l0Y2hpbmcgU2V4dG8K Segue vídeo do canal com a explicação do algortimo de encoding desse desafio:
  24. Keygen, que abrevia “Key Generator” é um software capaz de gerar chaves válidas de registro para um software protegido. A prática desta técnica pode (e provavelmente vai) infringir algumas leis quando usada em softwares comerciais. No entanto, existem alguns desafios na internet chamados de “keygenmes”, que são programas feitos justamente para serem quebrados. O desafio está em criar keygens para eles. Este é um estudo muito interessante que treina bastante a lógica, matemática, assembly e até mesmo massageia o ego, se você vencer. Obviamente você não tem acesso ao código-fonte do desafio, então um disassembler, software capaz de interpretar os bytes do binário como mnemônicos assembly, precisará ser usado. Para facilitar o keygenning, também é interessante usar um debugger. Neste artigo eu vou usar um poderoso debugger e disassembler multiplataforma chamado EDB, projeto este que apoio. Ele foi escrito em C++ e Qt (então você precisa ter a Qt instalada para rodar). No github do projeto há instruções para instalação no Debian/Ubuntu, Fedora, Gentoo e para compilação (em qualquer ambiente). Neste artigo, usaremos dois arquivos: keygenme.c -> código-fonte do keygenme (desafio). keygen.c -> código-fonte de um keygen para o keygenme (solução). NOTA: O texto prevê que não conhecemos o código-fonte do desafio. Logo, se você olhar o fonte do keygenme (keygenme.c) ou do keygen proposto (keygen.c) antes de seguir a leitura, tudo perde a graça! Fica tranqüilo que no final dá tudo certo. Aliás, se ainda não deu certo, é porque não chegou ao final. Depois de baixar os caras, pode compilar só o keygenme.c, com a sintaxe básica do gcc: $ gcc -o keygenme keygenme.c O keygenme espera que você passe como argumentos o seu nome e sua chave (esta última você não sabe). Vamos ver se damos sorte: $ ./keygenme Fernando FCKGW-90908-30BCD Chave inválida Claro que eu chutei uma chave qualquer para simular uma tentativa de registro mas não acertei (eu tinha alguma chance de acertar?). Até agora não sabemos nada sobre a chave. Ela pode ser alfanumérica, só numérica, ter ou não hífens, enfim, as possibilidades são infinitas. Desafio é desafio! Vamos abrir o binário no EDB. Aliás, uma das facilidades dele é já colocar automaticamente um breakpoint no entrypoint do binário, ou seja, ao abrir um binário, basta mandar o EDB rodá-lo (tecla F9) e ele parará justamente no início da main(). Após fazer isso, você provavelmente verá algo como: 00000000004006cc: push rbp 00000000004006cd: mov rbp, rsp 00000000004006d0: push rbx 00000000004006d1: sub rsp, 56 00000000004006d5: mov dword ptr [rbp-52], edi 00000000004006d8: mov qword ptr [rbp-64], rsi 00000000004006dc: mov rax, qword ptr [rbp-64] NOTA: Ao compilar o keygenme.c na sua máquina, os endereços vão mudar, mas é só ter isso em mente e seguir tranqüilo, fazendo as adaptações. Aqui você tem que notar 4 coisas: A seta vermelha (que aparece no EDB) indica qual é a próxima instrução a ser executada pelo processador. A sintaxe do assembly é Intel. Eu estou num sistema de 64-bits, por isso os registradores aparecem como rbp, rbx, rsp, rax, etc. Se fosse um sistema de 32-bits, seria ebp, ebx, esp, eax e por aí vai (só trocar o “r” pelo “e”). De um modo geral, toda função (e a main não é uma exceção), começa com um “push rbp” e termina com um “ret”. Não temos tempo para destrinchar linha a linha, mas vamos tratar das linhas mais importates. Aliás, o debugger te ajuda a não precisar de muito conhecimento em assembly para entender o que as linhas fazem. Teclando F8 (Step out), passamos para a próxima instrução. Podemos ir teclando F8 calmamente até chegar nas seguintes linhas: 00000000004006e7: test rax, rax 00000000004006ea: jz 0x00000000004006fc As instruções acima trabalham em conjunto. Em 0x4006e7, o registrador rax é verificado pela instrução test. Se seu valor for zero, a próxima instrução será um salto para 0x4006fc. Se seguirmos dando F8, veremos que este salto vai nos jogar para a linha a seguir: 00000000004006fc: call 0x00000000004006b4 Nela tem a instrução call, que é uma chamada de função. Mais um F8 e pimba, o programa encerra. Ok, e para que eu faço isso? Bem, a questão é perguntar-se: por que o programa encerrou? Porque ele chamou a função 0x4006b4. E por que ele a chamou? Porque o salto em 0x4006ea aconteceu. E por que o salto aconteceu? Porque o teste em 0x4006e7 deu verdadeiro. E porque deu verdadeiro? Porque rax estava zerado. Logo, para que o programa não encerre logo no início de sua execução, é preciso ter algo em rax. Para não alongar muito o texto, eu vou dar a cola: rax precisa apontar para um argumento (char *), do contrário, não dá pra começar a brincadeira né? A execução que acompanhamos foi como se tivéssemos feito: $ ./keygenme Chave inválida E pronto. Não passamos argumentos, então não há o que testar. O programa encerra depois de imprimir a mensagem de erro. Vamos corrigir isso. No EDB, é preciso colocar os argumentos em Options -> Applications Arguments. Coloquei dois argumentos para chamar o binário como: $ ./keygenme Fernando 30303030 Agora sim a gente passa naquele teste teste em 0x4006ea (e também no teste em 0x4006fa, que testa o segundo argumento). Mas é preciso reabrir o arquivo no EDB depois de configurar argumentos. Começando novamente com F8, ao passar por 0x4006ea sem pular, caímos em 0x4006ec. Esta instrução mov copia (pois é, ela não move!) o endereço de memória do primeiro argumento para o registrador rax. 00000000004006ec: mov rax, qword ptr [rbp-64] Ainda bem que agora ele não é nulo. Do contrário, teríamos um belo Segmentation Fault. Por isso do teste antes. O mesmo acontece em 0x400701, só que para o segundo argumento. Agora muita atenção no trecho abaixo: 0000000000400709: mov rax, qword ptr [rax] 000000000040070c: mov rdi, rax 000000000040070f: call 0x0000000000400588 Novamente o primeiro argumento (Fernando) é endereçado em rax. E ao passar da call em 0x40070f, o número 8 é posto em rax. Sabendo que o rax é o registrador geralmente usado tanto para passagem de parâmetro quanto para retorno de função, podemos entender que o endereço da string “Fernando” foi passado para a função 0x400588 e esta retornou 8. Consegue ver alguma relação? O que o número 8 tem a ver com a string “Fernando”? Se quiser confirmar sua suspeita, pode mudar este argumento no EDB, reabrir o keygenme e avaliar o novo número de retorno. Mais abaixo, segue uma tremenda sacanagem: 0000000000400717: cmp dword ptr [rbp-40], 3 000000000040071b: jle 0x0000000000400723 000000000040071d: cmp dword ptr [rbp-40], 20 0000000000400721: jle 0x0000000000400728 0000000000400723: call 0x00000000004006b4 Em 0x400717 o valor 8 (no caso do meu exemplo) é comparado com 3. Na seqüência vemos um salto jle (Jump if Lower or Equals) para 0x400723. E neste endereço, tem uma call pra 0x4006b4. Lembra desta call? Não foi ela quem encerrou o programa da outra vez? Não podemos cair nela. Sorte que 8 é maior que 3. Certo, não saltamos. Agora abaixo: 000000000040071d: cmp dword ptr [rbp-40], 20 0000000000400721: jle 0x0000000000400728 0000000000400723: call 0x00000000004006b4 0000000000400728: mov eax, dword ptr [rbp-40] Outra comparação. Desta vez para ver se o 8 é menor ou igual a 20. Se não for, ele não salta e cai na call maldita novamente, para encerrar o programa. Que conclusões podemos chegar? O programa testa se os dois argumentos existem. Basta que um não exista para que o programa seja encerrado. O 8 visto aqui é o tamanho da string nome (primeiro parâmetro, que no meu exemplo foi “Fernando”). Caso o tamanho da string não esteja entre 4 e 20 caracteres, o programa encerra. Seguindo com F8, chegamos neste bloco: 000000000040075d: movsxd rax, rbx 0000000000400760: add rax, qword ptr [rbp-32] 0000000000400764: movzx eax, byte ptr [rax] 0000000000400767: movsx eax, al 000000000040076a: mov edi, eax 000000000040076c: call 0x00000000004005a8 0000000000400771: test eax, eax 0000000000400773: jnz 0x000000000040077a 0000000000400775: call 0x00000000004006b4 000000000040077a: add ebx, 1 000000000040077d: cmp ebx, dword ptr [rbp-40] 0000000000400780: jl 0x000000000040075d Se você não conhece assembly, pode ser que não esteja claro, mas o debugger com certeza vai te entregar que isso é um loop determinado (um for). Vou deixar essa análise de lado, mas quem não conhece pode olhar o fonte em C depois e tentar identificá-lo aqui. Mais abaixo, outro loop: 0000000000400789: movsxd rax, rbx 000000000040078c: add rax, qword ptr [rbp-32] 0000000000400790: movzx eax, byte ptr [rax] 0000000000400793: movsx eax, al 0000000000400796: add eax, 10 0000000000400799: add dword ptr [rbp-36], eax 000000000040079c: add ebx, 1 000000000040079f: cmp ebx, dword ptr [rbp-40] 00000000004007a2: jl 0x0000000000400789 Esse já é mais simples. Pelo debugger você vai perceber que ele pega o valor ASCII de cada caracter do primeiro parâmetro e soma com 10. E vai somando esses resultados também (em memória, no endereço [rbp-36]). Quando este loop acabar, o endereço [rbp-36] conterá a soma em ASCII de todos os caracteres da string do nome somados, mais o resultado de 10 vezes o número de caracteres da string. Ou seja, se o nome fosse “ABCDE”, teríamos: A -> 65 B -> 66 C -> 67 D -> 68 E -> 69 65+10 + 66+10 + 67+10 + 68+10 + 69+10 = 385 Dá no mesmo que: 65 + 66 + 67 + 68 + 69 + 10 * 5 = 385 E esta é a lógica do programa. Ele pega o nome de usuário inserido, aumenta os valores de cada caracter em 10 unidades e depois os soma. O resultado é a chave para o nome de usuário inserido. Além disso, há as restrições de tamanho de nome e mais algumas que precisam ser implementadas no keygen. Agora é só fazer o keygen (lembrando que propus um no começo do artigo). Se quiser brincar, pode escrever um programa na sua linguagem preferida que receba um nome de usuário de acordo com as regras impostas pelo keygenme e gere uma chave válida para este usuário.
  25. Desde 2013 que estamos trabalhando duro numa nova versão do pev, nosso toolkit para análise de binários PE (Portable Executable), o formato utilizado pelos executáveis (EXE, DLL, OCX, etc) do Windows. O pev é um projeto em que me sinto muito feliz de fazer parte e o principal motivo é que existe algo de muito forte e especial nele: colaboração. Quase 30 pessoas contribuíram com o projeto de alguma forma (código, testes, empacotamento, etc) e hoje ele está presente nos repositórios das principais distribuições Linux, inclusive as focadas em segurança de alguma forma. Outro ponto importante é que ele começou como um projeto de faculdade lá em 2010, junto com outros alunos. Como a faculdade nem laboratório de pesquisa tinha, fizemos o nosso próprio grupo, batizado de Coding 40°, em homenagem ao calor da nossa cidade. O projeto atraiu muitos colaboradores, incluindo um que já commitou mais que eu no repositório e redefiniu, de forma muito profissional, toda a infraestrutura por trás do pev e da libpe. Foi o Jardel Weyrich. Sério, dá pra detectar que um projeto teve sucesso quando aparece alguém que já contribuiu com mais código para ele do que você! Por último, mas não menos importante: o pev é livre. E na boa, até agradeço as centenas de programadores que fizeram analisadores de PE proprietários, porque me motivaram a iniciar o pev. Era preciso um analisador de binários PE de código aberto, que permitisse estudantes e curiosos entender como um PE é parseado, que campos são importantes, por que a análise estática é tão poderosa, etc. Hoje, mesmo com todos os bugs que ainda hão de surgir (fora os que já surgiram e ainda não conseguimos corrigir!). Quero agradecer, de forma permanente, a todos que se envolvem ou se envolveram de alguma forma com o pev: usando, avisando-nos sobre bugs, contribuindo com código, ideias, testes, tudo importa! Obrigado, de coração. Emoções à parte, vamos partir para a análise de binários e falar das novidades do pev 0.80, lançado hoje! Acontece que um binário PE pode esconder muitas informações importantes antes mesmo de ser executado. Por exemplo, com a ferramenta readpe, do pev, você vai descobrir que um executável PE é dividido em cabeçalhos e seções e eles armazenam várias informações interessantes, por exemplo: $ readpe arquivo.exe DOS Header Magic number: 0x5a4d (MZ) Bytes in last page: 144 Pages in file: 3 Relocations: 0 Size of header in paragraphs: 4 Minimum extra paragraphs: 0 Maximum extra paragraphs: 65535 Initial (relative) SS value: 0 Initial SP value: 0xb8 Initial IP value: 0 Initial (relative) CS value: 0 Address of relocation table: 0x40 Overlay number: 0 OEM identifier: 0 OEM information: 0 PE header offset: 0x80 COFF/File header Machine: 0x14c IMAGE_FILE_MACHINE_I386 Number of sections: 3 Date/time stamp: 1425562907 (Thu, 05 Mar 2015 13:41:47 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 Optional/Image header Magic number: 0x10b (PE32) Linker major version: 11 Linker minor version: 0 Size of .text section: 0x2a800 Size of .data section: 0x3400 Size of .bss section: 0 Entrypoint: 0x2c78e Address of .text section: 0x2000 Address of .data section: 0x2e000 ImageBase: 0x400000 Alignment of sections: 0x2000 Alignment factor: 0x200 Major version of required OS: 4 Minor version of required OS: 0 Major version of image: 0 Minor version of image: 0 Major version of subsystem: 4 Minor version of subsystem: 0 Size of image: 0x34000 Size of headers: 0x200 Checksum: 0 Subsystem required: 0x2 (IMAGE_SUBSYSTEM_WINDOWS_GUI) DLL characteristics: 0x8540 DLL characteristics names IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE IMAGE_DLLCHARACTERISTICS_NX_COMPAT IMAGE_DLLCHARACTERISTICS_NO_SEH IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE Size of stack to reserve: 0x100000 Size of stack to commit: 0x1000 Size of heap space to reserve: 0x100000 Size of heap space to commit: 0x1000 Data directories Directory IMAGE_DIRECTORY_ENTRY_IMPORT: 0x2c738 (83 bytes) Directory IMAGE_DIRECTORY_ENTRY_RESOURCE: 0x2e000 (12728 bytes) Directory IMAGE_DIRECTORY_ENTRY_BASERELOC: 0x32000 (12 bytes) Directory IMAGE_DIRECTORY_ENTRY_IAT: 0x2000 (8 bytes) Directory IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR: 0x2008 (72 bytes) Imported functions Library Name: mscoree.dll Functions Function Name: _CorExeMain export directory not found Sections Section Name: .text Virtual Address: 0x2000 Physical Address: 0x2a794 Size: 0x2a800 (174080 bytes) Pointer To Data: 0x200 Relocations: 0 Characteristics: 0x60000020 Characteristic Names IMAGE_SCN_CNT_CODE IMAGE_SCN_MEM_EXECUTE IMAGE_SCN_MEM_READ Section Name: .rsrc Virtual Address: 0x2e000 Physical Address: 0x31b8 Size: 0x3200 (12800 bytes) Pointer To Data: 0x2aa00 Relocations: 0 Characteristics: 0x40000040 Characteristic Names IMAGE_SCN_CNT_INITIALIZED_DATA IMAGE_SCN_MEM_READ Section Name: .reloc Virtual Address: 0x32000 Physical Address: 0xc Size: 0x200 (512 bytes) Pointer To Data: 0x2dc00 Relocations: 0 Characteristics: 0x42000040 Characteristic Names IMAGE_SCN_CNT_INITIALIZED_DATA IMAGE_SCN_MEM_DISCARDABLE IMAGE_SCN_MEM_READ Só olhando para este cabeçalho, já dá pra dizer muita coisa sobre esse binário. Por exemplo, é possível inferir que ele foi compilado em 5 de março de 2015, é um executável de janelas (GUI – Graphical User Interface) e na Import Table dele tem apenas uma entrada, uma função chamada _CorExeMain importada da biblioteca mscoree.dll. Isso já dá a dica que esse é binário foi compilado em .NET. Como eu sei disso? É que eu já vi vários binários em .NET e todos eles importam somente essa função. Não se preocupe, com o tempo você também pega os macetes! Claro que, para estudar os binários de forma eficiente é legal ler também outros tutoriais sobre o assunto. O que mais gosto em Português é O formato PE, da Vovó Vicky, isso mesmo, uma simpática senhora, médica, do Sul do país que adora arquivos PE. ❤️ Voltando ao nosso binário, vamos dar uma olhada nas strings presentes nele com o pestr: $ pestr -so arquivo.exe 0x2d81e .rsrc FileDescription 0x2d840 .rsrc Gynx 0x2d852 .rsrc FileVersion 0x2d86c .rsrc 1.0.0.0 0x2d882 .rsrc InternalName 0x2d89c .rsrc Gynx.exe 0x2d8b6 .rsrc LegalCopyright 0x2d8d4 .rsrc Copyright 0x2d8ea .rsrc 2015 0x2d8fe .rsrc OriginalFilename A lista de strings completa é bem maior, mas eu cortei só para mostrar algumas. Primeira pergunta que pode surgir aqui é: por que fizemos um programa pestr se já existe o programa strings? Bem, você pode notar pela saída que dizemos muito mais que a string (texto) em si. Sabemos em que seção ela está e o offset (posição) no arquivo. Além disso, o pestr imprime strings ASCII e Unicode. No meio das strings tem outra informação de data, que reforça o ano da data de compilação. Tem também o nome original do arquivo (quando o programador compilou). Se você pesquisar no Google pelo nome do arquivo, já vai encontrar muita coisa! Continuando nossa análise, vamos ver com o pescan se tem algo estranho neste arquivo: $ pescan -v ~/dotnet file entropy: 5.252392 (normal) fpu anti-disassembly: no imagebase: normal - 0x400000 entrypoint: normal - va: 0x2c78e - raw: 0x2a98e DOS stub: normal TLS directory: not found timestamp: normal - Thu, 05 Mar 2015 13:41:47 UTC section count: 3 sections section .text: normal section .rsrc: normal section .reloc: small length A baixa entropia sugere que o binário não está comprimido (packed) e tudo parece estar normal. Isso significa que o binário não está protegido, não que seu código não seja malicioso. Agora, já entrando nos novos recursos, que tal saber os hashes deste arquivo? Buscando por eles no Google também é possível encontrar algumas informações. Ao invés de calcular um por um, utilizando vários programas, o pev conta com o pehash, que faz tudo pra você: $ pehash -a arquivo.exe file filepath: /home/user/arquivo.exe md5: 1f4c40ff46297bdc5a595cd574e0db64 sha1: 2bc6fa6558988d628dd4b95d0741405685c1c232 sha256: b823a04f49463e410c9f823baade182eb283ba073b40c6d8cc443a570a9a1df6 ssdeep: 3072:yLYmWbfXGKJy7DFR9KwHS+MASjB5jL0S9Q0VwuUcu:AYmWbfX3y7DFR9KwHS+MAS/js0VhU imphash: f34d5f2d4577ed6d9ceec516c1f5a744 headers header header_name: IMAGE_DOS_HEADER md5: 919f1c12cf0d1cd93d7f1077f13ac374 sha1: 3d43712e5606b4640b85f5f0e25e9db8ed552074 sha256: c46a3fc444808f3b86a7e757e5202d16f8ea9bf1c6aff2cabc593e7d0f2c9ad2 ssdeep: 3:WlWUqt/vllnln:idqP header header_name: IMAGE_COFF_HEADER md5: 15174f39bbe557d104f169053af5c7a2 sha1: 21f9311fa5e1a7c8724c657db39ef2fbb1b896ce sha256: 7430166bd1b2d5c400f2f5fb60c90393b50388b97e877aa7cf0c3057a413a85f ssdeep: 3:HHJl/fkn:HHJlkn header header_name: IMAGE_OPTIONAL_HEADER md5: 3b079fa2a88b6901db907bc47dee2d67 sha1: 21c1c517743170c94d1ade608ff7d2746fd7e3ea sha256: 0fec7657e9361e8262af5872bf3784a7647b6978f3d0e35a419d2f410c8654a0 ssdeep: 3:6FZ//llAlFlllXNB//llrllel/dglPt1l9tllH:pfGwlN sections section section_name: .text md5: 973d11759194c14071aa6963de8f55c7 sha1: 1934e0085c8776e3243bf658e95b8943d4f91bc9 sha256: e68349bfcb04b20c11973ce27770570ebb22c8c7750133d073f15f7ec9eeda38 ssdeep: 3072:XLYmWbfXGKJy7DFR9KwHS+MASjB5jL0S9Q0VwuUc:bYmWbfX3y7DFR9KwHS+MAS/js0VhU section section_name: .rsrc md5: 9d498bee08b1cf075d8aca022f1e16e9 sha1: f938a8c23185b023bb5dd12082013c2a628bc0d3 sha256: eb7454309df90fea2d01c887b0040e8237353919efc06a7081a7abb15091283a ssdeep: 24:3A5t/ug9GXBEiViKZWIDDOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO:weg90VWIDZ6Kp+l4BhDqFWSfbNtm section section_name: .reloc md5: 37ab911460a0b274ffa9f4e388ec933b sha1: 7c9eac3b843c9a7ab8ef08aac5c14e4c51b2c703 sha256: fb50df3b064d126999295b35f1a151f8fbe7e3593e306a9255a534999843f5b2 ssdeep: 3:LlQl:S Achou que teria os hashes do arquivo somente? Pois é, o pev surpreende mesmo! O pehash calcula MD5, SHA1, SHA-256 e ssdeep (uma técnica de fuzzy hash que vale a pena pesquisar sobre!) para cada cabeçalho e seção. Além disso, a partir das funções importadas do binário o pehash calcula o imphash, que já expliquei no artigo Entendendo o imphash. Tanto o imphash quanto o ssdeep podem ser utilizados, de maneiras diferentes, para saber se um arquivo é parecido com outro, se são variantes do mesmo código-fonte, mas neste caso o imphash não ajuda por ser um binário .NET (todos eles importam aquela mesma função única que comentei acima). Também é possível, com o peres (criação do Marcelo Fleury), extrair os recursos e checar, por exemplo, o manifesto em XML dos arquivo: $ peres -x arquivo.exe Save On: resources/icons/2.ico Save On: resources/icons/3.ico Save On: resources/icons/4.ico Save On: resources/icons/5.ico Save On: resources/icons/6.ico Save On: resources/icons/7.ico Save On: resources/icons/8.ico Save On: resources/groupicons/32512.ico Save On: resources/versions/1.rc Save On: resources/manifests/1.xml $ cat resources/manifests/1.xml <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <assemblyIdentity version="1.0.0.0" name="MyApplication.app"/> <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2"> <security> <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3"> <requestedExecutionLevel level="asInvoker" uiAccess="false"/> </requestedPrivileges> </security> </trustInfo> </assembly> O ícone utilizado também é extraído (groupicons) mas ele é quebrado em várias imagens (icons) e precisam ser reconstruídos se você precisar do ícone original do arquivo. De qualquer forma, você pode calcular o hash deles e comparar com os dos ícones de outros arquivos, se for essa a intenção. Uma outra novidade é o programa cpload, para analisar arquivos .cpl (Control Panel Applets). Ele é presente somente na versão do pev para Windows, pois serve para analisar binários dinamicamente. Este formato é um pouco diferente do EXE convencional (na verdade um CPL é uma “DLL que executa com dois cliques”). Na época o formato desses arquivos era muito pouco conhecido e até escrevi um artigo para a Wikipédia em Português sobre. Com o cpload, é possível enviar para a função presente no arquivo .cpl todas as mensagens que ela deve reconhecer. Assim o analista pode ver qual delas dispara o código. No exemplo abaixo, a mensagem CPL_DBLCLK (clique duplo) foi enviada para um .cpl presente no Windows chamado joy.cpl. Veja que o programa executa e mostra uma janela: Adicionalmente, se você fizer isso no contexto de um debugger, passando os argumentos como na imagem abaixo: O cpload força um software breakpoint antes do carregamento do arquivo .cpl pela função LoadLibrary() e antes da entrada na CPLApplet(), que é a função principal de um arquivo .cpl, o que permite ao analista enxergar o trecho exato de código dele, sem precisar debugar processos do sistema como o rundll32.exe. E ainda sobre o pev, os plugins de formato de saída estão dando um show (ideia do Jan Seidl e portada para plugins pelo Jardel). Olha só como fica a saída se você pedir ao pedis, nosso disassembler, para disassemblar o entrypoint de um binário PE em JSON: $ pedis --format json --entrypoint PUTTY.EXE { "54eb0": "6a 60 push 0x60", "54eb2": "68 70 7b 47 00 push 0x477b70", "54eb7": "e8 08 21 00 00 call 0x456fc4", "54ebc": "bf 94 00 00 00 mov edi, 0x94", "54ec1": "8b c7 mov eax, edi", "54ec3": "e8 b8 fa ff ff call 0x454980", "54ec8": "89 65 e8 mov [ebp-0x18], esp", "54ecb": "8b f4 mov esi, esp", "54ecd": "89 3e mov [esi], edi", "54ecf": "56 push esi", "54ed0": "ff 15 e0 d2 45 00 call dword [0x45d2e0]", "54ed6": "8b 4e 10 mov ecx, [esi+0x10]", "54ed9": "89 0d 48 e1 47 00 mov [0x47e148], ecx", "54edf": "8b 46 04 mov eax, [esi+0x4]", "54ee2": "a3 54 e1 47 00 mov [0x47e154], eax", "54ee7": "8b 56 08 mov edx, [esi+0x8]", "54eea": "89 15 58 e1 47 00 mov [0x47e158], edx", "54ef0": "8b 76 0c mov esi, [esi+0xc]", "54ef3": "81 e6 ff 7f 00 00 and esi, 0x7fff", "54ef9": "89 35 4c e1 47 00 mov [0x47e14c], esi", "54eff": "83 f9 02 cmp ecx, 0x2", "54f02": "74 0c jz 0x454f10", "54f04": "81 ce 00 80 00 00 or esi, 0x8000", "54f0a": "89 35 4c e1 47 00 mov [0x47e14c], esi", "54f10": "c1 e0 08 shl eax, 0x8", "54f13": "03 c2 add eax, edx", "54f15": "a3 50 e1 47 00 mov [0x47e150], eax", "54f1a": "33 f6 xor esi, esi", "54f1c": "56 push esi", "54f1d": "8b 3d d8 d2 45 00 mov edi, [0x45d2d8]", "54f23": "ff d7 call edi0x454f25", "54f25": "66 81 38 4d 5a cmp word [eax], 0x5a4d", "54f2a": "75 1f jnz 0x454f4b", "54f2c": "8b 48 3c mov ecx, [eax+0x3c]", "54f2f": "03 c8 add ecx, eax", "54f31": "81 39 50 45 00 00 cmp dword [ecx], 0x4550", "54f37": "75 12 jnz 0x454f4b", "54f39": "0f b7 41 18 movzx eax, word [ecx+0x18]", "54f3d": "3d 0b 01 00 00 cmp eax, 0x10b", "54f42": "74 1f jz 0x454f63", "54f44": "3d 0b 02 00 00 cmp eax, 0x20b", "54f49": "74 05 jz 0x454f50", "54f4b": "89 75 e4 mov [ebp-0x1c], esi", "54f4e": "eb 27 jmp 0x454f77", "54f50": "83 b9 84 00 00 00 0e cmp dword [ecx+0x84], 0xe", "54f57": "76 f2 jbe 0x45504b", "54f59": "33 c0 xor eax, eax", "54f5b": "39 b1 f8 00 00 00 cmp [ecx+0xf8], esi", "54f61": "eb 0e jmp 0x454f71", "54f63": "83 79 74 0e cmp dword [ecx+0x74], 0xe", "54f67": "76 e2 jbe 0x45504b", "54f69": "33 c0 xor eax, eax", "54f6b": "39 b1 e8 00 00 00 cmp [ecx+0xe8], esi", "54f71": "0f 95 c0 setnz al", "54f74": "89 45 e4 mov [ebp-0x1c], eax", "54f77": "56 push esi", "54f78": "e8 b4 28 00 00 call 0x457831", "54f7d": "59 pop ecx", "54f7e": "85 c0 test eax, eax", "54f80": "75 21 jnz 0x454fa3", "54f82": "83 3d 94 e1 47 00 01 cmp dword [0x47e194], 0x1", "54f89": "75 05 jnz 0x454f90", "54f8b": "e8 56 44 00 00 call 0x4593e6", "54f90": "6a 1c push 0x1c", "54f92": "e8 d8 42 00 00 call 0x45926f", "54f97": "68 ff 00 00 00 push 0xff", "54f9c": "e8 be fb ff ff call 0x454b5f", "54fa1": "59 pop ecx", "54fa2": "59 pop ecx", "54fa3": "e8 65 47 00 00 call 0x45970d", "54fa8": "89 75 fc mov [ebp-0x4], esi", "54fab": "e8 e9 25 00 00 call 0x457599", "54fb0": "85 c0 test eax, eax", "54fb2": "7d 08 jge 0x454fbc", "54fb4": "6a 1b push 0x1b", "54fb6": "e8 d0 fe ff ff call 0x454e8b", "54fbb": "59 pop ecx", "54fbc": "ff 15 a8 d1 45 00 call dword [0x45d1a8]", "54fc2": "a3 00 e9 47 00 mov [0x47e900], eax", "54fc7": "e8 af 4d 00 00 call 0x459d7b", "54fcc": "a3 8c e1 47 00 mov [0x47e18c], eax", "54fd1": "e8 03 4d 00 00 call 0x459cd9", "54fd6": "85 c0 test eax, eax", "54fd8": "7d 08 jge 0x454fe2", "54fda": "6a 08 push 0x8", "54fdc": "e8 aa fe ff ff call 0x454e8b", "54fe1": "59 pop ecx", "54fe2": "e8 bf 4a 00 00 call 0x459aa6", "54fe7": "85 c0 test eax, eax", "54fe9": "7d 08 jge 0x454ff3", "54feb": "6a 09 push 0x9", "54fed": "e8 99 fe ff ff call 0x454e8b", "54ff2": "59 pop ecx", "54ff3": "6a 01 push 0x1", "54ff5": "e8 95 fb ff ff call 0x454b8f", "54ffa": "59 pop ecx", "54ffb": "89 45 d8 mov [ebp-0x28], eax", "54ffe": "3b c6 cmp eax, esi", "55000": "74 07 jz 0x455009", "55002": "50 push eax", "55003": "e8 83 fe ff ff call 0x454e8b", "55008": "59 pop ecx", "55009": "89 75 bc mov [ebp-0x44], esi", "5500c": "8d 45 90 lea eax, [ebp-0x70]", "5500f": "50 push eax", "55010": "ff 15 ac d1 45 00 call dword [0x45d1ac]", "55016": "e8 2e 4a 00 00 call 0x459a49", "5501b": "89 45 e0 mov [ebp-0x20], eax", "5501e": "f6 45 bc 01 test byte [ebp-0x44], 0x1", "55022": "74 06 jz 0x45502a", "55024": "0f b7 45 c0 movzx eax, word [ebp-0x40]", "55028": "eb 03 jmp 0x45502d", "5502a": "6a 0a push 0xa", "5502c": "58 pop eax", "5502d": "50 push eax", "5502e": "ff 75 e0 push dword [ebp-0x20]", "55031": "56 push esi", "55032": "56 push esi", "55033": "ff d7 call edi0x455035", "55035": "50 push eax", "55036": "e8 93 3b ff ff call 0x448bce", "5503b": "8b f8 mov edi, eax", "5503d": "89 7d d4 mov [ebp-0x2c], edi", "55040": "39 75 e4 cmp [ebp-0x1c], esi", "55043": "75 06 jnz 0x45504b", "55045": "57 push edi", "55046": "e8 6f fc ff ff call 0x454cba", "5504b": "e8 8c fc ff ff call 0x454cdc", "55050": "eb 2b jmp 0x45507d", "55052": "8b 45 ec mov eax, [ebp-0x14]", "55055": "8b 08 mov ecx, [eax]", "55057": "8b 09 mov ecx, [ecx]", "55059": "89 4d dc mov [ebp-0x24], ecx", "5505c": "50 push eax", "5505d": "51 push ecx", "5505e": "e8 75 48 00 00 call 0x4598d8", "55063": "59 pop ecx", "55064": "59 pop ecx", "55065": "c3 ret" Os formatos de saída suportados por todos os programas do toolkit incluem JSON, XML, CSV e o padrão, texto. Assim o pev se faz útil para quem quer automatizar o processo de análise estática de binários PE, para salvar em um banco de dados para consulta posterior, por exemplo. A documentação para a versão 0.80 está pronta e recomendo fortemente que você a leia se quiser aproveitar o máximo do toolkit. O changelog completo também está disponível. Despeço-me com a certeza de que fizemos um bom trabalho e de que o software livre é viável, funciona e é, principalmente, algo prazeroso de se manter, que pode ajudar pessoas e empresas a atingirem seus objetivos, sem custo. Confere lá: https://github.com/merces/pev
×
×
  • Create New...