[WinAPI] DllMain() 주의점 그리고 DONT_RESOLVE_DLL_REFERENCES

출처 : http://isecurity-textcube.blogspot.com/2010/04/dll%EC%9D%98-%EA%B3%A0%EA%B8%89%EA%B8%B0%EB%B2%95.html
 

  • DONT_RESOLVE_DLL_REFERENCES
    이 플래그는 LoadLibraryEx함수를 호출하는 프로세스의 주소공간에 매핑할 것을 지정한다. 보통 dll이 프로세스의 주소공간에 매핑되면 시스템은 일반적으로 DllMain()이라고 불리는 특수한 함수를 호출하여 dll을 초기화하도록 한다. 

DONT_RESOLVE_DLL_REFERENCES 플래그를 사용하면 시스템은 DLL파일이미지가 매핑되는 작업까지만 수행하고 DllMain()함수는 호출하지 않는다. 또한 DLL 파일은 다른 DLL이 포함하고 있는 함수들을 임포트하기도 하는데, 시스템이 DLL을 프로세스 주소공간에 매핑할 때 다른 DLL이 필요한지를 확인하여 자동적으로 이러한 dll들을 로드해준다. 이 플래그를 사용하면 매핑할 DLL이 필요로 하는 추가적인 DLL을 프로세스의 주소공간에 자동으로 로드하지 않는다(DONT_RESOLVE_DLL_REFERENCES가 지정이 되면 매핑만 하고 DllMain() 호출하지 않고, 해당 DLL과 연관되어 있는 다른 DLL들을 자동으로 로드해주지 않는 특성을 갖고 있다)
DLL이 익스포트하고 있는 함수들은 내부적인 자료구조가 완전히 초기화되고 추가적으로 필요로 하는 DLL파일들이 로드되기 전까지는 호출될 수 없다. 따라서 가능하면 이플래그는 사용하지 않는 것이 좋다.잘못만들어진 플래그라고 한다(레이몬드첸의 블로그에서 확인, http://blogs.msdn.com/oldnewthing/archive/2005/02/14/372266.aspx)로 부터 "LoadLibraryEx(DONT_RESOLVE_DLL_REFERENCES)는 완전히 잘못만들어졌다"는 글을 살펴보기 바란다
 
  • LOAD_LIBRARY_AS_DATAFILE
이 플래그는 마치 데이터 파일처럼 프로세스의 주소공간에 dll을 매핑하는 작업까지만 수행한다는 점에서 DONT_RESOLVE_DLL_REFERENCES 플래그와 유사하다. 이 플래그를 사용하면 시스템이 DLL파일을 초기화하기 위해 어떤 추가적인 작업도 수행하지 않는다. 예를 들어 시스템은 일반적으로 프로세스의 주소공간에 DLL을 매핑하고 나면 DLL의 정보를 확인하여 DLL파일 내의 각 섹션별로 어떤 페이지보호특성을 지정해야 할지를 결정하게 된다. 하지만 파일내에 실행코드가 포함되어 있더라도 이플래그를 사용하면 DLL정보를 이용한 페이지 보호 톡성 설정작업을 수행하지 않는다.
DLL이 리소스만 가지고 있고 어떤 함수도 가지고 있지 않아서 DLL파일 이미지를 프로세스의 주소공간에 매핑하기만 하면 되는 경우에 유용하다. 일반적으로 exe파일은 새로운 프로세스를 수행하기 위해 로딩 되지만 LoadLibraryEx()함수를 이플래그와 함께 사용하면 exe파일 이미지를 프로세스의 주소공간에 단순매핑만 한다. 이렇게 exe파일을 매핑한 후 반환된 HMODULE을 이용하면 exe파일내에 포함되어 있는 리소스에 접근할 수 있다. exe 파일은 DllMain()을 가지고 있지 않기 때문에 LoadLibraryEx()함수를 이용하여 exe파일을 매핑하려는 경우에는 반드시 LOAD_LIBRARY_AS_DATAFILE 플래그를 사용해야 한다.(LoadLibraryEx로 EXE 파일을 로드하는 경우를 설명)
  • LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE
이 플래그는 LOAD_LIBRARY_AS_DATAFILE과 유사하지만 바이너리 파일을 사용하는 동안 다른 애플리케이션이 해당 파일을 수정하지 못하도록 배타적으로 파일에 접근한다는 점에서 차이가 있다
  • LOAD_LIBRARY_AS_IMAGE_RESOURCE
이 플래그는 LOAD_LIBRARY_AS_DATAFILE과 유사하지만 DLL파일을 로드할 때 운영체제가 상대가상주소(RVA)에 접근하는 방법에 있어 미세한 차이가 있다. 이 플래그를 사용하면 메모리 영역에 DLL을 로드한 후 각 심벌의 시작주소를 변경하지 않은 상태에서 RVA값에 직접적으로 접근할 수 있다. 이 플래그는 DLL의 포터블 익스큐터블 내의 여러 섹션들의 내용을 분석하고자 할때 유용하게 사용될 수 있다.
 
DllMain의 매개변수 와 주의점들
hInstDll
    DLL의 인스턴스 핸들 값, DLL 파일 이미지가 가상 주소공간의 어디로 매핑되었는지를 알려주는 가상 메모리 주소값이다. 보통의 경우 이 값을 전역변수에 저장해 두었다가 DialogBoz나 LoadString과 같이 리소스를 로드해야 하는 함수들을 호출할때 사용하는 것이 일반적이다.
 
fImpLoad
    DLL이 암시적으로 로드된 경우에는 0이 아닌 값이 전달되고, DLL이 명시적으로 로드된 경우에는 0이 전달된다.
 
fdwReason
    시스템이 이 함수를 호출한 이유를 나타내는 값이 전달된다. 이 이 매개변수로는 DLL_PROCESS_ATTACH,DLL_PROCESS_DETACH,DLL_THREAD_ATTACH,DLL_THREAD_DETACH 값이 올 수 있다.
 
DLL은 자기 자신을 초기화 하기 위해 DllMain()함수를 사용한다는 것을 기억하라.

아래의 문구는 유념해서 기억 또 기억할 것!!
특정 DLL에 대해 DllMain()함수가 호출된 시점에 동일 주소공간에 로드된 다른 DLL들은 자신의 DllMain()함수를 미처 호출하지 못했을 수도 있다. 다른 DLL들은 아직 초기화 되지 않은 상태일 수도 있으므로 DllMain()함수에서 다른 DLL이 익스포트하고 있는 함수를 호출해서는 안된다. 또한 DllMain() 내에서는 LoadLibrary(Ex)나 FreeLibrary와 같은 함수를 호출해서도 안되는데 만일 이러한 함수를 호출하면 여러 DLL들 사이에 의존관계 루프가 생길 수 있다.

플랫폼 SDK문서에는 DllMain()함수를 TLS,커널오브젝트생성,파일열기 작업 수행 등의 초기화를 위해서만 활용할 것을 명기하고 있다. 또한 User, Shell, ODBC, COM, RPC, 소켓함수 등을 호출해서는 안된다. 왜냐하면 이러한 함수들을 호출하였을때 해당 함수들의 기능을 구현하고 있는 DLL들이 아직 초기화되지 못했을 수도 있고, 해당 기능을 수행하는 함수들이 내부적으로 LoadLibrary(Ex) 함수를 호출하는 경우 의존 관계 루프가 생길수도 있기 때문이다.

또한 전역이나 정적으로 선언된 C++오브젝트를 생성하는 것도 이와 유사한 문제를 야기할 수 있음을 알아두어야 한다. 왜냐하면 이러한 오브젝트들의 생성자와 파괴자가 우리가 작성한 DllMain()함수가 호출되는 시점에 동시에 수행될 수도 있기 때문이다.


프로세스 이미지가 실행이 될때의 순서도
신규 프로세스 생성 -> 프로세스 주소 공간 생성 -> EXE 파일 & 모든 Dll 파일 주소공간에 매핑 -> 메인스레드 생성 -> 메인 스레드가 모드 Dll에 대해서 DLL_PROCESS_ATTACH값으로 DllMain 호출 -> 모든 Dll에서 해당 통지 메시지에 대해서 정상회신하면 -> 메인 스레드의 시작은 C/C++ 런타임 시작 코드를 가리키도록 설정

위 과정 이후에 특정 스레드가 dll을 로드할때의 순서도
10번 스레드가 LoadLibraryEx를 호출 -> 해당 dll이 주소 공간에 매핑 -> 10번 스레드가 dll의 DllMain()함수를 DLL_ATTACH_PROCESS 인자값으로 호출 -> LoadLibraryEx 반환

위 과정 이후에 특정 스레드가 생성될때의 순서도
11번 스레드가 생성됨(CreateThread) -> 11번 스레드는 현재 프로세스 공간에 매핑되어 있는 모든 Dll들에 대해서 DllMain(DLL_THREAD_ATTACH) 호출 -> 모든 dll에 대해서 DllMain 호출이 반환되고 나서 -> 11번 스레드가 재개

만약 11번 스레드가 생성된 이후에 특정 dll이 로드되면
11번 스레드는 DllMain(DLL_THREAD_ATTACH)를 받지 못한다.

a.dll의 DLLMain은 순차적으로 호출이 된다는 점을 유념
a.dll이 매핑이 되어 있고 -> 1번/2번 스레드가 각각 21번/22번 스레드를 생성 ->
21번 스레드는 a.dll의 DllMain(DLL_THREAD_ATTACH) 호출 -> 동시에 22번 스레드도 a.dll의 DllMain(DLL_TRHEAD_ATTACH) 호출 -> 여기서 시스템이 관여를 하는데 -> 21번 스레드의 a.dll의 DllMain() 의 호출이 완료되기까지 22번 스레드를 일시 정지 -> 21번 스레드의 DllMain() 호출 완료가 되면 22번 스레드를 resume시킨다.

댓글

이 블로그의 인기 게시물

[WinAPI] 모달리스 다이얼로그 설명

[WinDbg] Debugging a stack overflow

[WinDbg] first-chance, second-chance Exception