다음은 윈도우 프로세스가 실행되었을 때, 과연 어떤 초기화 과정을 거치는지에 대한
일반적인 순서도를 글로 표현한 것이다.
1. process 가 Win32 API 인 CreateProcess를 호출하면, 이 API는
프로세스를 위한 "process object" 와 "new memory space" 을 생성한다.
2. CreateProcess함수는 이렇게 새롭게 생성된 메모리 공간으로 ntdll.dll 과
프로그램 실행파일을 맵핑시키고 프로세스의 첫번째 쓰레드를 생성하여 그것을
위한 stack 공간을 할당한다.
3. 스택공간을 할당시키고 나면 프로세스의 첫번째 쓰레드는 재 시작하게 되는데,
그러기 위해선 ntdll.dll에 있는 LdrpInitialize 함수를 실행하여야한다.
다음은 LdrpInitialize 함수의 코드이다.
--------------------------------------------------
* NtContinue 함수의 원형(Undocumented) *
NtContinue( IN PCONTEXT TheadContext
IN BOOLEAN RaiseAlert );
--------------------------------------------------
0:000> u
ntdll!KiUserApcDispatcher+0x7:
77f767ff 6a01 push 0x1 ( 셋팅되면, 현재의 쓰레드 오브젝트로부터 Alert state를 지운다.)
77f76801 57 push edi
77f76802 e8950effff call ntdll!ZwContinue (77f6769c)
77f76807 90 nop
-----------------------------------------------------------------------------------------
LdrpInitialize 함수에 포함되어 있는 ZwContinue는 세이브된 CONTEXT의 실행(쓰레드)을
재 시작할 때 사용한다.
위에서 보는거와 같이, RaiseAlert 변수명에 hex값 0x1를 스택에 삽입한후 현재 쓰레드의
CONTEXT structure 값의 포인터값인 edi를 삽입하고 있는걸 볼 수 있다.
그리고 이 매개변수 값을 토대로 ZwContinue 함수를 호출한다.
이것은 인텔의 환경에서 컴파일 되어진 거 같다. little endian의 방식으로 인수가 삽입된다.
4. LdrpInitialize 함수는 최초 실행의 import table를 반복적으로 면밀히 조사하고
실행되기 위해 요구되어진 모든 실행을 메모리로 맵핑한다.
5. 이 시점에서 제어는 ntdll.dll 안에 존재하는 LdrpRunInitializeRoutines로 넘어간다.
LdrpRunInitializeRoutines 는 EXE 나 Dll 에서 명시된 엔트리 포인트로 호출되어지기 전에
실행되는 마지막 정류장이다. 그리고 이 함수는 Dll 코드가 실행되기 전에 실행되는 커널의
마지막 코드로써, LoadLibrary의 콜 때문에, 한번 또는 그 이상의 Dll 이 동적으로 로드되어질
때 마다 호출되는 특징이 있다.
이렇게 이 초기화 프로세스는 DLL_PROCESS_ATTACH 와 함께 각각의 Dll의 엔트리 포인트를
호출하는 것으로 이루어져 있다.
6. 일단 모든 Dll 초기화가 이루어지면, LdrpInitialize는 KERNEL32.DLL 에 있는
BaseProcessStart 즉, 쓰레드의 실제 초기화 루틴을 호출한다. 이 함수는 프로세스의
초기화 과정을 완료한 시점에서 실행파일의 WinMain 엔트리 포인트를 호출한다.
☆ 프로세스는 이와같은 단계로 프로그램을 실행하기전에 초기화 과정을 거치게 된다. ☆
--------------------------------------------------------------------------------
다음은 BaseProcessStart의 PseudoCode입니다.
BaseProcessStart( PVOID lpfnEntryPoint )
{
DWORD retValue
DWORD currentESP;
DWORD exceptionCode;
currentESP = ESP;
_try
{
NtSetInformationThread( GetCurrentThread(),
ThreadQuerySetWin32StartAddress,
&lpfnEntryPoint, sizeof(lpfnEntryPoint) );
retValue = lpfnEntryPoint();
ExitThread( retValue );
}
_except(
exceptionCode = GetExceptionInformation(),
UnhandledExceptionFilter( GetExceptionInformation() ) )
{
ESP = currentESP;
if ( !_BaseRunningInServerProcess )
ExitProcess( exceptionCode );
else
ExitThread( exceptionCode );
}
}
위와 같이, BaseProcessStart는 쓰레드의 엔트리 포인틀 값을 매개변수로 받아와서 쓰레드를
시작하기 위해 엔트리 포인트를 호출하고 난 후 제어가 ExitThread로 넘어가게 된다.
그러나 만약 쓰레드가 faults 나고 그에 대한 exception을 어떤 다른 handler에서 처리하지
않는다면 다음의 실행은 _except 인사이드 코드에 존재하는 UnhandledExceptionFilter가
실행되고 만약 이 함수가 EXCEPTION_EXECUTE_HANDLER 를 리턴하면 제어가 다시
_except 안의 다음라인으로 넘어가고 그리고 나서 _except하는것은 ExitProcess 함수를
호출함으로써 프로세스를 종료할 것이다.
그러나 fault 난 쓰레드가 thread-base-service로 실행되고 있는 중이였다면,
_except code는 ExitProcess를 호출하지 않는다. 그것은 대신에 ExitThread를 호출할 것이다.
그 이유는 한개의 서비스가 잘못된 것 때문에 단지 전체의 서비스 프로세스가 종료시키는 걸
원하진 않기 때문이다.
'System&Kernel' 카테고리의 다른 글
| Entry point로 진입하기 전의 프로세스 초기화 과정 (0) | 2008/09/10 |
|---|---|
| Art of Hooking (0) | 2008/08/30 |
