Notice
Recent Posts
Recent Comments
Link
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

Study

리버싱 핵심원리_13_PE File(2) 본문

Reversing/리버싱 핵심원리

리버싱 핵심원리_13_PE File(2)

마늘부추 2019. 1. 31. 03:35

13. PE File

 

13.3 IAT

▶ IAT란?

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

 

 

13.3.1 DLL

 

▶ DLL이란?

16비트 DOS 시절은 어떠한 라이브러리 함수를 사용하고자 할 때 라이브러리에서 해당 함수의 binary 코드를 그대로 가져와 프로그램에 포함시켰다.
그러나 32비트 Windows 환경에서는 멀티태스킹을 지원하기 때문에 여러 곳에서 자주 사용되는 라이브러리 함수를 매번 프로그램에 포함시키는 방법이 비효율적이 되어버렸다.
또한 Windows 환경을 제대로 지원하기 위해서는 매우 많은 라이브러리 함수가 필요하다. (process, memory, window, message 등)
따라서 심각한 자원낭비를 방지하기 위해 고안해낸 방법이 DLL 방법이다.

 

① 프로그램에 라이브러리를 포함시키지 않고 별도의 파일로 구성하여 필요할 때마다 불러 쓴다.
② 일단 한 번 로딩된 DLL의 코드, 리소스는 Memory Mapping 기술로 여러 Process에서 공유해 쓴다.
③ 라이브러리가 업데이트되었을 때 해당 DLL 파일만 교체하면 된다.

 

 

▶ DLL 로딩 방식
Explicit Lingking : 프로그램에서 사용되는 순간에 로딩하고 사용이 끝나면 메모리에서 해제하는 방법
Implicit Lingking : 프로그램 시작 시 같이 로딩되어 프로그램을 종료할 때 메모리에서 해제하는 방법

 

 

 

PageSetupDlgW 함수를 호출할 때 직접 호출하지 않고 010012C4 주소에 있는 값을 가져와서 호출한다.
010012C4(RAW=6C4) 위치는 .text 섹션의 영역(더 정확히는 IAT 영역)이다.
이 위치에 저장된 7518D9A0 주소는 바로 메모리에 로딩된 comdlg32.dll의 PageSetupDlgW 함수 주소이다.

 

바로 함수의 위치를 적는 것이 아니라 IAT 영역을 거치는 이유?
프로그램 제작자가 프로그램을 컴파일(생성) 하는 순간에는 이 notepad.exe 프로그램이 어떤 Windows, 어떤 언어, 어떤 Service Pack에서 실행될지 알 수 없다.
이러한 모든 환경에서 DLL 파일의 버전이 달라지고, 함수의 위치가 달라진다.
따라서 모든 환경에서 안정적으로 함수 호출을 보장하기 위해 컴파일러는 함수의 실제 주소가 저장될 위치(010012C4)를 준비하고
CALL DWORD PTR DS:[10012C4]

형식의 명령어를 적어두기만 한다.
그러다 파일이 실행되는 순간 PE 로더가 010012C4의 위치에 해당 함수의 주소를 입력해준다.

(IAT 영역은 실행되는 순간 PE 로더에 의해 갱신된다)

 

또한 컴파일러가 IAT를 거치는 또 다른 이유는 DLL Relocation 때문이다.
일반적인 DLL 파일의 ImageBase 값은 10000000이다. 어떤 두 가지 dll을 로딩하려 할 때 먼저 로딩된 dll이 해당 주소를 차지하고 있다면 후에 로딩되는 dll은 로딩될 다른 자리를 찾아야 한다.
이렇게 dll의 로딩 주소가 항상 일관적이지 않기 때문에 주소를 하드코딩할 수 없는 것이다.

 

※ 여기서 잠깐!

dll은 PE 헤더에 명시된 ImageBase에 로딩된다고 보장할 수 없다. 반면 process 생성 주체가 되는
EXE 파일은 자신만의 가상 메모리 공간을 가지기 때문에 자신의 ImageBase에 정확히 로딩된다.

 

 

13.3.2 IMAGE_IMPORT_DESCRIPTOR / IMAGE_IMPORT_BY_NAME

 

