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:
- Clique com o botĂŁo direito em qualquer linha.
- Vá em Copy To Executable → All Modifications → Copy All.
- 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!