HyeM

[3]IAT, EAT 로딩 과정 본문

Study/Reversing

[3]IAT, EAT 로딩 과정

Hailey_HyeM207 2020. 10. 25. 20:58

IAT

1. 설명 :

IAT는 DLL(동적 연결 라이브러리)를 구현할 때, DLL 로딩 방식 중 하나이다.

  + DLL 로딩 방식에는 2가지 방식이 있는데, 하나는 Explicit Linking이고 다른 하나는 Implicit Linking(IAT)이다. 

프로그램 실행할 때 같이 로딩되어, 프로그램이 종료될 때 메모리에서 해제되는 방법이다.

( PE파일이 어떤 라이브러리를 import하고 있는지 table에 기술함. )

 

2. 관련 구조체

  (1) IMAGE_IMPORT_DESCRIPTOR (IID) = IMPORT Directory table

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;            // 0 for terminating null import descriptor
        DWORD   OriginalFirstThunk;         // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
    };
    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;

  : PE파일이 어떤 라이브러리를 import하고 있는지 이 구조체에 명시함.

   라이브러리 개수 만큼 구조체의 배열 형식으로 존재한다. 배열 마지막은 NULL이다.

     ex)[0] KERNEL32.dll(IID) , [1] VERSION.dll (IID) .... NULL

 

: 주요 멤버

OriginalFirstThunk : INT(Import Name Table)의 주소(RVA)

Name : Library이름 문자열 주소 (RVA)

FirstThunk : IAT(Import Address Table)의 주소(RVA)

 

 

: PE 로더가 import 함수 주소를 IAT에 입력하는 기본적인 순서

  1. IID의 Name 멤버를 읽어 라이브러리 이름 문자열 (kernel32.dll) 을 얻는다.
  2. 해당 라이브러리를 로딩한다.
  3. IID의 OriginalFirstThunk 멤버를 읽어서 INT주소를 얻는다.
  4. INT에서 배열의 값을 하나씩 읽어, 해당 IMAGE_IMPORT_BY_NAME 주소 (RVA)를 얻는다.
  5. IMAGE_IMPORT_BY_NAME의 Hint(ordinal)또는 Name항목을 이용하여 해당 함수의 시작 주소를 얻는다. 
  6. IID의 FirstThunk(IAT)멤버를 읽어서 IAT주소를 얻는다.
  7. 해당 IAT 배열 값에 위에서 구한 함수 주소를 입력한다.
  8. INT가 끝날때 까지(NULL)을 만날때 까지 위의 4~7 과정 반복

 

(2) IMAGE_IMPORT_BY_NAME

