본문 바로가기
Study/Reversing

[Reversing] IAT와 EAT 로딩 과정

by Jamie Lim 2020. 10. 13.

IAT 로딩 과정

DLL이란?

Dynamic Link Library, 동적링크로 실행파일에서 해당 라이브러리 기능을 사용시에만 참조해 기능을 호출할 수 있는 방법이다. 여러 프로세스에서 공유하며 쓰는 라이브러리로 멀티태스킹 환경에서 각 프로세스마다 라이브러리를 갖고 그 용량을 차지하는 것보다 메모리에 한 번 로딩시켜 프로세스마다 공유시키면 메모리를 더 효율적을 사용할 수 있다.

 

IAT?

Import Address Table, 프로그램이 어떤 라이브러리에서 어떤 함수를 사용하는지에 대해 적은 테이블이다. 이는 Implicit Linking에 대한 메커니즘을 제공하는 역할로 프로그램이 시작할 때 같이 로딩되어 종료할 때 메모리에서 해제된다.

IATPE 파일이 어떤 라이브러리를 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_NAMEimport할 함수의 이름을 저장한다.

 

< 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 DirectoryRVA를 찾아 RAW를 구해야 한다. 아래는 NTheader 구간이다. 이 곳에서 빨간색 부분이 Import DirectoryRVA이고 초록색 부분이 Import Directory의 사이즈다. 이는 CFF Explorer을 통해 확인할 수 있다.

 

 

 

여기서 RVA는 우리가 위에서 구한 28648이고 VA22000이고 PointerToRawData20A00이다 (이는 IMAGE_SECTION_HEADER.idata에서 확인할 수 있다). 그래서 RAW27048이다.

 

 

 

그럼 해당 오프셋으로 이동해보겠다. 아래 사진은 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인지를 확인해보겠다.


 

라이브러리 이름이 있는 곳으로 이동하려면 전과 같이 NameRAW를 구해줘야 한다. NameRVA2949CVAPointerToRawData는 각각 2200020A00이다. 그럼 NameRAW27E9C가 된다. 해당 오프셋으로 이동하면 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_DIRECTORYExport에 대한 정보를 저장하고 있다. IAT는 섹션마다 존재하지만 EATPE 파일 내에서 딱 하나만 존재한다. 라이브러리는 가장 첫번째 라이브러리인 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 DirectoryRVA15300이고 사이즈는 9B28이다. 

 

 

 

RAW는 위에서 구한 Export DirectoryRVA15300 – D000 + C000 = 14300이 된다.

 

 

 

해당 오프셋으로 이동하면 다음 사진과 같다. 네모가 쳐진 부분은 순서대로 NumberOfFunctions, NumberOfNames, AddressOfFunctions, AddressOfNames, AddressofNamesOrdinals.

 

 

 

우선 AddressOfNames 멤버를 이용해서 함수 이름 주소 배열로 이동해보도록 하겠다. AddressOfNamesRVA162CC. 이때, RAW162CC – D000 + C000 = 152CC. 그럼 해당 오프셋으로 이동해보겠다. 이 구간은 4byteRVA로 이루어진 배열로 배열 원소 개수인 3C1개의 함수 이름의 RVA가 있다. 우리는 이곳에서 가장 첫 번째 함수인 AbortDoc을 찾을 것이다. 아까 봤을 때 해당 함수는 첫 번째 원소의 값을 갖고 있었기 때문에 가장 첫 4byte가 해당 함수 이름을 찾을 수 있는 RVA라고 볼 수 있다.

 

 

 

함수 이름 배열에서 첫 원소의 값은 1795C로 첫 원소의 RAW1795C – D000 + C000 = 1695C가 된다. 해당 오프셋으로 이동해보겠다. 해당 오프셋으로 가면 AbortDoc 함수 이름의 문자열을 확인할 수 있다. 그리고 배열의 인덱스는 첫 번째이므로 0번이다.

 

 

 

그 다음엔 AddressOfNameOrdinals 멤버를 이용해 ordinal 배열로 이동해야 한다.

Ordinals 배열의 RVA 171D0RAW171D0 – D000 + C000 = 161D0이 된다. 그럼 해당 오프셋으로 이동하겠다. CFF Explorer를 통해 확인하면 AbortDoc가 첫 번째 함수인 건 맞지만 OrdinalB인 것을 알 수 있다. 그래서 HxD에서 해당 오프셋으로 이동했을 때 첫 번째 값이 000B인 것이다. 그래서 위에서 구한 index 값을 Ordinal 배열에 적용해 Ordinal을 구할 수 있다. Ordinal = AddressOfNameOrdinals[index] (index = 0, Ordinal = B)

 

 

 

마지막으로 실제 함수 주소 배열 EAT를 찾아야한다. 위에서 구한 AddressOfFunctions가 실제 함수 주소의 RVA. 그러므로 RAW15328 – D000 + C000 = 14328이 된다. 역시 해당 오프셋으로 이동하겠다. 그리고 이 곳에서 Ordinal은 인덱스가 0일 때 B였다 그러므로 해당 함수의 주소의 RVAB110이 된다.

 

 

 

VAImageBaseRVA를 더한 값이다. 해당 지점으로 가면 AbortDoc의 함수를 볼 수 있다.

 

 

댓글