Reversing

[Reversing] DLL, IAT, EAT

chimita 2023. 5. 13. 02:57

DLL

동적 링크 라이브러리

다른 프로그램에서 사용할 수 있는 함수, 클래스, 변수 등의 코드와 데이터를 포함하는 라이브러리 파일이다.

-> 다른 프로그램에서 이용할 수 있는 함수들을 모아둔 파일

DLL 파일은 실행 파일에서 독립적으로 로드되고 메모리에 상주하며, 프로그램에서 필요할 때마다 호출된다.

이렇게 하면 여러 프로그램에서 동일한 코드를 공유하여 메모리를 절약하고 코드 유지 보수를 편리하게 할 수 있다.

 


Win32 API 가 대표적인 Library 이며, 그 중에서도 kernel32.dll 파일이 가장 대표적인 Library 파일이라고 할 수 있다.

 

장점

  • 프로그램 개발을 단순화 및 실행 파일 크기 감소 -> 메모리 절약 
  • 모듈식 아키텍처 활용 -> 모듈식 프로그램을 효율적으로 개발 가능
  • 손쉬운 배포와 설치 가능
  • 재사용성 높음

내 노트북 속 .dll 파일들

위 DLL 파일은 PE헤더 구조를 가지고 있다.

 

주의할 점

  • 파일 존재 여부 및 위치(경로) 확인
  • DLL 파일과 실행 파일 간 호환성 확인
  • DLL간 종속성 확인

DLL 종속성 

- 프로그램이나 DLL이 다른 DLL 파일의 함수를 사용하는 경우 종속성이 발생한다.

이 경우, 해당 프로그램은 더 이상 자체 포함 프로그램이 아니며 종속성이 손상되어 프로그램에 문제가 발생 할 수 있다.

  • 종속 DLL을 새 버전으로 업그레이드 할 경우
  • 종속 DLL을 수정하는 경우
  • 종속 DLL을 이전 버전으로 덮어쓰는 경우
  • 종속 DLL을 컴퓨터에서 제거하는 경우

위 4가지 경우를 보통 DLL 충돌이라고 하며, 이전 파일의 버전과 호환성이 맞지 않는다면 프로그램이 정상적으로 실행되지 않는다.


DLL 프로세스 내 주소 공간

- DLL 파일의 이미지는 실행 파일이나 다른 DLL이 DLL 내에 포함되어있는 함수를 호출하기 전, 반드시 프로세스의 주소 공간에 매핑되어 있어야 한다.

 

  1. Implicit Linking | 암시적 로드타임 링킹
    • 프로그램 시작할 때 같이 로딩되어 프로그램 종료 시 메모리에서 해제
      • 로드 : 프로그램 실행 시
      • 해제 : 프로그램 종료 시
    • 함수 호출이 간편하나, 프로그램이 실행되는 동안 DLL이 계속 동작하기 때문에 메모리 낭비 및 DLL 로딩 실패 시 프로그램 실행 안됨
  2. Explicit Linking | 명시적 런타임 링킹
    • 프로그램에서 사용되는 순간에 로딩하고, 사용 후에 메모리에서 해제
      • 로드 : LoadLibrary() 호출 시
      • 해제 : FreeLibrary() 호출 시
    • DLL이 필요한 경우에만 호출하여 사용하므로 메모리 소모 적고, DLL 로딩이 실패하더라도 대응 코드로 회피 가능
DLL 파일 이미지가 프로세스의 주소 공간에 매핑되고 나면, DLL이 가지고 있는 모든 함수들은 프로세스 내의 모든 쓰레드에 의해 호출될 수 있다.
프로세스 내의 Thread 관점에서는 DLL이 가지고 있는 코드와 데이터들은 단순히 프로세스의 주소 공간에 로드된 추가적인 코드와 데이터들로 여겨진다.
 
실습
#include <stdio.h>

__declspec(dllexport) int add(int a, int b) { // 해당 함수를 내보내기 위해 사용하는 지시자
    return a + b;
}
#include <iostream>
#include <windows.h>

using namespace std;

typedef int (*AddFunction)(int, int);

int main() {
    HINSTANCE hDLL = LoadLibrary("test.dll"); // dll 파일 로드
    if (hDLL != NULL) { // dll 파일이 비어있지 않은 경우
        AddFunction addFunc = (AddFunction)GetProcAddress(hDLL, "add"); //덧셈
        if (addFunc != NULL) { 
            int result = addFunc(3, 4); //함수 호출하여 인자값 넘겨줌 
            cout << "Result: " << result << endl; 
        } else { 
            cout << "Failed to get function address." << endl;
        }
        FreeLibrary(hDLL);
    } else { //파일이 없을 경우 혹은 빈 경우
        cout << "Failed to load DLL." << endl;
    }

    return 0;
}

IAT , Import Address Table

프로그램이 어떤 라이브러리에서 어떤 함수를 사용하는지에 대해 적은 테이블 및 메커니즘이다

notepad.exe의 kernel32.dll에 대한 &nbsp; IMAGE_IMPORT_DESCRIPTOR 구조

 

암시적 로드타임 링킹 | Implicit Linking 에 대한 메커니즘을 제공하는 역할로 프로그램이 시작할 때 같이 로딩되어 종료할 때 메모리에서 해제된다.

 

IAT 구조

