PE File Format(3)

Wooki·2021년 8월 16일
0

라이브러리?

PE File Format(2)에서 동적 연결 라이브러리에 대해서 이야기 해온 마당에 이제와서 라이브러리에 대한 정의를 내리기엔 좀 그렇지만, 라이브러리는
"다른 프로그램에서 불러서 사용할수 있도록 관련 함수들을 모아놓은 파일"이다.
Win32 API가 대표적이고 그 중에서 kernel32.dll이 핵심적이라고 한다.

EAT

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 함수 개수
NumberOfNamesExport 함수 중에서 이름을 가지는 함수 개수
AddressOfFunctionsExport 함수 주소 배열
AddressOfNames함수 이름 주소 배열
AddressOfNameOrdinalsordinal 배열

함수 주소를 얻는 과정을 살펴보며 위 멤버들의 용도를 알아보자.

GetProcAddress()가 라이브러리에서 함수 주소를 얻어내는 API다.

Cotton이라는 함수를 찾는다고 해 보자.

GetProcAddress("Cotton");

  1. AddressOfNames 멤버를 읽어서 '함수 이름 배열'로 이동한다.
    -> AddressOfNames 멤버는 '함수 이름 배열'의 주소를 가지고 있다.
  2. '함수 이름 배열'의 각 원소들은 문자열 주소를 가지고 있다. Strcmp(문자열 비교)를 통해서 "Cotton"이라는 함수 이름을 찾는다. 원하는 함수 이름을 찾으면 그때의 배열 인덱스를 name_index라고 한다.
    -> 예를들어 '함수 이름 배열'의 3번째 인덱스의 문자열이"Cotton"이라면 name_index는 3이 된다.
  3. AddressOfNameOrdinals멤버를 이용해서 Ordinal배열로 이동한다.
    -> AddressOfNameOrdinals멤버의 값은 Ordinal배열의 주소다.
  4. Ordinal 배열에서 2에서 구해둔 name_index의 값으로 해당 ordinal 값을 찾는다.
  5. AddressOfFunctions 멤버를 이용해서 '함수 주소 배열(EAT)'로 이동한다.
  6. '함수 주소 배열' 에서 아까 구한 ordinal을 배열 인덱스로 하여 Cotton함수의 시작 주소를 얻는다.

13.21 그림은 "kernel32.dll"파일에 대한 경우다. DLL파일은 "kernel32.dll"파일처럼 모든 함수에 이름이 존재하고, index와 ordinal이 같은 형태로 되어 있을 수도 있지만 모든 DLL 파일이 이와 같지는 않고 이름이 존재하지 않는 함수도 있고, index != ordinal인 경우도 있기에 위와 같은 순서를 거쳐야만 한다.

실제로 헥사 에디터를 이용해서 살펴보자.
위에서 구해둔 File Offset인 1A2C로 이동해보았다.

File OffsetMemberValueRaw
1A40NumberOfFunctions000003B9-
1A44NumberOfNames000003B9-
1A48AddressOfFunctions000026541A54
1A4CAddressOfNames000035382938
1A50AddressOfNamesOrdinals0000441C381C

위에서 했던 과정을 따라가서 'AddAtomW'함수를 찾아보자..

  1. '함수 이름 배열'을 찾아 보자.
    AddressOfNames의 값이 00003638(RVA) 오프셋은 2938이다. 2938로 이동해보자

    함수 이름 배열의 원소들은 문자열에 대한 주소라고 설명했다.(4바이트RVA)

  2. GetProcAddress함수는 strcmp를 통해서 "AddAtomW" 문자열을 비교해본다.
    지금 "AddAtomW"문자열은 배열의 3번째 원소에 있다. (2940의 4BB3값)
    4BB3(RVA) -> 3FB3(RAW)


문자열 비교를 통해서 AddAtomW()가 배열의 3번째 원소이고 인덱스는 2라는 것을 알아냈다.

  1. Ordinal 배열 AddAtomW()의 Ordinal값을 찾아 낼 차례다.
    AddressOfNameOrdinals멤버의 값은 441C (RAW:381C)다
    해당 주소로 이동해 보면

이와 같은 2바이트 ordinal 배열이 나타난다.

  1. 2번에서 얻은 인덱스 값 2를 위 배열에 적용하게 되면 ordinal 2를 구할 수 있다.

  2. 이제 함수 주소 배열의 주소를 가지고 있는AddressOfFunctions값을 보자
    해당 멤버는 2654(RAW 1A54)값을 가지고 있다. 이동해 보자.

4바이트 주소 배열이 나타난다 . 이것이 Export함수 주소들이다.

  1. 4에서 구해둔 ordinal을 위 배열의 index로 적용하면 RVA = 000326D9를 얻을 수 있다.

이렇게 구한 주소를 디버거로 한번 살펴보자.

일단 kernel32.dll파일의 imageBase 값을 PEview로 살펴보니 7C800000이 나온다

즉 실행시 AddAtomW의 주소는 7C800000 + 000326D9 = 7C8326D9가 된다.

ollydbg(디버거)로 주소를 확인해 보니 AddAtomW함수가 나오는 모습이다.

마무리

  • PE스펙은 그저 규격일 뿐 사용되지 않는 멤버들도 많다고 한다, 그래서 다양한 Patched PE파일이 존재한다고 한다.
profile
웹 개발자

0개의 댓글