A chamada exit()
do seu código acaba sendo vinculada à função da biblioteca C (libc) exit()
, que pode na verdade não fazer o int $0x80
.
A chamada da invocação do seu código da função exit()
é, na verdade, compilada como call
na Tabela de Ligação do Programa, ou PLT. O vinculador dinâmico de tempo de execução cuida do mapeamento do arquivo /usr/lib/libc.so
na memória. Essa é a biblioteca C. O vinculador dinâmico de tempo de execução também corrige entradas no PLT para, eventualmente, acabar chamando o código mapeado em /usr/lib/libc.so
.
Por mais que eu saiba (estou usando o Arch linux), suas segundas 3 instruções são a entrada PLT, que gdb
chama de "exit @ plt" quando eu dou um passo para dentro dela. O jmp 0x80482c0
pula para outro endereço que finalmente salta para o código libc.so
.
Você pode demonstrar isso para si mesmo com um exercício bastante complicado. Primeiro, você tem o endereço da entrada da tabela PLT, seja qual for o gdb
que é o endereço do jmp *0x8049698
- esse é o endereço de "exit @ plt". Na minha caixa xux Arch linux:
(gdb) disassemble 0x8048310,+20
Dump of assembler code from 0x8048310 to 0x8048324:
0x08048310 <exit@plt+0>: jmp *0x80496e8
0x08048316 <exit@plt+6>: push $0x10
0x0804831b <exit@plt+11>: jmp 0x80482e0
Em seguida, faça readelf -e _program_ > elf.headers
. Procure no arquivo elf.headers
. Você encontrará uma linha de texto que diz "Cabeçalhos da seção:" Em algum lugar nos cabeçalhos da seção, você verá algo assim:
[ 9] .rel.dyn REL 08048290 000290 000008 08 A 5 0 4
[10] .rel.plt REL 08048298 000298 000020 08 AI 5 12 4
[11] .init PROGBITS 080482b8 0002b8 000023 00 AX 0 0 4
[12] .plt PROGBITS 080482e0 0002e0 000050 04 AX 0 0 16
"exit @ plt" está no endereço 0x8048310. É isso mesmo na seção ".rel.plt". ".rel.plt" provavelmente significa "tabela de ligação do programa de realocação".
Agora chegamos à parte em que o int $0x80
pode nem existir. Do ldd _program_
. Novamente, Arch linux x86 diz isso:
linux-gate.so.1 (0xb77d9000)
libc.so.6 => /usr/lib/libc.so.6 (0xb7603000)
/lib/ld-linux.so.2 (0xb77da000)
Veja que "linux-gate.so.1"? Isso contém o código real que faz a chamada do sistema. Pode ser int $0x80
, ou pode ser uma instrução sysenter
, ou pode ser outra coisa. Supõe-se que o kernel Linux coloque uma "pequena biblioteca compartilhada" no espaço de endereço de um processo com o código real e, em seguida, entregue o endereço dessa pequena biblioteca compartilhada no "vetor auxiliar" do ELF. Do man vdso
para alguns detalhes. O vinculador dinâmico, /lib/ld-linux.so.2
, conhece os detalhes do vetor auxiliar ELF e, em última instância, coloca o endereço linx-gate.so.1
no PLT em algum lugar, portanto chamadas de função C reais podem acabar fazendo chamadas de sistema eficientes.
Se você fizer várias chamadas de ldd _program_
, verá que o endereço de linux-gate.so.1
não é o mesmo de invocação para invocação. O kernel na verdade não coloca o topo da pilha no mesmo endereço todas as vezes para tentar confundir o malware que precisa conhecer os locais da pilha para obter seu próprio código executado.