본문 바로가기

REVERSING

[SWING] Reversing 09

DLL

 

DLL (Dynamic Linked Library)은 우리말로 '동적 연결 라이브러리'라고 하며 마이크로소프트 윈도우에서 구현된 동적 라이브러리이다. 코드와 데이터를 지니고 있으며 동시에 하나 이상의 프로그램에서 사용할 수 있다. 내부에는 다른 프로그램이 불러서 쓸 수 있는 다양한 함수들을 가지고 있다.

 

사용 방법

(1) 묵시적 링킹 (Implicit Linking)

프로그램 시작할 때 같이 로딩되어 프로그램 종료할 때 메모리에서 해제된다.

 

(2) 명시적 링킹 (Explicit Linking)

프로그램에서 사용되는 순간에 로딩하고 사용이 끝나면 메모리에서 해제된다.

 

장점

프로그램에 라이브러리를 포함시키지 않고 별도의 파일(DLL)로 구성하여 필요할 때마다 불러쓸 수 있다.

일단 한 번 로딩된 DLL의 코드, 리소스는 Memory Mapping 기술로 여러 Process에서 공유해서 쓸 수 있다.

라이브러리가 업데이트 되었을 때 해당 DLL 파일만 교체하면 되어 쉽고 편하다.

 

DLLMain() 함수는 프로그래머가 생성하지 않아도 기본값이 제공된다.

하지만 쓰레드나 프로세스 레벨에서 DLL 코드 실행 관련 초기화 작업 필요 시 DllMain 파라미터 중 하나 dwReason 로 초기화한다.

 

BOOL APIENTRY DllMain
(
HMODULE hModule,  // 가상 주소 매핑되었을 때 핸들
DWORD dwReason,  // DLL 호출 시점 따라 동작되는 내용 기술
LPVOID lpReserved
)
{
switch (dwReason)
{
case DLL_PROCESS_ATTACH:
case DLL_PROCESS_DETACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}

 

DLL_PROCESS_ATTACH : DLL 로드 시점 수행 로직 넣을 수 있음

DLL_PROCESS_DETACH : DLL 언로드 시점 수행 로직 넣을 수 있음

 

DLL_THREAD_ATTACH : DLL 사용 프로세스가 스레드 생성 시 수행 로직 넣을 수 있음

DLL_THREAD_DETACH : DLL 사용 프로세스가 스레드 종료 시 수행 로직 넣을 수 있음

 

DLL 호출 방법

1) Implicit : 최초 실행 시 자동 해당 라이브러리 로딩

2) Explicit : 필요 시점에 해당 라이브러리 메모리 로딩 후

    파일 내에 사용되는 함수 포인터를 직접 검색하여 호출하는 방법

 

 

 

 

IAT

 

IAT는 프로그램이 어떤라이브러리에서 어떤 함수를 사용하고 있는지 기술한 테이블이다.

implicit 선언에 대한 메커니즘을 제공한다.

프로그램이 로딩될 때 함께 라이브러리도 로딩되며 프로그램이 필요로 하는 라이브러리와 함수들의 대한 정보들이 구성된다.

PE 파일은 어떤 라이브러리(.dll)를 임포트하는지 IMPORT_DESCRIPTOR 구조체에 명시한다.

 

IMAGE_IMPORT_DESCRIPTOR

: PE 바디에 위치하며, 찾아가기 위해서는 IMAGE_OPTIONASL_HEADER32 구조체 멤버 중 IMAGE_DATA_DIRECTORY의 2번째 배열의 Address 값이 IMAGE_IMPORT DESCRIPTOR(=IAT Table) 구조체 배열의 RVA값으로 가야 한다.

 

IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 15 =>8바이트];

 

중요 구조체 멤버

OriginalFirstThunk : INT(import name table)의 주소(RVA)

Name : 라이브러리 이름 문자열의 주소(RVA)

FirstThunk : IAT(Import Address Table)의 주소(RVA)

 

PE로더가 임포트 함수 주소를 IAT에 입력하는 순서

1. IID의 NAME멤버를 읽어 라이브러리 이름 문자열을 얻는다.

2. 해당 라이브러리를 로딩한다.

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

4. INT에서 배열의 값을 하나씩 읽고 HINT OR NAME값을 얻는다. (IAT테이블에 이 순서대로 저장되어 있다.)

5. HINT OR NAME값을 이용하여 해당함수의 시작주소를 얻는다.

6. IID의 IAT멤버를 읽고 IAT 주소를 얻는다.

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

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

 

IAT구조

 

IAT가 필요한 이유

