PE File Format(2)에서 동적 연결 라이브러리에 대해서 이야기 해온 마당에 이제와서 라이브러리에 대한 정의를 내리기엔 좀 그렇지만, 라이브러리는
"다른 프로그램에서 불러서 사용할수 있도록 관련 함수들을 모아놓은 파일"이다.
Win32 API가 대표적이고 그 중에서 kernel32.dll이 핵심적이라고 한다.
EAT는 (Export Address Table)로서 라이브러리 함수를 다른 프로그램에서 가져다 쓸 수 있도록 해주는 하나의 중요한 매커니즘이다.
앞서 PE File Format(2)에서 IAT를 설명할 때 이야기 했었는데 EAT를 통해서 Export하는 함수의 시작 주소를 정확히 구할 수 있다.
IAT처럼 IMAGE_EXPORT_DIRECTORY
라는 구조체에 정보를 저장하고 있다.
IMAGE_IMPORT_DIRECTORY
구조체는 라이브러리의 개수만큼 존재한다고 했었는데,
IED 구조체는 PE파일에 단 하나만 존재한다고 한다.
PE파일에서 IMAGE_EXPORT_DIRECTORY
구조체는 PE 헤더에 위치하고 있다.
IMAGE_OPTIONAL_HEADER32.DataDirectory[0].VirtualAddress
값이 IED 구조체의 시작 주소다.
이전에 IID의 시작 주소는
IMAGE_OPTIONAL_HEADER32.DataDirectory[1].VirtualAddress
라고 한 적이 있다.
드래그한 부분의 첫 번째 4바이트(0000262C)가 VirtualAddress가 될 것이고, 그 뒤의 4바이트(6CFD)가 Size가 될 것이다.
File Offset을 찾아보자, 공식에 대입하면
RAW = 262C - 1000 + 400 = 1A2C으로 IMAGE_EXPORT_DIRECTORY
구조체의 파일에서의 위치는 1A2C가 된다.
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name;
DWORD Base;
DWORD NumberOfFunctions;
DWORD NumberOfNames;
DWORD AddressOfFunctions; // RVA from base of image
DWORD AddressOfNames; // RVA from base of image
DWORD AddressOfNameOrdinals; // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
IMAGE_EXPORT_DIRECTORY
구조체의 코드다.
.
항목 | 의미 |
---|---|
NumberOfFunctions | 실제 Export 함수 개수 |
NumberOfNames | Export 함수 중에서 이름을 가지는 함수 개수 |
AddressOfFunctions | Export 함수 주소 배열 |
AddressOfNames | 함수 이름 주소 배열 |
AddressOfNameOrdinals | ordinal 배열 |
함수 주소를 얻는 과정을 살펴보며 위 멤버들의 용도를 알아보자.
GetProcAddress()가 라이브러리에서 함수 주소를 얻어내는 API다.
Cotton이라는 함수를 찾는다고 해 보자.
GetProcAddress("Cotton");
13.21 그림은 "kernel32.dll"파일에 대한 경우다. DLL파일은 "kernel32.dll"파일처럼 모든 함수에 이름이 존재하고, index와 ordinal이 같은 형태로 되어 있을 수도 있지만 모든 DLL 파일이 이와 같지는 않고 이름이 존재하지 않는 함수도 있고, index != ordinal인 경우도 있기에 위와 같은 순서를 거쳐야만 한다.
실제로 헥사 에디터를 이용해서 살펴보자.
위에서 구해둔 File Offset인 1A2C로 이동해보았다.
File Offset | Member | Value | Raw |
---|---|---|---|
1A40 | NumberOfFunctions | 000003B9 | - |
1A44 | NumberOfNames | 000003B9 | - |
1A48 | AddressOfFunctions | 00002654 | 1A54 |
1A4C | AddressOfNames | 00003538 | 2938 |
1A50 | AddressOfNamesOrdinals | 0000441C | 381C |
위에서 했던 과정을 따라가서 'AddAtomW'함수를 찾아보자..
'함수 이름 배열'을 찾아 보자.
AddressOfNames
의 값이 00003638(RVA) 오프셋은 2938이다. 2938로 이동해보자
함수 이름 배열의 원소들은 문자열에 대한 주소라고 설명했다.(4바이트RVA)
GetProcAddress
함수는 strcmp를 통해서 "AddAtomW" 문자열을 비교해본다.
지금 "AddAtomW"문자열은 배열의 3번째 원소에 있다. (2940의 4BB3값)
4BB3(RVA) -> 3FB3(RAW)
문자열 비교를 통해서 AddAtomW()
가 배열의 3번째 원소이고 인덱스는 2라는 것을 알아냈다.
AddAtomW()
의 Ordinal값을 찾아 낼 차례다.AddressOfNameOrdinals
멤버의 값은 441C (RAW:381C)다이와 같은 2바이트 ordinal 배열이 나타난다.
2번에서 얻은 인덱스 값 2를 위 배열에 적용하게 되면 ordinal 2를 구할 수 있다.
이제 함수 주소 배열의 주소를 가지고 있는AddressOfFunctions
값을 보자
해당 멤버는 2654(RAW 1A54)값을 가지고 있다. 이동해 보자.
4바이트 주소 배열이 나타난다 . 이것이 Export함수 주소들이다.
이렇게 구한 주소를 디버거로 한번 살펴보자.
일단 kernel32.dll파일의 imageBase 값을 PEview로 살펴보니 7C800000이 나온다
즉 실행시 AddAtomW의 주소는 7C800000 + 000326D9 = 7C8326D9가 된다.
ollydbg(디버거)로 주소를 확인해 보니 AddAtomW함수가 나오는 모습이다.