2 minute read

앞서 RVA to RAW 에 대해서 알아보았다.

Import Address Table(IAT) 에 대해서 알아보겠다.

IAT 를 이해하면 Windows 운영체제의 개념중 DLL 개념을 이해한다고 볼 수 있다.

Import Address Table, 말 그대로 어떤 dll 로부터 어떤 함수를 쓸건지 기술한 Table 이다. dll 에 대한 개념설명은 구글링을 통해 자세히 알아보도록 한다.

프로그램에서 dll 의 함수를 쓰는 방법은 두가지다.

  1. 프로그램에 메모리에 적재시에 PE loader 가 함수 테이블을 만들어서 호출
  2. 프로그래머가 실행타임에 동적으로 주소를 얻어와서 호출

우리는 1번 항목에 대한 IAT 를 알아본다. 2번 항목에 대해서 알아보려면 “getprocaddress example” 으로 구글링해보기 바란다. 실제로 pe loader 는 GetProcAddress 함수를 통해 IAT 에 값을 채운다.

왜 운영체제가 IAT 를 쓰는지?

image

예제를 통해 왜 IAT 를 쓰는지 알아보자.

디버거로 해당 프로그램을 열어서 살펴보자. MessageBoxA 에 break point 를 걸고 살펴보면, jmp dword ptr ds:[0x0040305C] 를 통해 MessageBoxA 함수를 호출하고 있다. 저 의미는 jmp 75657e60 이라는 의미와 동일하다.

즉 40305C 라는 곳을 간접참조하면서 jmp 를 한다. 컴파일러가 jmp 0x75657E60 라고 dll 주소를 때려박아주면 될 것을 왜 이런짓을 할까?

image

실제로 해보자. 패치를 통해 해당 간접주소 jmp 를 jmp 0x75657E60 로 변경시켜봤다. 정상적으로 실행이 된다.

하지만 이렇게 하드코딩된 dll 의 함수주소는 다른 컴퓨터, 다른 windows 버전, 다른 dll 버전에서는 정상적으로 돌아가지 않을 확률이 크다. 왜냐, dll 이 해당 process 에 로딩이 될 때, 함수의 주소 매핑이 다른 주소에 될 수 있기 때문.

어떻게 0x40305C 를 찾아낼까?

typedef struct _IMAGE_OPTIONAL_HEADER {
  WORD                 Magic;
  BYTE                 MajorLinkerVersion;
  BYTE                 MinorLinkerVersion;
  DWORD                SizeOfCode;
  DWORD                SizeOfInitializedData;
  DWORD                SizeOfUninitializedData;
  DWORD                AddressOfEntryPoint;
  DWORD                BaseOfCode;
  DWORD                BaseOfData;
  DWORD                ImageBase;
  DWORD                SectionAlignment;
  DWORD                FileAlignment;
  WORD                 MajorOperatingSystemVersion;
  WORD                 MinorOperatingSystemVersion;
  WORD                 MajorImageVersion;
  WORD                 MinorImageVersion;
  WORD                 MajorSubsystemVersion;
  WORD                 MinorSubsystemVersion;
  DWORD                Win32VersionValue;
  DWORD                SizeOfImage;
  DWORD                SizeOfHeaders;
  DWORD                CheckSum;
  WORD                 Subsystem;
  WORD                 DllCharacteristics;
  DWORD                SizeOfStackReserve;
  DWORD                SizeOfStackCommit;
  DWORD                SizeOfHeapReserve;
  DWORD                SizeOfHeapCommit;
  DWORD                LoaderFlags;
  DWORD                NumberOfRvaAndSizes;
  IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

typedef struct _IMAGE_DATA_DIRECTORY {
  DWORD VirtualAddress;
  DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

위 구조체를 참고하면서 아래 그림을 보자. image

IAT 는 IMAGE_DATA_DIRECTORY[1] 부분에 위치한다.

IAT 의 Address = 0x3000(RVA) 다.

0x3000(RVA) => 0xA00 (RAW) 이므로 0xA00 으로 가본다.

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; 

image

IMAGE_IMPORT_DESCRIPTOR 가 여러개 있는 모습이다. (4 bytes 가 5개 있는 형태, 마지막 요소는 NULL 로 채워진다.)

 0: KERNEL32.dll(0xA64)
 INT=0x303c(RVA) => 0xA3C
 NAME=0x3064(RVA) => 0xA64
 IAT=0x3050(RVA) => 0xA50
 
 1:USER32.dll(0xA71)
 INT=0x3048(RVA) => 0xA48
 NAME=0x3071(RVA) => 0xA71
 IAT=0x305C(RVA) => 0xA5C

image

0xA3C 와 0xA48 을 각각 빨간색, 초록색으로 나타내었다.

INT 와 IAT 는 따로 크기가 주어지지 않고 4 bytes null 로 마지막임을 나타낸다.

KERNEL32.dll::INT 는 2개, USER32.dll::INT 는 1개다.

image

두개의 dll 에 대한 INT 를 나타낸 그림이다.

typedef struct _IMAGE_IMPORT_BY_NAME {
    WORD    Hint;                         // ordinal
    BYTE    Name[1];                      // function name string
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

위에서 기술했다시피 INT 는 위 구조체로 이뤄졌다. 해당 예제에서는 Hint = 0x00 으로 동일하다.

우리는 ExitProcess 라는 함수의 IAT 를 확인해보고 실습을 끝내겠다.

INT 를 보고 ExitProcess 가 두번 째 요소라는 것을 알 수 있다. INT 와 IAT 는 같은 순서를 가지기 때문에 IAT 두번 째 요소에 pe loader 가 KERNEL32.dll::ExitProcess 의 주소를 구해놨을 것이라는 짐작을 할 수 있다.

image

실제로 0x403054(RVA) 에 값을 보면 0x76e63a10 이 들어가있다. 이 주소값은 내 컴퓨터에서 KERNEL32.dll::ExitProcess 의 주소다.

pe loader 가 실제로 값을 써넣었는지는 0x403054(RVA) 를 raw offset 으로 보면 확인할 수 있다. 보통은 INT 값과 동일하게 하드코딩되어있다.

Updated: