적고보니 이해가 좀 되는 것 같다.. 결론은 IMAGE_NT_HEADERS의 필드였던 OptionalHeader의 필드인 DataDirectory 배열의 첫번째 DataDirectory[1]이고, 이게 가리키는게 IMAGE_IMPORT_DESCRIPTOR의 배열인것이다. 그리고 IMAGE_IMPROT_DESCRIPTOR의 INT를 보고 실제 함수의 주소를 찾아서 IAT에 쓰는 과정이 dll의 주소를 메모리에 올리는 과정이다.
0. IAT
0-0 설명
Import Address Table
IAT를 이해하기 위한 사전 지식.. 32bit Windows 환경으로 넘어오면서 메모리나 디스크 등의 자원 활용의 낭비를 막기 위해 DLL
이 생겨났다. DOS 시절의 프로그램들은 특정 함수를 사용하기 위해 해당 라이브러리의 바이너리를 직접 프로그램에 박아버리는 방식으로 사용했기에 새로운 구조의 OS가 생성되면서 호환성 문제도 생기고, 앞서 말한 자원의 낭비가 생긴다.
이런 문제를 해결하기 위해 Dynamic Linked Library
가 생긴 것이다. 프로그램에 직접 삽입하는 것이 아니라, 메모리에 라이브러리를 올려놓으면 그 라이브러리를 사용하는 여러 프로세스들이 공유를 할 수 있는 것이다.
0-1 DLL 로딩 방식
- Explicit Linking
- 사용하는 순간에 로딩하고, 사용이 끝난 후에는 메모리에서 해제
- Implicit Linkng
- 프로그램이 시작될때 같이 메모리에 올라가고 프로그램이 종료될때 같이 해제됨
IAT는 Implict Linking과 관련이 있다.
내가 밑에 실습에 썼던 바이너리를 ollydbg에 올려서 확인한 내용이다.
보면 CALL을 하는데 DS:[&KERNEL32.GetCommandLineA]
에 있는 값을 호출한다. 직접 호출하는것이 아니라.. 그래서 아래 자세한 내용을 보면 실제 함수를 호출할때 참조하는 주소는 0x407000
이고, 그 주소가 가리키는 값이 실제 GetCommandLineA
의 주소(0x761E1EE0
)이다.
결론은 앞서 말한 환경 종속성을 피하고 범용적으로 사용이 가능하도록 하기 위해 함수를 호출할 때 위와 같이 테이블을 참조하여 사용하게 한것이다. 실제 함수의 주소는(IAT의 경우) 프로그램이 시작될 때 dll도 메모리에 로딩이 되니까 그 시점에 호출할 함수의 주소가 결정이 된다. 이때 앞의 예처럼 0x407000 위치의 값에 함수의 주소를 넣으면 되는 것이다.
이렇게 실행중에 동적으로 라이브러리 함수들의 주소를 쫓아가야하고 그렇기 때문에 IAT나 EAT가 필요한 것이다.
1. IMAGE_IMPORT_DISCRIPTOR
1-0. 구조체 설명
앞에 적은 것 처럼 dll 같은 경우 프로그램이 어떤 라이브러리를 사용할지(import 할지) 프로그램이 로딩이 되어야 주소를 알 수 있다. 어떤 라이브러리를 쓰는지가 IMAGE_IMPORT_DESCRIPTOR
에 명시되어있다.
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk; // Import Name Table address (RVA)
} DUMMYUNIONNAME;
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name; // library name string address (RVA)
DWORD FirstThunk; // IAT address (RVA)
} IMAGE_IMPORT_DESCRIPTOR,*PIMAGE_IMPORT_DESCRIPTOR;
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;
BYTE Name[1];
} IMAGE_IMPORT_BY_NAME,*PIMAGE_IMPORT_BY_NAME;
라이브러리를 한개만 사용하는 경우는 거의 없기 때문에, import한 라이브러리 개수 만큼 위 구조체를 배열의 형태로 가지게 된다. INT 주소를 따라가서 확인할 수 있는 내용이 _IMAGE_IMPORT_BY_NAME
구조체를 따른다.
필드 | 의미 |
---|---|
OriginalFirstThunk | INT(Import Name Table) RVA 주소 |
Name | 라이브러리 이름 문자열의 RVA 주소 |
FirstThunk | IAT(Import Address Table) RVA 주소 |
1-1. IAT에 함수 주소를 저장하는 과정
IMAGE_IMPORT_DESCRIPTOR
의 구조체에서 Name
필드를 통해 라이브러리 이름의 문자열을 얻는다.INT를 보고 IAT를 쓰게 되는 것이다.
이후 LoadLibrary
등을 이용해서 해당 라이브러리를 로딩한다(PE 로더가 하는 일이며, 이때 dll이 메모리에 올라감)
IMAGE_IMPORT_DESCRIPTOR
의 OriginalFirstThunk
필드에서 Import Name Table의 주소를 얻는다.
INT 배열 하나하나를 읽으면서 _IMAGE_IMPORT_BY_NAME
의 RVA 주소를 얻을 수 있다. INT 배열 원소 하나가 가리키는게 _IMAGE_IMPORT_BY_NAME
이다.
_IMAGE_IMPORT_BY_NAME
의 앞 2byte는 Hint이고 그 뒤에가 Name
이다(이건 위에 구조체에서 확인가능). 이 이름을 가지고 GetProcAddress
등을 이용해서 함수의 시작 주소를 얻는다.
이후 FirstThunk
필드를 읽어서 IAT
"의" 주소를 얻고, 그 주소값이 가리키는 곳에 앞에서 찾은 함수 주소를 저장한다.
이렇게해서 IAT가 함수의 실제 주소를 가리키게 된다.(이게 중요한거임)
2. 실습(1-1 과정의 실습)
나는 눈에 보인 Easy Keygen.exe를 이용해서 확인해봤다.
붉은색이 _IMAGE_NT_HEADERS.Signature
(4byte, \x50\x45\x00\x00)
초록색이 _IMAGE_NT_HEADERS.FileHeader
(20byte - 직접 세어봄 ㅎ)
노란색과 초록색 사이의 빈곳이 _IMAGE_NT_HEADERS.OptionalHeader
이며, 노란색 부분이 _IMAGE_NT_HEADERS.OptionalHeader.DataDirectory
이다.
이제 저 DataDirectory가 앞서 썼던 글에 있는 DataDirectory[0]~DataDirectory[15] 에 해당하는 내용들이 들어있는 것이다. (보라색이 DataDirectory[1], 파란색이 DataDirectory[12])
2-0. DataDirectory
IAT 내용을 자세히 파악하기 위해서는 DataDirectory의 내용을 잘 봐야한다.
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; // RVA of the data
DWORD Size; // Size of the data
};
내가 보고있는 exe에는 DataDirectory[1], DataDirectory[12]에만 내용이 들어있는 것을 확인할 수 있다. 1번은 Import table 자체에 대한 주소와 크기, 12번은 Import address table의 주소와 크기를 나타낸다.
중요한건 DataDirectory[1]의 VirtualAddress 값이 실제 IMAGE_IMPORT_DESCRIPTOR 구조체의 시작 주소임
1번을 구조체 필드와 함께 보면,
VirtualAddress: 74c8 (RVA)
Size: 28 byte
임을 알 수 있다.
.rdata의 VirtualAddress = 0x7000(RVA) >> 즉 DataDirectory[1]은 .rdata 영역에 있음
.rdata의 PointerToRawData = 0x7000
파일 offset = 74c8 - 7000 + 7000 = 0x74c8
크기는 28byte라고 했으니까 IMAGE_IMPORT_DESCRIPTOR 2개짜리 배열인 것으로 생각하면 될 것 같다.
2-1. IMPORT Directory Table
IMAGE_IMPORT_DESCRIPTOR 필드 | RVA | RAW |
---|---|---|
OriginalFirstThunk(INT) | 74F0 | 74F0 |
TimeDateStamp | 00000000 | |
ForwarderChain | 00000000 | |
Name | 789E | 789E |
FirstThunk(IAT) | 7000 | 7000 |
위 RVA 영역은 위에서 확인했듯이 .rdata의 영역이다.
- OriginalFirstThunk(INT)
- INT는 함수에 대한 정보가 있는 구조체에 대한 포인터 배열이다. 해당 정보를 통해 프로세스가 메모리에 로딩되었을 때 사용하려는 함수가 어느 주소에 있는건지 정확히 구할 수 있게 된다.
- 75A4를 따라가보면 함수명을 찾을 수 있다. INT에서 따라가서 확인하게되는 정보가 IMAGE_IMPORT_BY_NAME 구조체이다.
- 즉 앞 2byte 0108은 Hint(Ordinal) 값이고, 그 뒤에 NULL이 나오기 전까지가 함수명이다.
- Name
- 앞서 봤듯이 Name 필드는 라이브러리 이름이 저장된 포인터(주소값)을 가진다. 즉 789E 위치에 라이브러리 이름이 저장되어있을 것이다.
- 해당 위치에 가보면
KERNEL32.dll
이라는 문자열을 확인할 수 있다
- FirstThunk(IAT: Import Address Table)
- 7000으로 가보면 위와 같은 숫자들로 채워져 있고, INT처럼 IAT도 포인터 배열 형태이다. 그래서 75A4, 75B6 등을 따라가보았다.
- INT에서 따라갔던 것과 동일한 함수로 따라가게 된다.
'Reversing' 카테고리의 다른 글
PE Structure - EAT (0) | 2021.12.04 |
---|---|
PE Structure - Header (0) | 2021.11.28 |