typedef struct _IMAGE_IMPORT_BY_NAME {
    WORD    Hint;
    BYTE    Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

 

(3) IMAGE_THUNK_DATA32

typedef struct _IMAGE_THUNK_DATA32{
	union{
    	DWORD ForwarderString;
        DWORD Function;
        DWORD Ordinal;
        DWORD AddressOfData;
        } u1;
}IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;  

 

 INT와 IAT를 구성하고 있는 구조체.

OriginalFirstThunk와 FirstThunk는 IMAGE_THUNK_DATA32 구조체 배열을 갖고 있다.

 

 

 

3. 실습:

[ADVAPI32.dll 찾아가기]

 

더보기

자세히 Data Directory_ IMAGE_DATA_DIRECTORY

코드

DataDirectory[0]=EXPORT Directory
DataDirectory[1]=IMPORT Directory

....

DataDirectory[9]=TLS Directory

NT 헤더의 Data Directory에서 Import Directory (즉, DataDirectory[1]) 구조체를 찾아감.

 + Data Directory는 IMAGE_DATA_DIRECTORY 구조체 배열로, 배열의 각 항목마다 정의된 값을 갖는다.

Hex
CFF Explorer

RVA가 0000 A048이다. 

RVA to RAW 공식으로 file offset을 구하면 다음과 같다. 

ㄴ  RAW(FileOffset) = RVA - VirtualAddress + PointerToRawData 

VirtualAddress값과 PointerToRawData 참고

=> 0000 A048 - 0000 1000(A048은 .text영역이다.) +0000 0400 = 0000 9448 --> Import Directory 시작 offset

 

 

 

 

Import Directory

File Offset  Member RVA RAW
9448 OriginalFirstThunk(INT) 0000 A234 A234-1000+0400=9634
944C TimeDateStamp FFFF FFFF -
9450 ForWarderChain FFFF FFFF -
9454 Name 0000 A224 A224-1000+0400=9624
9458 FirstThunk(IAT) 0000 1000 1000-1000+0400=0400

 

1. NAME

"ADVAPI32.dll" 문자열

9624 Offset에 "ADVAPI32.dll" 문자열 있음.

 

 

2. OriginalFirstThunk-INT(Import Name Table)

INT는 함수의 정보가 담긴 구조체 포인터 배열이다. 이를 통해, 프로세스 메모리에 로딩된 라이브러리에서 해당 함수의 시작 주소를 알 수 있음.

INT

9634 Offset에 가면 INT 있음.

주소 배열 형태로 되어 있고, 배열 끝은 NULL이다. 주소 값 하나하나가 각각의 IMAGE_IMPORT_BY_NAME 구조체를 가르킨다.

배열 첫번째 값인 0000A634(RVA)를 따라가 본다. 따라가면 임포트하는 API함수 이름 문자열이 있다.

 

 

3. IMAGE_IMPORT_BY_NAME

A634(RVA) -> 9A34(RAW)

IMAGE_IMPORT_BY_NAME

파일 옵셋 9A34의 최초 1바이트(007E)는 Ordinal로, 라이브러리에서 함수의 고유 번호이다.

그 뒤로 'RegSetValueExW' 함수 이름의 문자열이 보인다.

 

==> INT는 IMAGE_IMPORT_BY_NAME의 구조체 포인터 배열이다. 배열의 첫번째 원소가 가리키는 함수의 Ordinal값은 007E이고, 함수의 이름은 RegSetValueExW 이다.

 

 

4. FirstThunk -IAT(Import Address Table)

0400offset

FirstThunk-IAT

ADVAPI32.dll 라이브러리에 해당하는 IAT 배열 영역이다. 첫번째 원소 값은 이미  77C71C82 로 하드 코딩 되어 있음. 

원래는 notepad.exe 파일이 메모리에 로딩될 때 이 값은 정확한 주소 값을 대체 되어야 한다.

 

하지만, 실습 환경 설정이 잘 안 맞았는지, 원하는 결과가 나오지 않았다.

win10(64bit)에서 열었을때
win7(32bit)

 

 

 

 

 

 


EAT

1. 설명 

라이브러리 파일에서 제공하는 함수를 다른 프로그램에서 가져다 사용할 수 있도록 해주는 핵심 매커니즘

( 라이브러리가 가진 함수를 다른 프로그램에서 사용할 수 .있도록 함)

 

2. 관련 구조체

IMAGE_EXPORT_DIRECTORY

EAT는 IAT와 마찬가지로 PE 파일 내에 특정 구조체(IMAGE_EXPORT_DIRECTORY)에 정보를 저장함.

이 구조체는 PE파일에 하나만 존재한다.

ypedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD Characteristics;
    DWORD TimeDateStamp;                // creation time date stamp
    WORD MajorVersion;
    WORD MinorVersion;
    DWORD Name;                         // address of library file name
    DWORD Base;                         // ordinal base
    DWORD NumberOfFunctions;            // number of functions
    DWORD NumberOfNames;                // number of names
    DWORD AddressOfFunctions;           // address of function start address array
    DWORD AddressOfNames;               // address of function name string array
    DWORD AddressOfNameOrdinals;        // address of ordinal array
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

: 중요 멤버

NumberOfFunctions : 실제 Export함수 개수 

NumberOfNames : export 함수 중에서 이름을 가지는 함수 개수 ( <=NumberOfFunctions)

AddressOfFunctions : export함수 주소 배열 ( 배열의 원소 개수 = NumberOfFunctions)

AddressOfNames : 함수 이름 주소 배열 ( 배열의 원소 개수 = NumberOfNames )

AddressOfNamesOrdinals : Ordinal  배열  ( 배열의 원소 개수 = NumberOfNames )

 

: EAT를 참조해서 원하는 API주소 구하기

(라이브러리에서 함수 주소를 얻는 API는 GetProcAddress() 이다. )

  1. AddressOfNames 멤버를 이용해 '함수 이름 배열' 로 간다.
  2. '함수 이름 배열'은 문자열 주소가 저장되어 있다. 문자열비교(strcmp)를 통하여 원하는 함수 이름을 찾는다.(이때 배열의 index를 name_index로 하자.)
  3. AddressOfNameOrdinals 멤버를 이용해 'ordinal배열' 로 간다.
  4. 'ordinal배열'에서 name_index로 해당 ordinal값을 찾는다.
  5. AddressOfFunctions 멤버를 이용해 '함수 주소 배열(EAT)'로 간다.
  6. '함수 주소 배열(EAT)'에서 아까 구한 ordinal을 배열 인덱스로 하여 원하는 함수의 시작 주소를 얻는다.

+ ordinal은 export function 의 고유번호이다.

 

3. 실습

[kernel32.dll 파일 EAT에서 AddAtomW 함수 주소 찾기]  _win10 64bit에서 진행

1. NT 헤더의 Data Directory에서 Export Directory (즉, DataDirectory[0]) 구조체를 찾아감.

   ( = IMAGE_EXPORT_DIRECTORY 구조체 배열의 시작 주소)

 

CFF Explorer
IMAGE_OPTIONAL_HEADER32.DATADIRECTORY[0]

RVA는 0008 EC80이다.

RVA TO RAW공식으로 file offset을 구하면 다음과 같다.

ㄴ  RAW(FileOffset) = RVA - VirtualAddress + PointerToRawData 

VirtualAddress값과 PointerToRawData 참고

==> 0008 EC80 - 0007 6000(8 EC80은 .rdata 영역이다) + 0007 4C00 = 0008 D880 --> Export Directory 시작 offset

 

 

kernel32.dll의 IMAGE_EXPORT_DIRECTORY 구조체

File Offset Member Value RAW
8 D880 Characteristics 0000 0000 -
8 D884 TimeDateStamp 2D28261F -
8 D888 MajorVersion 0000 -
8 D88A MinorVersion 0000 -
8 D88C Name 0009 2C4A 0009 184A
8 D890 Base 0000 0001 -
8 D894 NumberOfFunctions 0000 065D -
8 D898 NumberOfNames 0000 065D -
8 D89C AddressOfFunctions 0008 ECA8 0008 D8A8
8 D8A0 AddressOfNames 0009 061C 0008 F21C
8 D8A4 AddressOfNameOrdinals 0009 1F90 0009 0B90

 

GetProcAddress() 동작 원리의 순서대로 진행함.

 

1. 함수 이름 배열 

AddressOfNames raw값은 0008 F21C이다. 

AddressOfNames

여기는 4Byte의 RVA로 이루어진 배열로, 배열의 원소 개수는 NumberOfNames이다. 

이 모든 RVA값을 하나하나 따라 가면 함수 이름 문자열이 나타난다.

 

2. 원하는 함수 이름 찾기

내가 찾고자 하는 'AddAtomW' 함수 이름 문자열은 앞의 그림 에서 RVA배열의 여섯번재 원소값 (RVA: 0009 2CF0 ->RAW : 0009 18F0 )를 따라가면 된다.

'AddAtomW' 문자열

'AddAtomW' 함수 이름은 AddressOfNames 배열의 6번째 원소 이고, 배열의 인덱스로는 5이다.

 

 

3. Ordinal 배열

'AddAtomW'의 Ordinal값을 알아내보려 한다.

AddressOfNamesOrdinals의 멤버의 값의 RAW는 0009 0B90 이다.

AddressOfNamesOrdinals

2byte로 이루어진 배열이 나타난다. (ordinal 배열의 각 원소의 크기는 2바이트 이다.

'AddAtomW' 는 AddressOfNames의 배열의 인덱스로 5였으니까, 위의 그림에서 다음 위치에 해당한다.

 

 

4. ordinal

2번에서 구한 index값 (5)를 3번의 Ordinal 배열에 적용하면 Ordianal(5)를 구할 수 있다.

 

AddressOfNameOrdinals[index] = ordinal (index=5, ordinal=5 )

 

 

5. 함수 주소 배열 - EAT

마지막으로 'AddAtomW'의 실제 함수 주소로 찾아가면 된다.

AddressOfFunctions 멤버의 값의 RAW는 0008 D8A8 이다. 

AddressOfFunctions

4바이트 함수 주소 RVA배열이 나타난다. 여기 있는것들이 Export 함수 주소들이다.

 

 

6. AddAtomW 함수 주소

AddAtomW 함수 주소를 얻기 위해, 3단계에서 얻은 Ordinal 값을 5단계 배열 index로 적용하면 , RVA=0001 0890값을 얻을 수 있다. 

 

AddressOfFunctions[ordinal] = RVA (ordinal=2, RVA= 1 0890)

 

 'AddAtomW' 함수의 실제 주소(VA)를 구하기 위해서는 'kernel32.dll 의 ImageBase'값과  위에서 구한 1 0890더하면 된다.

 

먼저, ImageBase값을 알기 위해,  툴로 열어서 확인해보니, 다음과 같았다.

 ( ImageBase는 NT Header > Optional Header > Image Base)

imageBase값

32bit가 아닌 64bit로 실습해서 그런지, imageBase 값이 Qword로 되어있었다.

qword로 주소 계산을 하니까, 생각보다 더 큰 주소값이 나왔고 이는 실제 디버거로 열었을 때 주소가 될 수 없었다.

 

결론적으로  이 마지막 실습은 성공하진 못했지만, dll 파일에서 export 함수 주소를 찾아내는 방법을 알 수 있었다.

'Study > Reversing' 카테고리의 다른 글

DLL Injection(개념+실습)  (0) 2020.11.05
[3]패킹&UPX패킹(디버거로 언패킹)  (0) 2020.10.26
[3]Dreamhack_Rev 4번 문제  (0) 2020.10.12
[3]Dreamhack_Rev 5번 문제  (0) 2020.10.12
[2]PE구조&wow64 fs redirection + a  (0) 2020.09.20
Comments