Engenharia Reversa Primeiro Contato - Parte 2
Tags: braziliandevs, beginners, tutorial
Published: 2024-06-08 02:29:20

## DIABLO - ORiON_CrackMe1 +18

O tutorial é dedicado aos iniciantes em engenharia reversa, mas eu tentarei explicar, o máximo possível, alguns processos do Assembly e da arquitetura Intel x86, para que, até mesmo pessoas que nunca estudaram nada disso, possam acompanhar, e -se eu não falhar na clareza- entender os processos. 
E aí, quem sabe, você não crie interesse por está área de estudos.

Caso não saiba usar o Debugger x64dbg, segue uma breve introdução: https://dev.to/ryan_gozlyngg/engenharia-reversa-primeiro-contato-parte-1-2gih

Esse tutorial consiste na quebra de um programa feito para isso.
É de nível extremamente básico, feito, justamente, para iniciantes na área da engenharia reversa.
"Quebra de software" quer dizer: fazer o programa se submeter à sua vontade!

Algumas notas estão presentes para auxiliar no entendimento de coisas que eu julgue que devem ser de conhecimento do leitor.

---
## Lista de Conteúdo

[Requisitos para total entendimento do tutorial a seguir](#Requisitos-para-total-entendimento-do-tutorial-a-seguir)

[Termos utilizados durante o tutorial](#Termos-utilizados-durante-o-tutorial)

[Link para o programa CrackMe](#Link-para-o-programa-CrackMe)

[Análise Estática](#Analise-Estatica)
	[Examinando as strings do programa](#Examinando-as-strings-do-programa)

[Análise Dinâmica](#Análise-Dinâmica)

[Como o Code (input) é processado](#Como-o-Code-(input)-é-processado)

[TEST CL, CL](#TEST-CL-CL)

[Registrador EFLAGS](#Registrador-EFLAGS)

[CL](#CL)

[O Processo de Tratamento do Input](#O-Processo-de-Tratamento-do-Input)

[Vamos rastrear a origem desse valor no topo da stack/pilha](#Vamos-rastrear-a-origem-desse-valor-no-topo-da-stack-ilha)

[Observando a stack](#Observando-a-stack)

[Retorno de Função](#Retorno-de-Função)

[Observando a Função crackme1.46B828](#Observando-a-Função-crackme1-46B828)

[Encontrando valores no Dump](#Encontrando-valores-no-Dump)

[Entrando na Função crackme1.46B828](Entrando-na-Função-crackme1-46B828)

[Entendendo como chegamos ao resultado](#Entendendo-como-chegamos-ao-resultado)

[Caso o salto não seja executado, as seguintes instruções são usadas](#Caso-o-salto-não-seja-executado-as-seguintes-instruções-são-usadas)

[Resumindo a lógica da função](#Resumindo-a-lógica-da-função)

[Saindo da função e obtendo o resultado definitivo](#Saindo-da-função-e-obtendo-o-resultado-definitivo)

[SETNE - Instrução que gera o resultado](#SETNE-Instrução-que-gera-o-resultado)

[Considerações Finais](#Considerações-Finais)

---
### Requisitos para total entendimento do tutorial a seguir

* Conhecimento básico sobre segmentos de memória, principalmente sobre o segmento chamado stack/pilha
* Conhecimento sobre bases numéricas
* Conhecimento básico de programação em C/C++

Aqui eu espero poder mostrar a quebra desse software na forma de tutorial, usando partes do processo para compartilhar alguns conhecimentos úteis com aquele leitor que está iniciando, e ainda tem alguma dúvida, ou dificuldade, em entender algo específico.
Também é dedicado aos que desejam ver como esse tipo de coisa é feita.

No mais, é sempre bom ver como outras pessoas solucionam problemas, como a mente delas funciona em determinadas atividades, para, talvez, extrairmos alguma coisa para nós mesmos.
#### Se você já iniciou os estudos em engenharia reversa, tente quebrar esse software por sua conta primeiro, depois volte aqui para ler.

___
### Termos utilizados durante o tutorial

- **Code, CODE ou code**: nome da área de input; é o mesmo que "serial", "key", "senha" ou input. Nesse software, os desenvolvedores chamaram a área de input de **Code**.
- **Breakpoint**: ponto de interrupção, ponto de parada.
- **Setar**: ação de aplicar algo sobre alguma coisa: exemplo: "setar breakpoint" significa: aplicar, definir, introduzir um breakpoint em ou sobre determinada instrução.
- **Binário**: refere-se a um programa, um executável.

___
### Link para o programa CrackMe

* Baixe aqui: https://github.com/ReversingID/Crackmes-Repository/tree/master
* Encontre ele no path: ***Crackmes.DE\\1-very_easy_for_newbies\\windows\\diablo***
* Nota: Usar ou não uma máquina virtual dedicada para rodar o software está a seu critério.

_______________________________________________________________________
### Análise Estática

Em resumo, "análise estática" é uma análise feita apenas com as informações contidas no próprio arquivo do programa, também chamado **binário**, obtidas independentemente da execução do programa.

Para saber em qual arquitetura o programa foi compilado, você pode usar o programa DIE - Detect It Easy:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ad6wugkvpk7wshpgqjqf.PNG)

O DIE é um programa muito poderoso, por hora, vamos ficar somente com essas informações da interface inicial. Recomendo que você se aprofunde no uso do DIE.

Na caixa de baixo, com grande destaque em vermelho, podemos ver algumas informações interessantes. "PE32" indica que o programa é um executável de 32 bits. Nas caixas menores, marcadas em vermelho, podemos ver também, o **modo** e a **arquitetura**.

---
### Examinando as strings do programa

Você pode ver as strings contidas dentro do programa, usando o software que preferir (inclusive com DIE, clicando no botão "Strings", na interface inicial): 


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4myv2j7j01u64dsizd5a.PNG)

Não tem como examinarmos as strings, uma por uma, já que **3,750** strings depois, alguma coisa pode acabar passando batido...

As strings mais interessantes são as que o **programador** criou. Vamos dar uma olhada no local em que as variáveis globais e estáticas, inicializadas, são guardadas: ***data segment***, ou ***.data***.

Eu utilizei o programa ***IDA*** para acessar a seção de dados (.data) mencionada.
O **IDA** é um programa utilizado para análise estática de binários, ele é muito poderoso, possui várias funcionalidades, não vou falar muito sobre ele aqui, recomendo procurar mais sobre.

O IDA possui uma versão gratuita: https://hex-rays.com/ida-free/
Caso você nunca tenha usado o IDA, e mesmo assim baixou para testar aí, saiba que ele nos mostra várias mensagens ao carregarmos o binário nele, por hora não se preocupe, dê "ok" em tudo até chegar na tela principal. Caso queira se aprofundar, recomendo começar com alguma das muitas playlists de introdução ao IDA, encontradas no Youtube.

* No IDA: **SHIFT+F7** -> clique duas vezes em ***.data*** -> clique na aba **Hex view** -> vá descendo e observando a coluna lateral:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f8nu7qyxhkm4lxgxaofc.PNG)

Estamos vendo a seção de dados dentro do programa, em hexadecimal, e a sua respectiva representação em ASCII, na coluna da direita (circulada em vermelho).

Dica: Aqui podemos separar as strings em um bloco de notas, e ir testando uma a uma no input do programa, já que é a coisa mais fácil a se fazer.

Se você fez isso, então já sabe a resposta.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l8g66ojllfozkw0gd7gj.gif)

___
### Análise Dinâmica

A Análise Dinâmica consiste em observar e analisar o programa durante a execução.

Eu usei o debugger **x64dbg** aqui, e como de costume, a primeira coisa que faço é procurar pelas referências às strings.
Encontrando a string correta (nesse caso "Wrong Code! Try Again!") eu consigo observar as instruções que ocorrem antes dela ser usada na caixa de diálogo.
Seguindo essa string, eu chego ao momento em que ela é usada, e a partir desse local, eu posso buscar entender qual a lógica por trás da tomada de decisão, que julga se o nosso Input está correto ou não.

Abra o programa no x64dbg (não esqueça que o programa é de 32 bits, e precisa ser aberto no x32dbg.exe) e rode (apertando **F9**) o programa até ele abrir, e a janela com o input aparecer para você.
Geralmente o x64dbg tem alguns breakpoints iniciais, que só funcionam na primeira vez que o programa é rodado, então se o **EIP** -Instruction Pointer (ponteiro de instrução), responsável por dizer em qual instrução você está no momento (a setinha verde na lateral esquerda te mostra ele), estiver parado, continue clicando em **F9** para rodar o programa até ele ser completamente carregado, e aí ele estará disponível para você usar (observe a barra de tarefas do Windows).
	
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sjo0q4gz70jveplrwojs.PNG)


Com o programa já rodando, dentro do x64dbg:

1. Clique com o botão direito do mouse, no meio do debugger, na janela CPU
2. Vá em: "Pesquisar por" -> "All User Modules" -> "Referências String"

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/10htsj6w5j8pskd35km6.PNG)
Você será direcionado para está janela. A anterior está ali em cima, em **CPU**.

Aqui vemos algumas strings que chamam atenção...
Se começamos por esse método, fazemos como anteriormente:
 - Pegamos as strings "diferenciadas", e as testamos como input, uma a uma.

Você já deve saber que isso vai dar certo, mas por questão de curiosidade, se isso não tivesse funcionado, prosseguiríamos da seguinte maneira:

* Clique duas vezes na string "**Wrong Code! Try Again!**"

Você será direcionado para a instrução (onde ela é referida) que carrega ela na função (provavelmente a **main**), dentro da janela **CPU**.

* Sete um breakpoint nela, clicando em **F2**
* Abra a janela do programa e insira um code/input diferente do esperado, um errado qualquer.
* Clicando em "Ok" o programa irá rodar até parar em seu breakpoint setado.
* Agora vamos procurar pelo que decidiu que nossa resposta está errada, em outras palavras, procuramos entender como viemos parar na mensagem de erro.

Vamos fazer a engenharia reversa do programa, até descobrirmos o critério de "resposta certa" e "resposta errada".

---
### Como o Code (input) é processado

Vamos rever os passos até aqui:\*
	1. Buscamos as referências de strings em todos os **User Modules**
	2. Encontramos a string que aparece na caixa de diálogo ao errar o input
	3. Setamos um breakpoint nela
	4. Rodamos o programa depois de inserir um input qualquer


Subindo um pouco a partir do breakpoint (setado em "Wrong Code! try Again!"), vemos que há uma instrução `test cl, cl`, um pouco antes das instruções que nos mostram a mensagem de erro. 

Se você observar a instrução em \[**004016D7**], que é um salto condicional **JE** (Jump if Equal), 
``pula para crackme1.4016F6 se o resultado de TEST CL, CL for igual a 0``, ela nos direciona para a outra mensagem: a mensagem de sucesso.
Basta você dar dois cliques na instrução `je crackme.4016F6` que o programa te redireciona para esse endereço.
Com isso, sabemos que, se **CL** for **zero**, quer dizer que colocamos o input/code certo.

Agora nos fazemos uma pergunta: O que leva **CL** a ter o valor zero, ou mesmo o valor atual?


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pl68nt7gpv8b5n19y1va.PNG)

Nota: \**Se você não entendeu nada desse trecho, leia a parte seguinte, **TEST CL, CL**, com calma...*

___ 
### TEST CL, CL

Antes de tudo, o que significa a instrução ``TEST CL, CL``?
Caso saiba, pule para "Respondendo à pergunta: O que leva **CL** a ter o valor zero?".

***Instrução TEST: o programa está testando se **CL** é zero ou não ("ou não" é literalmente qualquer coisa diferente de zero, como, por exemplo, -1, 1, 90000, 0xABC, 0xFFFF, 6, etc.).

**CL** é a parte mais à direita do registrador **ECX** (ver imagem adiante).
Registradores são espaços de armazenamento dentro do processador.

**TEST**: instrução que compara dois operandos, no nosso caso, compara **CL** com ele mesmo.
Com "TEST" o programa performa uma operação bit a bit, (bitwise) chamada **AND**.
A operação **AND** é uma operação lógica, efetuada com binários.
Caso não saiba nada sobre operações lógicas, veja: https://imasters.com.br/desenvolvimento/conheca-os-operadores-bitwise-bit-bit

Vamos tentar entender essa operação bit a bit, ou bitwise, utilizada aqui.

***Nota:** aqui fica claro a importância de saber sobre as bases numéricas: você tem um dado da realidade, e tem várias maneiras de quantificá-lo. Nós temos razões culturais para usarmos as bases decimais para quantificar a maioria das coisas, por isso, ao nos referirmos, por exemplo, ao número dez, dizendo "temos aqui, dez árvores", sabemos exatamente a quantidade de um determinado dado da realidade; no exemplo, sabemos que temos dez árvores.*

*Por razões, também culturais, agora, com a existência dos computadores, nós passamos a usar as bases hexadecimal, octal e binária, nessa área.*
*Sendo assim, podemos dizer que temos: 10 árvores, em decimal. 0xA árvores em Hexadecimal, 012 árvores, em Octal, 0b1010 árvores, em Binário.
A quantidade na realidade não mudou, apenas a forma de representá-la é que foi alterada. 
Os sufixos utilizados aqui são uma convenção, utilizada na linguagem C, sendo ZERO (0) para octal, ZERO XIS (0x) para hexadecimal, ZERO BE (0b) para binário, e NADA DE SUFIXO para decimal.*
*Cada uma das bases têm sua razão de existir. Recomendo ler:*
* https://www.quora.com/Why-are-binary-octal-and-hexadecimal-number-systems-popular-in-computing
* https://www.computerengineeringconcepts.org/2.3-Binary-Octal-and-Hexadecimal
* https://en.wikipedia.org/wiki/Binary_number
* https://en.wikipedia.org/wiki/Octal
* https://en.wikipedia.org/wiki/Hexadecimal

Primeiro, um exemplo da operação bitwise **AND**, com o número **7**, que em binário é **0111**:

		7 AND 7 ou TEST 7, 7
		
		0b0111
		0b0111
		------
		0b0111

Operação **AND**: 0b1 AND 0b1 = 0b1
              0b1 AND 0b0 = 0b0
Ela só resulta em 0b1 se **ambos** os operandos forem 0b1.
Se um dos operandos for 0b0, a operação resulta em 0b0.
**No windows: abra sua calculadora, escolha o modo programador, e faça as contas.**

**Nota:** *O zero à esquerda não diz nada, nem precisaria ali estar. 0111 e 111 dão na mesma.*
*Você precisa saber sobre algumas convenções de tamanhos, chamadas WORD. DWORD e QWORD.*
*Observações: 1.essas WORDS são traduzidas como "palavra", eu não gosto dessa tradução, por isso não uso. Mas você pode ver materiais falando sobre "o tamanho da palavra".*
*2.O tamanho real a que cada WORD se refere pode mudar conforme o sistema operacional.* 
*Leia:* https://mentebinaria.gitbook.io/engenharia-reversa/numeros/o-byte

*Sobre o zero aqui mostrado: -resumindo- um byte tem oito bits. O número sete, se representado segundo uma convenção, que pede que, todos os números sejam mostrados como BYTES, seria representado assim: 0000 0111. Viu? Oito bits (1 BYTE = 8 bits), em duas colunas, com quatro valores cada.*
*Por costume, valores que usam quatro bits ou menos, eu os represento como "0000".*

#### Registrador EFLAGS 

Nós temos também um registrador especial, chamado **EFLAGS**, que possui todas as flags usadas pelo sistema, cada uma com um propósito diferente, que por sua vez, serão emitidas em determinadas operações, com o fim de sinalizar alguma coisa (leia o manual da Intel, link abaixo). Não vou me estender na explicação.
Para saber mais sobre EFLAGS, confira essa parte do livro gratuito de Assembly: https://mentebinaria.gitbook.io/assembly/aprofundando-em-assembly/flags-do-processador

Agora nós precisamos saber que, nesse registrador, nós temos uma flag chamada **ZeroFlag**.
Essa flag, em conjunto com as instruções de comparação, ``cmp`` e ``test``, e as instruções de salto condicional, serve para controle do fluxo de execução do programa. 
Sobre as instruções de salto condicional, veja: http://unixwiz.net/techtips/x86-jumps.html

**Nota:** *Baixe o manual da Intel. Lá tem todas as instruções listadas, com as condições necessárias para a sua execução (o nome é Jcc para todas as condições diferentes de JMP):* https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/buw3qquunfyw97e83c7w.PNG)
*É o primeiro link do site, apontado pela seta vermelha na imagem acima.*

A operação **TEST CL, CL** resultando em **0**, seta a **ZeroFlag** para 1: **ZF=1**.
E é isso que a combinação das instruções **TEST** e **JE** (Jump if equal) faz:
	
	se(ZF==1) então Pule para a localização, se não, se(ZF==0) não pule
	if(ZF==1) then Jump to location else if(ZF==0) don't jump

---
### CL

**ECX** é um registrador de 32 bits, e podemos usar apenas "uma parte dele", "dividindo-o":


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g3eb8ghnxy5bh097fjrw.PNG)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ghwzxgbadzaowz6jlysw.PNG)	
(OBS: ignore o **RCX**, que é o nome usado para registradores de 64 bits, os coloquei ali para que você saiba da existência dele). 

Quando usamos um registrador como **ECX**, usamos TODO ele, nossos dados de 4 bytes 
(4 bytes são 32 bits) ocupam todos os espaços -incluindo os 32 bits de ECX, os 16 bits de CX, os 8 bits de CH, e os 8 bits de CL.

Mantenha em mente que **ECX** é um corpo completo, e podemos usar uma parte específica dele, obrigando o computador a ler/escrever somente na parte desejada, sendo CX, CH ou CL.

"Curiosidade": antigamente existiam processadores de 16 bits, **CH** ficava com os 8 bits mais significantes e **CL** com os 8 bits menos significantes, e hoje em dia ainda é assim, só que os registradores possuem mais espaço; mais espaço, mais divisões.

Para saber mais sobre **bits mais significantes** e **menos significantes** (MSB e LSB),
acesse o link: https://www.morningstar.io/post/2016/12/25/midi-msb-and-lsb#:~:text=MSB%20stands%20for%20most%20significant,4%20bits%20would%20be%200011

**CH** e **CL**: o "C" é padrão para Counter (mas o registrador é de propósito geral, então pode ser usado para quase "qualquer coisa"), o **H** significa "High byte" e o **L** "Low byte"
(o E de ECX é de extended, lembra que era CX -16 bits? Ele foi "extendido" para 32 bits).
Mais sobre: https://en.wikipedia.org/wiki/IA-32

___
### O Processo de Tratamento do Input

Se você já sabe o input/code certo, coloque um breakpoint na instrução ``test cl, cl``, depois insira o input/code correto, rode o programa, e perceba como **CL** vai estar com o valor **0** durante o teste; inserindo um valor errado, ele vai estar com valor 1 nesse momento.
Assim temos a prova de que é realmente ali que acontece confirmação final do input inserido.

Mantenha o breakpoint na instrução `test cl, cl`, insira um input errado e rode novamente.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/11q0e7tzjeqzwptkqrbq.PNG)
O registrado EFLAGS (circulado em vermelho, a direita) nos mostra que a ZeroFlag vai ser modificada na próxima operação através do traço vermelho abaixo de **ZF**.
Circulado em preto, temos o valor contido em **CL**, bem como vemos sublinhado em verde na coluna dos registradores.
Foi inserido um input/code errado, e a ZeroFlag será setada para 0, dizendo que não tivemos o resultado 0 na operação TEST. Dê um stepover e veja por si mesmo.
___

Perceba que **antes** de ``test cl, cl`` nós temos a instrução ``pop ecx``.

A instrução ``pop`` pega o valor que está no **topo** da stack/pilha (visível no registrador **ESP**), e carrega para dentro do operando, que no nosso caso é o **ECX**.

**Nota:** *Não se esqueça de que o registrador **ESP** contém o endereço do topo da stack/pilha, e dentro desse endereço há um valor, e é ele que o pop "joga" para dentro de **ECX**.*

Breakpoint na Instrução ``pop ecx``, inserimos um input errado, clicamos em "OK":

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/08geibku4ooaw04yvnda.PNG)

Nesse momento, o topo da nossa stack/pilha está assim (o input foi "AAA", que é errado):

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l29fvaipv2q37j23uvvh.PNG)

Como o code/input não é o correto, o topo da stack está com 1.
(Se você já tem o code/input correto, como mencionado anteriormente, insira-o, e veja que, ao chegar nesse ponto, a stack/pilha está com o valor **0** no topo). Preste atenção no endereço do topo da stack/pilha, é ele que, mais tarde, vai nos levar até a função que dita se o input/code está certo ou errado.

Apenas o bit menos significativo de **ECX**, **CL**, é usado para a operação seguinte.
Com isso, na operação ``test cl, cl`` (é o mesmo que ``test 1, 1``) temos o resultado 1, a **ZeroFlag** não é setada (``ZF=0``), então o programa **não pula** para ``crackme1.4016F6``.

___
### Vamos rastrear a origem desse valor no topo da stack

Como já vimos que é o valor, zero ou um, que está no topo da pilha, no momento do ``pop ecx``, que determina o nosso sucesso ou erro, a depender do input, vamos agora tentar encontra a origem desse número.
Os valores que se encontram na stack/pilha, durante o breakpoint em ``pop ecx``, têm origem em alguma operação da função atual.

As instruções responsáveis por lidar com valores da stack/pilha, são **POP** e **PUSH**.
	**POP**: Carrega o valor do topo da pilha para o local especificado pelo operando de destino (ou opcode explícito) e, em seguida, incrementa o ponteiro da stack/pilha (**ESP**).
	**PUSH**: Decrementa o ponteiro da stack/pilha (**ESP**) e armazena o operando de origem no topo da pilha.
Não deixe de consultar o manual da Intel para saber mais!

Lembre-se que o valor retornado por uma função é "guardado" no registrador **EAX**.

*Nota: Aqui eu assumo que você já leu o primeiro tutorial, relacionado ao x64dbg, onde eu explico brevemente as funções em Assembly de x86. Novamente colocarei o link aqui:* https://dev.to/ryan_gozlyngg/engenharia-reversa-primeiro-contato-parte-1-2gih

#### Segue o passo a passo efetuado:

1. Setamos um breakpoint nas três instruções ``call`` mais próximas ao ``test cl, cl``.
2. Execute o programa novamente, até que ele chegue no breakpoint. Para fazer isso, basta dar um "Run" (atalho: **F9**), para ele sair dos breakpoints e pedir o input novamente.
3. Insira uma mensagem ERRADA no input/code. Clique em "Ok" para continuar.
5. Dê um **step over** com **F8** até o **EIP** passar da instrução call, e verifique registrador **EAX**.
6. Verificamos se o valor contido em **EAX** é colocado no topo da stack/pilha (geralmente com a instrução ``push eax``). 

**MUITA ATENÇÃO NO 6° PASSO**: o endereço do topo da stack/pilha, em **ESP**, precisa ser ``0x19EFDC`` imediatamente depois da instrução ``call``. Isso porque, no momento do ``pop ecx`` em xxxxx, o endereço (da stack/pilha) ``0x19EFD8`` é que está com o nosso valor (0 ou 1) para o ``test cl, cl``, e agora estamos assumindo que esse valor foi guardado na stack/pilha via instrução **PUSH**, a qual decrementa o endereço de **ESP**, e se você leu a documentação da Intel, sabe que **ESP** é decrementado por **4**. Logo ``0x19EFDC - 4 = 0x19EFD8``.

(O manual nos diz: The operand size (16, 32, or 64 bits) determines the amount by which the stack pointer is decremented (2, 4 or 8). No nosso caso, como veremos logo, o operado é **EAX**, logo 32 bits, decrementado por 4).

**Nota:** *Setando Breakpoint - alguns detalhes:*
 
*Como você já sabe, setamos um breakpoint em uma instrução, dentro do x64dbg, com a tecla **F2**:*

* *Selecione a linha desejada, e aperte **F2**, a "bolinha" no canto esquerdo ficará **vermelha**.*
* *Se você clicar em cima dessa "bolinha", a "bolinha" ficará com verde, desabilitando o breakpoint.*
* *Clicando na "bolinha" uma terceira vez, você removerá o breakpoint.*

---

**Nota:** *Ao clicar duas vezes em uma instrução ``call``, ou dar um **step in** com o EIP nela (lembre da "setinha" na esquerda, que aponta para onde está o EIP), você vai entrar, ser direcionado, até o código dessa função.*
*Como fazemos para voltar à instrução anterior? Para isso usamos o seguinte atalho:* ``-``

---
### Observando a stack

Ao efetuar os passos acima...


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yr0t7btc4n3cz1wexdrq.PNG)

Na imagem, você pode ver que eu inseri "AAA" como code/input, e por estar errado, precisamos ver o "1" no topo da stack/pilha ao passarmos por está ``call``, no endereço que buscamos.
Observe as instruções. Na imagem, paramos antes da função ser executada.
Executamos um stepover (**F8**).


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mrwj5ai4xrb52kiain86.PNG)

Podemos ver que no topo da stack/pilha temos o endereço desejado, e no campo das instruções temos ``push eax``, que decrementará o nosso **ESP**, colocando o valor "1" de **EAX** no topo da stack/pilha.

Bem como estávamos procurando!

---

### Retorno de Função

Um breve exemplo de um programa em C comentando o comportamento do código quando em Assembly de x86, para reforçar que o valor de retorno de uma função, em programas x86, é passado para **EAX** (na grande maioria dos casos, lembre-se das calling conventions).

```
// programa em C

/* Função SOMA: soma dois numeros a + b e retorna resultado */
int soma(int a, int b){
   int resposta_da_soma;

   resposta_da_soma = a + b;

   return resposta_da_soma; // esse valor é retornado em EAX
}

/* Função principal Main */
int main(){
   int a = 2;
   int b = 3;
   int resultado;

   // chama função com soma de dois valores 
   // aqui será uma call: call soma.endereço
   resultado = soma(a, b).

  // Aqui dentro da main, nesse momento teremos em EAX o número 5
}
```

---
### Observando a Função crackme1.46B828

Antes de entrarmos na função, vamos ver os argumentos que são passados para ela.
Eu não vou explicar como as **Calling Conventions** funcionam, e é esse conhecimento que vai lhe ajudar a entender como os argumentos são passados para funções. Sim, existe mais de uma maneira de passar argumentos para funções em Assembly. 
Para entender melhor sobre elas, acesse os links: https://mentebinaria.gitbook.io/assembly/programando-junto-com-c/convencoes-de-chamada-no-windows
https://en.wikipedia.org/wiki/X86_calling_conventions

Em resumo, as convenções de chamada (calling conventions) ditam como os argumentos de uma função vão ser passados a ela. Aqui nós podemos ver que antes da função ser chamada, o endereço de algo que está em ``[ebp-10]`` é carregado em **EDX**, e também vemos que o que está no topo da stack é colocado dentro de **EAX**.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/18skqq5201jsd78c234f.PNG)

Vamos ver, brevemente, o que essas funções, ``lea`` e ``pop`` estão fazendo:

``lea`` - Load Efective Address: Carrega o endereço de qualquer coisa que está em ``[ebp-10]`` e guarda em **EDX**. Para entender ``[ebp-10]``, segue o link: https://mentebinaria.gitbook.io/assembly/a-base/enderecamento

``pop`` - Pega oque está no TOPO da stack/pilha e guarda no operando, aqui é guardado em **EAX**, e em seguida incrementa o topo da stack/pilha.

Os argumentos foram passados através dos registradores **EAX** e **EDX**.
Entrando na função isso vai ficar claro.

---
### Encontrando valores no Dump

Vamos ver rapidamente como rastreamos valores no x64dbg.
(Algo como ``[ebp-10]`` é carregado em **EDX**, basta um **step-over** para executar a instrução, e aí verificamos oque é que foi carregado em **EDX**, muito mais simples)...

Vamos ver quais valores se encontram em ``[ebp-10]``, e no topo da stack nessa hora:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1rxdu4lnd0nxo7ivehw4.PNG)
Você já deve saber buscar esse valor no dump. Botão direito do mouse na instrução desejada, etc.(basta fazer como na imagem acima). 

Encontre o valor desejado, que no caso é ``[ebp-10]``.

Agora vamos ao topo da stack/pilha:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jfhnf0p9jiqy8ybpj1xt.PNG)
Perceba que o topo da stack/pilha (em **ESP**) é sempre destacado em verde na Janela da Stack.
Vamos ver os valores carregados nos registradores:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bm7xblca27fk0v06k33w.PNG)

Perceba que **EDX** tem um comentário que nos diz que o valor ``0x0019F02C`` é um endereço para a string "\*\*\*vErYeAsY\*\*\*". Sabemos que é um endereço pelo símbolo "&".
Você pode segui-lo no dump e confirmar isso.

Em **EAX** temos o valor ``0x0019F030``, e para sabermos oque tem lá, podemos segui-lo no dump.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mkojmy3tyjvh7s5s9kau.PNG)
Perceba o valor que se encontra aqui. Parece outro endereço.
Podemos tentar seguir ele no dump também.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qsq8pygepicuy5eb3to1.PNG)
Chegamos nesse lugar. Observe que é aqui que se encontra o nosso code/input.
0x41 é o mesmo que o char 'A'. Cada 0x41 é um byte, você tem ali a seguinte string:

     "AAA\0" 
 Os três caracteres 'A', mais o ``null terminator`` ``\0``, que indica o fim da string.
 Confira: https://man7.org/linux/man-pages/man7/ascii.7.html

Então é isso, essa função está recebendo os endereços das duas strings como argumento.
Nos perguntamos: Será que ela vai comparar a nossa string "AAA" com essa string diferente aí??? E então testamos a string "\*\*\*vErYeAsY\*\*\*" como code/input.
E então é isso, funcionou!

___
### Entrando na Função crackme1.46B828

Para entrar na função utilize o **STEP-INTO** (**F7**).

Dentro da função:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6s8095yb9t16wo3rrqsx.PNG)