PE 파일은 자신이 어떤 라이브러리를 Import 하고 있는지 IMAGE_IMPORT_DESCRIPTOR 구조체에 명시하고 있다.

 

 

여러 개의 라이브러리를 동시에 Import 할 수 있기 때문에 IAT는 여러 개의 멤버를 가진 배열 형태로 존재한다.

 

 

▶ IMAGE_IMPORT_DESCRIPTOR

 

 

IMAGE_IMPORT_DESCRIPTOR의 구조체는 위와 같다.

이 구조체의 주요 멤버들을 comdlg32.dll의 PageSetupDlgW 함수를 관찰하며 알아본다.

 

 

 

PEview를 이용하여 IMPORT Name Table을 보면 comdlg32.dll 영역의 첫 번째 라이브러리 함수는 <PageSetupDlgW>임을 알 수 있다.

 

 

 

 

OriginalFirstThunk - INT(Import Name Table)의 주소(RVA)
해당 DLL에 속하는 라이브러리 ro수만큼의 크기인 배열로 이루어져 있으며

배열의 각 공간에는 IMAGE_IMPORT_BY_NAME 구조체의 주소를 담고 있다.
마지막은 4byte의 NULL로 채워져있다.
INT는 IMAGE_IMPORT_BY_NAME 구조체를 가리키는 멤버이다.

 

 

 

 

마지막이 NULL로 채워진 INT 배열의 모습이다.

PageSetupDlgW 함수는 첫 번째 함수이므로 00007A7A(offset=6E7A)가 해당 함수의 IMAGE_IMPORT_BY_NAME 구조체의 위치이다.

 

 

 

 

offset 6E7A로 가면 Hint(2byte)와 Name 문자열을 확인할 수 있다.

 

 

 

 

 

Name - Library 이름 문자열의 주소(RVA)
kernel32.dll, comdlg32.dll과 같은 dll 이름이 저장된 주소를 담는 멤버이다.

6EAC 주소로 가면 comdlg32.dll 이름을 확인할 수 있다.

 

 

 

 

FirstThunk - IAT(Import Address Table)의 주소(RVA)
INT와 마찬가지로 배열 형태로 이루어져 있고, 라이브러리 함수들이 로드된 주소를 담는다.
따라서 INT와 배열의 크기가 같아야 한다.
마지막은 4byte의 NULL로 채워져있다.
프로그램이 시작되면 이곳에 PE 로더가 라이브러리 함수의 실제 주소를 새로 입력한다.

 

 

 

 


위 HxD에서 보이는 IAT 영역의 맨 처음 4byte가 바로 PageSetupDigW 함수의 주소가 입력될 부분이다.

 


HxD(실행하지 않은 상태)에서의 IAT 값은 실제 로드된 주소(7518D9A0)과 일치하지 않는 것을 볼 수 있다.

(실행 시 PE 로더에 의해 IAT 영역이 갱신되기 때문)

 

 

▶ IMAGE_IMPORT_BY_NAME

 

Hint(2byte)과 Name으로 이루어진 구조체이다.

INT에 의해 참조된다.

 

 

13.3.3 IAT 입력 순서

 

① IID의 Name 멤버를 읽어서 라이브러리의 이름 문자열("kerneI32.dll")을 얻는다.
② 해당 라이브러리를 로딩한다.

  -> LoadLibrary("kernel32.dll")

③ IID의 OriginalFirstThunk 멤버를 읽어서 INT 주소를 얻는다.
④ INT에서 배얼의 값을 하나씩 읽어 해당 IMAGE_IMPORT BY NAME 주소(RVA)를 얻는다.
⑤ IMAGE_IMPORT_BY_NAME의 Hint(ordinal) 또는 Name 항목을 이용하여 해당 함수의 시작 주소
를얻는다.
 -> GetProcAddress("GetCurrentThreadId")
⑥ IID의 FirstThunk(IAT) 멤버를 읽어서 IAT 주소를 얻는다.
⑦ 해당 IAT 배열값에 위에서 구한 함수 주소를 입력한다.
⑧ INT가 끝날 때까지 (NULL을 만날 때까지) 위 4-7 과정을 반복한다.

 

 

 

