Análise prática de Assembly
Começaremos com alguns problemas básicos de Engenharia Reversa de Assembly.
Os problemas são do repositório: kablaa-CTF-Workshop.
Hello World
Arquivo: hello_word
Em Linux, podemos usar o seguinte comando para olhar o código em Assembly:
$ objdump -D hello_world -M intel | less
Depois de procurar pela string main (que é a função principal), vemos isso:
080483fb <main>:
80483fb: 8d 4c 24 04 lea ecx,[esp+0x4]
80483ff: 83 e4 f0 and esp,0xfffffff0
8048402: ff 71 fc push DWORD PTR [ecx-0x4]
8048405: 55 push ebp
8048406: 89 e5 mov ebp,esp
8048408: 51 push ecx
8048409: 83 ec 04 sub esp,0x4
804840c: 83 ec 0c sub esp,0xc
804840f: 68 b0 84 04 08 push 0x80484b0
8048414: e8 b7 fe ff ff call 80482d0 <puts@plt>
8048419: 83 c4 10 add esp,0x10
804841c: b8 00 00 00 00 mov eax,0x0
8048421: 8b 4d fc mov ecx,DWORD PTR [ebp-0x4]
8048424: c9 leave
8048425: 8d 61 fc lea esp,[ecx-0x4]
8048428: c3 ret
8048429: 66 90 xchg ax,ax
804842b: 66 90 xchg ax,ax
804842d: 66 90 xchg ax,ax
804842f: 90 nop
Olhando para o código, vemos uma function call para puts:
push 0x80484b0
call 80482d0 <puts@plt>
O puts é uma função que imprime algo no terminal como texto. O resto não é nada muito interessante. Assim, sabemos que algo será impresso na saída do programa. Se executarmos o código:
$ ./hello_world
hello world!
If Then
Começamos vendo o código em assembly com objdump:
$ objdump -D if_then -M intel | less
Após o comando, procuramos pela função main:
080483fb <main>:
80483fb: 8d 4c 24 04 lea ecx,[esp+0x4]
80483ff: 83 e4 f0 and esp,0xfffffff0
8048402: ff 71 fc push DWORD PTR [ecx-0x4]
8048405: 55 push ebp
8048406: 89 e5 mov ebp,esp
8048408: 51 push ecx
8048409: 83 ec 14 sub esp,0x14
804840c: c7 45 f4 0a 00 00 00 mov DWORD PTR [ebp-0xc],0xa
8048413: 83 7d f4 0a cmp DWORD PTR [ebp-0xc],0xa
8048417: 75 10 jne 8048429 <main+0x2e>
8048419: 83 ec 0c sub esp,0xc
804841c: 68 c0 84 04 08 push 0x80484c0
8048421: e8 aa fe ff ff call 80482d0 <puts@plt>
8048426: 83 c4 10 add esp,0x10
8048429: b8 00 00 00 00 mov eax,0x0
804842e: 8b 4d fc mov ecx,DWORD PTR [ebp-0x4]
8048431: c9 leave
8048432: 8d 61 fc lea esp,[ecx-0x4]
8048435: c3 ret
8048436: 66 90 xchg ax,ax
8048438: 66 90 xchg ax,ax
804843a: 66 90 xchg ax,ax
804843c: 66 90 xchg ax,ax
804843e: 66 90 xchg ax,ax
Podemos ver que o valor 0xa (10 em hexadecimal) é carregado em ebp-0xc:
mov DWORD PTR [ebp-0xc],0xa
Imediatamente após isso, vemos que há uma instrução cmp, seguida de jne. Isso checa se é o dado presente em ebp-0xc é igual a 0xa. Se não for igual, jne irá pular para main+0x2e (obs: main+0x2e é a mesma coisa que pegar o endereço base da função main, ou seja, o endereço da primeira instrução, e acrescentar 0x2e a ele)
Como o valor é igual, o jump não será executado:
cmp DWORD PTR [ebp-0xc],0xa
jne 8048429 <main+0x2e>
Prosseguindo, é feita uma chamada para a função puts, o que quer dizer que algo será impresso na tela:
sub esp,0xc
push 0x80484c0
call 80482d0 <puts@plt>
Após rodar o código, podemos rodá-lo e ver o que faz:
$ ./if_then
x = ten
Loop
Começamos vendo o código em assembly com objdump:
$ objdump -D loop -M intel | less
Procurando pela função main, vemos isso:
080483fb <main>:
80483fb: 8d 4c 24 04 lea ecx,[esp+0x4]
80483ff: 83 e4 f0 and esp,0xfffffff0
8048402: ff 71 fc push DWORD PTR [ecx-0x4]
8048405: 55 push ebp
8048406: 89 e5 mov ebp,esp
8048408: 51 push ecx
8048409: 83 ec 14 sub esp,0x14
804840c: c7 45 f4 00 00 00 00 mov DWORD PTR [ebp-0xc],0x0
8048413: eb 17 jmp 804842c <main+0x31>
8048415: 83 ec 08 sub esp,0x8
8048418: ff 75 f4 push DWORD PTR [ebp-0xc]
804841b: 68 c0 84 04 08 push 0x80484c0
8048420: e8 ab fe ff ff call 80482d0 <printf@plt>
8048425: 83 c4 10 add esp,0x10
8048428: 83 45 f4 01 add DWORD PTR [ebp-0xc],0x1
804842c: 83 7d f4 13 cmp DWORD PTR [ebp-0xc],0x13
8048430: 7e e3 jle 8048415 <main+0x1a>
8048432: b8 00 00 00 00 mov eax,0x0
8048437: 8b 4d fc mov ecx,DWORD PTR [ebp-0x4]
804843a: c9 leave
804843b: 8d 61 fc lea esp,[ecx-0x4]
804843e: c3 ret
804843f: 90 nop
Nessa função, podemos ver que é inicializada uma variável de stack em ebp-0xc como 0, e depois pula para 0x804842c (main_0x31):
mov DWORD PTR [ebp-0xc],0x0
jmp 804842c <main+0x31>
Olhando para as instruções em 0x804842c vemos isso:
cmp DWORD PTR [ebp-0xc],0x13
jle 8048415 <main+0x1a>
Isso compara o valor da stack em ebp-0xc contra 0x13 (19 em decimal), e se for menor ou igual então irá pular para 0x8048415 (0x80483fb+0x1a). Isso nos leva a uma call para uma função printf:
sub esp,0x8
push DWORD PTR [ebp-0xc]
push 0x80484c0
call 80482d0 <printf@plt>
Parece que isso está imprimindo o coteúdo de ebp-0xc em algum tipo de format string. Depois disso podemos ver que o valor de ebp-0xc é incremendado em 1, antes de fazer o cmp de novo:
add DWORD PTR [ebp-0xc],0x1
Certo, juntando todas as peças, agora nós estamos provavelmente olhando para um loop for que vai rodar 20 vezes, e printar o contador de iteração em cada uma das iterações. Isso parece algo similar a:
int i = 0;
for (i = 0; i < 20; i ++)
{
printf("%d", i);
}
Quando rodamos o binário, vemos que isso é verdade:
$ ./loop
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19