-->

PE Viewer 개발 (2) - IMAGE_NT_HEADERS

반응형

C언어로 개발하는 PE Viewer(PE 뷰어) 두번째 포스팅에서는 NT 헤더(IMAGE_NT_HEADERS) 구조체를 파싱해볼 것이다. 찾는 부분이 아니라면 PE Viewer 관련 다른 포스팅을 확인해보길 바란다.

 

이번 포스팅은 상당히 길다. 나누기가 애매해서 그냥 한꺼번에 올렸다.

 

 

NT Header (IMAGE_NT_HEADER)

IMAGE_NT_HEADER 구조체의 시작 위치는 IMAGE_DOS_HEADER 구조체의 e_lfanew 변수를 참조해 구할 수 있다.

 

  • 시작 위치: IMAGE_DOS_HEADER.e_lfanew
  • 크기: 248바이트(0xF8)

 

IMAGE_DOS_HEADER의 elfanew

 

IMAGE_NT_HEADERS 정의

IMAGE_NT_HEADER 구조체는 아래 사진에서 구분지어있는 순서대로 ‘Signature’, ‘FileHeader’, ‘OptionalHeader’ 3개의 멤버로 이루어져 있는 248바이트(0xF8) 크기의 구조체이다.

 

 

 

IMAGE_NT_HEADR 구조체는 다음과 같다 (출처: MSDN).

