안 쓰던 블로그

[리버싱] DLL과 32bit IAT 로딩 과정 본문

CTF/Reversing

[리버싱] DLL과 32bit IAT 로딩 과정

proqk 2020. 10. 8. 02:27
반응형

시작 전에 읽으면 좋은 이전 글: VA, RVA, RAW의 개념 foxtrotin.tistory.com/330

 

DLL(Dynamic Linked Library)

여러 프로세스에서 공유하면서 쓰는 동적 연결 라이브러리

 

예전 16비트 DOS시절에 DLL개념이 없고 Library만 존재했을 때는 함수 하나(예를 들어 printf)를 실행하고자 하면 라이브러리에서 binary코드를 그대로 가져와서 프로그램에 삽입시켜 버렸다. 즉, 실행 파일에 printf()함수의 바이너리 코드를 가지고 있는 식이었다

 

그런데 Windows OS로 넘어오면서 멀티태스킹 환경이 지원되었는데, 여러 프로그램들이 동시에 실행되어야 하는 상황에서 각 프로그램마다 기본 라이브러리들과 프로그램을 위한 라이브러리들을 포함시키려니 중복으로 인한 메모리 낭비와 디스크 공간 낭비가 심해졌다

 

그래서 탄생한 개념이 DLL이다. 프로그램에 라이브러리를 포함시키지 않고 별도의 파일 DLL로 구성하여 필요할 때마다 불러 쓰는 식으로 바뀌게 되었다. 메모리에 한 번 로딩된 DLL의 코드, 리소스를 프로세스마다 공유시키므로써 메모리를 더 효율적으로 사용할 수 있게 바뀌었다. 또한 이후에 라이브러리가 업데이트 된다 하여도 해당 DLL파일만 교체하면 되어 쉽고 편했다

 

DLL의 로딩 방식에는 2가지가 있는데, 그 중에 프로그램이 시작할 때 같이 로딩되어 프로그램이 종료될 때 메모리에서 해제되는 로딩 방식을 기준으로 살펴본다. Implicit Linking이라고 하는데, IAT가 바로 Implicit Linking에 대한 메커니즘을 제공하는 역할은 한다


라이브러리: 소프트웨어 개발에서 자주 쓰고 기초적인 함수들을 중복해서 개발하는 것을 피하기 위해서 표준화된 함수 및 데이터 타입을 만들어 놓은 것

 

Static Link Library: 정적링크라고 하며, 컴파일 시점에 라이브러리가 링커에 의해서 연결되는 것. 실행파일의 일부분이 된다
Linux : *.a , Windows : *.lib

 

Dynamic Link Library: 동적링크라고 하며, 실행파일에서 해당 라이브러리 기능을 사용시에 라이브러리 파일을 참조하여 기능을 호출하는 것
Linux : *.so , Windows : *.dll


IAT(IMPORT ADDRESS TABLE)

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

IAT로 어떤 API가 쓰이고, 그 API는 어떤 DLL을 사용하는지까지 알 수 있다(이 주소들도 다 RVA값)

 

IAT를 쓰면 해당 프로그램 안에 사용하고자 하는 함수에 대한 모든 정보가 없어도, 주소만 가지고 DLL로 넘어가 해당 주소에 있는 함수 정보를 가져다가 쓰기 때문에 용량이 클 필요도, 메모리를 크게 점유할 필요도 없다

이는 다시 말해 어떤 함수나 API가 필요할 때 직접 호출하지 않고 어떤 주소에 있는 값을 갖와서 호출하는 식으로 동작한다는 의미이다.

 

왜 이렇게 두 번 거치는 작동 방식이 되었는지는 위에서 설명한 DLL을 통해 알 수 있다. DLL이 도입되면서 프로그램을 컴파일(생성)하는 순간에는 그 프로그램이 어떤 운영체제, 어떤 국가의 언어, 어떤 환경에서 실행될지 알 수가 없기 때문에 변화하는 환경에 맞춰 프로그램이 실행될 수 있도록 해야 했다. 그렇기 때문에 IAT개념을 넣어서 컴파일러는 함수의 실제 주소가 저장될 위치와 명령어만 가지고 있고, 파일이 실행되는 순간 PE로더가 실행되는 환경에 맞추어 해당 주소에 프로그램 주소를 입력해 주는 식으로 동작하게 만든 것이다

 

실제 주소를 하드코딩하지 못하는 이유로는 위처럼 실행 환경에 따른 문제도 있지만 DLL Relocation때문이기도 하다. 일반적인 DLL 파일의 ImageBase값은 10000000이다. 그런데 a.dll도 10000000을 쓰고 b.dll도 10000000을 쓰면 문제가 된다. 그래서 PE로더는 다른 비어있는 메모리 공간을 찾아서 뒤에 온 dll파일을 로딩한다. 그러므로 DLL은 PE헤더에 명시된 ImageBase에 로딩된다고 보장할 수가 없다