Perceba que aqui dentro, a função pega os argumentos, que são endereços, e os "desreferencía", fazendo com que os valores reais dentro daqueles endereços sejam guardados agora em **EAX** e **EDX**. Portanto, depois dessas operações **MOV**, os nossos registradores conterão os endereços diretos para nossas strings puras (perceba que antes disso nós temos um endereço para um endereço).


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rqohitav40kliumkda4f.PNG)
___

Observação: geralmente as funções chamam outras funções, que chamam outras funções, e chega em um nível em que você deve se perguntar: vale mesmo apena entrar em todas essas funções? Será que estou no caminho certo?

O que me fez perceber que estava no caminho certo aqui, foi a instrução que vem depois da ``call crackme1.460C48``, a instrução ``setne al`` (sobre ela mais a frente).
**AL** é a parte "mais baixa" de **EAX**, sabemos que ele contém o retorno da função após a execução dela. E a operação que é feita com **AL** nos dá o retorno "1" que buscamos.

Por isso achei que valia apena continuar entrando nas funções.
Como aqui o programa já foi quebrado, e o desafio já foi vencido, eu estava motivado apenas por pura curiosidade de entender como o programa funciona, então achei que valia continuar com a busca.

Vamos para essa outra função agora.

---
### Entendendo como chegamos ao resultado

