Sobreescrevendo a GOT via LD_PRELOAD

Para quem não conhece, a GOT (Global Offset Table) é um seção de leitura/escrita no segmento de dados que é usado por um objeto para localizar símbolos em outros objetos.

Usando a mesma idéia de post anterior, onde usamos LD_PRELOAD para interceptar a chamada da função puts(), faremos agora alterando o endereço na GOT para apontar diretamente para uma função.

$ cat hello.c
#include <stdio.h>
 
int main(int argc, char **argv) {
	puts("Hello ");
	puts("World!");
	return 0;
}

Compilamos e então usamos readelf para verifica qual é o endereço na GOT para a função puts().

$ gcc -o hello hello.c
$ readelf -r hello
 
Relocation section '.rela.dyn' at offset 0x370 contains 1 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000600878  000100000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0
 
Relocation section '.rela.plt' at offset 0x388 contains 2 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000600898  000200000007 R_X86_64_JUMP_SLO 0000000000000000 puts + 0
0000006008a0  000300000007 R_X86_64_JUMP_SLO 0000000000000000 __libc_start_main + 0

Pronto! Sabemos que o endereço virtual na GOT para a função puts() é 0x000000600898, então é esse endereço que iremos usar para apontar para nossa função na shared library usada como preload. Segue abaixo o código:

#define _GNU_SOURCE
#include <stdio.h>
#include <stdint.h>
 
void my_puts(const char* str) {
	puts("[Minha versão da puts()]");
	puts(str);
}
 
void __attribute__((constructor)) change_got(void) {
	uintptr_t *puts_ptr = (uintptr_t*)0x000000600898;
 
	*puts_ptr = (uintptr_t)my_puts;
}

Então vamos compilar e testar!

$ gcc -shared -fPIC -o preload.so preload.c
$ LD_PRELOAD="./preload.so" ./hello
[Minha versão da puts()]
Hello 
[Minha versão da puts()]
World!

Note que usamos normalmente a função puts() no preload.c, pois como cada objeto possui a sua GOT, a nossa alteração foi apenas na GOT do executável hello.

Se você não está familiarizado com trabalho da GOT e PLT, vamos analisar melhor tudo isso no GDB!

Primeiro, sem nossa biblioteca como preload:

$ gdb --nx -q hello
Reading symbols from /home/felipe/stuff/got/hello...(no debugging symbols found)...done.
(gdb) disas main
Dump of assembler code for function main:
0x00000000004004e4 <main+0>:	push   %rbp
0x00000000004004e5 <main+1>:	mov    %rsp,%rbp
0x00000000004004e8 <main+4>:	sub    $0x10,%rsp
0x00000000004004ec <main+8>:	mov    %edi,-0x4(%rbp)
0x00000000004004ef <main+11>:	mov    %rsi,-0x10(%rbp)
0x00000000004004f3 <main+15>:	mov    $0x4005fc,%edi
0x00000000004004f8 <main+20>:	callq  0x4003e0 <puts@plt>
0x00000000004004fd <main+25>:	mov    $0x400603,%edi
0x0000000000400502 <main+30>:	callq  0x4003e0 <puts@plt>
0x0000000000400507 <main+35>:	mov    $0x0,%eax
0x000000000040050c <main+40>:	leaveq 
0x000000000040050d <main+41>:	retq   
End of assembler dump.
(gdb) b *0x4003e0
Breakpoint 1 at 0x4003e0
(gdb) r
Starting program: /home/felipe/stuff/got/hello 
 
