본문 바로가기

리버싱/리버싱핵심원리

PE File Format(EAT)

EAT

- 라이브러리 파일에서 제공하는 함수를 다른 프로그램에서 가져다 사용할 수 있도록 해주는 매커니즘
 즉, EAT를 통해서만 해당 라이브러리에서 익스포트하는 함수의 시작 주소를 정확히 구할 수 있다
 
- PE파일 내의 IMAGE_EXPORT_DIRECTORY 구조체에 익스포트 정보를 저장하고 있는데 이 구조체는
 PE파일에 하나만 존재한다

- IMAGE_EXPORT_DIRECTORY 구조체는 PE 헤더에서 찾을 수 있는데, IMAGE_OPTIONAL_HEADER32.DataDirectory[0].Virtual Address 값이

실제 IMAGE_EXPORT_DIRECTORY 구조체 배열의 시작 주소이다

위 사진을 보면 RVA값은 262C이므로 파일 옵셋은 1A2C이다 

 

IMAGE_EXPORT_DIRECTORY


IMAGE_EXPORT_DIRECTORY 구조체이다

typedef struct _IMAGE_EXPORT_DIRECTORY { 
DWORD Characteristics; 
DWORD TimeDateStamp; 
WORD MajorVersion; 
DWORD Name;  // 라이브러리 파일 이름 주소
DWORD Base;  // Ordinal의 시작 번호
DWORD NumberOfFunctions;  // 실제 Export 함수 개수
DWORD NumberOfNames;  // Export 함수 중에서 이름을 가지는 함수 개수
DWORD AddressOfFunctions;  // Export 함수 주소 배열 (배열의 원소 개수 = NumberOfFunctions)
DWORD AddressOfNames;  // 함수 이름 주소 배열 (배열의 원소 개수 = NumberOfNames)
DWORD AddressOfNameOrdinals;  // Ordinal 배열 (배열의 원소 개수 = NumberOfNames)
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY; 

라이브러리에서 함수 주소를 얻는 API는 GetProcAddress()인데 이 API가 EAT를 참조해서 원하는 API의 주소를 구한다

 

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

 

kernel32.dll로 실습

 

1. 위에서 처름 CFF Explorer를 실행시키고 'kernel32.dll'을 열고 Data Directories로 가서

IMAGE_EXPORT_DIRECTORY 구조체의 RVA를 확인하고 RAW로 변환해

준다 RAW값은 1A2C이다

HxD를 열어서 1A2C 옵셋으로 이동한다

2. 함수 이름 배열 : AddressOfNames 멤버의 값은 RVA는 353C이고, RAW는 293C이고, 배열 원소의 개수는                                                          NumberOfNames(3BA)이다

 

 

3. 원하는 함수 이름 찾기 : 임의로 'AddConsoleAliasW'라는 함수를 찾아보려고 한다. AddressOfNames의 범위에서 임의로 4BD7을 'RVA to RAW' 변환을 해서 3FD7을 구하고 이 주소를 따라간다. 이 때 찾으려는 배열 이름이 몇 번째 원소인지 알아야 다음에 Ordinal 값을 알 수 있다. 이 함수는 배열의 다섯 번째 원소이고, 배열 인덱스로는 4이다

해당 주소로 가면 위 사진처럼 'AddConsoleAliasW' 문자열을 볼 수 있다

4. Ordinal 배열 : 이제 'AddConsoleAliasW' 함수의 Ordinal값을 알아낼 차례이다.

위 사진은 2바이트의 ordinal로 이루어진 배열이다. 다섯 번째 원소는 4이다 

5. ordinal : 앞에서 구한 인덱스 값(4)를 5번의 'Ordinal 배열'에 적용하면 Ordinal(4)을 구할 수 있다

 

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

 

6. 함수 주소 배열 - EAT : 'AddConsoleAliasW'의 실제 함수 주소를 찾을 수 있다. AddressOfFunctions 멤버의 값은

                                            RVA: 2654 -> RAW: 1A54이다

1A54부터 시작해서 배열의 다섯 번째 원소를 보면 72B29임을 알 수 있다

7. AddConsoleAliasW 함수 주소

 'AddConsoleAliasW' 함수의 주소를 얻기 위해 앞에서 구한 Ordinal을 위의 사진에서 배열 인덱스로 적용하면 

RVA = 72B29를 얻을 수 있다

 

AddressOfFunctions[ordinal] = RVA(ordinal = 2, RVA = 72B29)

 

CFF Explorer - Optional Header에서 확인가능

kernel32.dll의 ImageBase = 7C800000이므로 'AddConsoleAliasW' 함수의

실제 주소(VA)는 7C872B29이다(7C800000 + 72B29)

 

7C872b29 번지에 'AddConsoleAliasW' 함수를 볼 수 있다

여기까지가 DLL 파일에서 Export 함수 주소를 찾는 방법이다