흰싸라기 2022. 3. 23. 20:53

 IAT 

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

  • Windows 운영체제의 핵심 개념인 process, memmory, DLL 구조 등에 대한 내용이 함축

 

 DLL  

: 동적 연결 라이브러리(Dynamic Linked Library)

 

동적 연결 라이브러리라 불리는 이유?

  • 16비트 DOS 시절, C 라이브러리에서 함수의 binary 코드를 그대로 프로그램에 삽입
  • Windows OS에서 멀티 태스킹을 지원하여 16비트 DOS 시절 방식이 비효율적
  • 따라서, 새로운 방법 고안
    • 프로그램에 라이브러리를 포함시키지 말고 별도의 파일(DLL)로 구성하여 필요할 때마다 불러 쓰자.
    • 한 번 로딩된 DLL 코드, 리소스는 Memory Mapping 기술로 여러 Process에서 공유해 쓰자.
    • 라이브러리가 업데이트되었을 때 해당 DLL 파일만 교체하면 돼서 쉽고 편하다. 

 

  • 실제 DLL 로딩 방식은 2가지
    • Explicit Linking : 프로그램에서 사용되는 순간에 로딩하고 사용이 끝나면 메모리에서 해제
    • Implicit Linking : 프로그램 시작할 때 같이 로딩되어 프로그램 종료할 때 메모리에서 해제
  • IAT는 Implicit Linking에 대한 메커니즘을 제공하는 역할

 

OllyDbg를 통한 IAT 확인 : CreatFileW 호출 코드 

  • CreateFile 호출 시 직접 호출하지 않고 01001104 주소에 있는 값을 가져와 호출 (모든 API 호출 방식과 동일)
  • 01001104 주소는 '.text' 섹션의 메모리 영역 (더 정확히는 IAT 메모리 영역), 값은 768F3A90

  • 768F3A90 주소가 notepad.exe 프로세스 메모리에 로딩된 kernel32.dll의 CreateFileW 함수 주소

(?) 그냥 CALL 768F3A90라고 하면 더 편하지 않을까?

=> 해당 방식이 16비트 DOS 시절 사용한 방식

=> 어떤 Windows, 언어, Service Pack에서 실행될지 알 수 없음. 환경에 따라 kernel32.dll 버전이 달라지고 함수의 주소가 달라짐. 따라서, 모든 환경에서 함수 호출을 보장하기 위해 실제 주소가 저장될 위치(ex. 01001104)를 준비 → 파일 실행 시 저장될 주소(ex. 01001104)에 함수 주소를 입력해줌.

=> DLL Relocation 때문에 실제 주소를 하드코딩할 수 없다. (VA가 아닌 RVA를 사용해야하는 이유도 됨)

      ex) a.dll을 ImageBase 값인 메모리 1000000에 로딩

      b.dll을 로딩하려니 a.dll이 존재 따라서 비어있는 메모리 공간(ex. 3E000000)을 찾아 로딩

 

 IMAGE_IMPORT_DESCRIPTOR  

: 어떤 라이브러리를 import하고 있는지를 명시

  • IMAGE_IMPORT_DESCRIPTION 구조체
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;            
        DWORD   OriginalFirstThunk;       // INT(Import Name Table) address (RVA)
    };
    DWORD   TimeDateStamp;
    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;
  • 라이브러리의 개수만큼 위 구조체의 배열 형식으로 존재, 마지막은 NULL 구조체로 끝남

중요 멤버

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

참고)

- PE 헤더에서 Table은 배열을 뜻함

- INT와 IAT는 long type(4바이트 자료형) 배열, NULL로 끝남. 크기 따로 명시 X

- INT에서 각 원소의 값은 IMAGE_IMPORT_BY NAME 구조체 포인터

- INT와 IAT의 크기는 같아야함. 

 

notepad.exe의 kernel32.dll에 대한 IMAGE_IMPORT_DESCRIPTION

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

1. IID(IMAGE_IMPORT_DESCRIPTION)의 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 항목을 이용해 해당 함수의 시작 주소를 얻는다.

     → GerProcAddress("GetCurrentThreadId")

6. IID의 FirstThunk(IAT) 멤버를 읽어 IAT 주소를 얻는다.

7. 해당 IAT 배열 값에 위에서 구한 함수주소를 입력한다. 

8. INT가 끝날 때까지(NULL을 만날때 까지) 위 4~7과정을 반복한다. 

 

 

 실습  

: notepad.exe를 이용한 실습

  • IMAGE_IMPORT_DESCRIPTION 위치 : PE 바디
    • PE 헤더의 IMAGE_OPTIONAL_HEADER32.DataDirectory[1].VirtualAddress 값이 실제 배열 시작 주소(RVA)
  • IMAGE_IMPORT_DESCROPTIOM을 IMPORT Directory Table 이라고도 한다. 

IMAGE_OPTIONAL_HEADER32.DataDirectory[1] 구조체 값

  • 첫 번째 4바이트가 VirtualAddress, 두 번째 4바이트가 Size 멤버
  • RVA of IMPORT Directory : 00007604
  • size of IMPORT Directory : 000000C8
  • RVA to RAW를 통한 File Offset 구하기

File Offset(6A04) 확인

 

IMAGE_IMPORT_DESCRIPTION 구조체 멤버별로 살펴보기

File Offset Member RVA RAW
6A04 OriginalFirstThunk(INT) 00007990 00006D90
6A08 TimeDateStamp FFFFFFFF -
6A0C ForwarderChain FFFFFFFF -
6A10 Name 00007AAC 00006EAC
6A14 FirstThunk(IAT) 00012C4 000006C4

위 표를 참고하여 순서대로 진행

 

1. 라이브러리 이름(Name)

: Name 항목은 임포트 함수가 소속된 라이브러리 파일의 이름 문자열 포인터

 

RVA : 7AAC → RAW : 6EAC 

  • "comdlg32.dll" 문자열 확인

 

 

2. OriginalFirstThunk - INT

: INT는 임포트하는 함수의 정보(Ordinal, Name)가 담긴 구조체 포인터 배열 → 프로세스에 로딩된 라이브러리에서 해당 함수의 시작 주소를 정확히 구할 수 있음

 

RVA : 7990 → RAW : 6D90 

  • 주소 배열 형태 (배열의 끝은 NULL)
  • 주소 값 하나하나가 각각의 IMAGE_IMPORT_BY_NAME 구조체를 가리킴
  • 배열의 첫번째인 7A7A(RVA)를 따라가보자

 

 

3. IMAGE_IMPORT_BY_NAME

 

RVA : 7A7A → RAW : 6E7A 

  • 파일 옵셋의 최초 2바이트 값(000F)는 Ordinal, 함수 고유번호
  • Ordinal 뒤로 'PageSetUpDlgW' 함수 이름 문자열 확인 (마지막은 '\0' : Terminating NULL)

 

4. FirstThunk - IAT(Import Address Table)

 

RVA : 12C4 → RAW : 6C4  

  • 파일 옵셋 6C4~6EB 영역 : 'comdlg32.dll' 라이브러리에 해당하는 IAT 배열 영역
  • 배열은 NULL로 끝남
  • IAT 첫 번째 원소 값은 76324906으로 하드코딩, 메모리에 로딩될 때 정확한 주소 값으로 대체됨