01. Encontrando a mensagem secreta

Esse é meu primeiro tutorial sobre engenharia reversa. Se você sabe tanto quanto eu (ou seja, quase nada), esta página é um bom ponto de partida. Vou explicar alguns conceitos básicos e fazer um "debug" manual de um programa em Assembly, cujo objetivo será encontrar uma frase escondida.

O programa que vou utilizar neste tutorial é extremamente simples, feito em Assembly e compilado com o MASM32. O código está incluído junto com o executável (mas não olhe o código antes de concluir este tutorial!).

- Download fergo_ex1.zip

Antes de tudo, precisamos de algo que transforme o nosso executável em uma linguagem compreensível para humanos (ou pelo menos tentável). Para isso, você precisa de um Debugger ou Disassembler. Neste tutorial, vou utilizar um dos depuradores mais completos atualmente (e também um dos mais famosos), por ter uma interface mais visual e ser gratuito: OllyDbg

Ao iniciar o Olly, você verá uma tela semelhante a esta (as cores podem variar, dependendo da configuração do usuário):

Vamos abrir o nosso executável para analisar seu código (em linguagem de máquina, Assembly). Vá em "File → Open" e selecione "fergo_ex1.exe". Como se trata de um executável pequeno, ele abre instantaneamente e possui poucas linhas de código efetivas. Você verá algo parecido com isso:

Quanta coisa, né? Mas calma, com o tempo você se acostuma. Na janela principal (superior esquerda), temos 4 colunas:

  • Address: o endereço de cada instrução, usado para saltos (jumps), chamadas (calls), etc.
  • Hex Dump: a instrução em formato hexadecimal (nĂŁo Ă© relevante agora).
  • Disassembly: a mesma instrução, mas "traduzida" em Assembly.
  • Comment: comentários que ajudam a identificar algumas partes do cĂłdigo (como chamadas de função).

Como o código é pequeno, vou numerar as linhas para começarmos o nosso debug:

00401000 2BC0 SUB EAX,EAX  
00401002 83F8 00 CMP EAX,0  
00401005 74 0E JE SHORT fergo_ex.00401015  
00401007 8D05 25304000 LEA EAX,DWORD PTR DS:[403025]  
0040100D 8D1D 25304000 LEA EBX,DWORD PTR DS:[403025]  
00401013 EB 0C JMP SHORT fergo_ex.00401021  
00401015 8D05 00304000 LEA EAX,DWORD PTR DS:[403000]  
0040101B 8D1D 09304000 LEA EBX,DWORD PTR DS:[403009]  
00401021 6A 00 PUSH 0 ; Style = MB_OK|MB_APPLMODAL  
00401023 50 PUSH EAX ; Title  
00401024 53 PUSH EBX ; Text  
00401025 6A 00 PUSH 0 ; hOwner = NULL  
00401027 E8 14000000 CALL <JMP.&user32.MessageBoxA> ; MessageBoxA  
0040102C 6A 00 PUSH 0 ; ExitCode = 0  
0040102E E8 01000000 CALL <JMP.&kernel32.ExitProcess> ; ExitProcess  
00401033 CC INT3  
00401034 -FF25 00204000 JMP DWORD PTR DS:[<&kernel32.ExitProcess>]  
0040103A -FF25 0C204000 JMP DWORD PTR DS:[<&user32.wsprintfA>]  
00401040 $-FF25 08204000 JMP DWORD PTR DS:[<&user32.MessageBoxA>]

Vamos ao debug!

Linha 1: SUB EAX, EAX
SUB indica uma operação de subtração entre os dois argumentos. EAX é um registrador (uma espécie de variável temporária usada pelo processador). Aqui, o comando faz: EAX = EAX - EAX. Isso zera o valor de EAX. É uma forma comum de "zerar" um registrador em Assembly.

Linha 2: CMP EAX, 0
CMP significa "Compare". Ele compara EAX com 0 e ajusta flags internas com base no resultado. Como acabamos de zerar EAX, a comparação será verdadeira.

Linha 3: JE SHORT fergo_ex.00401015
"Jump if Equal". Se o resultado da comparação for igual (zero == zero), o código salta para 00401015. E sim, é o que acontece.

Vamos pular as linhas 4, 5 e 6 por enquanto, pra nĂŁo estragar a surpresa.

Linha 7: LEA EAX, DWORD PTR DS:[403000]
LEA carrega o endereço do valor (não o valor em si). Aqui, EAX recebe o endereço 403000.

Linha 8: LEA EBX, DWORD PTR DS:[403009]
Mesma ideia da anterior, mas com EBX e 403009.

Linha 9: PUSH 0
Empilha o valor 0. Ele será usado mais tarde por uma função.

Linhas 10, 11, 12: PUSH ...
Empilham EAX, EBX, e outro 0. Isso prepara os argumentos para a função MessageBoxA.

Linha 13: CALL <JMP.&user32.MessageBoxA>
Chama a função MessageBoxA da DLL user32.dll.
Os parâmetros são lidos da pilha em ordem reversa. A função espera:

MessageBoxA(hwnd, text, title, type);

Ou seja:

  • PUSH 0 → hwnd
  • PUSH EAX → title
  • PUSH EBX → text
  • PUSH 0 → type

Linha 14: PUSH 0
Empilha 0 novamente, agora para a próxima função.

Linha 15: CALL <JMP.&kernel32.ExitProcess>
Chama a função ExitProcess(0), que encerra o programa.


CadĂŞ a mensagem escondida?

Reanalise: as linhas 7 e 8 preparam os valores para EAX e EBX, que vĂŁo ser exibidos na MessageBoxA.

Agora olhe as linhas 4 e 5: temos outras instruções LEA, apontando para endereços diferentes.

Mas… essas linhas não são executadas porque a linha 3 (JE) salta diretamente para a linha 7. Resultado: as mensagens dessas linhas são ignoradas.

Solução?

Simples: anular o salto da linha 3. Existem várias formas, mas a mais direta é substituir o salto por um NOP (No Operation).

No OllyDbg:

  • Clique com o botĂŁo direito na linha 3.
  • Vá em Binary → Fill with NOPs.

Pronto! A linha 3 nĂŁo faz mais nada, entĂŁo o cĂłdigo segue para as linhas 4 e 5 e executa normalmente.

O programa agora pega os valores das linhas 4 e 5, ignora os das linhas 7 e 8 (porque sĂŁo puladas com o JMP da linha 6) e exibe a mensagem secreta.


Teste e salve

Para salvar as alterações:

  1. Clique com o botĂŁo direito em qualquer linha.
  2. Vá em Copy To Executable → All Modifications → Copy All.
  3. Uma nova janela será aberta. Clique com o botão direito nela e selecione Save File.

Execute o arquivo salvo — e voilà:

"Parabéns, você a encontrou!"


Considerações Finais

Espero ter dado o pontapé inicial para quem não sabia por onde começar ou queria entender o básico das instruções em Assembly.

F3rGO!