IAT 로딩 과정
DLL이란?
Dynamic Link Library, 동적링크로 실행파일에서 해당 라이브러리 기능을 사용시에만 참조해 기능을 호출할 수 있는 방법이다. 여러 프로세스에서 공유하며 쓰는 라이브러리로 멀티태스킹 환경에서 각 프로세스마다 라이브러리를 갖고 그 용량을 차지하는 것보다 메모리에 한 번 로딩시켜 프로세스마다 공유시키면 메모리를 더 효율적을 사용할 수 있다.
IAT란?
Import Address Table, 프로그램이 어떤 라이브러리에서 어떤 함수를 사용하는지에 대해 적은 테이블이다. 이는 Implicit Linking에 대한 메커니즘을 제공하는 역할로 프로그램이 시작할 때 같이 로딩되어 종료할 때 메모리에서 해제된다.
IAT는 PE 파일이 어떤 라이브러리를 import하고 있는지에 대해 IMAGE_IMPORT_DESCRIPTOR 구조체에 RVA형태로 명시되어 있다. 해당 구조체는 다음과 같다. (WinNT.H 참고)
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk;
};
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name;
DWORD FirstThunk;
} IMAGE_IMPORT_DESCRIPTOR;
IMAGE_IMPORT_DESCRIPTOR 구조체의 중요 멤버
> OriginalFirstThunk : INT(Import Name Table)의 주소 (RVA)
> Name : Library 이름문자열의 주소 (RVA)
> FirstThunk : IAT(Import Address Table)의 주소 (RVA)
* RVA : ImageBase로부터의 상대주소 (메모리에 로딩된 상태)
* 여기서 ‘Table’은 ‘배열’을 뜻한다.
IMAGE_THUNK_DATA32는 ILT와 IAT를 구성하고 IMAGE_IMPORT_BY_NAME는 import할 함수의 이름을 저장한다.
< IMAGE_THUNK_DATA32 >
typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString; // PBYTE
DWORD Function; // PDWORD
DWORD Ordinal;
DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA32;
< IMAGE_IMPORT_BY_NAME >
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;
CHAR Name[1];
} IMAGE_IMPORT_BY_NAME;
* RAW = RVA – VirtualAddress + PointerToRawData
위 식을 앞으로 계속 사용할 것이니 기억해 두어라
< IAT 로딩 과정 예시 >
1. IID의 Name 멤버를 읽어서 라이브러리의 이름 문자열을 얻는다
2. 해당 라이브러리를 로딩한다.
→ LoadLibrary(“ADVAPI32.dll”)
3. IID의 OriginalFirstThunk 멤버를 읽어서 INT 주소를 얻는다.
4. INT에서 배열의 값을 하나씩 읽어 해당 IMAGE_IMPORT_BY_NAME주소(RVA)를 얻는다.
5. IMAGE_IMPORT_BY_NAME의 Hint or Name의 이용하여 해당 함수의 시작 주소를 얻는다.
→ GetProcAddress(“OpenProcessToken”)
6.IID의 FirstThunk 멤버를 읽어서 IAT의 주소를 얻는다.
7.해당 IAT 배열 값에 위에서 구한 함수 주소를 입력한다.
8. INT가 끝날 때까지 (NULL을 만날 때까지) 4~7의 과정을 반복한다
우선 IAT 위치를 알기 위해 Import Directory의 시작점을 찾아야한다. 그러기 위해선 Import Directory의 RVA를 찾아 RAW를 구해야 한다. 아래는 NTheader 구간이다. 이 곳에서 빨간색 부분이 Import Directory의 RVA이고 초록색 부분이 Import Directory의 사이즈다. 이는 CFF Explorer을 통해 확인할 수 있다.
여기서 RVA는 우리가 위에서 구한 28648이고 VA는 22000이고 PointerToRawData는 20A00이다 (이는 IMAGE_SECTION_HEADER.idata에서 확인할 수 있다). 그래서 RAW는 27048이다.
그럼 해당 오프셋으로 이동해보겠다. 아래 사진은 27048부터 384byte를 잡은 Import Directory 부분이다. 네모가 쳐진 순서대로 Import Name Table RVA, Time Date Stamp, Forwarder Chain, Name RVA, Import Address Table RVA다. CFF Explorer로 확인하면 첫 라이브러리가 GDI32.dll임을 확인할 수 있다. 실제로 Name으로 이동했을 때 GDI32.dll인지를 확인해보겠다.
라이브러리 이름이 있는 곳으로 이동하려면 전과 같이 Name의 RAW를 구해줘야 한다. Name의 RVA는 2949C고 VA와 PointerToRawData는 각각 22000와 20A00이다. 그럼 Name의 RAW는 27E9C가 된다. 해당 오프셋으로 이동하면 Library의 이름 문자열을 통해 GDI32.dll임을 알 수 있다. 이를 통해 해당 라이브러리를 로딩 할 수 있게 된다.
INT(Import Name Table)를 찾기 위해서는 INT의 RVA를 담고 있는 OriginalFirstThunk의 값을 사용한다. 위에서 이미 OFT의 값은 구했으니 RAW 값을 구하면 289E8 – 22000 + 20A00 = 273E8이 된다. 그럼 해당 오프셋으로 이동하겠다. 아래 사진 부분이 OriginalFirstThunk 구간이다. 그리고 여기서 배열의 첫 부분이 IMAGE_IMPORT_BY_NAME의 RVA는 2938E가 된다.
IMAGE_IMPORT_BY_NAME의 RAW는 2938E – 22000 + 20A00 = 27D8E가 된다. 해당 오프셋으로 이동하면 우리는 라이브러리의 함수가 “SelectObject”라는 것을 알 수 있다. 그리고 앞에 2byte는 hint로 함수의 고유번호를 의미한다. 이 hint 혹은 위에서 본 Name을 통해 해당 함수의 시작 주소를 얻을 수 있다. 이로써 INT의 첫번째 원소가 가리키는 함수는 “SelectObject”임을 알 수 있다.
이제는 이 함수가 실제 메모리에 메핑된 주소를 얻어 IAT에 입력하면 로딩 할 수 있다. IAT의 RVA는 FirstThunk의 값이다. 이를 통해 구한 IAT의 RAW는 22788 – 22000 + 20A00 = 21188이다. 해당 오프셋으로 이동하면 IAT의 첫 원소 값은 2938E로 하드 코딩 되어있음을 알 수 있다.
* 위와 같은 과정을 INT 배열이 끝날 때까지 반복한다. (NULL을 만날 때까지)
EAT
EAT란?
Export Address Table로 라이브러리 파일에서 제공하는 함수를 다른 프로그램에서 가져다 사용할 수 있도록 해주는 핵심 메커니즘이다. EAT의 정보는 IMAGE_EXPORT_DIRECTORY 구조체에서 확인이 가능하다.
< IMAGE_EXPORT_DIRECTORY >
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name;
DWORD Base;
DWORD NumberOfFunctions;
DWORD NumberOfNames;
DWORD AddressOfFunctions;
DWORD AddressOfNames;
DWORD AddressOfNameOrdinals;
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY
> NumberOfFunctions : 실제 Export 함수의 개수IMAGE_EXPORT_DIRECTORY 구조체의 중요 멤버
> NumberOfNames : Export 함수 중 이름을 갖는 함수의 개수 (<= NumberOfFunctions)
> AddressOfFunctions : Export 함수 주소 배열 (배열의 원소 개수 = NumberOfFunctions)
> AddressOfNames : 함수 이름 주소 배열 (배열 원소 개수 = NumberOfNames)
> AddressOfNameOrdinals : Ordinal 배열 (배열의 원소 개수 = NumberOfNames)
IMAGE_EXPORT_DIRECTORY는 Export에 대한 정보를 저장하고 있다. IAT는 섹션마다 존재하지만 EAT는 PE 파일 내에서 딱 하나만 존재한다. 라이브러리는 가장 첫번째 라이브러리인 GDI32.dll로 실습하도록 하겠다.
< EAT 로딩 과정 예시 >
1. AddressOfNames 멤버를 이용해 '함수 이름 배열'로 이동
2. '함수 이름 배열'은 문자열 주소가 있으므로, 문자열 비교를 통하여 원하는 함수 이름을 찾는 다.(name_index)
3. AddrssOfNameOrdinals멤버를 이용하여 'ordinal' 배열로 이동
4. 'ordinal 배열'에서 name_index를 이용하여 해당 ordinal 값을 찾는다.
5. AddrssOfFunctions 멤버를 이용하여 함수 주소 배열(EAT)로 이동
6. EAT에서 ordinal을 배열 인덱스로 하여 원하는 함수의 시작주소를 얻는다.
그럼 IMAGE_EXPORT_DIRECTORY의 위치를 찾아보자. Export Directory의 RVA는 15300이고 사이즈는 9B28이다.
RAW는 위에서 구한 Export Directory의 RVA인 15300 – D000 + C000 = 14300이 된다.
해당 오프셋으로 이동하면 다음 사진과 같다. 네모가 쳐진 부분은 순서대로 NumberOfFunctions, NumberOfNames, AddressOfFunctions, AddressOfNames, AddressofNamesOrdinals다.
우선 AddressOfNames 멤버를 이용해서 함수 이름 주소 배열로 이동해보도록 하겠다. AddressOfNames의 RVA는 162CC다. 이때, RAW는 162CC – D000 + C000 = 152CC다. 그럼 해당 오프셋으로 이동해보겠다. 이 구간은 4byte의 RVA로 이루어진 배열로 배열 원소 개수인 3C1개의 함수 이름의 RVA가 있다. 우리는 이곳에서 가장 첫 번째 함수인 AbortDoc을 찾을 것이다. 아까 봤을 때 해당 함수는 첫 번째 원소의 값을 갖고 있었기 때문에 가장 첫 4byte가 해당 함수 이름을 찾을 수 있는 RVA라고 볼 수 있다.
함수 이름 배열에서 첫 원소의 값은 1795C로 첫 원소의 RAW는 1795C – D000 + C000 = 1695C가 된다. 해당 오프셋으로 이동해보겠다. 해당 오프셋으로 가면 AbortDoc 함수 이름의 문자열을 확인할 수 있다. 그리고 배열의 인덱스는 첫 번째이므로 0번이다.
그 다음엔 AddressOfNameOrdinals 멤버를 이용해 ordinal 배열로 이동해야 한다.
Ordinals 배열의 RVA는 171D0로 RAW는 171D0 – D000 + C000 = 161D0이 된다. 그럼 해당 오프셋으로 이동하겠다. CFF Explorer를 통해 확인하면 AbortDoc가 첫 번째 함수인 건 맞지만 Ordinal은 B인 것을 알 수 있다. 그래서 HxD에서 해당 오프셋으로 이동했을 때 첫 번째 값이 000B인 것이다. 그래서 위에서 구한 index 값을 Ordinal 배열에 적용해 Ordinal을 구할 수 있다. Ordinal = AddressOfNameOrdinals[index] (index = 0, Ordinal = B)
마지막으로 실제 함수 주소 배열 EAT를 찾아야한다. 위에서 구한 AddressOfFunctions가 실제 함수 주소의 RVA다. 그러므로 RAW는 15328 – D000 + C000 = 14328이 된다. 역시 해당 오프셋으로 이동하겠다. 그리고 이 곳에서 Ordinal은 인덱스가 0일 때 B였다 그러므로 해당 함수의 주소의 RVA는 B110이 된다.
VA는 ImageBase와 RVA를 더한 값이다. 해당 지점으로 가면 AbortDoc의 함수를 볼 수 있다.
'Study > Reversing' 카테고리의 다른 글
[Reversing] UPX packing & unpacking (0) | 2020.10.26 |
---|---|
[Reversing] 어셈블리어 C로 변환하기 #2 (0) | 2020.09.22 |
[Reversing] PE Header 개념 정리 & wow64 fs redirection (0) | 2020.09.21 |
[Reversing] 어셈블리어 C언어로 변환하기 #1 (0) | 2020.09.14 |
[Reversing] abex' Crackme #1 (0) | 2020.09.14 |
댓글