Vou expor o processo de maneira linear (atenção: não vou expor o processo todo devido ao tamanho que o texto ficaria, vou focar nos pontos mais importantes para esse exemplo).

*Nota: antes de tudo, perceba que, conforme o tamanho da nossa string digitada no input, o programa faz um caminho diferente dentro dessa função, já que ele compara alguns caracteres por vez. Sendo assim, quanto maior a nossa string, mais testes serão feitos.*
*No teste abaixo foi utilizado uma string de três caracteres: "AAA".*

Acompanhe parte do processo da função ``0x00460C48``, começando pela comparação:

Passando do prólogo da função nós temos:

1.``cmp eax, edx``

Compara os endereços das duas strings.
A instrução **CMP** faz uma subtração: ``eax - edx``. A diferença é que aqui o resultado não é salvo, e os operandos permanecem os mesmos.
Perceba que como os endereços estão sendo comparados, a próxima instrução, que é um salto "pule se for igual", não acontece.

---

2.``test esi, esi``

Verificando se o code/input não está vazio, caso esteja, ele pula para o fim da função e termina retornando **1**.

---

3.``test edi, edi``

Verificando se a string com o code/input correto, passada pelo programador, não está vazia.

---

4.``mov eax, dword ptr ds:[esi+4]``
   ``mov edx, dword ptr ds:[edi+4]``
