domingo, 27 de setembro de 2015

Formato PE(Portable Executable) e funções

A dll Dbghelp e ImageHlp do windows,tem apis que servem exatamente para esse propósito.Com ela é possível que você veja todas as informações(úteis e um pouco inúteis)que os módulos(exe,dll)que estão mapeados no seu espaço de endereço,guardam.Você pode pensar em um módulo como sendo um arquivo cheio de estruturas.Se você fosse fazer um debugger independente das versões do windows,essas apis seriam a maneira mais prática para os ler.Nós vamos ver o que essas funções fazem e se possível acessarmos nós mesmos diretamente essas informações.

As estruturas que vão ser vistas aqui podem ser encontradas no header "WinNT" do seu compilador,se você quiser saber mais sobre seus campos,acesse https://msdn.microsoft.com/pt-br/library/windows/desktop/ms680198%28v=vs.85%29.aspx .
Um módulo PE é composto basicamente de :
IMAGE_XXX_HEADERS - estruturas usadas para representar cabeçalhos
IMAGE_SECTION_HEADER - estrutura geral para representar seções
IMAGE_XXX_DIRECTORY - estruturas usadas para representar diretórios,(para alguns só chamados de tabelas,para outros até dado cru),muitas vezes isso fica dentro da própria seção.

Considerados "PE HEADERS"  são :
IMAGE_NT_HEADERS,IMAGE_FILE_HEADER ,IMAGE_OPTIONAL_HEADER
Veja que todas as estruturas que formam o PE HEADER estão dentro de IMAGE_NT_HEADERS

struct _IMAGE_NT_HEADERS {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;

}

Logo após os HEADERS vem as seções :
.text ,.data,.bss ,.CRT,.rdata,.rsrc,.edata,.rdata,.idata,.reloc,.icode,.debug
 
Para você acessar os diretórios a primeira coisa é saber quem guarda os deslocamentos para esses diretórios,e isso fica nos headers.
Para você acessar seções basta deslocar o espaço de todos os headers juntos.

"IMAGE_NT_HEADERS" é o começo do PE HEADER,para ler e acessar esse header você usa uma estrutura IMAGE_NT_HEADERS com a função "ImageNtHeader"
IMAGE_NT_HEADERS *pheadernt = ImageNtHeader(EnderecoBasedoModulo);
 
Primeiro vamos o que ImageNtHeader faz :
PIMAGE_NT_HEADERS ImageNtHeader(PVOID base)
{
    IMAGE_DOS_HEADER *pidh_apoio;
    IMAGE_NT_HEADERS *pnt = NULL;

     if(base != NULL && (int)base != -1)
//Verifica a validade do ponteiro passado
    {
        pidh_apoio = (IMAGE_DOS_HEADER*)base;
        if(pidh_apoio->e_magic == IMAGE_DOS_SIGNATURE && pidh_apoio->e_lfanew < 0x10000000)
        {
            pnt = (IMAGE_NT_HEADERS*)((char*)base + pidh_apoio->e_lfanew);
            if(pnt->Signature != (DWORD)"PE")
            {
                pnt = NULL;
            }
        }
    }

    return pnt;
}


Comentários 
1 - No pedaço "pidh_apoio = (IMAGE_DOS_HEADER*)base;" ,nos mostra que todo executável tem logo no começo,uma estrutura "IMAGE_DOS_HEADER"
2 - IMAGE_DOS_SIGNATURE é uma assinatura padrão "MZ" , o membro "e_lfanew" tem o endereço relativo de onde começa realmente a estrutura "IMAGE_NT_HEADERS" do módulo,
veja que ele é somado ao endereço base de onde módulo foi carregado.O membro "Signature" fica no deslocamento 0 da estrutura,logo a constante "PE" deve estar ali.

"ImageDirectoryEntryToData",é uma função que simplismente retorna o diretório desejado.
Tipos de diretórios :
IMAGE_DIRECTORY_ENTRY_ARCHITECTURE
Dados sobre arquitetura

 
IMAGE_DIRECTORY_ENTRY_BASERELOC        
Se o módulo não pode ser carregado no seu endereço base preferido,logo essa seção será usada(dificilmente é usada)
IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT
Se o módulo foi bindado com os endereços para tais dados

IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR       
Dados sobre interface COM

IMAGE_DIRECTORY_ENTRY_DEBUG
Dados sobre como debugar o módulo(diretório para o arquivo de símbolos por exemplo)
IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT
Dados sobre dlls que devem esperar por algum motivo para serem carregadas

IMAGE_DIRECTORY_ENTRY_EXCEPTION
Dados sobre exceções

IMAGE_DIRECTORY_ENTRY_EXPORT
Dados que uma dll exporta(geralmente módulos exe não exportam nada)

IMAGE_DIRECTORY_ENTRY_GLOBALPTR
Endereço relativo do ponteiro global

IMAGE_DIRECTORY_ENTRY_IAT
Endereços que a dll ou exe importam(funções de uma outra dll por exemplo)

IMAGE_DIRECTORY_ENTRY_IMPORT
Dados sobre  o que é importado pelo módulo

IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG
Dados sobre a configuração de carregamento 

IMAGE_DIRECTORY_ENTRY_RESOURCE
Dados sobre recursos que o módulo pode requerer

IMAGE_DIRECTORY_ENTRY_SECURITY
Diretório sobre informações de segurança

IMAGE_DIRECTORY_ENTRY_TLS
Diretório com informações sobre armazenamentos específicos para cada thread

