-->

PE Viewer 개발 (5) - IAT(Image Address Table), INT(Image Name Table)

C언어로 개발하는 PE Viewer(PE 뷰어) 다섯번째 포스팅에서는 지난 포스팅에 Import Directory Table 에 이어서 PE 파일 파싱의 꽃? 이라고 할 수 있는 IAT(IMAGE Address Table)와 INT(Image Name Table)를 파싱해보도록 한다. IAT와 INT에 접근하기 위해선 Import Directory Table에 대한 접근이 필수인데 관련 내용은 아래 포스팅에서 참고하길 바란다.

 


 

PE Viewer 개발 (4) - IMPORT Directory Table

 

PE Viewer 개발 (4) - IMPORT Directory Table

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

liveyourit.tistory.com

 

 

IAT(Image Address Table)

IAT는 이름을 보면 알 수 있지만 프로그램에서 임포트하는 DLL에서 어떤 함수들을 사용하고 있는지 함수명, 시작 주소 등에 대한 정보를 기록해놓은 테이블, 구조체 배열이다. 아래 notepad.exe의 경우 GDI32.dll에서 사용하고 있는 함수 주소 배열이 파일 오프셋 2080C에서부터 시작하고있다.

 

 

 

실제로 2080C 부분을 보면 함수 주소 배열이 오고 배열의 끝은 NULL 값인 것을 볼 수 있다.

 

 

 

IAT의 시작 위치는 IMAGE_IMPORT_DESCRIPTOR 구조체의 FirstThunk 값이며 해당 값은 RVA 값이기 때문에 파일 오프셋으로 접근하려면 RAW로 변경하는 과정이 필요하다.

 

  • 시작위치 : IMAGE_IMPORT_DESCRIPTOR.FirstThunk -> RAW
  • 배열끝 : NULL

 

IMAGE_THUNK_DATA 구조체 정의

IAT는 아래의  IMAGE_THUNK_DATA 구조체 배열로 이루어져 있다. 음 이게 처음엔 잘 이해가 가지 않았는데 Function, Ordinal에는 임포트한 함수의 시작주소, 서수값이 저장되어 있고 AddressOfData는 IMAGE_IMPORT_BY_NAME 구조체 RVA 값이 저장되어 있는 변수로 함수명을 파싱할 때 사용하면 된다정도만 일단 알고있으면 될 것 같다..

 

typedef struct _IMAGE_THUNK_DATA {
    union {
        uint32_t* Function; // address of imported function
        uint32_t  Ordinal; // ordinal value of function
        PIMAGE_IMPORT_BY_NAME AddressOfData; // RVA of imported name
        DWORD ForwarderStringl // RVA to forwarder string
    } u1;
} IMAGE_THUNK_DATA, *PIMAGE_THUNK_DATA;

 

 

IAT(IMAGE_THUNK_DATA 구조체 배열) 파싱 및 출력

일단 제일 처음으로 Import Directory Table이 위치한 섹션을 알아야 한다. 따라서 저번 포스팅에서 섹션 헤더들을 파싱할 때 어떤 섹션 내에 위치하는지 인덱스(idt_section)를 미리 저장했다고 했었다. 그리고 어떤 섹션에 위치하고 있는지 알아야 하는 이유는 IAT의 주소가 저장되어 있는 FirstThunk 값이 RVA이기 때문에 RAW로 변환하는 작업이 필요하기 때문이다. 

 

  • Import Directory Table이 위치한 섹션 알아내기 (idt_section)

 

 

따라서 첫번째로 IMAGE_SECTION_HEADERS 구조체 변수인 ish가 Import Directory Table이 존재하는 섹션에 위치하도록 옮겨준 다음 해당 섹션의 VirtualAddressPointerToRawData 값을 활용해서 DataDirectory[1].VirtualAddress (Import Table의 RVA 값)의 RAW 값을 구해준다.

 

  • IMAGE_SECTION_HEADERS 구조체 변수 isth, idt_section으로 설정
  • DataDirectory[1].VirtualAddress (Import Table의 RVA값), RAW 값 구하기

 

 

