arrow-left

All pages
gitbookPowered by GitBook
1 of 4

Loading...

Loading...

Loading...

Loading...

Bibliotecas

As bibliotecas, ou DLLs no Windows, são também arquivos PE, mas sua intenção é ter suas funções utilizadas (importadas e chamadas) por arquivos executáveis. Elas também importam funções de outras bibliotecas, mas além disso, exportam funções para serem utilizadas.

Novamente, é possível utilizar o DIE para ver as funções importadas e exportadas por uma DLL, mas no exemplo a seguir, utilizamos novamente o dumpbin contra a biblioteca Shell32.dll, nativa do Windows:

C:\>dumpbin /exports %windir%\system32\shell32.dll | findstr /i shellab
        568  1A7 002D7D90 ShellAboutA
        569  1A8 002D7EC0 ShellAboutW

Utilizamos o comando findstr do Windows para filtrar a saída por funções que criam caixas de mensagem. Este comando é como o grep no Linux. A sua opção /i faz com que o filtro de texto ignore o case (ou seja, funciona tanto com letras maiúsculas quanto com minúsculas).

Para chamar uma função desta DLL, teríamos que criar um executável que a importe. No entanto, o próprio Windows já oferece um utilitário chamado rundll32.exe, capaz de chamar funções de uma biblioteca. Ele é bem limitado, mas para este exemplo funciona. A maneira via linha de comando é:

Como a função ShellAboutA() recebe um texto ASCII para ser exibido na tela "Sobre" do Windows, podemos testá-la da seguinte forma:

Utilizar o rundll32.exe para chamar funções de biblioteca não é a maneira mais adequada de fazê-lo e não funciona com todas as funções, principalmente as que precisam de parâmetros que não são do tipo string. Somente o utilizamos aqui para exemplificar a chamada de funções exportadas por uma DLL.

Tanto para DLLs quanto para executáveis, quando eles rodam, um processo é criado. Vamos agora ver que isto significa.

C:\>rundll32 <DLL>,<Função> <Parâmetros>

Executáveis

Normalmente chamamos de arquivos executáveis os arquivos que quando clicados duas vezes (no caso do Windows) executam. Os mais famosos no Windows são os de extensão .exe, que são o foco do nosso estudo. Para entender como estes arquivos são criados, é preciso notar os seguintes passos:

  1. Escrita do cĂłdigo-fonte na linguagem escolhida num arquivo de texto.

  2. Compilação.

  3. Linkedição (Linking).

O compilador cria os chamados arquivos objeto a partir do código-fonte. Estes objetos contém instruções de máquina e dados.

O linker serve para juntar todos os objetos num único arquivo, realocar seus endereços e resolver seus símbolos (nomes de função importadas, por exemplo).

O processo de compilação é a transformação do código-fonte em texto em código de máquina. Como saída, ele um arquivo chamado de objeto.

No que diz respeito ao processo de linking, estes executáveis podem ser de dois tipos:

hashtag
Estáticos

Todo o código das funções externas ao executável principal é compilado junto a ele. O resultado é um executável livre de dependências, porém grande. É raro encontrar executáveis assim para Windows.

hashtag
Dinâmicos

O executável vai depender de bibliotecas externas (DLLs, no caso do Windows) para funcionar e fará uso da Import Table conforme estudamos no capítulo anterior.

Como exemplo, vamos checar as dependências do binário da calculadora:

Mas e as DLLs, como "rodam"? Vejamos agora.

Processos

Processo é um objeto que representa uma instância de um executável rodando. No Windows, processos não rodam. Quem roda mesmo são as threads de um processo.

Um processo possui um PID (Process IDentificator), uma tabela de handles abertos (será explicado no capítulo sobre a Windows API), um espaço de endereçamento virtual, e outras informações associadas a ele.

Para ver os processos ativos no seu sistema Windows neste momento, vocĂŞ pode usar o Gerenciador de Tarefas (experimente apertar Ctrl+Shift+ESC) ou o comando tasklist:

Na saída do comando tasklist, a coluna Image Name mostra o nome do arquivo executável (o arquivo no disco) associado ao processo. Perceba que há vários processos do svchost.exe por exemplo. É normal.

Há muito mais para falar sobre processos, mas para nosso objetivo aqui, saber que eles representam um programa em execução é suficiente. Agora vamos entender como os programas fazem uso da API que o Windows oferece para que ações significativas ocorram no sistema.
C:\>tasklist

Image Name                     PID Session Name        Session#    Mem Usage
========================= ======== ================ =========== ============
System Idle Process              0 Services                   0          8 K
System                           4 Services                   0      1,888 K
Secure System                  188 Services                   0    273,300 K
Registry                       232 Services                   0     37,224 K
smss.exe                      1020 Services                   0      1,632 K
csrss.exe                     1292 Services                   0      7,452 K
wininit.exe                   1396 Services                   0      9,364 K
services.exe                  1472 Services                   0     12,892 K
LsaIso.exe                    1492 Services                   0      4,676 K
lsass.exe                     1500 Services                   0     41,256 K
svchost.exe                   1724 Services                   0     44,368 K
WUDFHost.exe                  1756 Services                   0      8,504 K
fontdrvhost.exe               1776 Services                   0      5,816 K
svchost.exe                   1888 Services                   0     20,828 K
svchost.exe                   1956 Services                   0     15,724 K
svchost.exe                   1320 Services                   0      6,924 K
svchost.exe                   1184 Services                   0     15,944 K
svchost.exe                   2108 Services                   0     13,308 K
svchost.exe                   2116 Services                   0     15,300 K
-- suprimido --
dumpbin /nologo /dependents c:\windows\system32\calc.exe

Dump of file c:\windows\system32\calc.exe

File Type: EXECUTABLE IMAGE

  Image has the following dependencies:

    SHELL32.dll
    KERNEL32.dll
    msvcrt.dll
    ADVAPI32.dll
    api-ms-win-core-synch-l1-2-0.dll
    api-ms-win-core-processthreads-l1-1-0.dll
    api-ms-win-core-libraryloader-l1-2-0.dll

Execução de Programas

hashtag
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 usuário e modo kernel, mais conhecidos por seus nomes 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 chamados de anéis (rings), numerados de 0 a 3, onde o anel zero é o mais privilegiado. 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", também chamada de 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. Essa comunicação é ilustrada no diagrama a seguir:

hashtag
DependĂŞncias

Quando alguém cria um programa, em muitos casos se utiliza de funções de bibliotecas (ou libraries em inglês), também chamadas de DLLs (Dynamic-Link Library). Sendo assim, analise o seguinte simples programa em C:

Este programa utiliza a função printf(), que não precisou ser implementada por quem o programou. Quem escreveu o código só precisou chamar a função, 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 da printf() está nela.

Esta arquitetura garante que diferetens programadores e programadoras usem tal função e que ela terá sempre o mesmo comportamento se usada da mesma forma. Mas você 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. Na verdade, a biblioteca de C, que contém a implementação da printf(), pede ao kernel através de uma função de sua API para que ele escreva na tela. O kernel, por sua vez, utiliza o driver da placa de vídeo que conhece a placa e a escrita acontece. Sendo assim, temos, neste caso um EXE que chama uma função de uma DLL que chama o kernel. Estudaremos mais a frente como essas chamadas acontecem.

hashtag
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 "contêiner" 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 Windows 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 Windows.

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, que Ă© quando ele de fato começa a rodar.

  • Diagrama simplificado da execução de programas no Windows
    #include <stdio.h>
    
    int main(void) {
        printf("Olá, mundo!\n");
    }