Aqui está o que a função faz ,ImageDirectoryEntryToData chama ImageDirectoryEntryToDataEx:
PVOID WINAPI ImageDirectoryEntryToDataEx(
    PVOID Base,
//Endereço base de onde o módulo foi carregado(pego com "GetModuleHandle")
    BOOLEAN MappedAsImage,
//Se o módulo foi mapeado como imagem ou dado
    USHORT DirectoryEntry,
//Uma das constantes dos diretórios acima
      PULONG Size,
//Ponteiro que recebe o tamanho do diretório procurado
      PIMAGE_SECTION_HEADER *FoundHeader //
Ponteiro para a seção do tal diretório procurado(se existir)
    )
{
    PIMAGE_NT_HEADERS pnt_apoio;

    if((int)Base & 1 == 0)/
/Testa alinhamento do endereço(verifica se é par)
    {
        pnt_apoio =  ImageNtHeader(Base);
        if(pnt_apoio != NULL)
        {
            if(pnt_apoio->OptionalHeader.Magic == 0x10B)
//Isso testa para ver se é um executável
            {
                return RetornaDiretorioSecao(Base,MappedAsImage,DirectoryEntry,Size,FoundHeader,
                    &pnt_apoio->FileHeader,&pnt_apoio->OptionalHeader);
            }
        }
    }
}


Comentários
A função é simples,veja que ela também chama  ImageNtHeader.
Quem faz o grosso é RetornaDiretorioSecao,essa é uma função não documentada,retornando um ponteiro para o começo do diretório no módulo

O que RetornaDiretorioSecao faz:
PVOID RetornaDiretorioSecao(
    PVOID Base,
    BOOLEAN MappedAsImage,
    USHORT DirectoryEntry,
    PULONG Size,
     PIMAGE_SECTION_HEADER *FoundHeader,
    PIMAGE_FILE_HEADER pifh,
    PIMAGE_OPTIONAL_HEADER pioh
    )
{

   //Checa para ver se o diretório passado é válido
    if(DirectoryEntry > pioh->NumberOfRvaAndSizes)
    {
        *Size &= 0;

        return;
    }


//Checa para ver se o diretório é válido
     if(pioh->DataDirectory[DirectoryEntry * 8].VirtualAddress == 0)
    {
        *Size = 0;
        return;
    }

   
   //Põe o tamanho do diretório no argumento passado

    *Size = pioh->DataDirectory[DirectoryEntry * 8].Size;
    IMAGE_SECTION_HEADER *secao;


    if(MappedAsImage == FALSE) 

    {    
              if(pioh->DataDirectory[DirectoryEntry * 8].VirtualAddress > pioh->SizeOfHeaders )
        {

           //Acessa as seções
            secao_ecx = (IMAGE_SECTION_HEADER*)((char*)pioh +pifh->SizeOfOptionalHeader);
            if(pifh->NumberOfSections <= 0)
            {
                return NULL;
            }
            
            int contador = 0;
            while(true)
            {  //
Verifica se o rva do diretório procurado seria para a seção atual
                if(pioh->DataDirectory[DirectoryEntry * 8].VirtualAddress > secao_ecx->VirtualAddress &&
                     pioh->DataDirectory[DirectoryEntry * 8].VirtualAddress <
                      
secao->VirtualAddress + secao->SizeOfRawData )
                    {
                        break;
                    }
                }           
               
               
//Vai para a próxima IMAGE_SECTION_HEADER(tem 0x28 bytes essa estrutura) no array,dado em "IMAGE_NT_HEADER.FileHeader.NumberOfSections"
                secao_ecx += 0x28;
                contador +=1;
                if(contador < pifh->NumberOfSections)
                {
                    return NULL;
                }
            }
           
            if(FoundHeader != NULL)
                *FoundHeader = secao; 

          //Retorna o ponteiro para o diretório procurado com algumas correções de alinhamento
            return (PVOID)((char*)Base + pioh->DataDirectory[DirectoryEntry * 8].VirtualAddress +
                (secao->PointerToRawData - secao->VirtualAddress));   
        }
    }

    if(FoundHeader != NULL)
    {
        FoundHeader = NULL;
    }
 
//Arquivo foi mapeado pelo loader do S.O, logo "VirtualAddress" tem o rva para o diretório em si
 return (PVOID)((char*)Base +  pioh->DataDirectory[DirectoryEntry * 8].VirtualAddress);
 
}

Comentários:
1 - //"IMAGE_OPTIONAL_HEADER.NumberOfRvaAndSizes" é uma constante que tem o número  de diretórios possíveis.No deslocamento OptionalHeader existe o membro "DataDirectory" que é um array do tipo "IMAGE_DATA_DIRECTORY",onde o membro "VirtualAddress" tem o rva para esse diretório. "IMAGE_DATA_DIRECTORY "é uma estrutura de 8 bytes,logo  "pioh->DataDirectory[DirectoryEntry * 8]".
2 - "if(MappedAsImage == FALSE)",se você mesmo carregou o módulo para o seu programa ,e esse argumento for FALSE,logo a função faz algumas checagens para ver onde estão as informações.
IMAGE_SECTION_HEADER.VirtualAddress é o rva de onde a seção seria carregada no módulo, IMAGE_SECTION_HEADER.SizeOfRawData é o tamanho da seção,
IMAGE_SECTION_HEADER.PointerToRawData tem o rva de onde essa seção fica no arquivo.
3 -  As seções ficam logo após os headers no arquivo. IMAGE_FILE_HEADER.SizeOfOptionalHeader tem o tamanho da estrutura IMAGE_OPTIONAL_HEADER, e IMAGE_FILE_HEADER.NumberOfSections  tem a quantidade de seções no arquivo.




Nenhum comentário:

Postar um comentário