Breakpoint 1, 0x00000000004003e0 in puts@plt ()
(gdb) x/3i $rip
0x4003e0 <puts@plt>:	jmpq   *0x2004b2(%rip)        # 0x600898 <_GLOBAL_OFFSET_TABLE_+24>
0x4003e6 <puts@plt+6>:	pushq  $0x0
0x4003eb <puts@plt+11>:	jmpq   0x4003d0
(gdb) x/a 0x600898
0x600898 <_GLOBAL_OFFSET_TABLE_+24>:	0x4003e6 <puts@plt+6>
(gdb) # Perceba que nesta primeira chamada da puts(), a GOT está com o endereço
(gdb) # para a próxima instrução, isto porque o dynamic-linker não resolveu o endereço do 
(gdb) # símbolo
(gdb) stepi
0x00000000004003e6 in puts@plt ()
(gdb) x/i $rip
0x4003e6 <puts@plt+6>:	pushq  $0x0
(gdb) 
0x4003eb <puts@plt+11>:	jmpq   0x4003d0
(gdb) stepi
0x00000000004003e6 in puts@plt ()
(gdb) stepi
0x00000000004003eb in puts@plt ()
(gdb) x/i $rip
0x4003eb <puts@plt+11>:	jmpq   0x4003d0
(gdb) stepi
0x00000000004003d0 in ?? ()
(gdb) x/i $rip
0x4003d0:	pushq  0x2004b2(%rip)        # 0x600888 <_GLOBAL_OFFSET_TABLE_+8>
(gdb) stepi
0x00000000004003d6 in ?? ()
(gdb) x/i $rip
0x4003d6:	jmpq   *0x2004b4(%rip)        # 0x600890 <_GLOBAL_OFFSET_TABLE_+16>
(gdb) stepi
_dl_runtime_resolve () at ../sysdeps/x86_64/dl-trampoline.S:30
30	../sysdeps/x86_64/dl-trampoline.S: No such file or directory.
	in ../sysdeps/x86_64/dl-trampoline.S
Current language:  auto
The current source language is "auto; currently asm".
(gdb) # pronto, nesta função ele irá resolver o endereço do símbolo, então que na segunda
(gdb) # execução ele já vai ter o endereço absoluto na GOT para a implementação da função 
(gdb) # puts().
(gdb) c
Continuing.
Hello 
 
Breakpoint 1, 0x00000000004003e0 in puts@plt ()
(gdb) x/3i $rip
0x4003e0 <puts@plt>:	jmpq   *0x2004b2(%rip)        # 0x600898 <_GLOBAL_OFFSET_TABLE_+24>
0x4003e6 <puts@plt+6>:	pushq  $0x0
0x4003eb <puts@plt+11>:	jmpq   0x4003d0
(gdb) x/a 0x600898
0x600898 <_GLOBAL_OFFSET_TABLE_+24>:	0x7ffff7ae2db0 <_IO_puts>
(gdb) # viu só?

Agora veremos que como o endereço na GOT referente a puts() do executável `hello' é escrito no preload.so, não haverá segundo passo. Pois é como se nós estivessemos resolvido o endereço no lugar do dynamic-linker.

$ gdb --nx -q hello
Reading symbols from /home/felipe/stuff/got/hello...(no debugging symbols found)...done.
(gdb) set exec-wrapper env LD_PRELOAD="./preload.so"
(gdb) disas main
Dump of assembler code for function main:
0x00000000004004e4 <main+0>:	push   %rbp
0x00000000004004e5 <main+1>:	mov    %rsp,%rbp
0x00000000004004e8 <main+4>:	sub    $0x10,%rsp
0x00000000004004ec <main+8>:	mov    %edi,-0x4(%rbp)
0x00000000004004ef <main+11>:	mov    %rsi,-0x10(%rbp)
0x00000000004004f3 <main+15>:	mov    $0x4005fc,%edi
0x00000000004004f8 <main+20>:	callq  0x4003e0 <puts@plt>
0x00000000004004fd <main+25>:	mov    $0x400603,%edi
0x0000000000400502 <main+30>:	callq  0x4003e0 <puts@plt>
0x0000000000400507 <main+35>:	mov    $0x0,%eax
0x000000000040050c <main+40>:	leaveq 
0x000000000040050d <main+41>:	retq   
End of assembler dump.
(gdb) b *0x4003e0
Breakpoint 1 at 0x4003e0
(gdb) r
Starting program: /home/felipe/stuff/got/hello 
 
Breakpoint 1, 0x00000000004003e0 in puts@plt ()
(gdb) x/3i $rip
0x4003e0 <puts@plt>:	jmpq   *0x2004b2(%rip)        # 0x600898 <_GLOBAL_OFFSET_TABLE_+24>
0x4003e6 <puts@plt+6>:	pushq  $0x0
0x4003eb <puts@plt+11>:	jmpq   0x4003d0
(gdb) x/a 0x600898
0x600898 <_GLOBAL_OFFSET_TABLE_+24>:	0x7ffff7bde60c <my_puts>

Até o próximo post! :)

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

More information about formatting options