**EAX** recebe o tamanho da string que passamos para o code/input
``edx`` recebe o tamanho da string que o programador passou para a função.
O tamanho das strings foi calculado em outra função.

---

5.``sub eax, edx``

subtrai **EDX** de **EAX**. A string correta tem o tamanho de 14 bytes, em hexa são 0xE.
A conta: 
	COM O INPUT "AAA": 3 - 14 = -11 ou 0xFFFFFFF5
	COM O INPUT CORRETO: 14 - 14 = 0	

O resultado é guardado em **EAX**.

Se ambos estiverem com o valor de 14 bytes, sendo a string correta ou não, a operação ``sub eax, edx`` resultaria em ZeroFlag = 1, e CarryFlag = 0.
Lembre-se que se uma operação resultar em zero, a **ZeroFlag** é setada, recebendo o valor "1".

---

6.``ja crackme1.460C6B ``

**JA** - Jump if above, pula se as flags **CF** e **ZF** forem ambas iguais a **0**.
Em outras palavras, o salto só será executado caso a nossa string seja **==maior==** que a string do programador. 
Com a nossa string sendo maior que a string do programador, a CarryFlag não é setada, porque não temos um resultado negativo, e a ZeroFlag não é setada, porque a operação não resulta em zero.

**CF ou Carray Flag**: Caso a operação aritmética precise utilizar o bit mais significante (MSB - que é o bit mais à esquerda) para guardar o resultado, a **CF** é setada para "1".