typedef struct _IMAGE_NT_HEADERS {
  DWORD                   Signature;
  IMAGE_FILE_HEADER       FileHeader;
  IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

 

 

IMAGE_NT_HEADERS 변수 선언

IMAGE_NT_HEADERS의 시작은 IMAGE_DOS_HEADER의 elfanew이므로 IMAGE_NT_HEADERS 구조체의 포인터 변수 inh의 위치는 'buffer + (idh->e_lfanew)'이 된다. 

int pe_print_32(char* file, char* option)
{
	
	// 코드...
    
	// IMAGE_NT_HEADERS // Signature
	// IMAGE_NT_HEADERS의 시작주소는 IMAGE_DOS_HEADER의 e_lfanew에 저장되어 있음(=IMAGE_NT_HEADERS.Signature의 시작주소)
	PIMAGE_NT_HEADERS inh = (PBYTE)buffer + (idh->e_lfanew);
    
    // 코드...
    
}

 

 

IMAGE_NT_HEADERS.Signature

IMAGE_NT_HEADERS의 첫 번째 멤버 변수인 Signtature는 PE 파일임을 의미하는 시그니처로 ‘PE’ 값을 가지며 이 값은 변경될 수 없다. 

 

ntdll.dll의 Signature

 

 

IMAGE_NT_HEADERS.Signature 파싱 및 출력

Signature는 DWORD 값으로 그냥 'inh->Signature'로 파싱할 수 있다.

int pe_print_32(char* file, char* option)
{
	// 코드...
	
	// IMAGE_NT_HEADERS // Structure
	printf("***************************** [IMAGE_NT_HEADER.Signature] *******************************\n\n");
	printf("- Signature: %08X\n\n", inh->Signature);
    
	// 코드...
}

 

 

IMAGE_NT_HEADERS.Signature 출력 결과는 아래와 같다. PE 파일 시그니쳐인 'PE'가 정상적으로 확인된다.

 

PE 뷰어 - IMAGE_NT_HEADER.Signature

 

 

IMAGE_NT_HEADERS.IMAGE_FILE_HEADER

IMAGE_NT_HEAEDERS의 두 번째 멤버인 IMAGE_FILE_HEADER 구조체는 Signature 뒤부터 시작되며 20바이트 (0x14) 크기를 갖는다. 

 

  • 시작위치: IMAGE_FILE_HEADER.Signature 뒤
  • 크기: 20바이트(0x14)

 

ntdll.dll의 IMAGE_FILE_HEADER

 

 

IMAGE_NT_HEADERS.IMAGE_FILE_HEADER 정의

파일의 개략적인 속성을 나타내는 구조체이다. 중요히 여겨지는 멤버로는 아래와 같이 ‘Machine’, ‘NumberOfSections’, ‘SizeOfOptionalHeader’, ‘Characteristics’ 등이 있다.

 

  • Machine : CPU ID를 나타내는 시그니쳐
  • NumberOfSections : 섹션의 갯수
  • SizeOfOptionalHeader : IMAGE_OPTIONAL_HEADER 구조체의 크기
  • Characteristics : 파일의 속성

 

보통 Machine이 나타내는 CPU ID로 x32, x64를 구분하기도 한다. 또한 NumberOfSections가 나타내는 섹션의 갯수는 가변적이기 때문에 IMAGE_SECTION_HEADER를 파싱할 때 꼭 필요한 정보이다. 정의된 섹션의 갯수가 실제 섹션의 수보다 많다면 파싱하다가  에러가 나지만, 정의된 섹션의 갯수가 실제 섹션의 갯수보다 작다면 뒤에 섹션은 파싱되지 못하고 정의된 갯수만큼만 섹션이 인식된다. Characteristics를 보고는 이게 dll파일인지 실행가능한 exe 파일인지 등에 대한 정보를 얻을 수 있다.

 

IMAGE_FILE_HEADER

 

 

IMAGE_FILE_HEADR 구조체는 다음과 같다 (출처: MSDN).

typedef struct _IMAGE_FILE_HEADER {
  WORD  Machine; // CPU ID를 나타내는 시그니쳐
  WORD  NumberOfSections; // 섹션의 갯수
  DWORD TimeDateStamp;
  DWORD PointerToSymbolTable;
  DWORD NumberOfSymbols;
  WORD  SizeOfOptionalHeader; // IMAGE_OPTIONAL_HEADER 구조체의 크기
  WORD  Characteristics; // 파일의 속성
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

 

 

IMAGE_NT_HEADERS.IMAGE_FILE_HEADER 변수 선언

IMAGE_FILE_HEADER 구조체의 포인터 변수가 가리켜야 할 위치는 ‘buffer + (idh->e_lfanew) + (Signature 크기(4))’가 된다.

int pe_print_32(char* file, char* option)
{
	
	// 코드...
    
	// IMAGE_NT_HEADERS // IMAGE_FILE_HEADER
	// IMAGE_FILE_EHADER는 Signature가 끝나고 시작됨
	PIMAGE_FILE_HEADER ifh = (PBYTE)buffer + idh->e_lfanew + 4;
    
    // 코드...
    
}

 

 

IMAGE_NT_HEADERS.IMAGE_FILE_HEADER 파싱 및 출력

IMAGE_NT_HEADERS.IMAGE_FILE_HEADER 의 파싱 및 출력은 아래와 같이 이루어진다.

int pe_print_32(char* file, char* option)
{
	// 코드...
    
	// IMAGE_NT_HEADERS // IMAGE_FILE_HEADER
	printf("************************* [IMAGE_NT_HEADER.IMAGE_FILE_HEADER] ***************************\n\n");
	printf("- Machine: %02X\n", ifh->Machine);
	printf("- NumberOfSections: %02X\n", ifh->NumberOfSections);
	printf("- TimeDateStamp: %04X\n", ifh->TimeDateStamp);
	printf("- PointerToSymbolTables: %04X\n", ifh->PointerToSymbolTable);
	printf("- NumberOfSymbols: %04X\n", ifh->NumberOfSymbols);
	printf("- SizeOfOptionalHeader: %02X\n", ifh->SizeOfOptionalHeader);
	printf("- Characteristics: %02X\n\n", ifh->Characteristics);

	// 코드...
}

 

 

IMAGE_NT_HEADERS.IMAGE_FILE_HEADER 의 출력 결과는 아래와 같다. Machine 값이 14C인 것을 보니 x32 이고 섹션 갯수는 총 7개임을 알 수 있다.

 

PE 뷰어 - IMAGE_NT_HEADERS.IMAGE_FILE_HEADER

 

IMAGE_NT_HEADERS.IMAGE_OPTIONAL_HEADER32

IMAGE_NT_HEAEDERS의 세번째 멤버인 IMAGE_OPTIONAL_HEADER 구조체는 IMAGE _FILE_HEAEDERS 뒤부터 시작되며 구조체 크기는 240바이트로 정해져 있지만 PE 로더는 IMAGE_FILE_HEADER의 SizeOfOptionalHeader 멤버의 값을 보고 IMAGE_OPTIONAL_HEADER32 구조체의 크기를 인식하게 된다.

 

IMAGE_OPTIONAL_HEADER는 32bit 구조체와 64bit 구조체가 따로 있다.

 

- 시작위치 : IMAGE_FILE_HEADER  뒤

- 크기 : IMAGE_FILE_HEADER.SizeOfOptinalHeader 참조 (224바이트 (0xE0))

 

ntdll.dll의 IMAGE_OPTIONAL_HEADER

 

 

IMAGE_NT_HEADERS.IMAGE_OPTIONAL_HEADER32 정의

35개의 기본 필드와 IMAGE_DATA_DIRECTORY 구조체 배열로 이루어져 있다. IMAGE_OPTIONAL_HEADER32에는 아래와 같이 중요한 멤버들이 굉장히 많다. 

 

 

특히, PE파일이 로딩되는 시작 주소인 ImageBase가 있고 어떻게 보면 PE 파일 분석의 핵심이라고 할 수 있는 IAT와 같은 테이블이 포함된 DataDirectory도 존재한다.

 

  • Magic : IMAGE_OPTIONAL_HEADER를 나타내는 시그니쳐  
  • AddressOfEntryPoint : EP의 RVA 값
  • ImageBase : PE 파일이 로딩되는 시작 주소
  • SectionAlignment : 메모리에서의 섹션의 최소단위
  • FileAlignment : 파일에서의 섹션의 최소단위
  • SizeOfImage : 가상 메모리에서 PE Image가 차지하는 크기(로딩된 크기)
  • SizeOfHeader : PE 헤더의 전체 크기(FileAlignment의 배수)
  • Subsystem : 서브시스템 종류 정의
  • NumberOfRvaAndSizes : DataDirectory 배열의 갯수
  • DataDirectory : IMAGE_DATA_DIRECTORY 구조체의 배열

 

 

 

IMAGE_OPTIONAL_HEADER 구조체 정의는 아래와 같다 (출처: MSDN).

typedef struct _IMAGE_OPTIONAL_HEADER {
  WORD                 Magic; // IMAGE_OPTIONAL_HEADER를 나타내는 시그니쳐
  BYTE                 MajorLinkerVersion;
  BYTE                 MinorLinkerVersion;
  DWORD                SizeOfCode;
  DWORD                SizeOfInitializedData;
  DWORD                SizeOfUninitializedData;
  DWORD                AddressOfEntryPoint;
  DWORD                BaseOfCode;
  DWORD                BaseOfData;
  DWORD                ImageBase; // PE 파일이 로딩되는 시작 주소
  DWORD                SectionAlignment; // 메모리에서의 섹션의 최소단위
  DWORD                FileAlignment; // 파일에서의 섹션의 최소단위
  WORD                 MajorOperatingSystemVersion;
  WORD                 MinorOperatingSystemVersion;
  WORD                 MajorImageVersion;
  WORD                 MinorImageVersion;
  WORD                 MajorSubsystemVersion;
  WORD                 MinorSubsystemVersion;
  DWORD                Win32VersionValue;
  DWORD                SizeOfImage; // 가상 메모리에서 PE Image가 차지하는 크기(로딩된 크기)
  DWORD                SizeOfHeaders; // PE 헤더의 전체 크기(FileAlignment의 배수)
  DWORD                CheckSum;
  WORD                 Subsystem; // 서브시스템 종류 정의
  WORD                 DllCharacteristics;
  DWORD                SizeOfStackReserve;
  DWORD                SizeOfStackCommit;
  DWORD                SizeOfHeapReserve;
  DWORD                SizeOfHeapCommit;
  DWORD                LoaderFlags;
  DWORD                NumberOfRvaAndSizes; // DataDirectory 배열의 갯수
  IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

 

 

IMAGE_NT_HEADERS.IMAGE_OPTIONAL_HEADER 변수 선언

IMAGE_OPTIONAL_HEAEDERS 구조체의 포인터 변수인 ioh가 가리켜야 할 위치는 ‘buffer + (idh->e_lfanew) + (SIgnature 크기) + (IMAGE_FILE_HEADER 크기)’가 된다. IMAGE_OPTIONAL_HEADER에는 구조체 배열이 포함되어 있는데 이 다음 포스팅인 IMAGE_SECTION_HEADER를 분석하고 파싱할 때 필요한 arr_num도 같이 계산되어 있다. 이건 추후에 다른 포스팅에서 설명하도록 하겠다.

int pe_print_32(char* file, char* option)
{
	
	// 코드...
    
	// IMAGE_NT_HEADERS // IMAGE_OPTIONAL_HEADER
	// IMAGE_OPTIONAL_HEADER는 IMAGE_FILE_HEADER가 끝나고 시작됨
	PIMAGE_OPTIONAL_HEADER ioh = (PBYTE)buffer + (idh->e_lfanew) + 24;
    
	// 코드...
    
}

 

 

IMAGE_NT_HEADERS.IMAGE_OPTIONAL_HEADER 파싱 및 출력

IMAGE_NT_HEADERS.IMAGE_OPTIONAL_HEADER 의 파싱 결과 및 파일 출력은 아래와 같이 이루어진다. NumberOfRvaAndSizes는 DataDirectory 배열의 갯수를 나타내므로 DataDirectory는 해당 갯수만큼 반복적으로 돌며 출력해야 한다.

int pe_print_32(char* file, char* option)
{
	// 코드...
    
	// IMAGE_NT_HEADERS // IMAGE_OPTIONAL_HEADER
	printf("*********************** [IMAGE_NT_HEADER.IMAGE_OPTIONAL_HEADER] *************************\n\n");
	printf("- Magic: %02X\n", ioh->Magic);
	printf("- MajorLinkerVersion: %01X\n", ioh->MajorLinkerVersion);
	printf("- MinorLinkerVersion: %01X\n", ioh->MinorLinkerVersion);
	printf("-  SizeOfCode: %04X\n", ioh->SizeOfCode);
	printf("- SizeOfInitializedData: %04X\n", ioh->SizeOfInitializedData);
	printf("- SizeOfUninitializedData: %04X\n", ioh->SizeOfUninitializedData);
	printf("- AddressOfEntryPoint: %04X\n", ioh->AddressOfEntryPoint);
	printf("- BaseOfCode: %04X\n", ioh->BaseOfCode);
	printf("- BaseOfData: %04X\n", ioh->BaseOfData);
	printf("- ImageBase: %04X\n", ioh->ImageBase);
	printf("- SectionAlignment: %04X\n", ioh->SectionAlignment);
	printf("- FileAlignment: %04X\n", ioh->FileAlignment);
	printf("- MajorOperatingSystemVersion: %02X\n", ioh->MajorOperatingSystemVersion);
	printf("- MinorOperatingSystemVersion: %02X\n", ioh->MinorOperatingSystemVersion);
	printf("- MajorImageVersion: %02X\n", ioh->MajorImageVersion);
	printf("- MinorImageVersion: %02X\n", ioh->MinorImageVersion);
	printf("- MajorSubsystemVersion: %02X\n", ioh->MajorSubsystemVersion);
	printf("- MinorSubsystemVersion: %02X\n", ioh->MinorSubsystemVersion);
	printf("- Win32VersionValue: %04X\n", ioh->Win32VersionValue);
	printf("- SizeOfImage: %04X\n", ioh->SizeOfImage);
	printf("- SizeOfHeaders: %04X\n", ioh->SizeOfHeaders);
	printf("- CheckSum: %04X\n", ioh->CheckSum);
	printf("- Subsystem: %02X\n", ioh->Subsystem);
	printf("- DllCharacteristics: %02X\n", ioh->DllCharacteristics);
	printf("- SizeOfStackReserve: %04X\n", ioh->SizeOfStackReserve);
	printf("- SizeOfStackCommit: %04X\n", ioh->SizeOfStackCommit);
	printf("- SizeOfHeapReserve: %04X\n", ioh->SizeOfHeapReserve);
	printf("- SizeOfHeapCommit: %04X\n", ioh->SizeOfHeapCommit);
	printf("- LoaderFlags: %04X\n", ioh->LoaderFlags);
	printf("- NumberOfRvaAndSizes: %04X\n\n", ioh->NumberOfRvaAndSizes);
    
    	// IMAGE_NT_HEADERS // Data Directory Table
	printf("******************************** [Data Directory Table] *********************************\n\n");
	for (i = 0; i < (ioh->NumberOfRvaAndSizes); i++)
	{
		printf("- DataDirectory[%d]: %04X\n", i, ioh->DataDirectory[i].VirtualAddress);
	}

	// 코드...
}

 

 

IMAGE_NT_HEADERS.IMAGE_OPTIONAL_HEADER 의 출력 결과는 아래와 같다. Machine 값이 14C인 것을 보니 x32 이고 섹션 갯수는 총 7개임을 알 수 있다.

 

PE 뷰어 - IMAGE_NT_HEADERS.IMAGE_OPTIONAL_HEADER

 

 

NumgerOfRvaAndSizes에 나와있듯이 DataDirectory 테이블 수는 16개(0x10)이다.

 

PE 뷰어 - IMAGE_NT_HEADERS.IMAGE_OPTIONAL_HEADER 의 DataDirectory 테이블

 

 

여기까지, C언어로 하는 PE Viewer(PE 뷰어) 개발을 위한 두번째 포스팅을 마쳤다. 개발은 다 됐는데 정리해서 올리는게 상당히 오래 걸린다. 다음 포스팅에서는 섹션 헤더를 들어가도록 하겠다.

 

댓글

Designed by JB FACTORY