본문 바로가기

시스템/등드등

DLL이란?

DLL(Dynamic Link Library) 이란 일반적으로 확장자가 DLL인 파일을 말한다.

라이브러리라는 말에서 알 수 있듯이 다른 프로그램에서 이용하는 함수들을 모아둔 것이다.
하지만 표준 C 라이브러리 같은 일반 라이브러리의 파일과는 구조나 사용법이 다소 다르다.
일반 라이브러리는 소스코드를 컴파일한 결과로 생성되는 객체 파일(.obj)을 그대로 모아둔 것이다.
링커는 이 중에서 필요한 함수가 포함된 객체 파일을 꺼내서 실행 파일에 결합하는 '정적 링크' 방식이다.

정적 링크는 C/C++ 프로그램의 소스 코드를 기계어 코드로 변환하는 컴파일 단계를 거치게 된다. 
여기서 C/C++에는 수많은 표준함수들이 존재하고 있는데 

이들은 표준 라이브러리 파일 안에 어셈블리 코드의 형태로 담겨 있다. 
소스 코드는 하나 이상 존재할 수가 있는데, 링크 단계는 이 여러 개의 소스 파일들이 하나의 실행 파일로 구성해낸다. 
이때 각각의 소스파일에서 호출한 표준 함수들을 표준 라이브러리에서 가져와 실행파일에 붙여준다. 
이러한 과정을 링크 과정이라 한다. 그리고 이러한 방식이 바로 '정적 링크'이다. 

 

 

  

 

하지만 많은 표준 함수를 사용할수록 프로그램의 크기가 커지게 되며, 

똑같은 함수를 사용한다고 하더라도 이러한 정적 링크 방식은 각 프로그램마다 링크 과정에서 라이브러리를 가져와 

프로그램 안에 저장하기 때문에 이는 비효율적이라 할 수 있다.

정적 링크 방식과는 다르게 DLL은 '동적 링크' 방식으로, 

이는 링크 시에 실행 파일에 결합되는 것이 아니라 프로그램 실행 시에 DLL도 함께 프로그램의 메모리 공간으로 읽어와 호출될 주소 등을 적절하게 바꾸는 것을 말한다. 
일단 읽어온 DLL 함수는 프로그램 내부 함수처럼 호출할 수 있다. 
DLL은 실행 파일과 다른 파일이므로 필요한 시점에 메모리로 읽어오고 불필요하면 메모리에서 내릴 수 있다.

 

 

 

이러한 방식으로 인해 DLL은 여러 장점을 갖게 된다. 우선 여러 프로그램에서 동시에 사용할 수 있다는 것이다.
정적 링크 방식은 자신이 가진 코드를 자기 혼자만 사용하지만, 동적 링크 방식은 하나의 DLL로 존재하여 다른 프로그램에게 라이브러리를 제공해준다.

실행 파일은 DLL에 있는 함수를 Import하게 되는 것이고, DLL은 실행파일에게 함수를 export 해주게 되는 것이다.

 

 

Export Name Table

 

단, 함수나 변수가 실행 파일 안에 포함되지 않았기 때문에, 

사용하고자 하는 함수나 변수를 컴파일러나 링커에게 알려주어야 한다.
만약 Export에 대한 정보가 없다면 실행파일은 DLL의 함수를 Import할 수 없게 된다.
그러므로 위와 같이 Export하는 함수에 대한 정보가 DLL에 기록되어 있어야 한다.

 

DLL Binding


EXE 파일은 사용하고자 하는 DLL의 함수를 메모리에 같은 메모리상에 올리게 되는데 이 때 IAT에는 실제 사용하고자 함수들의 주소가 오게 된다. 다시 말해, 파일에서 IAT는 실제 함수의 주소를 가리키고 있지 않다.
왜냐하면 사용하고자 하는 함수의 주소를 아직 알 수 없기 때문이다.
하지만 메모리에 올라오면서 PE로더는 IAT에 사용하고자 하는 함수의 실제 주소를 올려주므로 우리는 아무런 의심없이 사용할 수 있다. 하지만 이러한 작업은 프로그램의 초기화 시간을 지연시키므로 MS는 이러한 제약을 피할 수 있도록 하나의 기능을 제공한다. 