O bit mais a esquerda é usado quando precisamos representar um número com sinal, um número negativo.

Como o resultado da operação com a string menor "AAA" seta a Carry Flag, o programa não executa o salto. Já com a string correta, ele executa o salto.

**Resumindo:**
	Com Uma ==String Menor==: \[CarryFlag é setada e o salto não é executado]
	Com Uma ==String de 14 bytes==: \[ZeroFlag é setada e o salto não é executado]

Todo esse processo foi para ver se o code/input passado por nós é maior que a string do programador.

---

#### Caso o salto não seja executado, as seguintes instruções são usadas

7.``add edx, eax``

**COM UMA STRING MENOR**
	Usei a string "AAA".
	``eax: 0xFFFFFFF5 - no caso de "AAA" (ou qualquer string de três bytes)``
	``edx: 0xE``
	Cálculo: 0xE + 0xFFFFFFF5 = 00000003 (Resultado vem com o MSB setado: 100000003)

**COM UMA STRING DE 14 BYTES**
	``eax: 0 - resultado da subtração de 14 - 14``
	``edx: 0xE - tamanho da string em hexadecimal (0xE = 14)``
	Cálculo: 0xE + 0 = 0xE

Aqui estamos restaurando o número de bytes da nossa string para **EDX**.

O resultado é guardado em **EDX**.
E em seguida o resultado é guardado na stack com ``push edx``.

