Atualizado: 16/11/2025

Este conteúdo é original e não foi gerado por inteligência artificial.

Código Independente de Posição (PIC) em Assembly NASM

A principal característica das bibliotecas compartilhadas é que o loader dinâmico pode carregá-las em qualquer endereço de memória disponível. De fato, essa é uma medida de segurança fundamental nos sistemas Linux modernos, conhecida como ASLR (Address Space Layout Randomization), que randomiza os endereços base da pilha, bibliotecas e do próprio executável para dificultar ataques.

Isso significa que tanto as bibliotecas quanto as aplicações devem ser escritas de uma maneira especial, de forma que possam funcionar corretamente independentemente de onde sejam carregadas na memória. Esse tipo de código é conhecido como código independente de posição, ou PIC (Position-Independent Code).

Para criar um código que seja PIC, precisamos ajustar a forma como ele acessa endereços de memória em três áreas principais:

  1. Chamadas a funções externas.
  2. Acesso a dados dentro da própria biblioteca (seção .data).
  3. Acesso a dados externos (em outras bibliotecas ou no executável principal).

1. Chamadas a Funções Externas (PLT)

As chamadas a funções externas são gerenciadas automaticamente pelo linker e pelo loader dinâmico através das tabelas PLT (Procedure Linkage Table) e GOT (Global Offset Table). Ao escrever o código, indicamos ao montador que a chamada deve ser resolvida via PLT:

call print wrt ..plt

A diretiva wrt ..plt (with regard to the PLT) instrui o montador a gerar uma instrução de chamada que salta para uma entrada na PLT, que por sua vez se encarrega de encontrar o endereço real da função print através da GOT.

2. Acesso a Dados Internos (Endereçamento Relativo ao RIP)

Para acessar dados definidos na seção .data da própria biblioteca ou executável, utiliza-se um modo de endereçamento conhecido como endereçamento relativo ao PC (Program Counter), que na arquitetura x86-64 corresponde ao registrador RIP (Instruction Pointer).

Em vez de usar um endereço absoluto, o código referencia os dados como um deslocamento (offset) a partir da instrução atual. Isso garante que, não importa onde o código seja carregado, a distância entre a instrução e o dado será sempre a mesma.

No NASM, isso é feito com o operador rel. Por exemplo, para obter o valor de uma variável:

section .data
    count: db 10
section .text
    mov al, [rel count]  ; Carrega o valor de count

E para obter o endereço de uma variável:

section .data
    message: db "Hello"
section .text
    lea rsi, [rel message] ; Carrega o endereço de message

3. Acesso a Dados Externos (GOT)

Quando uma biblioteca precisa acessar uma variável global definida no executável principal (ou em outra biblioteca), o acesso é feito através da GOT. A GOT conterá o endereço real da variável, que será preenchido pelo loader dinâmico.

mov rsi, [rel message wrt ..got] ; Carrega o endereço da variável externa 'message'

Criando um Executável Independente de Posição (PIE)

Um executável que utiliza código independente de posição é chamado de PIE (Position-Independent Executable). Vamos criar um exemplo.

Primeiro, a biblioteca print.asm (a mesma do artigo anterior):

global print:function
section .text
print:
    mov rdx, rdi
    mov rdi, 1
    mov rax, 1
    syscall
    ret

Compilamos a biblioteca:

$ nasm -felf64 print.asm -o print.o
$ ld -shared print.o -o libprint.so

Agora, o aplicativo principal app.asm, escrito como PIC:

global _start
extern print

section .data
    message: db "Hello World!", 10
    count:   db $ - message

section .text
_start:
    mov rdi, [rel count]      ; Acesso a dados internos relativo ao RIP
    lea rsi, [rel message]    ; Obtém endereço interno relativo ao RIP
    call print wrt ..plt      ; Chamada externa via PLT

    mov rax, 60
    syscall

Para compilar um executável PIE, passamos a flag -pie para o linker:

$ nasm -felf64 app.asm -o app.o
$ ld --dynamic-linker=/lib64/ld-linux-x86-64.so.2 -pie app.o -lprint -L. -o app
$ export LD_LIBRARY_PATH=.
$ ./app
Hello World!

Podemos verificar que o executável foi criado como PIE com o comando readelf -h app:

$ readelf -h app
ELF Header:
  ...
  Type:                              DYN (Position-Independent Executable file)
  ...

O campo Type confirma que o arquivo é um executável independente de posição.

Bibliotecas Independentes de Posição

A mesma lógica se aplica às bibliotecas. Se uma biblioteca precisa acessar dados de fora, ela deve fazê-lo via GOT. Vamos inverter nosso exemplo: a biblioteca print.so irá imprimir uma mensagem definida no aplicativo principal.

Arquivo print.asm modificado:

global print:function

extern message
extern count

section .text
print:
    lea rsi, [rel message wrt ..got]  ; Carrega o endereço de 'message' via GOT
    mov rdx, [rel count wrt ..got]    ; Carrega o endereço de 'count' via GOT
    mov rdx, [rdx]                    ; De-referencia para obter o valor de 'count'
    mov rdi, 1
    mov rax, 1
    syscall
    ret

Note que, como a GOT armazena o endereço da variável count, precisamos de uma segunda instrução para ler o valor contido nesse endereço.

Arquivo app.asm modificado, agora exportando as variáveis:

global _start
global message:data   ; Exporta o símbolo 'message' como dado
global count:data     ; Exporta o símbolo 'count' como dado
extern print

section .data
    message: db "Hello programicio.com!", 10
    .end:
    count: dq message.end - message

section .text
_start:
    call print wrt ..plt
    mov rax, 60
    syscall

A compilação segue os mesmos passos, e o resultado será o mesmo. A diferença é que agora a biblioteca está acessando dados que pertencem ao executável principal.


Resumo

  • Código Independente de Posição (PIC) é essencial para criar bibliotecas compartilhadas e executáveis seguros (PIE) que funcionam com ASLR.
  • Acesso a dados internos: Usa endereçamento relativo ao RIP com a diretiva rel.
  • Chamadas a funções externas: Usam a Tabela de Ligação de Procedimentos (PLT) com a diretiva wrt ..plt.
  • Acesso a dados externos: Usa a Tabela de Deslocamento Global (GOT) com a diretiva wrt ..got.
  • Para criar um executável PIE, a flag -pie deve ser passada ao linker ld.
Política de Privacidade

Copyright © www.programicio.com Todos os direitos reservados

É proibida a reprodução do conteúdo desta página sem autorização prévia do autor.

Contato: programicio@gmail.com