Copie o repositório Git digitando git clone https://github.com/mentebinaria/engrev.git
. Depois entre no diretório engrev e crc32, e compile o programa para 32 bits digitando gcc -m32 *c -no-pie -o keygenme
.
Digite no terminal hte keygenme
, onde podemos ver os bytes e dados em geral do programa. Falamos dos headers anteriormente (no Windows costumamos chamar de segmentos, no Linux como sessões). Digite F6 para vermos as sessões do programa.
O segmento do tipo phdr é onde está o program header (entry 0). Os mais importantes no momento é os segmentos do tipo load, que serão carregados (mapeados) na memória, e dentro deles.
Podemos ver as sessões do Assembly do programa digitando F6 e section header.
A sessão PLT é como se fosse o import table do PE, que é onde ficam as chamadas para funções externas (de bibliotecas).
Vá na sessão .symtab, e depois na .dynsym (no F6, lá embaixo aparecerão), lá está as chamadas externas de funções, além de objetos e coisas de outros tipos. Algumas funções, como o printf e o puts, reconhecemos do C.
Podemos olhar os códigos-fonte do programa em C. São os mesmos na sessão .symtab.
Muitos das funções na .symtab são usados apenas para fins de debug, e são locais (mas tem alguns externos), enquanto na .dynsym só teremos as funções externas. Podemos digitar no terminal gdb -q ./keygenme
e digitar break main
para colocar um breakpoint na função main do programa.
Se dermos no terminal o comando strip -s keygenme
, ele suprimirá a função main, e não irá aparecer mais a sessão .symtab no programa. Nesse caso, podemos usar o comando do gdb de novo e tentar dar um break no main, que dará erro por não estar mais no programa.
Compile o programa novamente, pois corrompemos o mesmo.
Volte aos sections headers e vá no .plt, que é onde estão os endereços das funções externas. Uma biblioteca dinâmica é carregada no sistema ao executar o programa, o printf por exemplo tá na libc. Digite o comando nm keygenme | less
para vermos os símbolos do binário, onde veremos todas as bibliotecas das quais pertencem cada função.
Digite gdb -q ./keygenme
e dentro do programa, digite disassemble main
, onde vemos o código Assembly do main. Faça o mesmo com printf e com o endereço do mesmo.
Entre no hte novamente, clique F6 e vá em elf/image, onde está o programa desassemblado. Digitando F8 podemos ver as funções em Assembly, incluindo o main. A sessão .got.plt é onde estará o endereço real da função printf.
Syscalls no Linux são os equivalente as API do Windows, mas funcionam de forma parecida.
Vamos usar o programa ltrace para nossas análises. Vamos supor esse programa em C:
#include <stdio.h>
int main(int argc, char *argv[]) {
printf("O número é: %d\n", 255);
return 0;
}
Dê um make no programa pra compilar ele.
Quando um programa em C é executado, ele chama a função printf, que é nativa do C, presente em uma determinada biblioteca (podemos dar um ldd no programa pra ver elas), mas quem exibirá a mensagem mesmo é o kernel. Dê um ltrace ./hello
para ver quais funções foram executadas.
PS: O número 17 que aparecerá é o número de caracteres que o printf retorna. Para ver os retornos digite man 3 printf
.
Dê o comando ltrace ls
para vermos as funções que o programa ls chama. Dê o comando ltrace -S ./hello
para ver as syscalls que as funções em C chamam. Um exemplo é a SYS_write()
, syscall chamada pelo printf.
Resumindo, as syscalls são funções nativas do kernel.
Vamos refazer o programa em C, chamando a função write ao invés do printf:
#include <unistd.h>
int main(int argc, char *argv[]) {
char* msg = "Oi\n";
write(STDOUT_FILENO, msg, 3);
return 0;
}
Mesmo usando outra função (write()), ele chama a mesma syscall SYS_write(), até porque a linguagem permanece sendo a mesma (C).
PS: Da mesma forma que acontece no Windows, ao usar uma linguagem como a Java (em qualquer sistema, Windows, Linux, Mac, BSD, etc.), por exemplo, a System.out.println() chama a printf do C que consequentemente chama a mesma syscall SYS_write.