알쓸전컴(알아두면 쓸모있는 전자 컴퓨터)

[reversing] IAT 본문

리버싱

[reversing] IAT

백곳 2018. 5. 11. 23:23

IAT






DLL (Dynamic Linked Library)



IAT 를 설명하기 앞서 Windows OS 의 근간을 이루는 DLL(Dynamic Linked Library) 개념을 짚고 넘어가야 합니다.
(뭐든지 이유를 알면 이해하기 쉬운 법이지요...)

DLL 을 우리말로 '동적 연결 라이브러리' 라고 하는데요, 그 이유를 알아 보겠습니다.

16 bit DOS 시절에는 DLL 개념이 없었습니다. 그냥 'Library' 만 존재하였습니다. 

예를 들면 C 언어에서 printf() 함수를 사용할 때 컴파일러는 C 라이브러리에서 
해당 함수의 binary 코드를 그대로 가져와서 프로그램에 삽입(포함)시켜 버렸습니다.
즉, 실행 파일내에 printf() 함수의 바이너리 코드를 가지고 있는 것입니다.

Windows OS 에서는 Multi-Tasking 을 지원하기 때문에 이러한 라이브러리 포함 방식이 비효율적이 되어 버렸습니다.

32 bit Windows 환경을 제대로 지원하기 위해 기본적으로 매우 많은 라이브러리 함수(process, memory, window, message, etc)를 사용해야 합니다.

여러 프로그램이 동시에 실행되야 하는 상황에서 모든 프로그램마다 위와 같이 동일한 라이브러리가 포함되어서 실행된다면 
심각한 메모리 낭비를 불러오게 됩니다. (물론 디스크 공간의 낭비도 무시할 수 없지요.)

그래서 Windows OS 설계자들은 (필요에 의해) 아래와 같은 DLL 개념을 고안해 내었습니다.

"프로그램내에 라이브러리를 포함시키지 말고 별도의 파일(DLL)로 구성하여 필요할 때마다 불러쓰자."
"일단 한번 로딩된 DLL 의 코드, 리소스는 Memory Mapping 기술로 여러 Process 에서 공유해 쓰자."
"라이브러리가 업데이트 되었을때 해당 DLL 파일만 교체하면 되니 쉽고 편해서 좋다."


실제 DLL 로딩 방식은 2가지 입니다.
프로그램내에서 사용되는 순간에 로딩하고 사용이 끝나면 메모리에서 해제 시키는 방법(Explicit Linking)
프로그램 시작할 때 같이 로딩되어 프로그램 종료 할 때 메모리에서 해제되는 방법(Implicit Linking)이 있습니다.

IAT 는 바로 Implicit Linking 에 대한 매카니즘을 제공하는 역할을 합니다.

IAT 의 확인을 위해 OllyDbg 로 notepad.exe 를 열어보겠습니다.
아래 그림은 kernel32.dll 의 CreateFileW 를 호출하는 코드입니다.


CreateFileW 를 호출할 때 직접 호출하지 않고 01001104 주소에 있는 값을 가져와서 호출합니다.
(모든 API 호출은 이런 방식으로 되어 있습니다.)

01001104 주소는 notepad.exe 의 ".text" 섹션 메모리 영역입니다. (더 정확히는 IAT 메모리 영역입니다.)
01001104 주소의 값은 7C8107F0 이며, 
7C8107F0 주소가 바로 notepad.exe 프로세스 메모리에 로딩된 kernel32.dll 의 CreateFileW 함수 주소입니다.

여기서 한가지 의문이 생깁니다.
"그냥 CALL 7C8107F0 이라고 하면 더 편하고 좋지 않나요?" 
컴파일러가 CALL 7C8107F0 이라고 정확히 써줬다면 더 좋지 않냐는 의문이 들 수 있습니다만,
그건 바로 위에서 설명 드렸던 DOS 시절의 방식입니다.

notepad.exe 제작자가 프로그램을 컴파일(생성)하는 순간에는 이 notepad.exe 프로그램이 
어떤 Windows(9X, 2K, XP, Vista, etc), 어떤 언어(KOR, ENG, JPN, etc), 어떤 Service Pack 에서 
실행 될 지 도저히 알 수 없습니다.