1. 어떤 프로그램(test.exe)이 kernel32.dll에 있는 WinExec()를 호출하고 싶은 경우에 프로그램이 실행되는 환경에 따라 kernel32.dll의 버전이 다르기 때문에 실제 WInExec() 주소는 달라지게 된다.

따라서 해당 환경이 달라져도 실행될 수 있게 먼저 함수의 위치를 저장할 수 있는 테이블을 구성하고, 프로그램 내부에 함수를 호출하는 코드가 들어간다.

서로 다른 환경(다른 .dll 버전)에서도 프로그램이 실행될 수 있게 하는 것은 IAT 때문이다.

 

2. DLL Relocation으로 어떤 DLL이 프로그램의 실행으로 메모리(가상 메모리)에 올라가려고 할 때 이미 다른 DLL이 올라와 있으면 다른 위치로 재배치가 일어나게 되므로, 올라갈 메모리 주소를 보장할 수 없음 (RVA가 필요한 이유이며, 동시에 IAT 정보들이 필요한 이유)

 

 

 

 

EAT

 

IAT는 응용 프로그램이 불러다 사용하는 dll과 함수의 테이블이었다면,

EAT는 프로그램이 dll과 함수에 접근할 수 있도록 주소와 정보를 기록해놓은 테이블이다.

EAT를 통해서만 해당 라이브러리에서 Export하는 함수의 시작 주소를 정확히 구할 수 있다.

라이브러리 파일에서 제공하는 함수를 다른 프로그램에서 가져다 사용할 수 있도록 해주는 핵심적인 메커니즘이라고 할 수 있다.
Windows 라이브러리(DLL/SYS) 중에서 Win32 API가 대표적이고, 그 중에서도 kernel32.dll 파일이 가장 핵심적이다.
EAT를 통해서만 해당 라이브러리에서 Export하는 함수의 시작 주소를 정확히 구할 수 있다.
IAT와 마찬가지로 IMAGE_EXPORT_DIRECTORY에 Export 정보를 저장하고 있다.
IAT와 차이라면, IMAGE_EXPORT_DIRECTORY 구조체가 PE파일에 하나만 존재한다.
(lmport는 여러개 동시에 할 수 있다.)

 

EAT는 IMAGE_EXPORT_DIRECTORY구조체 안에 기록되어 있는데,

이것 또한 IAT처럼 PE바디 안에 있고, PE구조에 단 한개만 존재한다.

 

IMAGE_OPTIONAL_HEADER.DataDirectory[1].VirtualAddress 값이 IAT의 구조체 배열 시작 주소였다면,

IMAGE_OPTIONAL_HEADER.DataDirectory[0].VirtualAddress 값이 실제 IMAGE_EXPORT_DIRECTORY 구조체 배열의 시작 주소이다. 이것도 물론 RVA로 된 값이다.

 

 

주요 멤버 설명

NumberOfFunctions : 실제 Export 함수 개수

NumberOfNames : Export 함수 중에서 이름을 가지는 함수 개수(≤ NumberOfFunctions)

AddressOfFunctions : Export 함수 주소 배열(배열의 원소 개수 = NumberOfFunctions)

AddressOfNames : 함수 이름 주소 배열(배열의 원소 개수 = NumberOfNames)

AddressOfNameOrdinals : Ordinal 주소 배열(배열의 원소 개수 = NumberOfNames)

 

라이브러리에서 함수 주소를 얻는 API는 GetProcAddress() 이며,

이 API함수가 바로 EAT를 참조해서 원하는 API의 주소를 구한다.

 

라이브러리에서 함수 주소를 얻는 GetProcAddress() 과정

1. AddressOfNames 멤버를 이용해서 '함수 이름 배열'로 간다.
2. '함수 이름 배열'은 문자열 주소가 저장되어 있다. 문자열 비교 (strcmp)를 통해서 원하는 함수 이름을 찾는다.
(이 때, 배열의 인덱스를 name_idx라 함)
3. AddressOfNameordinals 멤버를 이용해서 'ordinal 배열'로 간다.
4. 'ordinal 배열'에서 name_idx로 해당 ordinal 값을 찾는다.
5. Addressoffunctions 멤버를 이용해서 '함수 주소 배열(EAT)'로 간다.
6. '함수 주소 배열(EAT)'에서 아까 구한 Oridinal을 배열 인덱스로 해서 원하는 함수의 시작주소를 얻는다.

 

 

'REVERSING' 카테고리의 다른 글

리버싱 스터디 5~8주차  (1) 2024.01.31
[SWING] Reversing 10  (0) 2023.05.22
[SWING] Reversing 08  (0) 2023.04.30
[SWING] Reversing 07  (0) 2023.04.02
[SWING] Reversing 06  (0) 2023.03.26