🚗 Execução de programas

Privilégios de execução

Para impedir que os programas do usuĂĄrio acessem ou modifiquem dados crĂ­ticos do sistema operacional, o Windows suporta dois nĂ­veis de execução de cĂłdigo: modo de usuĂĄrio e modo de kernel, mais conhecidos por suas variante em inglĂȘs: user mode e kernel mode.

Os programas comuns rodam em user mode, enquanto os serviços internos do SO e drivers rodam em kernel mode.

Apesar de o Windows e outros sistemas operacionais modernos trabalharem com somente estes dois níveis de privilégios de execução, os processadores Intel e compatíveis suportam quatro níveis, também chamado de anéis (rings), numerados de 0 a 3. Para kernel mode é utilizado o ring 0 e para user mode, o ring 3.

Programas rodando em user mode tampouco possuem acesso ao hardware do computador. Essencialmente, todos estes fatores combinados fazem com que os programas rodando neste privilégio de execução não gerem erros fatais como a famosa "tela azul da morte" (ou BSOD - Blue Screen Of Death).

Passa que toda a parte legal acontece em kernel mode, sendo assim, um processo (na verdade uma thread) rodando em user mode pode executar tarefas em kernel mode através da API do Windows, que funciona como uma interface para tal.

DependĂȘncias

Quando um programador cria um programa, em muitos casos ele utiliza funçÔes de bibliotecas (ou libraries em inglĂȘs), tambĂ©m chamadas de DLL (Dynamic-Link Library). Sendo assim, analise o seguinte simples programa em C:

#include <stdio.h>
​
int main(void) {
printf("OlĂĄ, mundo!\n");
return 0;
}

Este programa utiliza a função printf(), que não precisou ser implementada pelo programador. Ao contrårio, ele simplesmente a chamou, jå que esta estå definida no arquivo stdio.h.

Quando compilado, este programa terĂĄ uma dependĂȘncia da biblioteca de C (arquivo msvcrt.dll no Windows) pois o cĂłdigo para a printf() estĂĄ nela.

Tudo isso garante que vårios programadores usem tal função, que tenha sempre o mesmo comportamento se usada da mesma forma. Mas jå parou para pensar como a função printf() de fato escreve na tela? Como ela lidaria com as diferentes placas de vídeo, por exemplo?

O fato é que a printf() não escreve diretamente na tela. Ao contrårio, a biblioteca de C pede ao kernel através de uma função de sua API que seja feito. Sendo assim, temos, neste caso um EXE que chama uma função de uma DLL que chama o kernel. Estudaremos mais a frente como isso é feito.

Loader

Quando um programa é executado (por exemplo, com um duplo-clique no Windows), ele é copiado para a memória e um processo é criado para ele. Dizemos então que um processo estå rodando, mas esta afirmação não é muito precisa: na verdade, todo processo no Windows possui pelo menos uma thread e ela sim é que roda. O processo funciona como um "container" que contém vårias informaçÔes sobre o programa rodando e suas threads.

Quem cria esse processo em memĂłria Ă© um componente do sistema operacional chamado de image loader, presente na biblioteca ntdll.dll.

O código do loader roda antes do código do programa a ser carregado. É um código comum a todos os processos executados no sistema operacional.

Dentre as funçÔes do loader estão:

  • Ler os cabeçalhos do arquivo PE a ser executado e alocar a memĂłria necessĂĄria para a imagem como um todo, suas seçÔes, etc.

    • As seçÔes sĂŁo mapeadas para a memĂłria, respeitando-se suas permissĂ”es.

  • Ler a tabela de importaçÔes do arquivo PE a fim de carregar as DLLs requeridas por este e que ainda nĂŁo foram carregadas em memĂłria. Esse processo tambĂ©m Ă© chamado de resolução de dependĂȘncias.

  • Preencher a IAT com os endereços das funçÔes importadas.

  • Carregar mĂłdulos adicionais em tempo de execução, se assim for pedido pelo executĂĄvel principal (tambĂ©m chamado de mĂłdulo principal).

  • Manter uma lista de mĂłdulos carregados por um processo.

  • Transferir a execução para o entrypoint (EP) do programa.