Import Table

Foi visto que um dos diretórios de dados presentes no cabeçalho opcional é reservado para a Import Table, também conhecida por sua sigla IT. Nele há um ponteiro para a IDT (Import Directory Table), apontado pelo valor do campo VirtualAddress.

A IDT, por sua vez, é um array de estruturas do tipo IMAGE_IMPORT_DESCRIPTOR definidas a seguir:

typedef struct {
union {
uint32_t Characteristics;
uint32_t OriginalFirstThunk; // Endereço da ILT
} u1;
uint32_t TimeDateStamp;
uint32_t ForwarderChain;
uint32_t Name; // Endereço do nome da DLL
uint32_t FirstThunk; // Endereço da IAT
} IMAGE_IMPORT_DESCRIPTOR;

Não se deve confundir a IDT (Import Descriptor Table) com a IDT (Interrupt Descriptor Table). Esta última é uma estrutura que mapeia interrupções para seus devidos handlers, assunto que não é coberto neste livro.

O número de elementos do array de estruturas IMAGE_IMPORT_DESCRIPTOR é igual ao número de bibliotecas que o executável PE depende, ou seja, o número de DLL's das quais o executável importa funções. Há ainda um elemento adicional, completamente zero (preenchido com null bytes) para indicar o fim do array.

OriginalFirstThunk / rvaImportLookupTable

O campo OriginalFirstThunk (chamado de rvaImportLookupTable em algumas literaturas) aponta para o que chamamos Import Lookup Table (ILT), um array de números de 32-bits (64-bits para PE32+), onde seu bit mais significativo (MSB - Most Significant Bit) define se a função será importada por número ordinal (caso o bit seja 1). Já no caso de este bit estar zerado, a importação da função dá-se por nome e os outros 31 bits do número representam um endereço para uma estrutura que finalmente contém o nome da função.

Sendo assim, o número de elementos do array ILT é igual ao número de funções importadas por uma DLL em particular, definida na estrutura IMAGE_IMPORT_DESCRIPTOR.

FirstThunk

Este campo aponta finalmente para o que chamamos de IAT (Import Address Table), muito conhecida dos engenheiros reversos. Essa tabela é em princípio idêntica à Import Lookup Table, mas no processo de carregamento do executável (load time, que estudaremos mais à frente no livro), é preenchida com os endereços reais das funções importadas. Isto porque um executável dinamicamente linkado não sabe ainda qual o endereço de cada função de cada DLL que ele precisa chamar.

É importante lembrar o conceito de biblioteca compartilhada aqui. A ideia é ter apenas uma cópia dela carregada em memória e todos os programas que a utilize possam chamar suas funções. Por isso todo este esquema de preenchimento da IAT pelo loader é necessário.

Para fixar o conteúdo, é interessante validar tais informações. O exemplo abaixo utiliza o DIE para ver o diretório de dados de um arquivo PE:

Diretório de dados de um arquivo PE

Neste exemplo o campo VirtualAddress do Import Directory tem o valor 0x3000. Este é um endereço relativo, que você aprenderá sobre na próxima seção, no entanto, por agora você precisa apenas saber que este endereço é somado ao ImageBase para funcionar. No caso, o ImageBase deste binário é 0x400000 (muito comum), então o endereço da Import Descriptor Table, apontado por este campo VirtualAddress, é 0x403000.

Perceba que o DIE chama o campo VirtualAddress dos diretórios apenas de Address.

Ao clicar no botão "H" à direita do diretório IMPORT, vemos o conteúdo do primeiro elemento do array IDT, que é justamente o campo OriginalFirstThunk:

Valor do campo OriginalFirstThunk do primeiro elemento do array IDT

Neste exemplo o valor é 0x00003078 (lembre-se que números são armazenados em little-endian). Sendo novamente um endereço relativo, então o endereço da ILT é 0x403078.

Seguindo este endereço, encontramos achamos a ILT, que é um array de números de 32-bits como já dissemos:

O primeiro número deste array é então 0x000032cc. Como seu MSB está zerado, sabemos que se trata de uma importação por nome (e não por número da função). Se seguirmos este endereço, novamente somando o ImageBase, finalmente chegamos ao nome da função:

A estrutura que contém o nome da função é chamada de Hint/Name Table **onde o nome da função começa no terceiro byte, neste caso, em 0x4032ce. O tamanho do nome é variável (naturalmente o tamanho em bytes do nome de uma função pode variar).