위에서 열거한 모든 환경에서 kernel32.dll 의 버전이 틀려지고, CreateFileW 함수의 위치(주소)가 틀려집니다.
모든 환경에서 CreateFileW 함수 호출을 보장하기 위해서 컴파일러는 CreateFileW 의 실제 주소가 저장될 위치(01001104)를 
준비하고 CALL DWORD PTR DS:[1001104] 형식의 명령어를 적어두기만 합니다.


파일이 실행되는 순간 PE Loader 가 01001104 의 위치에 CreateFileW 의 주소를 입력해줍니다.

또 다른 이유는 DLL Relocation 때문입니다.
일반적인 DLL 파일의 ImageBase 값은 10000000h 입니다.

예를 들어 어떤 프로그램이 a.dll 과 b.dll 을 사용한다고 했을때, 
PE Loader는 먼저 a.dll 을 ImageBase 값인 메모리 10000000h 에 잘 로딩합니다.
그 다음 b.dll 을 ImageBase 값인 메모리 10000000h 에 로딩하려고 봤더니, 이미 그 주소는 a.dll 이 사용하고 있었습니다.
그래서 PE Loader 는 다른 비어있는 메모리 공간(ex:3E000000h) 을 찾아서 b.dll 을 로딩시켜 줍니다.

이것이 DLL Relocation 이며 실제 주소를 하드코딩 할 수 없는 이유입니다.
또한 PE Header 에서 주소를 나타낼때 VA 를 쓰지 못하고 RVA 를 쓰는 이유이기도 합니다.

DLL 은 PE Header 에 명시된 ImageBase 에 로딩된다고 보장할 수 없습니다.
  
반면에 process 생성 주체가 되는 EXE 파일은 자신의 ImageBase 에 정확히 로딩되지요. 
  (자신만의 가상 메모리 공간을 가지기 때문입니다.)


이것은 매우 중요한 설명입니다. 다시 한번 잘 읽어보시기 바랍니다.

이제 IAT 의 역할을 이해할 수 있으실 겁니다.
(아래에서 설명드릴 IAT 구조가 왜 이리 복잡해야 하는지에 대해서도 약간 이해가 되실 겁니다.)




"IAT(Import Address Table)" 입니다. 이전에 "RVA to RAW" 라고, PE 파일이 메모리상에 

 로딩된 주소인 RVA(프로세스 가상 메모리의 절대주소)를 RAW(File Offset)으로 바꾸는 연습을 했습니다. 

 이번 포스팅 주제인 IAT 내용을 잘 이해하시기 위해서는 RVA를 RAW로 바꾸는데 어려움이 없으셔야 합니다.

 PE File Format에서 사용되는 구조체에서는 대부분이 RVA로 주소값들을 명시하기 때문입니다.





# IAT (Import Address Table)


-  IAT는 "Import Address Table"의 약어입니다. 영단어 그대로 해석해보면 [ Import : (컴퓨터) (다른 프로그램

  에서 데이를) 불러오다 ] [ Address : 주소 ] [ Table : 테이블 ], "불러오는 주소 테이블" 정도로 생각해볼 수 있겠네요. 영단어 의미 그대로  붙이니까 대충 어떤것인지 느낌은 오는데, 뭔가 좀 이상하네요. 어떤건지 느낌이 오셨나요? 


  IAT(Import Address Table)는 프로그램에서 사용되는 라이브러리에서 어떠한 함수들을 사용하고 있는지, 함수명, 함수시작 주소 등에 대한 정보를 기술한 테이블입니다.


IMAGE_IMPORT_DESCRIPTOR 구조체는 PE 파일 자신이 어떤 라이브러리(DLL)를 임포트(Import)하고 있는지에 대한 정보들을 담고 있는 구조체입니다. 우선 IMAGE_IMPORT_DESCRIPTOR 구조체가 어떻게 생겨먹었는지 한번 구경해보고 알아보도록 하죠.


IMAGE_IMPORT_DESCRIPTOR


PE 파일은 자신이 어떤 라이브러리를 Import 하고 있는지 IMAGE_IMPORT_DESCRIPTOR 구조체에 명시하고 있습니다.

