- IAT (Import Address Table / PE File 관련)
# 이번 포스팅 내용은 "IAT(Import Address Table)" 입니다. 이전에 "RVA to RAW" 라고, PE 파일이 메모리상에
로딩된 주소인 RVA(프로세스 가상 메모리의 절대주소)를 RAW(File Offset)으로 바꾸는 연습을 했습니다.
이번 포스팅 주제인 IAT 내용을 잘 이해하시기 위해서는 RVA를 RAW로 바꾸는데 어려움이 없으셔야 합니다.
PE File Format에서 사용되는 구조체에서는 대부분이 RVA로 주소값들을 명시하기 때문입니다.
# IAT (Import Address Table)
- 우선 IAT가 무엇인지 알아야겠죠? IAT는 "Import Address Table"의 약어입니다. 영단어 그대로 해석해보면 [ Import : (컴퓨터) (다른 프로그램
에서 데이터를) 불러오다 ] [ Address : 주소 ] [ Table : 테이블 ], "불러오는 주소 테이블" 정도로 생각해볼 수 있겠네요. 영단어 의미 그대로
붙이니까 대충 어떤것인지 느낌은 오는데, 뭔가 좀 이상하네요. 어떤건지 느낌이 오셨나요?
IAT(Import Address Table)는 프로그램에서 사용되는 라이브러리에서 어떠한 함수들을 사용하고 있는지, 함수명, 함수시작 주소 등에 대한
정보를 기술한 테이블입니다.
# IAT에 대해서 간단하게 설명했는데요. 이제 좀 더 자세하게 알아보도록 하겠습니다.
# IMAGE_IMPORT_DESCRIPTOR
- IMAGE_IMPORT_DESCRIPTOR 구조체는 PE 파일 자신이 어떤 라이브러리(DLL)를 임포트(Import)하고 있는지에 대한 정보들을 담고 있는
구조체입니다. 우선 IMAGE_IMPORT_DESCRIPTOR 구조체가 어떻게 생겨먹었는지 한번 구경해보고 알아보도록 하죠.
[ IMAGE_IMPORT_DESCRIPTOR 구조체와 IMAGE_IMPORT_BY_NAME 구조체 (출처:winnt.h) ]
- 위에 보이시는 것이 IMAGE_IMPORT_DESCRIPTOR 구조체 정의에 대한 내용입니다. 그리고 그 아래에 IMAGE_IMPORT_BY_NAME 구조체도 있
는데요. IMAGE_IMPORT_BY_NAME 구조체는 IMAGE_IMPORT_DESCRIPTOR 구조체 멤버인 OriginalFirstThunk 멤버 값과 관련있습니다.
조금 후 자세하게 설명하도록 하겠습니다.
- 우선 IMAGE_IMPORT_DESCRIPTOR 구조체에 대해서 얘기해보도록 하겠습니다. 위에서 말했듯이 이 구조체는 PE 파일에서 임포트해서 사용하
는 라이브러리, 그리고 라이브러리에서 사용하고 있는 함수에 대한 정보를 담고 있는 구조체입니다. PE 파일은 여러 개의 라이브러리를 임포트
하는 경우가 대부분입니다. 이러한 점을 때문에 IMAGE_IMPORT_DESCRIPTOR 구조체는 PE 파일에서 임포트하는 라이브러리 개수개만큼 배열
형식으로 되어 있으며, 구조체 배열의 마지막 구조체는 NULL 구조체로 구성되어 있습니다. 간단한 그림을 통해서 예를 들어보겠습니다.
- 이해가 가시죠? 이런식으로 임포트하는 라이브러리의 개수보다 1개 더 많은 수로 IMAGE_IMPORT_DESCRIPTOR 구조체가 배열 형식으로
존재하게 됩니다.
- 이제 IMAGE_IMPORT_DESCRIPTOR 구조체의 멤버 변수에 대해서 알아보도록 하겠습니다.
# IMAGE_IMPORT_DESCRIPTOR 구조체의 멤버 변수
1. Characteristics 와 OriginalFirstThunk 멤버 변수
- 위 2개의 멤버 변수는 union 구조체로 정의되어있습니다. union 구조체는 멤버 변수들중 가장 큰 변수의 데이터형에 맞게끔 메모리가 할당되고,
그 공간을 공유해서 쓰는 구조체입니다. 그런데 위에 선언된 두 멤버 변수 중 하나인 Characteristics 멤버 변수는 현재 사용되지 않는 멤버 변수
입니다. OriginalFirstThunk 멤버 변수만 사용되는데, 이 멤버 변수는 INT(Import Name Table)의 RVA 값을 가집니다. INT(Import Name
Table)은 IMAGE_THUNK_DATA32 구조체 배열로 구성되어 있는데요.
즉, OriginalFirstThunk 멤버 변수가 명시하는 값은 INT의 IMAGE_THUNK_DATA32 구조체 배열의 주소(RVA)입니다.
[ 출처 : 리버싱 핵심원리 ]
- IMAGE_THUNK_DATA32 구조체가 뜬금없이 나왔는데요. 이 구조체에 대해서도 알아보고 넘어가도록 하겠습니다.
[ IMAGE_THUNK_DATA32 구조체 (출처:winnt.h) ]
- 위에 보이시는 것이 IMAGE_THUNK_DATA32 구조체입니다. 이 구조체는 INT(Import Name Table), 그리고 IAT(Import Address Table)를
구성하는 구조체입니다. (각 4Byte 크기의 IMAGE_THUNK_DATA32 구조체들이 배열로 구성되어 있음)
- IMAGE_THUNK_DATA32 구조체의 멤버 변수들은 union으로 선언되어 있는데요. 상황에 따라 사용하는 변수가 다르기 때문입니다. 그렇다면,
각 멤버 변수들이 어떤 용도로 사용되는지에 대해서 알아보도록 하겠습니다.
- INT를 구성하는 IMAGE_THUNK_DATA32 구조체는 크게 2가지 용도로 사용되어 집니다.
( INT를 구성하는 IMAGE_THUNK_DATA32 구조체에서는 ForwarderString , Function 멤버 변수는 사용되지 않습니다 )
① AddressOfData 변수 : IMAGE_IMPORT_BY_NAME 구조체의 주소(RVA)를 명시할 때 사용하는 변수
- IMAGE_IMPORT_DESCRIPTOR 구조체를 설명하면서 IMAGE_IMPORT_BY_NAME 구조체에 대해서 잠깐 얘기를 했었죠?
IMAGE_IMPORT_DESCRIPTOR 구조체의 OriginalFirstThunk 멤버 변수와 관련이 있다고 했었습니다. 좀 더 정확하게 얘기한다면,
OriginalFirstThunk 멤버 변수가 명시하고 있는 INT(Import Name Table)를 구성하고 있는 IMAGE_THUNK_DATA32 구조체의 값과
관련이 있는 겁니다. 지금 IMAGE_THUNK_DATA32 구조체의 멤버 변수에 대해서 설명하고 있었는데, AddressOfData 변수를 사용
하게 되면 바로 IMAGE_IMPORT_BY_NAME 구조체의 주소를 명시하게 되는 것입니다.
② Ordinal 변수 : 임포트한 함수에 대한 Ordinal 값을 명시할 때 사용하는 변수
- 4Byte 크기를 가지는 IMAGE_THUNK_DATA32 구조체의 값이 MSB(최상위 비트)가 1이면 Ordinal 변수로 사용된 것입니다.
다시 말해서 IMAGE_THUNK_DATA32 구조체의 값이 0x80000000 이상인 경우에 Ordinal 변수로 사용되어 임포트한 함수에 대한 Ordinal
값을 명시한 것이며, MSB(최상위 비트)를 제외한 비트의 값이 Ordinal(서수)를 나타내게 됩니다.
- IAT를 구성하는 IMAGE_THUNK_DATA32 구조체의 변수 사용 용도에 관한 것은 IMAGE_IMPORT_DESCRIPTOR 구조체의 FirstThunk 멤버
변수와 관련 있습니다. 지금부터 IMAGE_IMPORT_DESCRIPTOR 구조체의 FirstThunk 멤버 변수에 대해서 알아보도록 하겠습니다.
2. FirstThunk 멤버 변수
- FirstThunk 멤버 변수는 IAT(Import Address Table)의 RVA 값을 가지는 멤버 변수입니다. IAT도 INT와 마찬가지로 IMAGE_THUNK_DATA32 구
조체배열로 구성되어 있습니다. 그리고 INT는 IAT와 매우 유사합니다. 정확하게 말하자면, 바인딩(Binding)이라는 과정을 거치기 전에는 INT와 IAT는
완전하게 동일한 IMAGE_THUNK_DATA32 구조체 배열로 구성되어 있습니다. 하지만 바인딩 과정을 거치게 되면서 기존의 IAT의 IMAGE_THUNK
_DATA32 구조체의 값들이 실제적인 함수의 주소로 바뀌게 됩니다.
* 바인딩이란 : Windows 로더가 IAT(Import Address Table)의 정보를 통하여 해당 라이브러리에 연결되어 있는 다른 라이브러리를 같이 로드하며,
해당 라이브러리에서 참조하고 있는 다른 라이브러리의 함수 주소를 EAT(Export Address Table)에서 찾아 그 함수 주소를 IAT의
IMAGE_THUNK_DATA32 구조체에 기록하는 것을 의미합니다.
- IAT 역시 IMAGE_THUNK_DATA32 구조체를 사용하므로 union으로 선언된 멤버 변수들을 상황에 따라 다른 변수들을 사용하게 되는데요.
각 멤버 변수들이 어떤 용도로 사용되는지 알아보도록 하겠습니다.
( IAT를 구성하는 IMAGE_THUNK_DATA32 구조체에서는 INT를 구성하는 구조체와는 달리 모든 멤버 변수들이 사용될 수 있습니다 )
① AddressOfData 변수 : IMAGE_IMPORT_BY_NAME 구조체의 주소(RVA)를 명시할 때 사용하는 변수
- INT를 구성하는 IMAGE_THUNK_DATA32 구조체에서 사용되는 용도와 동일합니다.
② Ordinal 변수 : 임포트한 함수에 대한 Ordinal 값을 명시할 때 사용하는 변수
- Ordinal 변수 사용 역시 INT를 구성하는 IMAGE_THUNK_DATA32 구조체에서 사용되는 용도와 동일합니다.
③ ForwarderString 변수 혹은 Function 변수 : 바인딩 과정을 거친 후, 실제 함수의 주소를 명시할 때 사용하는 변수
- IAT는 바인딩 과정을 거치기 전에는 INT와 동일한 IMAGE_THUNK_DATA32 구조체 배열을 가진다고 했었습니다. 하지만, 바인딩 과정을
거치게 되면 IAT의 IMAGE_THUNK_DATA32 구조체에서 사용하던 멤버 변수가 ForwarderString 혹은 Function 멤버 변수로 변경되어
사용되게 됩니다.
3. Name 멤버 변수
- IMAGE_IMPORT_DESCRIPTOR 구조체의 Name 멤버 변수는 해당 Library (DLL) 이름 문자열이 존재하는 주소(RVA)를 명시합니다.
즉, 임포트하고자하는 Library 이름이 있는 곳을 가리키는 포인터 역할을 합니다.
4. TimeDateStamp 멤버 변수와 ForwarderChain
- 이 2개의 멤버 변수들은 PE 로더에 의해 각 IMAGE_IMPORT_DESCRIPTOR 구조체에 해당하는 라이브러리(DLL)가 바인딩 과정을 거치기 전에는
항상 0의 값을 가지며, 바인딩이 완료된 후에는 -1의 값을 가지게 됩니다.
# 여기까지가 IAT의 IMAGE_IMPORT_DESCRIPTOR 구조체에 대한 내용이였습니다. 아직 IMAGE_IMPORT_BY_NAME 구조체 멤버 변수에 대해
서는 언급하지 않았는데요. 마지막으로 IMAGE_IMPORT_BY_NAME 구조체에 대해서 설명하도록 하겠습니다.
[ IMAGE_IMPORT_BY_NAME 구조체 (출처:winnt.h) ]
- IMAGE_IMPORT_BY_NAME 구조체는 IMAGE_THUNK_DATA32 구조체에서 AddressOfData 라는 멤버 변수를 사용할 때 사용되는 구조체
입니다. 앞서 다 이야기 한 내용인데요. IMAGE_IMPORT_BY_NAME 구조체에 선언되어 있는 멤버 변수들은 어떤 정보를 의미하는 것인지
알아보도록 합시다.
1. Hint 멤버 변수
- PE 로더가 임포트하려는 함수를 빠르게 찾을 수 있도록 도와주기 위해 사용하는 멤버 변수로서, 임포트하려는 라이브러리의 EAT(Export
Address Table)의 인덱스 값을 가지고 있는 멤버 변수입니다. 이 멤버 변수의 값은 필수적이지 않기 때문에 0으로 채우져 있는 경우가 많습니다.
2. Name 멤버 변수
- 해당 라이브러리에서 임포트하고자 하는 함수의 이름을 명시하는 멤버 변수입니다.
# 이렇게 해서 IAT에서 사용되는 IMAGE_IMPORT_DESCRIPTOR 구조체와 멤버 변수에 대한 설명이 끝이 났습니다.
이제 전체적인 IAT 모식도와 PE 로더가 임포트 함수 주소를 IAT에 입력하는 기본적인 순서에 대해서 알아보겠습니다.
[ MSpaint.exe 파일의 "ADVAPI32.dll"에 대한 IMAGE_IMPORT_DESCRIPTOR 구조입니다 ]
- 위 그림이 바로 전체적인 IAT 모식도입니다. NT Header에 포함하는 Optional Header의 멤버 변수인 IMAGE_DATA_DIRECT
ORY 구조체 배열의 두 번째 구조체(Import)의 VirtualAddress 멤버 변수(IMAGE_IMPORT_DESCRIPTOR 구조체 시작주소)
를 통하여 IMAGE_IMPORT_DESCRIPTOR 구조체 배열에서 각 라이브러리에 해당하는 함수에 대한 정보를 IMAGE_THUNK
_DATA32 구조체와 IMAGE_IMPORT_BY_NAME 구조체를 통하여 알 수 있게 됩니다.
- 참고로 위 그림에서 IAT는 INT와 동일한 값들을 가지고 있는데, 바인딩 과정을 거치게 되면 실제 Function 멤버 변수로 실제
함수의 주소 값을 가지게 됩니다.
[ 출처 : 리버싱 핵심원리 ]
# MSPaint.exe (그림판) 프로그램을 대상으로하여 앞에서 이야기한 내용들을 실제로 살펴보도록 하겠습니다.
1. IAT(Import Address Table)를 찾기 위해서는 PE 파일에서 NT Header - Optional Header의 IMAGE_DATA_DIRECTORY [1]의 멤버 변수인
VirtualAddress 값을 참고하여 IMAGE_IMPORT_DESCRIPTOR 구조체를 찾습니다. 헥사 에디터를 이용하여 해보도록 하겠습니다.
- IMAGE_DATA_DIRECTORY [1] 구조체의 VirtualAddress 멤버 변수 값("0008688C")을 구했습니다. 현재 이 주소 값은 IMAGE_IMPORT_
DESCRIPTOR 구조체의 RVA 값입니다.
- VirtualAddress : 0008688C / Size : 00000168
- 지금 우리가 알고 있는 VirtualAddress 값은 IMAGE_IMPORT_DESCRIPTOR 구조체 배열의 시작 RVA 값입니다. 따라서, RVA 값을 RAW
값으로 변경하고 해당 RAW로 찾아 가보도록 하겠습니다.
- 이전 포스팅 주제였던 "RVA to RAW" 내용이 여기에서 사용되네요. RAW 구하는 공식 기억하고 계시죠? 다시 한번 "RVA to RAW" 공식을
써보고 계산하도록 하겠습니다.
RAW = RVA - VirtualAddress + PointerToRawData |
- 위 식에 해당 값들을 대입하게 되면, RAW = 8688C - 1000 + 400 = "85C8C" RAW 값 = "85C8C"
- 위의 계산식이 이해가 안가시면 이전의 "RVA to RAW" 포스팅을 다시 한번 보시길 바랍니다.
- RAW 값을 구했습니다. 우리가 구한 RAW 값인 "85C8C"는 IMAGE_IMPORT_DESCRIPTOR 구조체 배열의 시작 주소라고 했습니다. 헥사
에디터로 이동해보도록 하겠습니다.
- Offset "85C8C" 부터가 첫 번째 IMAGE_IMPORT_DESCRIPTOR 구조체의 멤버 변수들입니다. ( 구조체 하나당 20 Byte )
- IMAGE_IMPORT_DESCRIPTOR 구조체 배열에서 끝은 NULL 구조체로 끝이 난다고 했었습니다. 위 그림에서 가장 마지막 20Byte가 NULL로
구성된 것을 볼 수 있습니다.
[ RVA 값 ]
- OriginalFirstThunk(INT) : 00086AD4
- TimeDateStamp : FFFFFFFF
- ForwarderChain : FFFFFFFF
- Name : 00086AC4
- FirstThunk(IAT) : 00001000
- 첫 번째 구조체에서 구한 멤버 변수들에 대한 값들은 모두 RVA 값입니다. 따라서, RAW 값으로 모두 변경하고 차례대로 해당 Offset을
찾아보도록 하겠습니다.
[ RAW 값 ]
- OriginalFirstThunk(INT) : 86AD4 - 1000 + 400 = 85ED4
- Name : 86AC4 - 1000 + 400 = 85EC4
- FirstThunk(IAT) : 1000 - 1000 + 400 = 400
# 참고로 OriginalFirstThunk, Name, FirstThunk 멤버 변수들의 RVA는 .text 영역에 해당하기 때문에, .text 영역의 VirtualAddres 값과
그리고 PointerToRawData 값을 확인해보면 VirtualAddress = 1000 , PointerToRawData = 400 임을 확인할 수 있습니다.
- 각 멤버 변수들의 RAW 값으로 찾아가보도록 하겠습니다. 우선 Name 멤버 변수의 RAW 위치로 찾아가 첫 번째 구조체가 어떤 라이브러리
인지 확인해 보도록 하죠.
- Name 멤버 변수의 RAW 위치인 "85EC4"로 찾아와 확인해보니 "ADVAPI32.dll" 이라는 것을 확인할 수 있습니다.
- 두 번째로 OriginalFirstThunk(INT) 멤버 변수의 RAW 위치로 이동해 보도록 하겠습니다.
- OriginalFirstThunk(INT)가 가리키는 곳은 IMAGE_THUNK_DATA32 구조체 배열로 이루어진 INT 입니다. 각 구조체는 4Byte씩 구성
되어 있으며, 마지막은 NULL 구조체로 구성되어 있습니다. 그리고 각 구조체 배열의 값은 일반적으로 IMAGE_IMPORT_BY_NAME
구조체를 가리키는 포인터 값으로 채워져 있습니다. 이 값 역시 RVA 값으로 되어 있습니다. 따라서, RVA 값을 RAW 값으로 변경한
후 RAW 위치를 참조해야 합니다.
- 첫 번째 IMAGE_THUNK_DATA32 구조체 배열의 값인 "00087B04" RVA 값을 RAW 값으로 변경후 위치를 찾아가 보겠습니다.
- RAW = 87B04 - 1000 + 400 = "86F04"
- 보시는 것 처럼, IMAGE_IMPORT_BY_NAME 구조체 배열의 첫 번째 구조체의 멤버 값들입니다. 처음 2Byte는 Hint 멤버 변수의 값으로
라이브러리에서 함수의 고유번호인 Ordinal을 의미합니다. 이어지는 멤버 변수는 함수 이름을 명시하는 Name 멤버 변수입니다. 이 멤버
변수는 함수 명의 길이에 따라 크기가 가변적이며 마지막엔 NULL('\0')로 끝이 납니다.
- 마지막으로 확인해볼 FirstThunk(IAT) 멤버 변수의 RAW 값 위치를 확인해 보도록 하겠습니다.
FirstThunk(IAT) : 1000 - 1000 + 400 = 400
- IAT 역시 IMAGE_THUNK_DATA32 구조체 배열로 구성되어 있고, 마지막엔 NULL 구조체로 구성됩니다. 이 구조체에서의 배열 값들은
하드코딩되어 있는 값들로 MS사에서 서비스팩을 제작하면서 관련 시스템 파일을 재빌드할 때 정확한 주소를 하드 코딩한 것입니다.
하지만, 이렇게 하드 코딩한 주소 값들은 운영체제 버전에 따라 하드 코딩된 주소 값이 정확하지 않는 경우도 있는데요.
그런 경우에는 해당 프로세스가 실행될 때 PE 로더가 이 값들을 해당 API의 시작 주소 값으로 대체 시키게 됩니다. 따라서, 정확한
주소 값으로 대체되는 것이죠. ( 바인딩 과정을 거쳐 실제 함수의 주소 값들로 채워지는 것입니다 )
그리고, 일반적으로는 INT와 같은 값을 가지는 경우가 많습니다. 이런 경우 역시 바인딩 과정을 거치게 되면 실제 함수의 주소 값들로
채워지게 됩니다.
# 지금까지 IAT(Import Address Table)에 대하여 설명했습니다. 공부하고 있는 학생이라 틀린 부분도 많이 있을 것 같은데요.
혹시 잘못된 부분들이 있으면, 댓글 달아주시면 수정하도록 하겠습니다. IAT 부분도 공부하면서 느낀거지만 정말 중요한
내용인 것 같습니다. 여러번 보면서 익숙해질 수 있도록 해야겠습니다.
# 참고 도서 및 참고 자료
- 리버싱 핵심원리
- Windows 구조와 원리
- PEFormat.pdf [PE 파일 전체 구조도] : PE 파일 전체 구조도가 정말 잘 정리되어 있습니다. 이 파일은 첨부하겠습니다.]
- 리버스 엔지니어링 바이블
- http://stonycode.egloos.com/3191147
- http://printf.egloos.com/2046777
- http://bsodtutorials.blogspot.kr/2014/03/import-address-tables-and-export.html
- http://zesrever.tistory.com/archive/20070908