#define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // Import Directory
오늘 확인할 calc.exe의 ENTRY_IMPORT 이다.
이것은 어떤 것으로 구성되어 있을까?
우선 IMAGE_IMPORT_DESCRIPTOR 구조체를 보면 된다.
IMAGE_IMPRT_DESCRIPTOR
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // INT (Import Name Table)' RVA
} DUMMYUNIONNAME;
DWORD TimeDateStamp; // 0 if not bound,
// -1 if bound, and real date\time stamp
// in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
// O.W. date/time stamp of DLL bound to (Old BIND)
DWORD ForwarderChain; // -1 if no forwarders
DWORD Name;
DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
OriginalFirstThunk, Name, FirstThunk를 중요하게 봐야한다.
첫번째 변수인 OriginalFirstThunk는 INT(Import Name Table)의 RVA 값이다.
INT는 IMAGE_IMPORT_BY_NAME 구조체 포인터이다.
Name 변수에는 문자열의 주소가 존재한다.
어떤 문자열이 들어가냐면 말그대로 이름이 들어가는데 그건 Library 이름으로 사실 이 구조체는 이 파일에 import 된 라이브러리를 알려주는 구조체 이기때문이다.
FirstThunk 는 IAT(Import Address Table)의 RVA가 들어있다.
즉 해당 라이브러리의 이름과 INT, IAT의 주소가 들어 있는 곳이다.
왜 우리는 IMAGE_IMPORT_DESCRIPTOR 구조체를 봐야할까?
그것은 사실 Import Directory 가 가르키고 있는 주소는 이 구조체 배열의 시작 주소때문이다.
그리고 이 구조체 배열을 Import Directory table이라고 합니다.
그럼 이 구조체의 크기를 우선 계산해보자.
DWORD * 5 = 20Bytes 크기라는 것을 알 수 있다.
위 사진에 보면 20bytes 크기의 구조체 배열이 존재한다는 뜻으로 가장 마지막에는 NULL로 배열의 끝을 알려주는데 00의 개수를 보면 11FF8부터 널값이 20개가 들어가면서 배열의 끝을 알려준다.
그럼 이제 아까 사진에 나온 영역에서 20bytes 먼저 확인해 보자.
OriginalFirstThunk (INT)
4bytes 씩 끊어서 읽으면 그게 각 변수에 들어있는 값이 된다.
OriginalFirstThunk : 00012CA8 (리틀엔디안 표기를 주의하자) = INT의 RVA 값
TimeDateStamp : FFFFFFFF = IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 값을 참조하세요.
ForwarderChain : FFFFFF = no forwarders
Name : 00 01 2E 42 = string이 들어있는 주소
FirstThunk : 0000109C = IAT의 RVA 값
우선 OriginalFirstThunk의 들어간 곳을 확인해 보자.
RVA를 Offset으로 변환해보면
RAW = RVA - VirtualAddress + PointerToRawData
RAW = 12CA8 - VirtualAddress + PointerToRawData
12CA8는 .data section에 속해 있다.
210 offset에 .data section header의 정보가 포함되어있다.
NAME[8] : 2E 64 61 74 61 (.data)
VirtualSize : 1C 10 (101C)
VirtualAddress : 00 40 01 ( 14000)
SizeOfRawData : 00 0A (A00)
PointerToRawData : 00 2C 01 (12C00)
이 정보를 토대로 RAW를 구하면 된다.
RAW = 12CA8 - 14000 + 12C00 = 118A8
118A8이 첫번째 IMAGE_IMPORT_DESCRIPTOR의 INT Offset 값이다.
가보면 3D1F7400 이라는 IMAGE_IMPORT_BY_NAME 구조체를 가르키는 주소값이 들어 있다.
하지만 이것도 RVA 값임으로 RAW를 구해줘야한다.
RAW = 12E34 -1000 + 400 = 12234
12234로 가보면 IMAGE_IMPORT_BY_NAME 구조체가 존재한다.
우선
IMAGE_IMPORT_BY_NAME 구조체 정의를 살펴보면
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;
BYTE Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
인 것을 알 수 있다.
여기서 중요한 것은 BYTE Name[1] 라고 적혀있지만 사실은 00으로 끝날때까지 Name으로 이루어져있다.
무슨 말인지는 사진을 통해 보면 이해가 쉽다.
00이 오는 곳에 name은 끝이 난다.
Hint(Ordinal, 라이브러리에서 사용하는 함수의 고유번호) 값은 94이고
'ShellAboutW' 라는 함수 문자열이 존재하는 것을 확인할 수 있다.
정말 많은 길을 걸어 왔다.
이로써 우리는 Import Directory에서 첫번째 값이 어떤 의미로 사용되는지 알아봤다.
Name
이제 Name 변수에 있던 값인 00012E42 를 찾아가보자
RAW를 계산해보자
RAW = 12E42 - 1000 + 400 = 12242
12242로 가보자.
사실 가볼 것도 없다 바로 옆에 있었다.
'SHELL32.dll' 이라고 적혀있었다.
우리가 살펴본 이 Name 변수는 import 함수가 소속된 라이브러리 파일의 이름 문자열 포인터이다.
Name에서는 참조하고 있는 파일 이름이, INT에는 그 라이브러리 파일에서 참고하고 있는 함수이름이 적혀있다.
이제 마지막으로 IAT를 찾아보고 Import Directory에 대한 글을 마무리하려고 한다.
FirstThunk (IAT)
지난 번 글에서 Import directory 배열과 IAT 배열의 차이를 모르겠다는 글을 남겼었었다.
하지만 이번 글을 정리하면서 차이를 이제 알게 되었다.
Import directory는 INT, name, IAT 등을 갖고 있는 구조체 배열의 포인터였고 IAT는 IAT 배열의 시작점을 가리키는 포인터였다.
그럼 이제 이 FirstThunk가 가리키는 IAT(Import Address Table)로 가보자.
0000109C를 계산해보자
109C - 1000 + 400 = 49C
49C로 가보겠다.
이곳이 바로 'SHELL32.dll'에서 참고하는 'ShellAboutW'의 하드코딩된 주소이다.
이런식으로 Import Directory에 들어간 값들을 20Bytes 단위로 끊어서 분석하면 어떠한 라이브러리 파일들에서 어떤 함수를 참조해서 사용하는지 확인 할수가 있다.
이런 내용들은 간단한 도구들로도 보기쉽게 나와있지만 공부를 위해서 hex editor만 사용해서 분석해보는 것도 좋은 공부 방법이라고 생각된다.
필자는 많은 분들이 읽으시는 리버싱 핵심원리 책에 나온 notepad.exe 실습을 응용해서 직접 calc.exe를 헥스값을 보고서만 찾아가며 분석해보았다.
굉장히 의미 있는 시간이였다고 생각한다.