---

8.``shr edx, 2 ``

Aqui o programa usa a instrução **SHR**, com **EDX** e o número **2**. 
**SHR** é usada para mover os bits de **EDX** duas casas para a direita.
Para cada bit movido para a direita, um zero é adicionado à esquerda.

**EXEMPLO COM UMA STRING DE 14 BYTES**:
>	
>	EDX está com o valor 0xE ou 14.
>	14 em binário é: 1110
>	Agora vamos mover os bits para a direita duas vezes:
>	
>	1110
>	0111 -> 1.movemos um bit para a direita e adicionamos um zero à esquerda
>	0011 -> 2.movemos um bit para a direita e adicionamos um zero à esquerda
>	
>	Agora nós ficamos com o resultado 0011 que é 3 (três é representado da mesma forma em hexadecimal e em decimal).
>	
>	Ao terminar a instrução, aqui temos **EDX** = 3.

**EXEMPLO COM UMA STRING DE 3 BYTES**:
>	
>	EDX está com o valor 3.
>	3 em binário é: 0011
>	Agora vamos mover os bits para a direita duas vezes:
>	
>	0011
>	0001 -> 1.movemos um bit para a direita e adicionamos um zero à esquerda
>	0000 -> 2.movemos um bit para a direita e adicionamos um zero à esquerda
>	
>	Agora nós ficamos com o resultado 0000 que é 0 (zero é representado da mesma forma em hexadecimal e em decimal).
>	
>	Ao terminar a instrução, aqui temos **EDX** = 0.