이는 PE헤더에서 주소를 나타낼 때 VA를 쓰지 못하고 RVA를 써야 하는 이유이기도 한다


IMAGE_IMPORT_DESCRIPTOR(IID)

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

IID구조체의 구조를 보면 주목해야 하는 멤버 세 개가 있다

 

 

 

OriginalFirstThunk: INT(Import Name Table)의 주소(RVA)

Name: Library 이름 문자열의 주소(RVA)

FirstThunk: IAT(Import Address Table)의 주소(RVA). IAT를 가리킨다

 

보통 하나의 프로그램에서 여러 개의 라이브러리를 갖기 때문에 IID는 배열 형식으로 나타내게 된다


 

note.exe의 kernel32.dll - IMAGE_IMPORT_DESCRIPTOR 구조

이 그림은 notepad.exe의 kernel32.dll 에 대한 IMAGE_IMPORT_DESCRIPTOR 구조이다


PE Loader가 Import함수 주소를 IAT에 입력하는 순서

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의 과정을 반복한다

위에 이미지에서는 INT 와 IAT 의 각 원소가 동시에 같은 주소를 가리키고 있지만 그렇지 않은 경우도 많다

 


notepad.exe를 이용한 실습

실제 IMAGE_IMPORT_DESCRIPTOR 구조체는 PE 구조 중에 PE Header가 아닌 PE Body에 위치한다

PE Body를 찾기 위한 정보는 PE Header에 있다

 

원래대로면 HxD에서 다 진행해야 하지만 이해하기 쉽게 PEView에서 먼저 따라가고 찾아가는 식으로 하겠다

 

일단 RVA값을 찾는다

 

여기서 보면 RVA값은 234B8이고 size는 370이다

이제 이 RVA값이 어느 섹션에 속하는지를 알면 파일이 속하는 섹션도 알 수 있다

 

RVA로 RAW를 구하는 계산식은 RAW = RVA - VA + PointerToRawData (foxtrotin.tistory.com/330)

(파일의 임의 위치 = 메모리의 임의 위치 - 메모리의 해당 섹션 시작 위치 + 파일의 해당 섹션 시작 위치)

 

RVA값을 알았지만 RAW를 구하려면 VA랑 PointerToRawData값도 구해야 한다

 

PEView에서 세 번째 아래 화살표를 누르면 주소값이 RVA형태가 된다

시작 값은 23000 마지막 값은 (사진엔 안 나왔지만)255F0이므로 .idata의 영역은 23000~255F0이고, 위에서 나온 RVA값 234B8은 .idata영역에 속한다는 것을 알 수 있다

 

VA값은 메모리의 해당 섹션 시작위치이므로 VA는 23000이다(표시 방법을 VA로 변경하고 읽어야 한다)

ImageBase값은 제외하고 읽는다

 

파일의 해당 섹션 시작 위치는 PEView에 이곳을 클릭해서 알 수 있다

PointerToRawData, 즉 파일의 해당 섹션 시작 위치는 20800

 

RVA=234B8

VA=23000

PointerToRawData=20800 이니까

 

RAW = RVA - VA + PointerToRawData에서

RAW=234B8-23000+20800=20CB8

 

RAW가 20CB8 이므로, Import Directory 시작 offeset 이 20CB8 이다

 

PEView에서 보면 Import Name Table RVA가 20CB8인 것을 확인할 수 있다

IMAGE_IMPORT_DESCRIPTOR 구조체 배열을 다른 말로 IMPORT Directory Table이라고도 한다

 

우리가 찾아야 하는 IMAGE_OPTIONAL_HEADER32의 DataDirectory[1].VirtualAddress값이 바로 IMAGE_IMPORT_DESCRIPTOR 구조체 배열의 시작 주소이다 (역시 RVA값이다)

 

 

1. 라이브러리 이름 Name 찾기

Name항목은 Import함수가 소속된 라이브러리 파일의 이름 문자열 포인터이다

PEView에서 보면 RVA는 23834이다 (여기서 좀 헷갈릴 수 있는데 Data값을 봐야 한다 나만 헷갈렸나)

RAW계산: 23834-23000+20800=21034

 

Name Table로 내려가서 해당 값을 본다

IMAGE_IMPORT_BY_NAME 의 구조체인 HINT 와 함수 이름(Name RVA)들이 쭉 있다

 

그 중에 하나만 선택해서 따라가 본다

첫 번째 인덱스의 Value값은 SelectObject니까 실제로 저 문자열이 있으면 되겠다

 

RVA값 23D12 를 가지고 다시 RAW를 계산한다

23D12-23000+20800=21512

 

해당 문자열이 실제로 있음을 확인한다

 

HxD에서도 똑같은 결과를 볼 수 있다

 

 

 

결론적으로 INT를 통해서 IAT 테이블 정보를 만들게 됨을 알 수 있다
PE로더에서 INT정보를 가져가고 IAT RVA에 실제 로딩될 함수 주소를 적는 게 IAT이다

반응형
Comments