바로 IAT에 함수의 주소를 기록하는 작업을 미리 수행하여 로딩 시의 속도 향상을 도모하도록 한다. 
이 과정을 바로 DLL 바인딩이라고 하며, 바인드된 실행 파일의 IAT는 실제 함수의 주소를 가리키고 있게 된다.

 

Import Name Table
Import Address Table

 

메모리에 올라오면서 IAT에 실제 주소가 기록되어 변경된다.

 

메모리에 올린 후 Import Table

 

DLL Relocation


DLL 재배치에 대하여 알아보기 저에 먼저 ImageBase에 대해 알아보자.
ImageBase란 PE 구조에서 해당 PE 파일이 PE 로더에 의해 메모리에 로드될 때 로그시키고자 하는 메모리의 주소가 된다. 보통 EXE파일의 경우 0x4000000이며 DLL 파일의 경우 0x10000000이다.

 

IMAGE_OPTIONAL_HEADER

 

하지만 위에서 말한 것과 같이 하나의 EXE 파일은 여러 라이브러리를 필요로 하는 경우가 일반적이기 때문에, 여러 DLL을 메모리에 올리고자 한다. 이 경우 DLL들의 ImageBase가 중첩된다면 하나의 메모리 주소에 여러 DLL이 존재할 수 없으므로 사용할 수 없게 된다.
다행히 DLL 재배치를 통해 원하는 ImageBase에 이미 다른 DLL이 올라와 있다면, 다른 주소에 맵핑될 수가 있다.
하지만 이러한 재배치 작업이 일어나면 PE로더는 부차적인 작업을 수행해야 한다. DLL의 주소를 바꾸어 올리는 것뿐만 아니라, 해당 DLL의 Code Section의 일부 내용을 수정해야만 한다.
아래의 표는 Relocation Section의 내용으로 하단 두 줄에 RVA가 있는 것을 확인할 수 있다.
일반적으로 이 주소가 가리키는 부분은 0x????????과 같은 4bytes의 주소를 나타내는 것으로 PE구조에 기록되어 있는 ImaeBase에 맞게 주소가 설정되어 있다. 하지만 ImageBase와 다른 곳에 로드되면
이 주소들은 ImageBase에 로드된 다른 DLL을 가리키게 되는 문제가 발생하므로 값을 수정해주어야 한다.

 

다행히 이러한 주소값의 수정을 사용자가 직접 하나하나 하는 것이 아니라 

PE로더가 재배치 섹션을 확인하여 알아서 수정해준다. 하지만 여기서 몇가지 문제점이 존재하게 된다.
우선 재배치 정보가 가리키고 있는 값들은 대개 어떤 주소에 관한 값으로, 

이러한 값들이 대개 코드 섹션에 위치하고 있다는 것이다.
따라서 이 값을 PE로더가 수정하기 위해선 해당 섹션에 Write속성을 추가한 뒤 수정을 하고, 

수정을 마치면 다시 원래의 속성으로 되돌려야 한다는 것이다.
이에 더해 위 예에서는 2개의 값만 나타냈지만, 

실제로는 더많은 경우가 많기 때문에 PE로더는 그 많은 주소의 값들을 직접 찾아 수정해주어야 한다.
만약 하나의 EXE파일에 여러 DLL에 대하여 이러한 작업을 수행해야 한다면 프로그램을 실행하기 위한 초기화 시간이 길어질 수 있다.

 

DLL Delay Loading & DLL Forwarding

 

DLL Delay Loading


