Um breakpoint nada mais é que um ponto no código onde o debugger vai parar para que você analise o que precisa. É o mesmo conceito dos breakpoints presentes em ambientes de desenvolvimento como Visual Studio, NetBeans ou CodeBlocks. A diferença é que nestes ambientes colocamos breakpoints em determinadas linhas do código-fonte. Já nos debuggers destinados à engenharia reversa, colocamos breakpoints em endereços (VAs), onde normalmente há instruções.
Há várias maneiras de se colocar um breakpoint em um endereço utilizando o x64dbg. Você pode selecionar a instrução e pressionar F2, usar um dos comandos SetBPX/bp/bpx, dar um duplo clique sobre os bytes da instrução (coluna 3 no disassembly) ou simplesmente clicar na bolinha cinza à esquerda do endereço (coluna 1 do disassembly). Ao fazê-lo, este ficará com um fundo vermelho, como mostra a imagem:
Um segundo clique na bolinha desabilita o breakpoint, mas não o exclui da aba Breakpoints (Alt+B). Um terceiro clique o exclui totalmente.
Após colocar o breakpoint nesta CALL, rode o programa (F9). O que acontece? O debugger executa todas as instruções anteriores a este breakpoint e pára onde você pediu. Simples assim.
Talvez você tenha notado que ao atingir um breakpoint, o x64dbg mostra na barra de status a palavra “Paused” e a frase INT3 breakpoint at analyzeme00.0000000140001019!. Este é um tipo de breakpoint de software. Para entender como ele funciona, acompanhe a explicação a seguir.
A instrução INT é uma instrução Assembly que gera uma interrupção. A interrupção número 3 é chamada de Breakpoint Exception (#BP) no manual da Intel. Seu opcode (0xcc) tem somente um byte, o que facilita sua implementação nos debuggers.
De forma resumida, para parar nesta CALL, o que o x64dbg faz é:
Substituir o primeiro byte do opcode da CALL (0xff, neste caso) por 0xcc e salvar o original numa memória.
Rodar o programa.
Restaurar o primeiro byte do opcode da CALL, substituindo o 0xcc por 0xff (neste caso).
Isso poderia ser feito manualmente, mas os debuggers facilitam o trabalho, bastando você pressionar F2 ou clicar na bolinha para que todo este trabalho seja executado em segundo plano, sem que o usuário perceba. Incrível, não é?
Você pode adicionar quantos breakpoints de software quiser numa sessão de debugging. Todos ficam acessíveis na aba Breakpoints, a não ser que você os exclua. Veja como eles ficam organizados:
Você também pode assistir a , que trata sobre este assunto.
Existem ainda os breakpoints de memória e de hardware, mas não trataremos deles neste livro.


Ao observar a região que chamamos de disassembly, você verá cinco colunas. A primeira exibe algumas informações e relações entre endereços. A segunda mostra os endereços em si. A terceira mostra os bytes do opcode e dos operandos das instruções. A quarta mostra os mnemônicos onde podemos ler Assembly e, por fim, a quinta mostra comentários, sejam estes gerados automaticamente pelo debugger ou adicionados por você.
Perceba que por padrão o debugger já realça (highlight em inglês) vários aspectos das instruções na janela de disassembly. As instruções CALL estão com fundo azul e os destinos das chamadas com fundo amarelo quando o endereço é conhecido. Os saltos também são realçados com fundo amarelo. Dessa forma é possível identificar rapidamente as instruções que alteram o fluxo do execução do programa.
O endereço para o qual o ponteiro de instrução (RIP) aponta é destacado com um fundo preto.
Na coluna de comentários, temos os comentários automáticos em marrom.
É importante lembrar que o conteúdo do arquivo sendo depurado é composto somente pelos bytes referentes às instruções (terceira coluna) e dados. Toda essa análise adicional é feita pelo debugger para que a nossa experiência seja mais agradável ao depurar o programa.
Neste primeiro momento, o debugger está parado e a próxima instrução a ser executada é justamente o que chamamos de EP (EntryPoint).
O primeiro comando que aprenderemos é o Step over, que pode ser ativado de pelo menos quatro maneiras:
Menu Debug ► Step over.
Botão Step over na barra de botões (por padrão, é o sétimo botão).
Tecla de atalho F8.
Se você emitir este comando uma vez, verá que o debugger vai executar uma única instrução e parar. Na janela do disassembly, você vai perceber que o cursor (RIP) "pulou uma linha" e a instrução anterior foi executada. No caso de nosso binário de teste, é a instrução SUB RSP, 98. Após sua execução, perceba que o valor de RSP foi atualizado. Os valores que mudaram são destacados em vermelho pelo x64dbg.
Você pode seguir teclando F8 até alcançar a primeira instrução CALL, destacada por um fundo azul claro.
O comando Step over sobre uma CALL faz com que o debugger execute a rotina apontada pela instrução e "volte" para o endereço imediatamente após a CALL. Você não verá essa execução, pois o debugger não a instrumentará, mas ela acontece. Caso queira observar o que foi executado "dentro" da CALL, é necessário utilizar o Step into (F7). Vamos fazer dois testes:
Com o RIP apontado para a CALL em 140001019, tecle F8. Você verá que a execução simplesmente "passa para a linha abaixo da CALL". Isso quer dizer que ela foi executada, mas você não "viu" essa execução no debugger.
Agora reinicie o programa no debugger clicando em Debug ► Restart. Depois, vá teclando F8 até chegar sobre a CALL novamente e tecle F7, que é o Step into. Perceba que o debugger agora "entrou" na CALL. Não se preocupe em analisar essa função. Ela pertence à API do Windows e seu funcionamento é conhecido. A ideia aqui é você diferenciar o Step over do Step into. Pode reiniciar o programa novamente.
Outro comando importante é o Run (F9). Ele simplesmente inicia a execução a partir do RIP de todas as instruções subsequentes e só para se encontrar um breakpoint (nosso próximo assunto), uma exceção ou se o programa for encerrado. Se você der este comando, verá a execução terminada em menos de um segundo, o que significa que o programa rodou até o final e saiu. Aí basta reiniciar o programa (Ctrl + F2) para recomeçar nossos estudos. ;)
Na próxima seção, vamos entender os pontos de paradas, mais conhecidos como breakpoints.
Na sua máquina Windows, baixe o snapshot mais recente do x64dbg. É um arquivo .zip chamado snapshot_YYYY-MM-DD_HH-MM.zip que vai variar dependendo da data e hora do release (quando o software é liberado) pelos autores do projeto.
Ao descompactar o arquivo .zip, execute o arquivo x96dbg.exe dentro do diretório release. Esse nome deve-se ao fato de que o x64dbg tem suporte tanto a 64 quanto a 32-bits, então o autor resolveu somar 32+64 e nomear o binário assim.
O x96dbg.exe é o launcher do x64dbg e tem três botões. Escolha Install e responda "Sim" para todas as perguntas.
À esta altura você já deve ter o atalho x64dbg na sua área de trabalho. Dê um duplo-clique nele e você verá a tela inicial do debugger.
Se o x64dbg estiver em português, mude o idioma para inglês. Isso é necessário, pois vários termos em inglês não estão traduzidos e isso pode dificultar o aprendizado. Para mudar o idioma, vá em Opções ► Idiomas e escolha [en_US] American English - United States. Você precisará fechar o x64dbg e abri-lo novamente para que o novo idioma entre em vigor. Depois que o software estiver em inglês, siga com a configuração:
Vá em Options ► Preferences e, na aba Events, desmarque a caixa System Breakpoint. Isso vai fazer com que o debugger pare direto no entrypoint de um programa ao abrirmos.
Clique na aba Engine e marque a caixa Disable ASLR, que desabilita a randomização de endereços de memória.
Clique em Save.
Existem muitas outras opções de configuração que você pode experimentar. Para o momento, essas bastam.
Na tela inicial do x64dbg, clique em File ► Open. Localize o arquivo analyseme00.exe e clique em em Open. Você deverá ver uma tela como esta:
A aba CPU, realçada em preto, é sem dúvida a mais utilizada no processo de debugging, por isso, fizemos questão de nomear algumas de suas áreas, que descreveremos agora.
Nesta região são exibidos os endereços (VAs), opcodes e argumentos de cada instrução, seu disassembly (ou seja, o que os bytes significam em Assembly) e alguns comentários na quarta coluna, que podem ser automáticos (gerados pelo x64dbg ou por plugins) ou inseridos por você. No endereço inicial, por exemplo, há o texto OptionalHeader.AddressOfEntryPoint na quarta coluna, que nos diz que aquela instrução é a primeira executada pelo programa. Dizemos também que ela é o entrypoint do programa.
Tomei a liberdade de nomear essa seção de Helper, porque de fato ela ajuda. Por exemplo, quando alguma instrução faz referência a um dado em memória ou em um registrador, ela já mostra que dado é este. Assim você não precisa ir buscar. É basicamente um economizador de tempo. Para entender melhor, suponha que o debugger esteja parado numa instrução que esteja lendo de [rsp+20]. No Helper, aparecerá o valor que está na posição de memória RSP+20, assim você não precisa ir até lá manualmente para ver tal valor.
O dump é um visualizador que você pode usar para inspecionar bytes em qualquer endereço. Por exemplo, você pode ir até o endereço RSP+20 e ver o que tem lá.
Há cinco abas de dump, onde cada uma pode mostrar o conteúdo de uma região de memória diferente para o mesmo alvo. Há ainda as abas Watch, Locals e Struct, que fogem do escopo deste livro, mas também são ferramentas de inspeção.
Como o nome sugere, nesta região são mostrados os valores de cada registrador do processador, incluindo o do registrador de flags. Na verdade, o x64dbg vai um pouco além e mostra também variáveis globais úteis como LastError e LastStatus, ambas modificadas por chamadas à algumas funções da API do Windows.
Nesta janela é possível configurar a convenção de chamada com a qual estamos trabalhando e o número de argumentos que você quer ver em cada chamada de função.
Mostra a pilha de memória, onde o endereço com fundo em preto indica o topo da pilha, ou seja, o endereço que está em RSP.
Na próxima seção, iremos depurar o binário de exemplo e devemos nos atentar às informações exibidas em cada uma das regiões da tela do debugger, acima apresentadas.
Agora que já sabemos o básico do debugging e sabemos colocar breakpoints de software, vamos começar a manipular o programa da maneira que queremos.
Em geral, quando falamos de manipulação, falamos de alguma alteração no código do programa, para que este execute o que queremos, da forma como queremos.
Tomemos como exemplo o AnalyseMe-00 mesmo. Um bom início para a engenharia reversa é a busca por chamadas intermodulares (o botão com um celular e uma seta azul). Ao clicar nele, você encontrará chamadas às funções DeleteFileA(), FatalExit(), GetEnvironmentVariableA() e lstrcat(), todas da KERNEL32.DLL. Colocaremos um breakpoint em todas as chamadas à estas funções, bastando para isso dar um clique com o botão direito do mouse em uma delas e escolher a opção "Set breakpoint on all calls to DeleteFileA", como sugere a imagem abaixo:
Ao voltar à aba CPU e rodar o programa (F9), paramos aqui:
Esta função é bem simples. No endereço 00401105 há um PUSH que coloca o endereço 402020 na pilha, depois há a chamada da DeleteFileA em si.
O x64dbg já resolve a referência do endereço e, caso encontre uma string, exibe ao lado (na quarta coluna), como acontece com a string "C:\Windows\System32\cmd.exe". Ora, se este é o argumento passado para a função DeleteFile(), este é o caminho do arquivo que o programa AnalyseMe-00 pretende deletar.
O que a gente vai fazer é mudar esta string, mudando assim o programa que o AnalyseMe-00 tenta deletar. Para isso, clique com botão direito do mouse sobre a instrução PUSH e escolha "Follow in Dump -> 402020".
O endereço em questão é exibido no Dump 1. Outra opção seria ir no Dump 1, teclar Ctrl+G, digitar 402020 e clicar em OK.
Para alterar a string, você vai precisar selecionar todos os bytes desejados, pois o x64dbg não sabe exatamente onde começa e onde termina cada bloco de dados usado pelo programa. Supondo que queiramos alterar "cmd.exe" para "calc.exe", fazendo assim com que o programa tente excluir a calculadora do Windows. Para este caso, selecionamos o trecho e pressionamos Ctrl+E, que é o equivalente ao clicar com o botão direito sobre a seleção e escolher "Binary -> Edit".
Após fazer a alteração e clicar em OK, perceba que o Dump 1 agora destaca os bytes alterados em vermelho:
Ao seguir com a execução da chamada à DeleteFileA (F8), o programa tenta excluir o calc.exe ao invés de o cmd.exe. No entanto, como em versões modernas do Windows o conteúdo deste diretório é protegido, a função retorna zero (perceba o registrador EAX zerado), que no caso desta função, indica que houve falha, e as variáveis LastError e LastStatus são modificadas para refletir o que aconteceu.
Espero que com esta seção você entenda que, tendo o programa sob o controle de um debugger, é possível modificar praticamente tudo o que queremos. Podemos impedir que funções sejam chamadas, podemos chamar novas funções, alterar dados, modificar parâmetros, enfim, a lista é quase infinita. Na próxima seção vamos ver como salvar as alterações feitas.
Um patch é qualquer alteração, seja nos dados ou no código de um programa. O que fizemos na seção anterior é justamente isto. No entanto, não salvamos nossas modificações e neste caso elas serão perdidas caso você feche o x64dbg.
É possível salvar as alterações acessando o menu View ► Patch file, clicando no botão com um pequeno curativo (tipo um band-aid) na barra de ferramentas ou pressionando Ctrl+P.
Se você veio da seção anterior e tem ainda as modificações no AnalyseMe-00, sua tela de patches aparecerá assim:
A partir desta tela, é possível exportá-los para um arquivo (Export), mas também criar um novo arquivo executável com as alterações salvas (Patch File).
Perceba que você pode fazer qualquer tipo de patch, desde uma simples alteração da lógica de um salto até inserir de fato novas funções num programa. Pode dar trabalho, mas é possível.
E assim, chegamos ao fim desta primeira etapa de estudo dos fundamentos de engenharia reversa de software. Espero que tenha curtido a jornada e aproveitado o conteúdo. Ainda há um longo caminho pela frente, mas tenho certeza que agora será mais fácil entender conceitos mais profundos de Assembly x86, iniciar estudos de outras arquiteturas como x86-64 e ARM, de conceitos mais avançados no Windows ou mesmo de engenharia reversa em outras plataformas como Linux e macOS.
Se este livro te ajudou, considere nos apoiar em https://menteb.in/apoie, pois a Mente Binária depende de doações e venda de cursos para sustentar sua operação e continuar entregando conteúdo de qualidade para milhares de pessoas. Muito obrigado por chegar até aqui! Sucesso!
StepOver, step, sto, ou st.
Chegamos no capítulo onde a engenharia reversa de fato começa. Aqui vamos estudar a depuração, ou debugging em inglês. O conceito, como o nome em sugere, é buscar identificar erros (bugs) num programa, a fim de corrigi-los. No entanto, os debuggers - como são chamados os softwares que servem a este fim - servem para muito mais que isso.
Neste livro usaremos o x64dbg. Ele é um debugger gratuito, de código aberto e frequentemente atualizado para Windows.
Na próxima seção apresentaremos como baixar e configurar o x64dbg. Também utilizaremos um binário de exemplo durante o livro, que é um desafio disponível em . Com ele estudaremos os conceitos de engenharia reversa que precisamos para criar um fundamento sólido para avançar nesta área.





