minhui study
32bit로 IAT, EAT 로딩 과정 본문
IAT(Import Address Table)
프로그램에서 어떤 라이브러리의 어떤 함수를 참조하고있는지 가르쳐주는 테이블
IMAGE_IMPORT_DESCRIPTOR
PE파일은 자신이 어떤 라이브러리를 임포트하고 있는지 IMAGE_IMPORT_DESCRIPTOR구조체에 명시하고 있다.
< 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의 과정을 반복한다
notepad.exe를 이용한 실습
실제 IMAGE_IMPORT_DESCRIPTOR구조체 배열은 PE파일의 어느 곳에 존재할까?
PE바디에 위치하는데 그곳을 찾아가기 위한 정보는 PE헤더에 있다. IMAGE_IMPORT_DESCRIPTOR 구조체 배열 정보는 다음과 같다.
RVA가 234B8이므로 Import Directory 시작 offset은 다음과 같다.
* RAW = RVA - VirtualAddress + PointerToRawData
VA | 프로세스 가상 메모리의 절대주소 |
RVA | ImageBase로부터의 상대주소(메모리에 로딩된 상태) |
RAW | PE 파일이 로딩되기 전에 FileOffset |
RAW = 234B8 - 23000 + 20800 = 20CB8 -> Import Directory 시작 offset
20CB8 + 370 = 21028 -> IMAGE_IMPORT_DESCRIPTOR 끝나는 부분
File Offset | Member | RVA | RAW |
20FD8 | OriginalFirstThunk(INT) | 00023828 | 00021028 |
20FDC | TimeDateStamp | 00 00 00 00 | - |
20FE0 | ForwarderChain | 00 00 00 00 | - |
20FE4 | Name | 0002539E | 00022B9E |
20FE8 | FirstThunk(IAT) | 00023000 | 00020800 |
(1) 라이브러리 이름(Name)
Name 항목은 임포트 함수가 소속된 라이브러리 파일의 이름 문자열 포인터이다.
RVA:2539E -> RAW:22B9E
file offset 22B9E 에 "COMCTL32.dll"문자열이 있는 것을 확인할 수 있다.
(2) OriginalFirstThunk(INT)_Import Name Table
INT는 임포트하는 함수의 정보가 담긴 구조체 포이터 배열이다. 이 정보를 얻어야 프로세스 메모리에 로딩된 라이브러리에서 해당 함수의 시작 주소를 정확히 구할 수 있다.
RVA:23828 -> RAW:21028
INT는 주소 배열 형태로 되어 있고 주소 값 하나하나가 각각의 IMAGE_IMPORT_BY_NAME구조체를 가리키고 있다. 배열의 첫번 째 값인 25388(RVA)를 따라가 보자
(3) IMAGE_IMPORT_BY_NAME
RVA:25388 -> RAW:22B88
file offset 22B88의 최초 2바이트 값 (000C)는 라이브러리에서 함수의 고유번호이고, 그 뒤로 CreateStatusWindowW함수 이름 문자열이 보인다. (문자열의 마지막은 NULL로 끝난다.)
정리하자면 INT는 IMAGE_IMPORT_BY_NAME구조체 포인터 배열이라고 할 수 있다. 배열의 첫번째 원소가 가리키는 함수의 고유번호는 000C이고 함수의 이름은 CreateStatusWindowW이다.
(4) FirstThunk - IAT(Import Address Table)
RVA:23000 -> RAW:20800
INT와 마찬가지로 구조체 포인터 배열 형태로 되어 있다. IAT의 첫번 째 원소 값은 025388로 notepad.exe파일이 메모리에 로딩될 때 이 값은 정확한 주소 값으로 대체된다.
EAT(Export Address Table)
프로그램에서 어떤 라이브러리 파일에서 제공하는 함수를 다른 프로그램에서 가져다 사용할 수 있도록 해주는 핵심 메커니즘이다. 즉 EAT를 통해서만 해당 라이브러리에서 익스포트하는 함수의 시작 주소를 정확히 구할 수 있다.
- IAT와 마찬가지로 PE 파일 내의 특정 구조체 IMAGE_EXPORT_DIRECTORY에 익스포트 정보를 저장하고 있으며 라이브러리의 EAT를 설명하는 이 구조체는 PE파일에 하나만 존재한다.
- PE 파일에서 IMAGE_EXPORT_DIRECTORY구조체의 위치는 PE헤더에서 찾을 수 있으며IMAGE_OPTIONAL_HEADER32.DataDirectory[0].VirtualAddress값이 실제 IMAGE_EXPORT_DIRECTORY구조체 배열의 시작주소이다.
< IMAGE_EXPORT_DIRECTORY >
항목 | 의미 |
NumberOfFunctions | 실제 Export 함수 개수 |
NumberOfNames | Export 함수 중에서 이름을 가지는 함수 개수 |
AddressOfFunctions | Export 함수 주소 배열 (배열의 원소 개수 = NumberOfFunctions) |
AddressofNames | 함수 이름 주소 배열 (배열의 원소 개수 = NumberOfNames) |
AddressOfNameOrdinals | Ordinal 배열 (배열의 원소 개수 = NumberOfNames) |
아래 사진은 kernel32.dll 파일의 IMAGE_EXPORT_DIRECTORY 구조체와 EAT 전체 구조를 나타내고 있다.
GetProcAddress()는 라이브러리에서 함수 주소를 얻는 API로 EAT를 참조해서 원하는 API의 주소를 구하는 것이다.
< EAT 로딩 과정 >
1. AddressOfNames 멤버를 이용해 '함수 이름 배열'로 이동
2. '함수 이름 배열'은 문자열 주소가 있으므로, 문자열 비교를 통하여 원하는 함수 이름을 찾는다.(name_index)
3. AddrssOfNameOrdinals멤버를 이용하여 'ordinal' 배열로 이동
4. 'ordinal 배열'에서 name_index를 이용하여 해당 ordinal 값을 찾는다.
5. AddrssOfFunctions 멤버를 이용하여 함수 주소 배열(EAT)로 이동
6. EAT에서 ordinal을 배열 인덱스로 하여 원하는 함수의 시작주소를 얻는다.
* RAW = RVA - VirtualAddress + PointerToRawData
IMAGE_EXPORT_DIRECTORY 의 위치를 찾아보자.
위에서 보면 알다시피 RVA가 92250이므로 Export Directory의 시작 offset은 다음과 같다.
RAW = 92250 - 80000 + 65000 = 77250 -> Export Directory 시작 offset
77250 + DA48 = 84C98 -> IMAGE_EXPORT_DIRECTORY 끝나는 부분
93B84
Export Directory 시작 offset으로 이동하면 다음과 같다. 네모쳐진 부분은 순서대로 Characteristics, TimeDateStamp, MajorVersion, MinorVersion, Name, Base, NumberOfFunctions, NumberOfNames, AddressOfFunctions, AddressOfNames,AddressOfNameOrdinals이다.
표로 정리해보면 다음과 같다.
File Offset | Member | Value | RAW |
00077250 | Characteristics | 00000000 | - |
00077254 | TimeDateStamp | 59B71F87 | - |
00077258 | MajorVersion | 0000 | - |
0007725A | MinorVersion | 0000 | - |
0007725C | Name | 00096116 | |
00077260 | Base | 00000001 | - |
00077264 | NumberOfFunctions | 00000643 | - |
00077268 | NumberOfNames | 00000643 | - |
0007726C | AddressOfFunctions | 00092278 | 77278 |
00077270 | AddressOfNames | 00093B84 | 78B84 |
00077274 | AddressOfNameOrdinals | 00095490 | 7A490 |
(1) 함수 이름 배열
AddressOfNames 멤버의 값은 RVA = 93B84 이고 RAW = 93B84 - 80000 + 65000 = 78B84이다.
RVA:93B84 -> RAW:78B84
file offset 78B84 에 가보자. 이곳은 4바이트의 RVA로 이루어진 배열로 배열의 원소 개수는 NumberOfNames(643)이다. 모든 RVA값을 하나하나 따라가면 함수 이름 문자열이 나타난다.
(2) 원하는 함수 이름 찾기
첫 번째 함수를 찾아보자 RAW = 96182 - 80000 + 65000 = 7B182
RVA:96182 -> RAW:7B182
해당 오프셋으로 가면 AcquireSRWLockExclusive 함수 이름의 문자열을 확인할 수 있다. 이 함수 이름은 배열의 첫번째 원소이고 배열 인덱스로는 0이다.
(3) Ordinal 배열
이제 AcquireSRWLockExclusive 함수의 Ordinal 값을 알아낼 차례이다. AddressOfNameOrdinals 멤버의 값은
RAW = 95490 - 80000 + 65000 = 7A490 이다.
RVA:95490 -> RAW:7A490
해당 오프셋으로 이동해보면 다음과 같이 2바이트의 ordinal로 이루어진 배열이 나타난다.
AcquireSRWLockExclusive 함수는 AddressOfNameOrdinals[0]이므로 ordinal(0)은 다음과 같다.
* index = 0, Ordinal = 3
(5) 함수 주소 배열 - EAT
이제 마지막으로 AcquireSRWLockExclusive의 실제 함수 주소를 찾아가보자.
AddressOfFunctions 멤버의 값은 RAW = 92278 - 80000 + 65000 = 77278이다.
RVA:92278 -> RAW:77278
해당 오프셋으로 이동해보면 다음과 같이 4바이트의 함수 주소 RVA 배열이 나타난다. 이게 바로 Export 함수 주소들이다.
그리고 위에서 AcquireSRWLockExclusive는 ordinal이 3이었으므로 해당 함수의 주소의 RVA는 9619A가 된다.
* AddressOfFunctions[3] = 9619A
(6) AcquireSRWLockExclusive함수의 주소
VA(함수의 실제 주소)는 ImageBase와 RVA를 더한 값이므로 다음과 같다.
6B800000 + 9619A = 6B89619A
'Hacking > Reversing' 카테고리의 다른 글
패킹 & UPX ( 툴없이 언패킹하기 ) (0) | 2020.10.27 |
---|---|
Wargame 'Dreamhack' rev-5 (0) | 2020.10.01 |
Wargame 'Dreamhack' rev-4 (0) | 2020.10.01 |
Wargame 'Dreamhack' rev-3 (0) | 2020.09.19 |
WOW64 file system redirection (0) | 2020.09.17 |