* Import : library 한테서 서비스(함수)를 제공 받는 일
* Export : library 입장에서 다른 PE 파일에게 서비스(함수)를 제공 하는 일


IMAGE_IMPORT_DESCRIPTOR 구조체는 아래와 같습니다.

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;

* 출처 : Microsoft 의 Visual C++ 에서 제공하는 winnt.h

일반적인 프로그램에서는 여러 개의 Library 를 Import 하기 때문에 
Library 의 갯수 만큼 위 구조체의 배열 형식으로 존재하게 되며, 구조체 배열의 마지막은 NULL 구조체로 끝나게 됩니다.

IMAGE_IMPORT_DESCRIPTOR 구조체에서 중요한 멤버는 아래와 같습니다. (전부 RVA 값을 가집니다.)

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

* PE Header 에서 'Table' 이라고 하면 '배열' 을 뜻합니다.
* INT 와 IAT 는 long type (4 byte 자료형) 배열이고 NULL 로 끝납니다. (크기가 따로 명시되어 있지 않습니다.)
* INT 에서 각 원소의 값은 IMAGE_IMPORT_BY_NAME 구조체 주소값을 가지고 있습니다.
   (IAT 도 같은 값을 가지는 경우가 있습니다.)
* INT 와 IAT 의 크기는 같아야 합니다.


아래 그림은 notepad.exe 의 kernel32.dll 에 대한 IMAGE_IMPORT_DESCRIPTOR 구조를 표시하고 있습니다.


<Fig. IAT 구조>

PE Loader 
가 Import 함수 주소를 IAT 에 입력하는 기본적인 순서를 설명드리겠습니다.

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


위 그림에서는 INT 와 IAT 의 각 원소가 동시에 같은 주소를 가리키고 있지만 그렇지 않은 경우도 많습니다.
(변칙적인 PE 파일에 대해서는 향후 많은 파일을 접해보면서 하나씩 배워 나가야 합니다.)


출처 http://reversecore.com/23


해당 내용을 위해서 notepad 의 PEview 에서 IMAGE_IMPORT_DESCRIPTOR  을 확인해 볼께요 


해당 값은 


그렇다면 204B0 는 메모상 위치이니  RAW로 변환 하면


구분 (16진수) (10 진수)
RVA 204B0 132272
Sestion RVA 20000 131072
point to RAW 1BE00 114176
RAW 1C2B0 115376



1C2B0가 되네요 



그럼 위와 같이 


IMAGE_IMPORT_DESCRIPTOR   구조체가 여러가 로딩되는 갯수의 DLL 만큼 존재하게 됩니다. 


여기서 중요한것은 


INT (Import Name Table RAV,구조체 에서는 OriginalFirstThunk) 의 정보를 가지고 

IAT에  (ImportAddress Table RVA, 구조체에서는 FirstThunk) 의 값을 실제 나중에 DLL 에서 로딩되어 실행 시킬 포인터 주소값이 덮여 씌여 지게 됩니다. 


현재 파일 상태에서는 값이 들어가 있어도 그다지 의미가 없습니다. 


추후 PE 로더에 의해서 INT 정보를 보고 IAT에 실제 DLL 주소를 덮어 씌었을때 의미가 생깁니다. 


여기서 첫번째 INT RVA 206E0 을 RAW 변환 하여 보면 


구분 (16진수) (10 진수)
RVA 206E0 132832
Sestion RVA 20000 131072
point to RAW 1BE00 114176
RAW 1C4E0 115936




해당 값에는 다시 RVA 값이 들이고 해당 RVA을 RAW로 변환해서 따라서 가보면 IMAGE_IMPORT_BY_NAME 의 구조체인 HINT 와 함수 이름이 있습니다. 

1개만 따라가 보면 20B8C


구분 (16진수) (10 진수)
RVA 20B8C 134028
Sestion RVA 20000 131072
point to RAW 1BE00 114176
RAW 1C98C 117132



 

이렇게 INT 를 통해서 IAT 테이블 정보를 만들게 됩니다. 


결론은 


PE 로더에서 INT 정보를 가져 가고 IAT RVA 에 실제 로딩될 함수 주소를 적는다 입니다. 



Comments