그리고나서 FirstThunk값에 접근한 후 PIMAGE_THUNK_DATA 구조체의 Function 값을 파싱 및 출력해주면 된다. 참고로, 0x80000000 보다 크다면 서수로 사용되고 있다는 뜻이다.

 

  • 각 IMAGE_IMPORT_DESCRIPTOR 구조체 배열의 FirstThunk (IAT의 RVA) 값에 접근
  • FirstThunk 값, RAW 값 구하기
  • IAT (PIMAGE_THUNK_DATA 구조체 배열),  Function 값 파싱 및 출력

 

 

 

반복해서 파싱할 때 헷갈리지 말아야 할 것이, (편하게 변수로 부르겠다) iid와 iat 둘다 구조체 배열이기 때문에 iid 반복하고 내부에 iat를 다시 반복해주어야 한다. 코드가 괜히 길어보이고 복잡해보이지만 RAW 계산을 위한 연산 부분을 제외하면 완전 간단하다. 

 

int pe_print_32(char* file, char* option)
{
	// 코드...

	// IMPORT Address Table
	PIMAGE_THUNK_DATA iat;

	// 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;    
    
	// IMPORT Address Table
	printf("**************************** [IMPORT Address Table] ************************************\n\n");

	for (; iid->FirstThunk; iid++)
	{
		// iid->FirstThunk의 RVA 값을 RAW로 변경
		file_offset = iid->FirstThunk - ish->VirtualAddress + ish->PointerToRawData;
		iat = (PBYTE)buffer + iid->FirstThunk - ish->VirtualAddress + ish->PointerToRawData;

		// DLL 이름 출력
		printf("[%s]\n", (char*)((PBYTE)buffer + iid->Name - ish->VirtualAddress + ish->PointerToRawData));

		for (; iat->u1.Function; iat++)
		{
			PIMAGE_IMPORT_BY_NAME iibn = (PBYTE)buffer + iat->u1.AddressOfData - ish->VirtualAddress + ish->PointerToRawData;;
			printf("-pFile: %04X | Data: %04X | Description: ", file_offset, iat->u1.Function);

			// Data가 0x80000000보다 크다면 Ordinal 변수로 사용된 것
			if (iat->u1.Function > 0x80000000) printf("\(Ordinal\)\n");
	
			// 그 외는, 함수 이름을 담고 있는 RVA 값(정상)
			else printf("Hint/Name RVA | Value: %s\n", (char*)iibn->Name);
			file_offset++;
		}
		printf("-----------------------------------------------------------------------------------------------------------------\n");
	}
    
	// 코드...
}

 

 

IAT출력 결과는 아래와 같다.

 

 

 

INT(Image Name Table)

INT(Image Name Table)의 경우 코드에 < FirstThunk -> OriginalFisrstThunk > 인 점만 다르다.

 

int pe_print_32(char* file, char* option)
{
	// 코드...

	// IMPORT Name Table
	PIMAGE_THUNK_DATA int_;

	// 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;

	// IMPORT Name Table
	printf("**************************** [IMPORT Name Table] ************************************\n\n");

	for (; iid->OriginalFirstThunk; iid++)
	{
		// iid->FirstThunk의 RVA 값을 RAW로 변경
		file_offset = iid->OriginalFirstThunk - ish->VirtualAddress + ish->PointerToRawData;
		int_ = (PBYTE)buffer + iid->OriginalFirstThunk - ish->VirtualAddress + ish->PointerToRawData;

		// DLL 이름 출력
		printf("[%s]\n", (char*)((PBYTE)buffer + iid->Name - ish->VirtualAddress + ish->PointerToRawData));

		for (; int_->u1.Function; int_++)
		{
			PIMAGE_IMPORT_BY_NAME iibn = (PBYTE)buffer + int_->u1.AddressOfData - ish->VirtualAddress + ish->PointerToRawData;;
			printf("-pFile: %04X | Data: %04X | Description: ", file_offset, int_->u1.Function);

			// Data가 0x80000000보다 크다면 Ordinal 변수로 사용된 것
			if (int_->u1.Function > 0x80000000) printf("\(Ordinal\)\n");

			// 그 외는, 함수 이름을 담고 있는 RVA 값(정상)
			else printf("Hint/Name RVA | Value: %s\n", (char*)iibn->Name);
			file_offset++;
		}
		printf("-----------------------------------------------------------------------------------------------------------------\n");
	}
    
	// 코드...
}

 

 

INT 출력 결과는 아래와 같다.

 

 

댓글

Designed by JB FACTORY