A memória
Quando o programa é chamado, as sessões são mapeadas para o segmento do processo, e os segmentos são carregados na memória como descritos pelo arquivo ELF.
.text | .data | .bss | Heap | empty space | Stack |
---|
.text
A secção .text
contem a instrução em Assembly do programa. Essa área costuma ser read-only
para prevenir que o processo acidentalmente modifique as instruções. Qualquer tentativa de sobrescrever essas instruções vai, inevitavelmente, resultar em segmentation fault
.
.data
O secção .data
contém variáveis globais e estáticas que estão explicitamente inicializadas com o programa.
.bss
Diversos compiladores e linkers usam a secção .bss
como parte do segmento .data
, que contém variáveis alocadas estaticamente representadas exclusivamente por bits 0.
Heap
A memória heap é armazenada nessa área. Ela termina logo após o .bss
e chega até o endereço mais alto.
Stack
A memória stack é uma estrutura de Last-In-First-Out onde são armazenados endereços de retorno, parâmetros, e, dependendo das opções do compilador, frame de ponteiros são armazenados. Variáveis locais de C e C++ são armazenadas aqui, e você até pode copiar código para a stack. A stack é definida na memória RAM. O linker normalmente coloca a stack no ponto mais baixo abaixo das variáveis globais e estáticas. O conteúdo é acessado através de um ponteiro, definido como a parte mais acida da pilha durante a inicialização. Durante a execução, a parte alocada cresce para baixo até o menor endereço de memória.
Memórias modernas possuem proteção (como DEP/ASLR) que previnem que danos sejam causados por Buffer overflow. DEP (Data Execution Prevention), marca regiões como “read-only”. As áreas marcadas como read-only são regiões onde algumas entradas do usuário são armazenadas (exemplo: o stack), então, a ideia por trás do DEP é prevenir que usuários consigam enviar códigos shell e mudando o ponteiro da memória para o código shell, fazendo com que o programa execute o código.
Ponto mais baixo vs Ponto mais alto
Quando estamos dizendo a respeito da memória da aplicação (memória RAM), dizemos que o ponto mais baixo é o maior endereço de memória, nesse caso, poderia ser um
0xFFFFFFFF
, e a memória mais alta é o menor endereço de memória, que pode ser0x00000000
Registradores
Registradores são componentes essenciais numa CPU. Quase todo registrador oferece uma pequena quantidade de espaço quando uma informação é temporariamente armazenada. Contudo, alguns deles possuem uma funcionalidade especifica.
Esses registradores são divididos em Registradores gerais, Registradores de controle e Registradores de segmento. Os registradores mais críticos são os registradores gerais. Nesses, existe uma subdivisão ainda maior entre Registradores de Informação, Registradores de ponteiro e Registradores de indexadores.
Registradores de dados
32-bit Register | 64-bit Register | Descrição | Outras informações |
---|---|---|---|
EAX | RAX | Acumulador usado para operações aritméticas e input/output | |
EBX | RBX | Base é utilizada para indexar o endereçamento | Extended BX: Registrador de proposito geral. (?) |
ECX | RCX | Contador é usado para rotacionar instruções e contar loops | |
EDX | RDX | Data é usado para I/O e operações de multiplicação e divisão com grandes números |
Registradores de ponteiro
32-bit Register | 64-bit Register | Descrição |
---|---|---|
EIP | RIP | Ponteiro de Instruções armazenam o desvio de endereço para a próxima instrução a ser executada |
ESP | RSP | Ponteiro de Stack aponta para o topo da Stack |
EBP | RBP | Ponteiro da Base aponta para a base da Stack |
Intel vs MT&T
Existem duas sintaxes diferentes para a linguagem Assembly, uma delas é a Intel, e a outra é a MT&T. Existem algumas diferenças entre elas, uma delas, é a distinção entre a instrução mov
.
Intel
Instrução | Destino | Fonte |
---|---|---|
mov | ebp | esp |
AT&T
Instrução | Fonte | Destino |
---|---|---|
mov | %esp | %ebp |
Stack Frames
Os stacks frames são divisões lógicas na memória para são reservadas para funções e partes especificas do código. Um stack frame é definido com um começo (EBP
) e um fim (ESP
), que é colocado na bateria assim que a função é chamada.
Desde que a memória stack seja construída numa estrutura de dados LIFO (parecida uma pilha), o primeiro passo é armazenar o EBP
anterior (o ponteiro da base anterior) na pilha, que depois pode ser restaurada quando a função terminar.
Cuidado
Perceba-se que eu usei o termo armazenar, o que significa que somente o valor daquele ponteiro está sendo armazenado na pilha, mas o ponteiro não está sendo alterado nessa etapa.
Prologo
0x0000054d <+0>: push ebp # <---- 1. Stores previous EBP
0x0000054e <+1>: mov ebp,esp
0x00000550 <+3>: push ebx
0x00000551 <+4>: sub esp,0x404
<...SNIP...>
0x00000580 <+51>: leave
0x00000581 <+52>: ret
Depois, a base EBP
é movida para o topo do stack
Portanto, a pilha foi disso:
0x00000000 | |
---|---|
0x00000001 | |
0x00000002 | |
0x00000003 | |
0x00000004 | ESP |
0x00000005 | <main stack frame> |
0x00000006 | argv |
0x00000007 | argc |
0x00000008 | EBP |
Para isso: |
0x00000000 | |
---|---|
0x00000001 | |
0x00000002 | EBX |
0x00000003 | 0x0000008 |
0x00000004 | ESP ,EBP |
0x00000005 | <main stack frame> |
0x00000006 | argv |
0x00000007 | argc |
0x00000008 | |
E depois, move-se o ESP , relativo ao topo da stack, para a diferença entre os tamanhos das variáveis, para deixar um espaço para as variáveis locais da função em específico |
0x0000054d <+0>: push ebp # <---- 1. Stores previous EBP
0x0000054e <+1>: mov ebp,esp # <---- 2. Creates new Stack Frame
0x00000550 <+3>: push ebx
0x00000551 <+4>: sub esp,0x404 # <---- 3. Moves ESP to the top
<...SNIP...>
0x00000580 <+51>: leave
0x00000581 <+52>: ret
0x00000000 | ESP |
---|---|
0x00000001 | <func stack frame> |
0x00000002 | EBX |
0x00000003 | 0x0000008 |
0x00000004 | EBP |
0x00000005 | <main stack frame> |
0x00000006 | argv |
0x00000007 | argc |
0x00000008 |
Epilogo
Para sair do stack frame, o contrário é feito, o epilogo. Durante o epilogo, o ESP
é trocado com o EBP
atual, e seu valor reseta para o valor que tinha anteriormente antes do prologo. O Epilogo é relativamente pequeno, e existem outras maneiras de fazê-lo. No nosso exemplo, é feito com duas funções:
0x0000054d <+0>: push ebp
0x0000054e <+1>: mov ebp,esp
0x00000550 <+3>: push ebx
0x00000551 <+4>: sub esp,0x404
<...SNIP...>
0x00000580 <+51>: leave # <----------------------
0x00000581 <+52>: ret # <--- Leave stack frame
Registradores de indexação (Index registers)
Register 32-bit | Register 64-bit | Descrição |
---|---|---|
ESI | RSI | Source Index é usado como ponteiro entre operações de string |
EDI | RDI | Destination é usado como ponteiro para operações de string |