물먹는산세베리아

32bit IAT, EAT 로딩 과정 본문

Waregame & CTF/Reversing

32bit IAT, EAT 로딩 과정

suntall 2021. 10. 2. 21:20

IAT


Import Address Table

프로그램이 어떤 라이브러리에서 어떤 함수를 사용하고 있는지 기술한 테이블

 

1.1. DLL

Dynamic Linked Library

멀티태스킹을 지원하면서 라이브러리의 바이너리 코드를 그대로 프로그램에 삽입하는 방식이 비효율적이 되어버려 등장한 것이 DLL이다.

Linux - *.so Windows - *.dll

- 프로그램에 라이브러리를 포함시키지 않고 별도의 파일(DLL)을 때마다 불러와 쓴다.

- 한번 로딩된 DLL의 코드, 리소스는 메모리 매핑 기술로 여러 process에서 공유해 쓴다.

- 라이브러리가 업데이트되어도 DLL파일만 교체하면 된다. 

 

DLL 로딩 방식

Explicit Linking : 프로그램에서 사용되는 순간에 로딩하고 사용이 끝나면 메모리에서 해제되는 방법

Implicit Linking : 프로그램 시작할 때 같이 로딩되어 프로그램 종료할 때 메모리에서 해제되는 방법

 

IAT는 Implict Linking에 대한 메커니즘을 제공하는 역할을 한다.

 

1.2. IMAGE_IMPORT_DESCRIPTOR (IDD)

PE 파일은 자신이 어떤 라이브러리를 import 하고 있는지 IMAGE_IMPORT_DESCRIPTOR 구조체에 명시한다. 라이브러리 개수만큼 구조체의 배열 형식으로 존재하고, 구조체 배열의 마지막은 NULL로 끝난다.

 

IAT 출처: Microsoft

위 그림은 notepad.exe의 kernel32.dll에 대한 IMAGE_IMPRT_DESCRIPTOR 구조를 표시한 것

* INT와 IAT의 원소가 같은 주소를 가리키고 있지만 그렇지 않은 경우도 있음

 

주요 멤버

항목 의미
OriginalFirstThunk INT(Impoort Name Table)의 주소(RVA)
Name Library 이름문자열의 주소
FirstThunk IAT(Import Address) 의 주소(RVA)

 

IAT 입력 순서

PE 로더가 임포트 함수 주소를 IAT에 입력하는 순서이다.

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

 

1.3. notepad.exe 실습

IMAGE_IMPORT_DESCRIPTOR 구조체 배열이 위치하는 곳은? PE 바디에 위치한다.

PE 헤더에 있는 IMAGE_OPRTIONAL_HEADER의 RVA값이 바로 그 구조체 배열의 시작주소이다. 

* IMAGE_IMPORT_DESCRIPTOR 구조체 배열 == IMPORT Directory Table

IMAGE_OPTIONAL_HEADER에서 Import와 관련된 정보만 보면 RVA는 2647C, Size는 21C이다.

근데 CFF로 열었을 때는 또 다르다. 이유는 모르겠지만 HxD로 열었을 때 CFF와 맞는 거 같아서 일단 이걸로 실습을 진행했다. RVA는 2F908, Size는 230

 

사용한 프로그램: HxD, PEview, CFF Explorer

RVA는 2F908, Size는 230이다.

RAW(File Offset)를 구하는 식은 다음과 같다.

RAW = RVA - VirtualAddress + PointerToRawData

디스크 상의 파일 주소(로딩 전 file offset) = 메모리 로딩 후 메로리 주소 - 메모리에서 섹션의 시작 주소 + 파일에서 섹션의 시작 위치

VA(Virtual Address) 프로세스 가상 메모리의 절대주소, 실제 메모리에 로딩되는 주소
RVA(Relative Virtual Address) ImageBase로부터의 상대 주소 (메모리에 로딩된 상태)
RAW 디스크 상의 파일에서의 주소, PE 파일이 로딩되기 전 FileOffset

 

RVA 값이 .rdata 섹션에 속하므로 virtual address는 28000, raw address(PointerToRawData)는 26c00이다.

RAW = 2F908 - 28000 + 26C00 = 2E508 → Import Directory 시작 offset

 

여기에 import Directory size를 더하면 

2E508 + 230 = 2E738 → IMAGE_IMPORT_DESCRIPTOR 끝나는 부분

 

① 라이브러리 이름(Name)

Name은 import 함수가 소속된 라이브러리 파일의 이름 문자열 포인터

RVA = 30A8E

RAW = 30A8E - 28000 + 26C00 = 2F68E

 

② OrininalFirstThunk - INT(Import Name Table)

INT는 import하는 함수의 정보(Ordinal, Name)가 담긴 구조체 포인터 배열

이걸 알아야 프로세스 메모리에 로딩된 라이브러리에서 해당 함수의 시작 주소를 구할 수 있다.

RVA = 2FC08

RAW = 2FC08 -28000 + 26C00 = 2E808

배열의 첫번째 값인 3068C(RVA)를 따라가면 import하는 API함수 이름 문자열이 나타난다.

 

③ IMAGE_IMPORT_BY_NAME

RVA = 3068C

RAW = 3068C - 28000 + 26C00 = 2F28C

