IAT는 응용 프로그램이 불러다 사용하는 dll과 함수의 테이블이였다면, EAT는 프로그램이 dll과 함수에 접근할 수 있도록 주소와 정보를 기록해놓은 테이블이다.
EAT를 통해서만 해당 라이브러리에서 익스포트하는 함수의 시작 주소를 정확히 구할 수 있다.
EAT는 IMAGE_EXPORT_DIRECTORY구조체 안에 기록 되어 있는데, 이것 또한 IAT처럼 PE바디 안에 있고, 이것은 PE구조에 단 한개만 존재한다.
IMAGE_OPTIONAL_HEADER.DataDirectory[1].VirtualAddress 값이 IAT의 구조체 배열 시작 주소였다면,
IMAGE_OPTIONAL_HEADER.DataDirectory[0].VirtualAddress 값이 실제 IMAGE_EXPORT_DIRECTORY 구조체 배열의 시작 주소이다. 이것도 물론 RVA로 된 값이다.
1. 구조체 형식
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp ; // creation time date stamp
WORD MajorVersion;
WORD MinorVersion;
DWORD Name; // address of librar y 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 stri ng array
DWORD AddressOfNameOrdinals; // add ress of ordinal array
} lMAGE_EXPORT_DIRECTORY, *PlMAGE_EXPORT_DIRECTORY;
- 주요 멤버 설명
NumberOfFunctions : 실제 Export 함수 개수
NumberOfNames : Export 함수 중에서 이름을 가지는 함수 개수(≤ NumberOfFunctions)
AddressOfFunctions : Export 함수 주소 배열(배열의 원소 개수 = NumberOfFunctions)
AddressOfNames : 함수 이름 주소 배열(배열의 원소 개수 = NumberOfNames)
AddressOfNameOrdinals : Ordinal 주소 배열(배열의 원소 개수 = NumberOfNames)
라이브러리에서 함수 주소를 얻는 API는 GetProcAddress() 이며, 이 API함수가 바로 EAT를 참조해서 원하는 API의 주소를 구한다.
- GetProcAddress() 동작 원리
- AddressOfNames 멤버를 이용해 '함수 이름 배열'로 간다.
- 문자열 비교(stcmp)를 통하여 원하는 함수 이름을 찾는다.(배열의 인덱스를 name_index)
- AddressOfNameOrdinals 멤버를 이용해 ordinal 배열로 간다.
- ordinal 배열에서 name_index로 해당 ordinal 값을 찾는다.
- AddressOfFunctions 멤버를 이용해 '함수 주소 배열(EAT)'로 간다.
- EAT에서 아까 구한 ordinal을 배열 인덱스로 하여 원하는 함수의 시작 주소를 얻는다.
kernel32.dll에서 GetProcAddress()가 EAT를 참조해서 AddConsoleAliasW의 실제 함수 주소를 찾아보기.
IMAGE_IMPORT_DESCRIPTOR 구조체는 PE바디에 있다.
NT header의 IMAGE_OPTIONAL_HEADER.DataDirectory[0].VirtualAddress값이 IMAGE_EXPORT_DIRECTORY 구조체 배열의 시작 주소(RVA)이다.
실제로 확인해보자.
1. CFF Explorer로 IMAGE_EXPORT_DIRECTORY 구조체 배열의 시작 주소를 찾는다.
IMAGE_OPTIONAL_HEADER.DataDirectory[1].VirtualAddress값, 즉 IMAGE_IMPORT_DESCRIPTOR 의 시작 주소는 "0009A1E0" 이며, .rdata섹션에 존재한다.
2. .rdata섹션의 Virtual address와 Raw address를 찾는다.
0009A1E0 = 구조체 배열의 시작 주소
0007EA00 = .rdata의 Raw address
00080000 = .rdata의 Virtual address
이렇게 되는데 이것을 바탕으로 RAW를 구하는 공식을 사용해 offset을 구해본다.
RAW = RVA - VirtualAddress + PointerToRawData(Raw address)
RAW = 0009A1E0 - 00080000 + 0007EA00 = 0x00098BE0
실제로 맞는지 CFF로 확인해볼 수 있다.
실제로도 맞다.
AddConsoleAliasW 함수를 찾아보자.
AddressOfNames (함수 이름 배열)에는 RVA 0009BB8C 가 들어있다.
0009BB8C 를 RAW로 변환하면,
0009BB8C = AddressOfNames의 RVA
0007EA00 = .rdata의 Raw address
00080000 = .rdata의 Virtual address
RAW = 0009BB8C - 00080000 + 0007EA00 = 0x0009A58C
해당 주소에는 많은 원소의 문자열 주소(RVA)로 이루어져 있다.
첫번째 원소 0009E1DF를 RAW로 변환하여 찾아가 본다.
0009E1DF 를 RAW로 변환하면,
0009E1DF = 함수 이름이 시작되는 RVA
0007EA00 = .rdata의 Raw address
00080000 = .rdata의 Virtual address
RAW = 0009E1DF - 00080000 + 0007EA00 = 0x0009CBDF
해당 주소로 접근해보면,
함수의 이름의 목록들이 보인다.
.으로 구분하고 함수의 위치를 찾게 되면 11번째의 인덱스의 함수가 있다.
하지만 이걸 x32dbg로 열었을 때는 11번째에 있는 걸로 보이지만,
hxd에서 본대로 11번째에 있지만, 64비트로 dll을 실행하게 되면 8번째로 나오게 되는데 그 이유는 아마도 32비트 환경에서 실행할 때와, 64비트에서 실행할 때의 Export하는 함수의 갯수가 달라서 그런 것 같다.
그런데 HxD나 CFF는 64비트에서 실행할때 바이너리를 분석하는 것이니, dll도 x64dbg로 열어서 확인해보자
궁금한 점이 있는데, HxD나 CFF Hex Editor로 봤을 때는 11번째였는데 CFF는 어떻게 파싱한지 모르겠다.
32비트 환경에서도 디버깅을 진행해봐야겠다.
그래서 CFF로 본 RVA로 계산하게 되면
0x00007FF8D9FD0000 + 0x00025650 = 0x7FF8D9FF5650
0x7FF8D9FF5650 = AddConsoleAliasW의 주소
확인 완료.
PE구조 끝
'윈도우 > PE 구조' 카테고리의 다른 글
PE 구조 (6) - IAT (0) | 2022.04.19 |
---|---|
PE 구조 (5) - (RVA to RAW) 예제 포함 (0) | 2022.04.05 |
PE 구조 (4) - (Section Header) (0) | 2022.04.02 |
PE 구조 (3) - (NT Header) (0) | 2022.03.31 |
PE구조 (2) - (DOS Header, DOS Stub) (0) | 2022.03.30 |
최근댓글