-->

PE Viewer 개발 (4) - IMPORT Directory Table

C언어로 개발하는 PE Viewer(PE 뷰어) 네번째 포스팅에서는 지난 포스팅의 섹션헤더에 이어 IMPORT Directory Table을 파싱해볼 것이다. 나중에 포스팅할 IAT나 INT와 같은 테이블을 파싱하기 위해 꼭 필요한 테이블이다.

 

 

IMPORT Directory Table (IMAGE_IMPORT_DESCRIPTOR)

IMPORT Directory Table은 IMAGE_IMPORT_DESCRIPTOR 구조체 배열로 이루어져 있으며 여태까지 살펴본 PE 헤더가 아닌 PE body에 위치해있다.

 

PE 파일은 자신이 어떤 라이브러리를 임포트하고 있는지 IMAGE_IMPORT_DESCRIPTOR 구조체에 명시하고 이 구조체 배열이 들어있는 테이블이 IMPORT Directory Table이다. IMPORT Directory Table의 시작 위치는 IMAGE_OPTIONAL_HEADER의 Datadirectory[1](IMPORT Table)의 VirtualAddress를 참조하면 되고 크기는 Size를 참조하면 된다.

 

  • 시작 위치 : IMAGE_OPTIONAL_HEADER의 Datadirectory[1].VirtualAddress
  • 테이블 크기 : IMAGE_OPTIONAL_HEADER의 Datadirectory[1].Size
  • 구조체 크기 : 20바이트 (0x14)

 

notepad.exe의 IMPORT Directory Table

 

 

IMAGE_IMPORT_DESCRIPTOR 정의

위에서도 설명했지만 IMAGE_IMPORT_DESCRIPTOR에는 INT(Import Name Table)과 IAT(Import Address Table)의 RVA 주소가 들어있기 때문에 INT, IAT 파싱을 위해선 필수적이다. OriginalFirstThunk에 INT의 RVA 값이 저장되어있고, FirstThunk 값에 IAT의 RVA 값이 저장되어있다.

 

  • OriginalFirstTHunk : INT(Import Name Table) RVA 주소
  • FirstTHunk : IAT(Import Address Table) RVA 주소
  • Name : DLL 이름 RVA 주소

 

구조체 정의는 아래와 같다.

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
   DWORD   OriginalFirstThunk; // INT(Import Name Table) RVA 주소
   DWORD   TimeDateStamp;
   DWORD   ForwarderChain;
   DWORD   Name; // DLL 이름 RVA 주소
   DWORD   FirstThunk; // IAT(Import Address Table) RVA 주소
} IMAGE_IMPORT_DESCRIPTOR, *PIMAGE_IMPORT_DESCRIPTOR;

 

 

IMAGE_IMPORT_DESCRIPTOR 구조체 변수 선언

여태까지는 구조체 변수를 선언함과 동시에 위치를 지정해주었는데 해당 구조체의 위치는 RVA 값으로 알 수 있고 RAW 값을 알기 위해선 어떤 섹션에 위치하고 있는지를 알아야 한다. 

int pe_print_32(char* file, char* option)
{
	// 코드...
    
	// IMAGE_IMPORT_DESCRIPTOR
	PIMAGE_IMPORT_DESCRIPTOR iid;
    
	// 코드...
}   

 

어떤 섹션에 위치하고 있는지를 확인하는 방법은 여러가지가 있겠지만 나는 아래와 같이 섹션 헤더를 파싱하는 반복문 내부에 DataDirectory[1].VirtualAddress 값(IMPORT Directory Table의 RVA)을 각 섹션의 VirtualAddress 값과 비교하는 방식으로 알아내어 인덱스를 idt_section 값으로 지정해주었다.

int pe_print_32(char* file, char* option)
{
	// 코드...
    
	for (i=0; i < (ifh->NumberOfSections); i++)
	{
		// 코드...

		// IMPORT Directory Table 이 위치한 섹션 구하기
		if (ioh->DataDirectory[1].VirtualAddress >= ish->VirtualAddress)
		{
			idt_section = i;
			//printf("Import Directory Table RVA: %x\n", ioh->DataDirectory[1].VirtualAddress);
			//printf("Import Directory Table Section : %d %s\n", i, ish->Name);
		}

		ish++;
	}
    
   	// 코드...
}

 

