Pe32의 Iat에대해
앞서 RVA to RAW 에 대해서 알아보았다.
Import Address Table(IAT) 에 대해서 알아보겠다.
IAT 를 이해하면 Windows 운영체제의 개념중 DLL 개념을 이해한다고 볼 수 있다.
Import Address Table, 말 그대로 어떤 dll 로부터 어떤 함수를 쓸건지 기술한 Table 이다. dll 에 대한 개념설명은 구글링을 통해 자세히 알아보도록 한다.
프로그램에서 dll 의 함수를 쓰는 방법은 두가지다.
- 프로그램에 메모리에 적재시에 PE loader 가 함수 테이블을 만들어서 호출
- 프로그래머가 실행타임에 동적으로 주소를 얻어와서 호출
우리는 1번 항목에 대한 IAT 를 알아본다. 2번 항목에 대해서 알아보려면 “getprocaddress example” 으로 구글링해보기 바란다. 실제로 pe loader 는 GetProcAddress 함수를 통해 IAT 에 값을 채운다.
왜 운영체제가 IAT 를 쓰는지?
예제를 통해 왜 IAT 를 쓰는지 알아보자.
디버거로 해당 프로그램을 열어서 살펴보자. MessageBoxA 에 break point 를 걸고 살펴보면, jmp dword ptr ds:[0x0040305C] 를 통해 MessageBoxA 함수를 호출하고 있다. 저 의미는 jmp 75657e60 이라는 의미와 동일하다.
즉 40305C 라는 곳을 간접참조하면서 jmp 를 한다. 컴파일러가 jmp 0x75657E60 라고 dll 주소를 때려박아주면 될 것을 왜 이런짓을 할까?
실제로 해보자. 패치를 통해 해당 간접주소 jmp 를 jmp 0x75657E60 로 변경시켜봤다. 정상적으로 실행이 된다.
하지만 이렇게 하드코딩된 dll 의 함수주소는 다른 컴퓨터, 다른 windows 버전, 다른 dll 버전에서는 정상적으로 돌아가지 않을 확률이 크다. 왜냐, dll 이 해당 process 에 로딩이 될 때, 함수의 주소 매핑이 다른 주소에 될 수 있기 때문.
어떻게 0x40305C 를 찾아낼까?
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
DWORD BaseOfData;
DWORD ImageBase;
DWORD SectionAlignment;
DWORD FileAlignment;
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage;
DWORD SizeOfHeaders;
DWORD CheckSum;
WORD Subsystem;
WORD DllCharacteristics;
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
위 구조체를 참고하면서 아래 그림을 보자.
IAT 는 IMAGE_DATA_DIRECTORY[1] 부분에 위치한다.
IAT 의 Address = 0x3000(RVA) 다.
0x3000(RVA) => 0xA00 (RAW) 이므로 0xA00 으로 가본다.
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk; // INT(Import Name Table) address (RVA)
};
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name; // library name string address (RVA)
DWORD FirstThunk; // IAT(Import Address Table) address (RVA)
} IMAGE_IMPORT_DESCRIPTOR;
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint; // ordinal
BYTE Name[1]; // function name string
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
IMAGE_IMPORT_DESCRIPTOR 가 여러개 있는 모습이다. (4 bytes 가 5개 있는 형태, 마지막 요소는 NULL 로 채워진다.)
0: KERNEL32.dll(0xA64)
INT=0x303c(RVA) => 0xA3C
NAME=0x3064(RVA) => 0xA64
IAT=0x3050(RVA) => 0xA50
1:USER32.dll(0xA71)
INT=0x3048(RVA) => 0xA48
NAME=0x3071(RVA) => 0xA71
IAT=0x305C(RVA) => 0xA5C
0xA3C 와 0xA48 을 각각 빨간색, 초록색으로 나타내었다.
INT 와 IAT 는 따로 크기가 주어지지 않고 4 bytes null 로 마지막임을 나타낸다.
KERNEL32.dll::INT 는 2개, USER32.dll::INT 는 1개다.
두개의 dll 에 대한 INT 를 나타낸 그림이다.
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint; // ordinal
BYTE Name[1]; // function name string
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
위에서 기술했다시피 INT 는 위 구조체로 이뤄졌다. 해당 예제에서는 Hint = 0x00 으로 동일하다.
우리는 ExitProcess 라는 함수의 IAT 를 확인해보고 실습을 끝내겠다.
INT 를 보고 ExitProcess 가 두번 째 요소라는 것을 알 수 있다. INT 와 IAT 는 같은 순서를 가지기 때문에 IAT 두번 째 요소에 pe loader 가 KERNEL32.dll::ExitProcess 의 주소를 구해놨을 것이라는 짐작을 할 수 있다.
실제로 0x403054(RVA) 에 값을 보면 0x76e63a10 이 들어가있다. 이 주소값은 내 컴퓨터에서 KERNEL32.dll::ExitProcess 의 주소다.
pe loader 가 실제로 값을 써넣었는지는 0x403054(RVA) 를 raw offset 으로 보면 확인할 수 있다. 보통은 INT 값과 동일하게 하드코딩되어있다.