처음 2byte(2B8)는 Ordinal값, 라이브러리에서 함수의 고유번호이다. 뒤의 GetProcAddress는 함수 이름이다.

 

FirstThunk - IAT(Import Address Table)

RVA = 28920

RAW = 28920E - 28000 + 26C00 = 27520

 첫 번째 원소 값이 3068C로 하드코딩 되어 있는데 notepad.exe 파일이 메모리에 로딩될 때 정확한 주소 값으로 대체되어 현재는 의미 없는 값이다.

 

 

 

EAT


Export Address Table

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

EAT를 통해서만 해당 라이브러리에서 export하는 함수의 시작 주소를 정확히 구할 수 있다

IMAGE_EXPORT_DIRECTORY에 export 정보를 저장함

*IAT의 IMAGE_IMPORT_DESCRIPTOR구조체는 여러 개의 멤버를 가진 배열 형태로 존재(PE 파일은 여러 개의 라이브러리를 동시에 import 할 수 있어서) 하지만 EAT의 IMAGE_EXPORT_DIRECTORYRNWHCPSMS PE파일에 하나만 존재함

 

2.1. IMAGE_EXPORT_DIRECTORY

주소는 모두 RVA이다.

 

주요 멤버

항목 의미
NumberOfFunctions 실제 Export 함수 개수
NumberOfNames Export 함수 중에서 이름을 가지는 함수 개수 (<= NumberOfFunctions
AddressOfFunctions Export 함수 주소 배열 (배열의 원소 개수 = NumberOfFunctions)
AddressOfNames 함수 이름 주소 배열 (배열 원소 개수 = NumberOfNames)
AddressOfNameOrdinals Ordinal 주소 배열 (배열의 원소 개수 = NumberOfNames)

 

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

 

동작 원리

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

 

2.2. kernel32.dll 실습

IMAGE_OPTIONAAL_HEADER32.DataDirectory 구조체 배열 정보 중 export 관련 정보

RVA = 9A1E0

Size = DF0C

Virtual Address = 80000

PointToRawData = Raw Address = 7EA00

Export Directory 시작 offset (Export Directory RVA 값을 RAW로 변환)

RAW = 9A1E0 - 80000 + 7EA00 = 98BE0

 

IMAGE_EXPORT_DESCRIPTOR 끝나는 부분

RAW + Export Directory Size = 98BE0 + DF0C = A6AEC

표로 정리하면 

File Offset Member Value RAW
98BE0 Characteristics 00000000  
98BE4 TimeDateStamp 0871FAE9  
98BE8 MajorVersion 0000  
98BEA MinorVersion 0000  
98BEC Name 0009E1D2 9CBD2
98BF0 Base 00000001  
98BF4 NumberOfFunctions 00000661  
98BF8 NumberOfNames 00000661  
98BFC AddressOfFunctions 0009A208 98C08
98C00 AddressOfNames 0009BB8C 9A58C
98C04 AddressOfNameOrdinals 0009D510 9BF10

 

 

① 함수 이름 배열

AddressOfNames 멤버를 통해 찾을 수 있다. 

RAW= 9A58C

4바이트의 RVA로 이루어진 배열이고, 배열 원소의 개수는 NumberOfNames(661 => 1633개)이다.

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

 

② 원하는 함수 이름 찾기

배열의 첫번째 원소 (9E1DF)을 따라간다.

RAW = 9E1DF - 80000 + 7EA00 = 9CBDF

첫번째 원소임을 확인할 수 있다.

 

③ Oridinal 배열

'AcquireSRWLockExclusive' 함수의 Ordinal 값을 알아내야 한다.

위의 표에 나와있듯 AddressOfNameOfdinals 멤버의 RAW 값은 9BF10이다.

해당 위치로 가보면

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

 

④ Ordinal

2번 과정에서 구한 index값인 0을 3번 과정의 Ordinal 배열에 적용하면 

AddressOfNameOrdinals[index] = ordinal

index = 0, ordinal = 0

 

⑤ 함수 주소 배열 - EAT

이제 AcquireSRWLockExclusive 함수의 실제 주소를 찾을 수 있다.

AddressOfFunctions 멤버의 RAW 값은 98C08이다.

 

4byte 함수 주소 RVA 배열을 확인 할 수 있다.

Export 함수의 주소들에 대한 배열이다.

 

⑥ AcquireSRWLockExclusive 함수 주소

4번 과정에서 구한 ordinal 값을 배열에 적용하면

AddressOfFunctions[ordinal] = RVA

ordinal = 0이므로 RVA는 9E1F7

kernel32.dll의 ImageBase = 6B800000

따라서 AcquireSRWLockExclusive 함수의 실제 주소는 

ImageBase + RVA = 6B800000 + 9E1F7 = 6B89E1F7

'Waregame & CTF > Reversing' 카테고리의 다른 글

[dreamhack.io] rev-basic-05  (0) 2021.10.02
[dreamkack.io] rev-basic-4  (0) 2021.10.02
PE 파일 구조 : PE 헤더  (0) 2021.09.28
어셈블리 명령어 모음  (0) 2021.09.26
64bit 레지스터 / 32bit, 64bit 함수호출규약  (0) 2021.09.11