IAT(Import Address Table)
프로그램이 어떤 라이브러리에서 어떤 함수를 사용하고 있는지를 기술한 테이블
DLL(Dynamic Linked Library)
- 프로그램에 라이브러리를 포함시키지 말고 별도의 파일(DLL)로 구성해 필요할 때마다 불러 씀
- 한 번 로딩된 DLL의 코드, 리소스는 Memory Mapping 기술로 여러 Process에서 공유해 사용
- 라이브러리가 업데이트 되었을 때 해당 DLL 파일만 교체하면 되니 편하다
RVA to RAW
PE 파일이 메모리에 로딩되었을 때 각 섹션에서 메모리의 주소(RVA)와 파일 옵셋을 매핑해야한다
방법
1. RVA가 속해 있는 섹션을 찾는다
2. 간단한 비례식을 사용해 파일 옵셋(RAW)를 계산한다
RAW = RVA - VirtualAddress + PointerToRawData
DLL 로딩 방식
- 명시적 링킹(Explicit) : 프로그램에서 필요한 순간 DLL + 함수 정보를 로딩하고 사용 후 메모리에서 해제
- 암시적 링킹(Implicit) : PE 파일 자체에 사용할 DLL + 함수 정보를 포함 후 프로그램 실행 시 로딩되어 프로그램을 종료할 때 메모리에서 해제
* IAT는 암시적 링킹에 대한 메커니즘을 제공하는 역할을 함
1. 올리디버거에서 특정 함수를 호출할 때 직접 호출하는 것이 아니라 특정 주소에 있는 값을 가져와서 호출한다
모든 환경에서 dll파일의 버전이 달라지고, 함수의 주소가 달라지기 때문에 모든환경에서 특정 함수 호출을 보장하기 위해 컴파일러는 함수의 실제 주소가 저장될 주소를 준비하고 'CALL DWORD PTR DS:[1001104]' 형식의 명령어를 적어둠
2. 어떤 프로그램이 a.dll과 b.dll을 사용한다고 했을 때, PE 로더는 먼저 a.dll을 ImageBase 값인 메모리 10000000에 로딩한다. 그 다음 b.dll을 ImageBase 값인 메모리 10000000에 로딩하려고 해도 이미 그 주소에는 a.dll이 사용하고 있어서 PE 로더는 다른 비어있는 메모리 공간을 찾아 b.dll을 로딩시켜준다 이것이 DLL Relocation이라 한다 그리고 PE 헤더에서 주소를 나타낼 때 VA가 아닌 RVA를 써야하는 이유이기도 하다
참고) DLL은 PE헤더에 명시된 ImageBase에 로딩된다고 보장할 수 없으나, process 생성 주체가 되는 EXE 파일은 자신만의 가상 메모리 공간을 가지기 때문에 자신의 ImageBase에 정확히 로딩된다
IMAGE_IMPORT_DESCRIPTOR
PE 파일은 자신이 어떤 라이브러리를 Import하고 있는지 IMAGE_IMPORT_DESCRIPTOR 구조체에 명시한다
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk; // INT(Import Name Table) address (RVA)
};
DWORD TimeDateStamp; // 0 or -1 (not bound vs bound)
DWORD ForwarderChain;
DWORD Name; // library name string address (RVA)
DWORD FirstThunk; // IAT(Import Address Table) address (RVA)
} IMAGE_IMPORT_DESCRIPTOR;
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint; // ordinal
BYTE Name[1]; // function name string
}; IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
- INT와 IAT는 long type(4바이트 자료형) 배열이고 NULL로 끝난다
- INT에서 각 원소의 값은 IMAGE-IMPORT_BY_NAME 구조체 포인터이다
- INT와 IAT의 크기는 같아야 한다
(***) IAT 입력 순서
1. IID의 Name 멤버를 읽어서 라이브러리의 이름 문자열("kernel32.dll")을 얻는다
2. 해당 라이브러리를 로딩한다 LoadLibrary("kernel32.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 과정을 반복한다
notepad.exe로 실습
1. CFF Explorer를 실행 후 notepad.exe를 열고, Optional Header - Data Directories에서 Import Directory의 RVA 값 7604를 확인한다.
2. File Offset은 'RVA to RAW' 변환 공식을 이용하면 6A04임을 알 수 있다
3. HxD로 File Offset 6A04로 이동하면 IMAGE_IMPORT_DESCRIPTOR 구조체를 참고해서 OriginalFirstThunk(INT), TimeDateStamp, ForwarderChain, Name, FirstThunk(IAT)의 RVA를 확인할 수 있고 이를 RAW로 바꾼다
4. 라이브러리 이름(Name) : Name 멤버의 RVA는 7AAC -> RAW는 6EAC가 되고, File Offset 6EAC로 가면 'comdlg32.dll' 문자열을 확인할 수 있다
5. OriginalFirstThunk(INT) : INT는 임포트하는 함수의 정보(Ordinal, Name)가 담긴 구조체 포인터 배열이다. 이 정보를 얻어야 프로세스 메모리에 로딩된 라이브러리에서 해당 함수의 시작 주소를 정확히 구할 수 있다
6. IMAGE_IMPORT_BY_NAME : 임의로 배열의 첫 번째 값인 7A7A(RVA)를 RAW: 6E7A로 바꿔주고 6E7A로 이동하면
파일 옵셋의 최초 2바이트 값(000F)은 Ordinal로, 라이브러리에서 함수의 고유번호이고, Ordinal 뒤에 있는 'PageSetupDlgW' 함수 이름 문자열을 볼 수 있다 즉, INT는 IMAGE_IMPORT_BY_NAME 구조체 포인터
배열이다
7. INT처럼 구조체 포인터 배열 형태로 되어 있으며 배열은 NULL로 끝나므로 6C4 ~ 6EB까지가
'comdlg32.dll' 라이브러리에 해당하는 IAT 배열 영역임을 알 수 있다
8. FirstThunk - IAT : CFF Explorer에서 Import Directory를 보면 comdlg32.dll 을 클릭하면 아래표에 방금 전 확인했던 함수이름인 'PageSetupDlgW'를 확인할 수 있다
이것은 올리디버거를 통해서도 확인할 수 있다
올리디버거로 763D9460(API 시작 주소)로 가면 comdlg32.dll의 PageSetupDlgW 함수 시작이
나타난다.
참고) 일반적인 DLL 파일은 ImageBase가 10000000으로 되어 있어서 보통 DLL relocation이 발생하는 경우가 많으나 Windows 시스템 DLL 파일들(kernel32, user32, gdi32 등)은 자신만의 고유한 ImageBase가 있어서 DLL relocation이 발생하지 않는다
'리버싱 > 리버싱핵심원리' 카테고리의 다른 글
실행 압축 (0) | 2019.12.01 |
---|---|
PE File Format(EAT) (0) | 2019.12.01 |
PE File Format(Dos Header ~ Optional Header) (0) | 2019.08.26 |
Process Explorer, 함수 호출 규약 (0) | 2019.08.25 |
3, 4장 요약(바이트 오더링, IA-32 Register) (0) | 2019.08.24 |