위치하고 있는 섹션의 인덱스 값을 알았기 때문에 IMAGE_SECTION_HEADER 구조체 변수인 ish를 해당 섹션의 시작 부분으로 위치시켜준다. 그 이유는 RVA 값을 RAW로 변경하기 위해선 섹션의 VirtualAddress 값과 PointerToRawData 값이 필요하기 때문이다. RVA 값을 RAW로 변경하는 방법에 대해 상세히 설명하진 않겠다. 뭔가 이것보다 더 간결하게 구하는 방법이 있을 것 같은데 현재 나로썬 이게 최선인 것 같다.ㅠ

int pe_print_32(char* file, char* option)
{
	// 코드...
    
	// ish가 IMPORT Directory Table이 존재하는 섹션에 위치하게 하기
	ish = (PBYTE)buffer + (idh->e_lfanew) + 24 + (ifh->SizeOfOptionalHeader) + 40 * idt_section;
	
	// IMPORT Directory Table의 RVA 값을 RAW로 변경
	iid = (PBYTE)buffer + ioh->DataDirectory[1].VirtualAddress - ish->VirtualAddress + ish->PointerToRawData;
	file_offset = ioh->DataDirectory[1].VirtualAddress - ish->VirtualAddress + ish->PointerToRawData; 
    
   	// 코드...
}

 

 

참고로, RAW로 변경할 필요가 있는 이유는 내가 PE 파일을 읽는 방식이, 인자로 파일을 입력받아서 해당 파일을 fread로 읽어서 buffer에 복사하는 방식이기 때문이다. 아래 링크에 나와있는 코드와 같이 파일을 직접 읽는 방식이 아니라 메모리에 올라와 있는 이미지를 읽는 방식이라면 RAW로 변경할 필요 없이 해당 값으로 바로 접근하면 된다.

 


참고포스팅

 DLL 인젝션을 통한 지뢰찾기 IAT 후킹 (Hooking) 구현

 

DLL 인젝션을 통한 지뢰찾기 IAT 후킹 (Hooking) 구현

지뢰찾기.exe를 사용해서 단순 DLL 인젝션부터 코드 후킹 (Code Hooking), API 후킹 (API Hooking)까지 구현을 해봤는데 오늘은 마지막으로 IAT 후킹 (IAT Hooking)을 구현해보려 한다. IAT 후킹은 사실 큰개념으..

liveyourit.tistory.com

 

 

IMPORT Directory Table 파싱 및 출력

세팅은 전부 끝났으니 이제 출력만 해주면 된다. 반복문 내에서 아예 iid++를 해주는 방식을 사용했고 Name 값의 경우도 RVA 값이기 때문에 RAW로 변경하는 작업이 필요하다. 

int pe_print_32(char* file, char* option)
{
	// 코드...
    
	// IMPORT Directory Table
	// IMAGE_IMPORT_DESCRIPTOR = IMPORT Directory Table (어떤 라이브러리를 임포트하고 있는지 명시한 테이블, 안에 IAT, INT가 존재함)
	printf("**************************** [IMPORT Directory Table] ************************************\n\n");

	for(; iid->OriginalFirstThunk; iid++)
	{
		printf("- pFile: %04X | Data: %04X | Description: OriginalFirstThunk\n", file_offset, iid->OriginalFirstThunk);
		printf("- pFile: %04X | Data: %04X | Description: TimeDataStamp\n", file_offset + 1, iid->TimeDateStamp);
		printf("- pFile: %04X | Data: %04X | Description: ForwarderChain\n", file_offset + 2, iid->ForwarderChain);
		printf("- pFile: %04X | Data: %04X | Description: FirstThunk\n", file_offset + 3, iid->FirstThunk);
		printf("- pFile: %04X | Data: %04X | Description: Name | Value: ", file_offset + 4, iid->Name);
		
		// iid->Name은 로드하는 dll의 이름을 가리키고 있는 RVA 주소이므로, RAW로 변경한 후 값을 읽어 dll 이름을 추출함
		printf("%s\n", (char*)((PBYTE)buffer + iid->Name - ish->VirtualAddress + ish->PointerToRawData));
		printf("-----------------------------------------------------------------------------------------------------------------\n");

		file_offset += 5;
	}
    
   	// 코드...
}

 

 

IMPORT Directory Table의 출력 결과는 아래와 같다.

 

IMPORT Directory Table 파싱 및 출력 결과

 

댓글

Designed by JB FACTORY