일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- Reversing
- tar
- Mobile
- crack
- 안티디버깅
- ctf-d
- Multimedia
- SW에듀서포터즈
- disk
- Autoware
- 모바일프로그래밍
- 침해사고대응
- 케쉴주
- John the ripper
- K-sheild Jr
- K-shield Jr 10기
- CodeEngn
- Interceptor
- 디스크
- 리버싱핵심원리
- Frida
- 써니나타스
- swing
- Android
- 파일해시생성
- upx
- ZIP
- 포렌식
- shadow
- Today
- Total
물먹는산세베리아
32bit IAT, EAT 로딩 과정 본문
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로 끝난다.
위 그림은 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 실습
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 |