GOT e PLT

Introdução

Quero te mostrar a base primeiro para depois você ver a real necessidade da GOT e PLT, seja muito bem vindo e boa leitura.

Binário Estático: executa por conta própria, não depende de libc nem nada, tudo que ele necessita ele mesmo tem Binário Dinâmico: necessita de algo para executar, como por exemplo da libc, vamos supor que meu binário apenas exibe uma mensagem na tela com o puts(), o puts não fui eu que escrevi, ele está presente na libc, ou seja, para o meu programa funcionar eu preciso da libc, o linker faz o processo de linkagem da puts() com o meu programa

Mas o por que disso disso? Simples, imagine, se não houvesse a libc o trabalho que seria escrever todas as funções que conhecemos, e outra, imagina se houvesse uma atualização em alguma dessas funções, TODOS os binários do mundo inteiro teriam que ser alterados, pois é, a vida sem o libc seria árdua. Com a libc tudo fica mais fácil, houve uma atualização/modificação? Basta alterar a libc, o linker vai continuar pegando as funções e linkando com o seu binário.

Antes de prosseguir quero te mostrar que o binário é dependente da libc (claro, se ele for dinâmico, sendo sincero nunca vi uma binário estático, mas deve existir algum):

Binário dependente da libc

Na segunda linha você pode notar que há uma dependência da libc, e na terceira linha temos o linker usado, acredite em mim, você não vai querer apagar a sua libc, isso resultaria em um kernel panic provavelmente irreversível, o ls cd etc, comandos do linux não funcionariam mais, digo isso porque já apaguei a minha libc e mesmo com ela em mãos colocando novamente no lugar que ela pertence atráves do windows, não consegui mais acessar meu linux, resumo, tive que formatar o computador.

Enfim, você já viu sobre binários estáticos e dinâmicos, agora vamos falar sobre o lazy binding, isso tem mais a ver com o linker Lazy Binding: irá resolver os endereços, linka-los, apenas quando for chamado Isso ajuda em diversos fatores mas o principal deles: desempenho e tempo de compilação menor, mas claro que irá atrapalhar um pouco no desempenho do programa. Obs: se o RELRO estiver FULL, os endereços são resolvidos antes da execução e a GOT tem sua permissão setada apenas para read-only, o que impede qualquer tipo de hook

Agora vamos falar sobre GOT e PLT

Como fazemos para resolver os endereços, o que eu quero dizer é que antes do programa ser executados não temos o endereço da puts da libc por exemplo, isso vai ficar mais facil com exemplos.

Código simples para usar de exemplo

Veja que no meu exemplo eu simplesmente printo na tela Hello World! Uso no meu código a puts(), função da libc, não fui eu que escrevi a puts, ela está presente na libc, eu estou apenas usando ela, como acessamos ela? Precisamos do endereço dela certo? Correto, mas como o linker faz isso em tempo de execução veremos a seguir, vou explicar primeiro na teoria e depois vou explicar com exemplos.

A PLT (procedure linkage table) tem uma instrução de jmp que pula para a GOT (global offset table), o linker irá resolver o endereço e mandará para .got.plt, resumindo: => PLT: é responsável por armazenar o endereço que saltará para a GOT para ser resolvido => GOT: aqui temos os offsets das funções presentes na libc, aqui também é usado para chamar o linker para resolver o endereço => .got.plt: com o endereço resolvido ele é armazenado na .got.plt

E depois? Simples, com o endereço já resolvido quando for chamar a mesma função depois bastar chamar func@plt, a plt terá um ponteiro para .got.plt que já terá a função, tentei ser o mais clarso possível, vamos ver isso na prática em um binário de 32 bits, cujo o código é aquele mencionado lá em cima.

Disas main()

Veja na main+37 a chamada para puts@plt, vamos confirmar o endereço da PLT com o objdump:

Objdump puts()

Ele da um call justamente no 0x1040, perfeito até aqui, lembrando que estamos vendo isso antes de resolver o endereço, vamos dar uma olhado nesse endereço:

Disas puts()

Perceba que em ebx+0xc terá o nosso ponteiro para a função puts, ele ainda não está lá porque não executamos ainda a puts, vamos executar e ver isso

Analisando puts()

Eu coloquei um breakpoint bem depois da puts para que eu pudesse analisar, enfim, vemos que o endereço da puts@plt já mudou, vamos ver o que tem nele

Analisando puts()

Vamos com calma, no primeiro comando podemos ver que não mudou muita coisa, mas perceba que ebx+0xc agora foi resolvido vamos ver o endereço que tem nele já que ele é um ponteiro, ele está apontando para 0xf7e19c0, demos um x/5i que pega 5 instruções do endereço, e nele está justamente a nossa puts, se você quiser dar um disas no endereço da puts() fique a vontade mas é bem grandinho por isso peguei apenas 5 instruções.

Se mesmo assim ficou confuso olhe esse pequeno exemplo:

Exemplo 1

Bônus:

Falamos sobre GOT, PLT e .got.plt mas o que elas são? Elas são sessões, antes de sessões temos segmentos, veja um simples mapa:

Exemplo 2

Outras seções interessantes: => .symtab: Essa seção guarda todos os simbolos internos e externos => .dynsym: Essa seção guarda todos os simbolos externos *Ex: na dynsym tem o simbolo do printf [ printf(“oi”); ] …: na symtab tem o simbolo da main [ int main(){return 0} ]

Last updated