Ir para conteúdo

Formato PE - 2


Leandro Fróes

Posts Recomendados

  • Moderadores

E aqui estamos para mais um trecho do que venho estudando sobre o formato PE!!

 

Esses dias estava pensando: o editor hexa me mostra tanto os bytes quanto suas representações em ASCII (caso haja), mas e se eu mesmo visse? Ai lembrei da função fseek e fread. A primeira, tratando-se de um binário, pula para o offset em específico e a outro lê os bytes. Dei uma leve lida sobre e fiz esse programinha só pra fazer um “PoC” do que estamos vendo:

 

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]){

FILE *f; //ponteiro para um arquivo
unsigned char buffer[4]; // espaço em memória que iremos colocar os bytes lidos

f = fopen(argv[1], "rb"); // abrindo o arquivo binário que recebemos como primeiro parâmetro do nosso programa com permissão de leitura (rb);

if(fopen == NULL){ // simples teste para ver se conseguimos abrir o arquivo

	printf("Erro\n");

	exit(1);
}

fseek(f, 0x80, SEEK_SET); // pulamos para o offset da assinatura PE. O parâmetro SEEK_SET indica que estamos pulando a partir do começo do arquivo

fread(buffer, 4, 1, f); // estamos colocando 1 elemento em buffer para leitura e este elemento possui 4 bytes do arquivo (f)

puts(buffer); // imprimindo a string dentro de buffer

fclose(f); // fechando o arquivo

return 0;

}

 

Capturar0.PNG.8571412edd33194a949cb6d1d379ef6b.PNG

Este tipo de teste (leitura de bytes) pode ser feito em qualquer lugar do arquivo, poderíamos ter imprimido os próprios bytes também, por que não tentar? :P. Caso queira saber um pouco mais [1].

 

Dando sequência aos nossos estudos temos a struct IMAGE_NT_HEADERS que segue o seguinte escopo segundo a documentação da Microsoft:

 

typedef struct _IMAGE_NT_HEADERS {
  DWORD                                    		    Signature;
  IMAGE_FILE_HEADER	     		    FileHeader; 
  IMAGE_OPTIONAL_HEADER  	OptionalHeader; 
} IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS;

 

Vimos no post anterior a existência da assinatura PE e comprovamos vendo a própria assinatura com o programinha em C ali em cima. Podemos observar que o segundo campo tem nome de FileHeader e é do tipo IMAGE_FILE_HEADER, ou seja, uma struct. Ela que iremos analisar:D

 