IAT PE 파일이 어떤 라이브러리를 import하고 있는지에 대해 IMAGE_IMPORT_DESCRIPTOR 구조체에 RVA형태로 명시되어 있다.

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD Characteristics;
        DWORD OriginalFirstThunk; //Import Name Table(배열) 의 주소 -> RVA 형태
    };
    
	// RVA : ImageBase로 부터의 상대 주소 -> 메모리에 로딩된 상태
    
    DWORD TimeDateStamp;
    DWORD ForwarderChain;
    DWORD Name; // Library 이름 문자열의 주소 -> RVA 형태
    DWORD FirstThunk; // Import Address Table(배열) 의 주소 -> RVA 형태
} IMAGE_IMPORT_DESCRIPTOR;

 

1. IID의 Name 멤버를 읽어서 라이브러리의 이름 문자열("kernel32.dll")을 얻는다

2. 해당 라이브러리("kernel32.dll")를 로딩한다.

3. IID의 OriginalFirstThunk 멤버를 읽어서 INT 주소를 얻는다.

4. INT에서 배열의 값을 하나씩 읽어 해당 IMAGE_IMPORT_BY_NAME주소(RVA)를 얻는다.

5. IMAGE_IMPORT_BY_NAME 의 Hint(ordinal) 또는 Name 항목을 이용하여 해당 함수("GetCurrentThreadId")의 시작 주소를 얻는다.

6.IID의 FirstThunk(IAT) 멤버를 읽어서 IAT의 주소를 얻는다.

7. 해당 IAT 배열 값에 위에서 구한 함수 주소를 입력한다

8. INT가 끝날 때까지 (NULL을 만날때 까지) 4~7의 과정을 반복한다

EAT , 

라이브러리 파일에서 제공하는 함수를 다른 프로그램에서 가져다 사용할 수 있도록 해주는 테이블 및 메커니즘이다.

-> 라이브러리가 다른 파일로 Export 하는 함수명, 변수명, 주소가 기록되어 있다.

 

IMAGE_EXPORT_DIRECTORY는 NT Header를 통해 구할 수 있다.

NT Header의 Optional Header를 보면 DataDirectory가 배열로 존재한다.

DataDirectory의 0번째 인덱스의 VirtualAddress가 IMAGE_EXPORT_DIRECTORY이다. 

 

EAT 구조

typedef struct _IMAGE_EXPORT_DIRECTORY {
  DWORD Characteristics;
  DWORD TimeDateStamp;
  WORD MajorVersion;
  WORD MinorVersion;
  DWORD Name; // address of library file name
  DWORD Base;
  DWORD NumberOfFunctions;
  DWORD NumberOfNames; 		//Export 함수 중에서 이름을 가지는 함수 개수 (<=NumberOfFunctions)
  DWORD AddressOfFunctions; // Export 함수 주소 배열 (배열의 원소 개수 = NumberOfFunctions)
  DWORD AddressOfNames; // 함수 이름 주소 배열 (배열 원수 개수 = NumberOfNames)
  DWORD AddressOfNameOrdinals; // Ordinal 배열 (배열의 원소 개수 = NumberOfNames)
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
1. AddressOfNames 멤버를 이용해 '함수 이름 배열'로 이동
2. '함수 이름 배열'은 문자열 주소가 있으므로, 문자열 비교를 통하여 원하는 함수 이름을 찾는다.(name_index)
3. AddrssOfNameOrdinals멤버를 이용하여 'ordinal' 배열로 이동
4. 'ordinal 배열'에서 name_index를 이용하여 해당 ordinal 값을 찾는다.
5. AddrssOfFunctions 멤버를 이용하여 함수 주소 배열(EAT)로 이동
6. EAT에서 ordinal을 배열 인덱스로 하여 원하는 함수의 시작주소를 얻는다

 


분석

PE 구조를 확인할 수 있는 툴인 DIE 를 사용해서 패킹 여부와 컴파일러 종류를 확인했다.

 

x64dbg로 notepad.exe 파일을 디버깅 해봤다.

바로 IAT 값을 확인하기 위해 먼저 dll 파일을 확인했다.

 

x64dbg에서는 기호 탭을 통해 dll이 어디에 로딩 되는지 dll함수는 어디에 로딩 되는지 알 수 있다.

왠지 익숙한 것들이 보인다. 

 

IAT 값을 확인하기 위해 가장 먼저 CreateFileW 함수를 찾아보았다.

 

* CreateFileW : 파일이나, 디바이스(I/O) 연결을 시도 할 때. 결과를 만들어내거나(create) 연결 시도할 대상 내용을 여는(open)데 사용

 

해당 함수를 클릭하면 자세한 내용을 확인할 수 있는데,

직접 함수를 호출하지 않고 특정 주소 00007FFAEA017B48에 있는 값을 가져와서 호출하는 것을 알 수 있다.

이 때, 이 주소 값이 Text섹션의 메모리 영역이며 더 정확히는 IAT메모리 영역을 의미한다.

 

 

해당 주소로 이동하면 제일 처음에 확인했던 dll 목록 속 kernelbase.dll인 것을 확인할 수 있다.

 

잘 분석한 것이 맞는 지 CFF Exploer로 확인해봤다.

 

IMAGE_OPTIONAL_HEADER.DataDirectory[1].VirtualAddress값, 즉 IMAGE_IMPORT_DESCRIPTOR 의 시작 주소는 "004868D8" 이며, .rdata섹션에 존재한다.

 

0x004868D8 = 구조체 배열의 시작 주소
0x003BB400 = .rdata의 Raw address
0x003BC000 = .rdata의 Virtual address

 

RAW를 구하는 공식을 사용해 offset을 구하면 아래와 같은 값이 나온다.

RAW = RVA - VirtualAddress + PointerToRawData(Raw address)

 

RAW = 003BC3C8

003BC3C8이 맞다!