---

9.``je`` 
se ``shr edx, 2 == 0`` (Testando para ver se a operação resultou em zero)
a operação serve para o programa decidir qual caminho tomar baseado no tamanho da string que nós passamos para ele.
Se for uma muito pequena, ele vai pular para uma comparação de um único byte, o mais a direita.
Se a nossa string tiver um tamanho parecido com a correta, ele compara os primeiros 4 bytes, 
e assim o programa prossegue, sempre se baseando no tamanho da nossa string.

Aqui segue os resultados para strings de 0 bytes até as de 14 bytes:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vjb63rwm9q9tvz9jvwch.PNG)
Perceba que as strings de 14, 13 e 12 bytes resultam em 3, forçando a função a ir pelo mesmo caminho, até detectar a divergência de tamanho.

---
### Resumindo a lógica da função

Em resumo, você pode entender 100% da lógica sendo usada em qualquer função, mas se você não quer gastar todo o seu tempo com isso, pode começar a pegar padrões de comportamento para todos os tipos de dados processadas, no nosso caso, as strings.

Aqui não vou mostrar o que cada uma das instruções seguintes faz, isso vai deixar esse tutorial mais maçante e longo ainda...

Mas para quem quer saber oque se passa depois do já mencionado, dentro da função:
Perceba a série de saltos (``jmp e jcc``) e comparações (``test e cmp``).
A função está percorrendo ambas as strings e comparando os bytes.
A ordem da comparação varia conforme o tamanho da string enviada.