typedef struct _IMAGE_FILE_HEADER {
  WORD  Machine;
  WORD  NumberOfSections; 
  DWORD TimeDateStamp;
  DWORD PointerToSymbolTable;
  DWORD NumberOfSymbols; 
  WORD  SizeOfOptionalHeader; 
  WORD  Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

 

IMAGE_MACHINE

Logo após a assinatura do PE temos a especificação de que tipo de máquina o executável está previsto para rodar, este campo é uma WORD(16 bits) e aqui está a especificação exata[2]:

 

IMAGE_FILE_MACHINE_UNKNOWN

0x0

The contents of this field are assumed to be applicable to any machine type

IMAGE_FILE_MACHINE_AM33

0x1d3

Matsushita AM33

IMAGE_FILE_MACHINE_AMD64

0x8664

x64

IMAGE_FILE_MACHINE_ARM

0x1c0

ARM little endian

IMAGE_FILE_MACHINE_ARM64

0xaa64

ARM64 little endian

IMAGE_FILE_MACHINE_ARMNT

0x1c4

ARM Thumb-2 little endian

IMAGE_FILE_MACHINE_EBC

0xebc

EFI byte code

IMAGE_FILE_MACHINE_I386

0x14c

Intel 386 or later processors and compatible processors

IMAGE_FILE_MACHINE_IA64

0x200

Intel Itanium processor family

IMAGE_FILE_MACHINE_M32R

0x9041

Mitsubishi M32R little endian

IMAGE_FILE_MACHINE_MIPS16

0x266

MIPS16

IMAGE_FILE_MACHINE_MIPSFPU

0x366

MIPS with FPU

IMAGE_FILE_MACHINE_MIPSFPU16

0x466

MIPS16 with FPU

IMAGE_FILE_MACHINE_POWERPC

0x1f0

Power PC little endian

IMAGE_FILE_MACHINE_POWERPCFP

0x1f1

Power PC with floating point support

IMAGE_FILE_MACHINE_R4000

0x166

MIPS little endian

IMAGE_FILE_MACHINE_RISCV32

0x5032

RISC-V 32-bit address space

IMAGE_FILE_MACHINE_RISCV64

0x5064

RISC-V 64-bit address space

IMAGE_FILE_MACHINE_RISCV128

0x5128

RISC-V 128-bit address space

IMAGE_FILE_MACHINE_SH3

0x1a2

Hitachi SH3

IMAGE_FILE_MACHINE_SH3DSP

0x1a3

Hitachi SH3 DSP

IMAGE_FILE_MACHINE_SH4

0x1a6

Hitachi SH4

IMAGE_FILE_MACHINE_SH5

0x1a8

Hitachi SH5

IMAGE_FILE_MACHINE_THUMB

0x1c2

Thumb

IMAGE_FILE_MACHINE_WCEMIPSV2

0x169

MIPS little-endian WCE v2

 

Já inverteram os bytes e descobriram a nossa?

Capturar1.PNG.f924baf1afc546cb06adb95ad1009297.PNG

Isso mesmo, 0x014c com o nome IMAGE_FILE_MACHINE_I386

 

NumberOfSections

Nossa próxima WORD define o número de seções que nosso binário possui (e o tamanho da section table). Se você não faz ideia do que seja uma seção fique tranquilo que vamos ver tudo isso com o tempo.

Capturar2.PNG.524a9e08cb7ba76afd88cdf91c9b60dd.PNG

Lembrando que estamos lidando com base 16 (hexadecimal). Uma simples conversão para a base 10 nos diz que temos 15 sections. 000Fh = 15.

 

TimeDataStamp

 

No campo seguinte temos a DWORD de nome TimeDataStamp(carimbo de data e hora). Praticamente corresponde ao número de segundos decorridos a partir de 1 de Janeiro de 1970 00:00:00, em UTC - o formato utilizado pela maioria dos compiladores C para time_t.

 

Capturar3.PNG.5f85adf9f2aa20934646dd892e0fc98b.PNG

Invertendo nossos bytes temos 5997 35E9, isto em decimal fica 1.503.081.961 segundos (credo).

Sabemos que um dia tem 24h, que cada hora tem 60 min, e que cada min tem 60 segundos, resumindo 24*60*60 = 86.400 segundos por dia. Fazendo 1.503.081.961/ 86.400 temos 17.396 DIAS desde 01/01/1970 e isso corresponde a mais ou menos 47 ANOS (credo 2 O.o) e realmente, se somarmos todos esses dias com a data de 01/01/1970… Criei esse executável na data informada :o

 

Capturar4.PNG.c56636b31d2f237014587fe080ee934a.PNG

 

No caso eram 18:45, o sistema não está com o horário de Brasília, mas você pegou a ideia!

 

PointerToSymbolTable e NumberOfSymbols

Estas duas DWORDS são, respectivamente, ponteiro para a tabela de símbolos e número de símbolos. O primeiro campo é o offset para a tabela de símbolos(em bytes) e o segundo é o número de entradas na tabela de símbolos.

 

Capturar5.PNG.4cb548c71dae61d4b553d9da397d16af.PNG

 

 

Capturar6.PNG.7470a1bf57e3365c667edd7e67ee2598.PNG

 

SizeOfOptionalHeader

Esta WORD é o tamanho do cabeçalho opcional. Caso estejamos tratando de um arquivo objeto este campo estará zerado. Para nós agora ele é simplesmente o tamanho do IMAGE_OPTIONAL_HEADER.

Capturar7.PNG.bbe2413b82f643f019ab8fc79658c8ce.PNG

 

Temos 00E0, ou seja, 224 em decimal, 224 bytes. Nao acredita?


 

#include <stdio.h>

#include <windows.h>


int main(){


printf("O tamanho do header eh de: %d bytes\n", sizeof(IMAGE_OPTIONAL_HEADER));


}

 

Capturar8.PNG.c46cb1c129a38223015f2fff785ebdd3.PNG

 

Characteristics

 

Aqui temos uma máscara de bits (16 no caso), sabe o registrador EFLAGS[3]? É por ai a ideia. Como a documentação da Microsoft é bem enxuta quanto a isso vou simplesmente dar Ctrl+C Ctrl+V na tabela :ph34r:

 

 

Flag

Value

Description

IMAGE_FILE_RELOCS_STRIPPED

0x0001

Image only, Windows CE, and Microsoft Windows NT® and later. This indicates that the file does not contain base relocations and must therefore be loaded at its preferred base address. If the base address is not available, the loader reports an error. The default behavior of the linker is to strip base relocations from executable (EXE) files.

IMAGE_FILE_EXECUTABLE_IMAGE

0x0002

Image only. This indicates that the image file is valid and can be run. If this flag is not set, it indicates a linker error.

IMAGE_FILE_LINE_NUMS_STRIPPED

0x0004

COFF line numbers have been removed. This flag is deprecated and should be zero.

IMAGE_FILE_LOCAL_SYMS_STRIPPED

0x0008

COFF symbol table entries for local symbols have been removed. This flag is deprecated and should be zero.

IMAGE_FILE_AGGRESSIVE_WS_TRIM

0x0010

Obsolete. Aggressively trim working set. This flag is deprecated for Windows 2000 and later and must be zero.

IMAGE_FILE_LARGE_ADDRESS_ AWARE

0x0020

Application can handle > 2‑GB addresses.

 

0x0040

This flag is reserved for future use.

IMAGE_FILE_BYTES_REVERSED_LO

0x0080

Little endian: the least significant bit (LSB) precedes the most significant bit (MSB) in memory. This flag is deprecated and should be zero.

IMAGE_FILE_32BIT_MACHINE

0x0100

Machine is based on a 32-bit-word architecture.

IMAGE_FILE_DEBUG_STRIPPED

0x0200

Debugging information is removed from the image file.

IMAGE_FILE_REMOVABLE_RUN_ FROM_SWAP

0x0400

If the image is on removable media, fully load it and copy it to the swap file.

IMAGE_FILE_NET_RUN_FROM_SWAP

0x0800

If the image is on network media, fully load it and copy it to the swap file.

IMAGE_FILE_SYSTEM

0x1000

The image file is a system file, not a user program.

IMAGE_FILE_DLL

0x2000

The image file is a dynamic-link library (DLL). Such files are considered executable files for almost all purposes, although they cannot be directly run.

IMAGE_FILE_UP_SYSTEM_ONLY

0x4000

The file should be run only on a uniprocessor machine.

IMAGE_FILE_BYTES_REVERSED_HI

0x8000

Big endian: the MSB precedes the LSB in memory. This flag is deprecated and should be zero.

 

 

No nosso caso:

 

Capturar9.PNG.0626dbd76b17070dbfdd86d969f8f229.PNG

 

Nossos bytes invertidos ficam 01 07 e isso em binário fica 0000 0001 0000 0111

 

 

Hexa

01

07

Binário

0

0

0

0

0

0

0

1

0

0

0

0

0

1

1

1

Bits

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

 

Agora basta ver com o que o nosso binário bate:

 

IMAGE_FILE_RELOCS_STRIPPED – Há informacoes de remanejamento

IMAGE_FILE_EXECUTABLE_IMAGE – É um executável

IMAGE_FILE_LINE_NUMS_STRIPPED – A numeracao de linhas foi eliminada

IMAGE_FILE_32BIT_MACHINE – A máquina é baseada em uma arquitetura de 32 bits

 

 

Fico por aqui e queria só falar duas coisas:

 

1 - Para quem curte estudar pelo blog numaboa[4] aconselho baixar os PDFs pois esses dias aparentemente estava fora do ar e não é a primeira vez que vejo isso acontecer. O material vale muito a pena ser guardado.

 

2 - A outra é que pretendo colocar isso tudo no meu github essa semana, para quem quiser baixar etc.

 

Como sempre, correções, dicas e opiniões são sempre bem-vindas. Obrigado e boa noite xD

[1] https://www.youtube.com/watch?v=oZeezrNHxVo&list=PLIfZMtpPYFP5qaS2RFQxcNVkmJLGQwyKE
[2] https://www.microsoft.com/en-us/download/details.aspx?id=19509
[*] https://msdn.microsoft.com/en-us/library/windows/desktop/ms680198(v=vs.85).aspx
[3] https://en.wikipedia.org/wiki/FLAGS_register
[4] http://numaboa.com.br/

 

 

 

Link para o comentário
Compartilhar em outros sites

  • 5 meses depois...
  • Moderadores
1 hora atrás, Marciano Calvi Ferri disse:

Fala Leandro,

Por acaso vc fez uma cópia do site numaboa.com.br? Lá no discord, se eu não entendi errado, alguém tinha copiado, mas não sei quem foi.

Abraço, T+.

Fala mano,

então, não fiz não (na real eu imprimi alguns artigos dela na época que estava lendo). Quem fez foi o @Gribel xD

Abraço!

Link para o comentário
Compartilhar em outros sites

  • 1 ano depois...

Apenas um detalhe... O header PE não começa, necessariamente, no offset 0x80. Isso depende do tamanho do DOS Stub. A especificação do PE diz, claramente, que é necessário ler o offset do header PE no offset 0x3C do arquivo.

Além disso, enquanto uma demonstração simples possa usar, sem problemas, streaming. Como a manipulação é binária, seria interessante evitar buffering e usar file descriptors ao invés do tipo FILE. Eis um exemplo do código inicial modificado (que checa qualquer possibilidade de erros, no caso de tentar usá-lo com um arquivo que não tem formato PE):

// test.c

// Compila apenas no Visual Studio ou GCC para Windows (MinGW, CygWin).
#if defined(_MSC_VER) || defined(__WINNT__)
# include <io.h>
/* Windows não tem basename()! */
# define basename(...) (__VA_ARGS__)
#else
# ifdef __GNUC__
/* Esse bloco é para outro systema que não seja Windows ou MinGW */
#  define _GNU_SOURCE
#  include <unistd.h>
#  include <string.h>
# else
#  error Need MSVC, MinGW, Cygwin or GCC  to compile!
# endif
#endif

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <ctype.h>

static void show_buffer ( void *, size_t );

int main ( int argc, char **argv )
{
  int fd;
  uint32_t pe_pos;
  char buffer[4];

  if ( argc != 2 )
  {
    fprintf ( stderr, "Uso: %s <exefile>\n", basename ( *argv ) );
    return EXIT_FAILURE;
  }

  // abrindo o arquivo binário que recebemos como primeiro parâmetro
  // do nosso programa com permissão de leitura.
  if ( ( fd = open ( *++argv, O_RDONLY ) ) < 0 )
  {
    perror ( "open" );
    return EXIT_FAILURE;
  }

  // pulamos para o offset 0x3C para descobrir o offset do header PE.
  if ( lseek ( fd, 0x3c, SEEK_SET ) == -1 )
  {
    perror ( "lseek 0x3c" );
    close ( fd );
    return EXIT_FAILURE;
  }

  // O offset tem 32 bits de tamanho!
  if ( read ( fd, &pe_pos, sizeof ( uint32_t ) ) <= 0 )
  {
    close ( fd );
    fputs ( "ERRO lendo do offset do header PE.\n", stderr );
    return EXIT_FAILURE;
  }

  // Pula para o offset do header PE.
  if ( lseek ( fd, pe_pos, SEEK_SET ) == -1 )
  {
    perror ( "lseek pe_hdr" );
    close ( fd );
    return EXIT_FAILURE;
  }

  // Le a assinatura PE (4 bytes)...
  if ( read ( fd, buffer, sizeof buffer ) <= 0 )
  {
    close ( fd );
    fputs ( "ERRO lendo do offset do PE header.\n", stderr );
    return EXIT_FAILURE;
  }

  close ( fd );

  // Mudei isso apenas para mostrar os 4 bytes lidos!
  show_buffer ( buffer, sizeof buffer );

  return EXIT_SUCCESS;
}

// Mostra o conteúdo apontado por p (size bytes)
// no formato de uma string em C.
static void show_buffer ( void *p, size_t size )
{
  char *q;

  q = p;

  while ( size-- )
  {
    if ( isprint ( *q ) )
      putchar ( *q );
    else
      printf ( "\\x%02hhx", *q );

    q++;
  }

  putchar ( '\n' );
}

 

Link para o comentário
Compartilhar em outros sites

  • 2 semanas depois...
  • 6 meses depois...

Arquivado

Este tópico foi arquivado e está fechado para novas respostas.

  • Quem Está Navegando   0 membros estão online

    • Nenhum usuário registrado visualizando esta página.
×
×
  • Criar Novo...