Pular para o conteúdo principal

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