13.4 EAT

 

▶ EAT란?

라이브러리 파일에서 제공하는 함수를 다른 프로그램에서 가져다 사용할 수 있도록 해주는 핵심 매커니즘.
EAT를 통해서만 해당 라이브러리에서 Export하는 함수의 시작 주소를 정확히 구할 수 있다.

 

 

13.4.1 IMAGE_EXPORT_DIRECTORY

 

 

IMAGE_EXPORT_DIRECTORY 구조체는 IAT와 달리 PE 파일에 하나만 존재한다.

 

 

(하나만!)

NumberOfFunctions - 실제 Export 함수 개수
NumberOfNames - Export 함수 중에서 이름을 가지는 함수 개수(<=NumberOfFunctions)
AddressOfFunctions - Export 함수 주소 배열(배열의 원소 개수 = NumberOfFunctions)
AddressOfNames - 함수 이름 주소 배열(배열의 원소 개수 = NumberOfNames)
AddressOfNameOrdinals - Ordinal 배열(배열의 원소 개수 = NumberOfNames)

 

※ 여기서 잠깐!

책에는
IAT : dll로부터 라이브러리를 제공받는 일
EAT : dll에서 다른 PE 파일에게 함수를 제공하는 일
이라고 설명이 되어 있다.
dll들은 각각 EAT를 하나씩 가지고 있으며, EAT를 참조하여 라이브러리 함수 주소를 구하고

그것들을 연결 프로그램(여기선 notepad.exe)에 제공한다는 것을 알 수 있다.
따라서 주는 입장에서야 자기 자신만 매칭하면 되니까 하나만 필요한 것이고
notepad.exe의 입장에서는 import 하는 dll이 kernel32.dll, shell32.dll 등등 여러 가지이기 때문에
dll을 매칭하는 공간(IAT) 여러 개 필요한 것이다.

 

 

13.4.2 함수 주소를 구하는 과정

 

라이브러리에서 함수 주소를 얻는 API는 GetProcAddress()(KERNEL32.dll)이다.
이 API가 EAT를 참조해서 원하는 API의 주소를 구하는 역할을 한다.

 


여기서 3번째 라이브러리 함수인 AddAtomW 함수를 찾아가는 과정을 알아보도록 한다.

 

 

 

주소를 얻는 과정에서 필요한 멤버는
*Address Table Rva (AddressOfFunctions)
*Name Pointer Table RVA (AddressOfNames)
*Ordinal Table RVA (AddressOfNameOrdinals)
이다.

 

① 맨 처음 함수 이름 배열에 접근하기 위해 Name Pointer Table RVA(2938) 위치를 HxD로 찾아간다. ▽

 

② 2938 위치부터 무수히 많은 함수 이름 배열이 늘어서 있다. 이중 3번째 함수의 위치를 체크하고 offset 주소(3FB3)로 변환 후,

해당 위치를 찾아가 함수 이름을 확인한다. ▽

 

 

③ 다시 구조체 그림으로 돌아가 Ordinal Table RVA(AddressOfNameOrdinals)를 확인한다. offset 381C 위치를 HxD로 찾아간다. ▽

 

 

위에서 구한 name_index를 배열의 인덱스로 하여 해당 ordinal(2byte) 값을 알아낸다.
(kernel32.dll에서는 배열의 index와 ordinal의 값이 일치한다.)

 

④ 다시 구조체 그림으로 돌아가 Address Table RVA(AddressOfFunctions)를 확인한다.

offset 1A54 위치의 함수 주소 배열(EAT)을 HxD로 찾아간다. ▽

 

 

⑤ 함수 주소 배열(EAT)에서 미리 구해둔 ordinal을 배열 index로 하여 원하는 함수의 시작 주소를 얻는다.

 

 

 

13.5 Comment

 

바로 정리하지 못하고 뒷부분까지 공부한 후에 이제 와서야 정리하게 됐다.

역시 예상한 대로 뒤에서도 굉장히 PE 파일을 볼일이 많았다.

처음 공부할 때 완전히 파악하지 못한 채로 넘어가면 이 파트를 본의 아니게 수십 번은 복습하게 될 것이다.