Bom, sabemos que a função vai retornar um valor dentro de **EAX**.

Com nossa string de três caracteres, ao fim da função, perceba que o valor em EAX é quase o mesmo que temos após a instrução ``sub eax, edx`` do passo cinco desse tutorial na parte intitulada "Entendendo como chegamos ao resultado".

Que tipo de alteração ele sofreu?

Vamos até a instrução mencionada (``sub eax, edx``), e a partir dela, seguimos com a atenção fixada em **EAX**, uma instrução por vez, com o step-over.

Se você fez isso, percebeu que o valor se mantém o mesmo em **EAX** até chegar em uma instrução que soma **EAX** com ele mesmo desde então.
(Mas lembre-se que o percurso depende do tamanho da string que usamos no code/input).

Após isso, o programa pula direto para a parte final da função com um salto obrigatório ``jmp``. 
**EAX** não sofre mais nenhuma alteração, e voltamos a função anterior. 

---
### Saindo da função e obtendo o resultado definitivo

Agora que você já tem uma noção de como o resultado retornado é formado, vamos ver oque é que nos dá o **resultado definitivo** para a comparação final.

Sabemos que a comparação final é, ou com o valor "1" ou com o valor "0".

A função em que entramos é a seguinte ``crackme1.46B828``:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e8c77sdolez2pmfbrgvo.PNG)

Já vimos oque acontece na função chamada em ``call crackme1.460C48``, e retornamos na instrução ``setne al``.

---
#### SETNE - Instrução que gera o resultado

``setne - Set byte if not equal (ZF=0)``: essa instrução vai mudar o operando (no nosso caso o **AL**) para **0** ou **1**, dependendo do status da **ZeroFlag**.

Como vimos antes, o caminho tomado pela instrução anterior, que retorna nosso valor em **EAX**, depende do tamanho da string enviada. Basta seguir o fluxo da função até ela tomar um salto para o final da instrução, e depois voltar a última instrução de comparação.

Para voltar para trás, e seguir esse fluxo, basta usar o atalho ``-`` (traço).
Assim vemos onde é que a ZeroFlag é setada.

Vamos seguir o caso da string "AAA":


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9mxe7mowjbu7rp9a4i1j.PNG)
A função nos faz saltar para o final, antes da instrução de retorno **RET**.
Usamos o atalho até voltarmos ao local com uma instrução de comparação.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qcbfmq9nvtjtlpnlzj9h.PNG)
Voltando, vemos o local do salto, e em cima dele, a última instrução de comparação, que está comparando o byte mais à direita da nossa string com a do programador.
Como o resultado é diferente, nós não temos a ZeroFlag setada.

Sabendo que a ZeroFlag não foi setada, nós também sabemos que a instrução ``setne al`` irá colocar o valor **"1"** na parte mais baixa (mais a direita) do nosso registrador EAX.

Após a execução de ``setne al``, **EAX** fica com o seguinte valor: ``0xFFFFFF01``, e para termos apenas o valor setado por ``setne`` em **EAX**, a instrução ``and eax, 1`` é performada.

Como "1" é representado como ``0x00000001`` e EAX é ``0xFFFFFF01``, somente o primeiro byte, mais a direita vai ter o valo "1", caso não seja "0", e qualquer outro byte vai ser mudado para zero. Lembra, operação AND só resulta em "1" se ambos os bits forem "1", e é por isso que, com a string correta, **AL** é setado para zero, e essa operação AND resulta em zero.

---

### Considerações Finais

Espero que você tenha conseguido seguir e entender até aqui.
Espero que tenha pegado o "espirito" da coisa, e se for o caso, que tenha aumentado a sua curiosidade e interesse por esse tipo de conhecimento.

Para iniciar de verdade na engenharia reversa, eu recomendo começar pela seguinte playlist:
##### CERO - Curso de Engenharia Reversa Online por Mente Binária:
Curso de engenharia reversa em português 100% gratuito.

https://youtube.com/playlist?list=PLIfZMtpPYFP6zLKlnyAeWY1I85VpyshAA&si=3wYZb0E7iHaAFaMm