상기의 이유들로 초기화 시간이 길어질 수 있다는 것에 대하여 알 수 있었다.
사실 하드웨어의 성능이 상향된 요즘은 별 상관이 없지만, 윈도우는 이러한 초기화 시간을 줄이기 위한 또 다른 방안을 구비해놓았다. 바로 DLL 지연 로딩으로, 단어에서와 같이 DLL을 프로그램 실행 시에 로드하는 것이 아니라 지연하여 로딩하는 것이다.
지연 로딩은 암시적 로딩에서의 간편함과 명시적 로딩에서의 유연함, 이 두 장점을 취하고자 하는 방식으로 EXE 작성에서 DLL 링크시에는 암시적인 방식으로, 실제 런타임에서 사용할 때는 명시적인 방식으로 작동하도록 한 것이다. 쉽게 말해 프로그램을 실행 시에 메모리에 매핑되는 것이 아니라 해당 DLL의 Export 함수들 중 하나가 최초로 실행될 때 그 시점에 해당 DLL을 로드해서 가상 주소 공간에 매핑한다는 것이다.

 

DLL Forwarding


DLL의 Export Function Forwarding이란 Export하고자 하는 함수를, 그 기능을 대신하는 다른 DLL내에 정의된 함수의 호출로 대체하는 것이다.
일반적인 경우의 DLL의 Export함수의 주소를 확인해보자.
아래의 표와 같이 Export하는 함수의 주소로 이동을 하면 해당 함수의 내용이 존재하고 있는 것을 확인할 수 있다. 즉, 자신의 DLL안에 해당 코드를 그대로 잘 가지고 있는 것이다.

 

 

이번에는 DLL Forwarding이 적용된 DLL의 내용을 확인해보자.

 

 

Export하고자 하는 함수의 이름 "CreateFileW"와 GetProcAddress 호출하는 것이 보인다.
그 전에 해당 Export 함수를 가진 대상(포워딩 대상)을 LoadLibrary API를 통해 로드하는 것을 확인할 수 있다. 이를 통해 해당 DLL이 로드되고 위의 과정에서와 같이 GetProcAddress를 통해 해당 함수가 로드되는 것이다.

 

그렇다면 DLL 포워딩이 어느 곳에 사용될 수 있을까?


필자는 악성코드에 관심이 많으므로 악성코드를 대상으로 설명하겠다.
악성코드는 문서의 형태로 존재할 수도 있고 실행파일의 형태로 존재할 수도 있다.
하지만 이 글의 취지에 맞게 DLL로 구성된 경우 악성코드 제작자는 매우 유용하게 DLL 포워딩을 사용할 수 있다.
악성코드 제작자 그 누구도 자신이 만든 파일이 누가 보아도 '악성'으로 보이고 싶지 않을 것이다.
그렇기에 정상 파일인 것처럼 위장을 하게 되는데, 

DLL의 경우 실제 Export하는 함수의 내용을 구현할 수 있어야 한다는 것이다.
물론 실제 DLL의 내용을 Ctrl+C/Ctrl+V를 통해 사용할 수는 있겠지만 이는 결코 좋은 방법이 아니다.
이 기능을 사용하면 해당 포워딩 설정을 실제 DLL 파일로 해놓으면 너무나 쉽게 정상적인 기능을 모두 구현할 수 있게 된다. 결국 정상적인 기능을 수행하면서, DllMain()에는 자신이 원하는 기능을 수행하도록 하면 이는 완벽한 위장이 된다. 물론 실제 시스템 DLL이 먼저 로드되는 상황이 발생되면 안되므로 DLL 로딩 순서를 변경하여 실행파일과 같은 디렉토리에 있는 악성 DLL을 먼저 로드하도록 하여 이를 로드시키면 모든 것은 끝이 난다.

 

참고

https://kali-km.tistory.com/entry/DLL%EC%9D%B4%EB%9E%80

'시스템 > 등드등' 카테고리의 다른 글

웹 메일 및 채팅 앱 구현  (0) 2019.03.12
Wannacry 1차 정리본  (0) 2019.01.26
pwntools 간단 정리  (0) 2019.01.11
ELF 파일 구조  (3) 2018.11.20
간단한 정리  (0) 2018.11.19