본문 바로가기

컴터 때찌/E​xperience

About Kernel Object

[제프리 리처의 Windows Via C/C++ 3장]

# A. 커널 오브젝트가 뭐지? 

운영체제나 우리가 개발하는 어플리케이션은 프로세스,스레드,파일 등과 같은 
리소스들을 관리하기 위해 커널 오브젝트를 사용함 

-Access Token Object
-Event Object
-File Object
-File-mapping Object
-I/O completion Object
-Job Object
-Mailslot Object
-Mutex Object
-Pipe Object
-Process Object
-Semaphore Object
-Thread Object
-Waitable Timer Object
-Thread Pool Worker Factory Object 
 .. 등의 커널 오브젝트들이 있고 이들을 윈도우가 생성하고 조작한다
 
커널 오브젝트의 데이터 구조체는 커널에 의해서만 접근 가능하다
따라서 커널 오브젝트에 접근하여 사용하기 위해선 커널 오브젝트를 생성하는 함수를 호출하고
이 반환값으로 주는 커널 오브젝트 핸들 값을 가지고 접근 한다
각 프로세스들을 각각 독립된 프로세스 핸들 테이블이 존재하기 때문에 
어떤 프로세스(A)의 스레드가 다른 프로세스(B)의 스레드에 핸들을 공유하는건 좋은 방법이 아니다 

1. 사용 카운트

커널 오브젝트는 프로세스가 아니라 커널에 종속적이기 때문에 커널 오브젝트 생성후 프로세스가 종료 된다고 해도 다른 프로세스에서 그 커널 오브젝트 사용하고 있다면 커널 오브젝트는 자신을 사용하는 모든 프로세스가 종료 될때까지 삭제되지 않고 남아있게 된다
이 사용 카운트는 자신을 사용하고 있는 프로세스의 카운트 값으로써 0이 되면 커널 오브젝트는 삭제된다


2. 보안

커널 오브젝트를 생성하는 대부분의 함수들은 SECURITY_ATTRIBUTES 구조체에 대한 포인터를 인자로 받아들인다. 

SECURITY_ATTRIBUTES sa;    //일반적으로는 구조체를 선언하지 않고 해당 인자를 NULL로 한다고함
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = pSD;
sa.bInheritHandle = FALSE;
HANDLE hFileMapping = CreateFileMapping(INVALID_HANDLE_VALUE,&sa,
                                                               PAGE_READWRITE,0,1024,TEXT("FileMapping Test"));


/*
HANDLE CreateFileMapping(
    HANDLE hFile,
    PSECURITY_ATTRIBUTES psa,
    DWORD flProctect,
    DWORD dwMaximumSizeHigh,
    DWORD dwMaximumSizeLow,
    PCTSTR pszName);

typedef strcut _SECURIT_ATTRIBUTES{
    DWORD nLength;
    LPVOID lpSecurityDescriptor;  // 실질적인 보안 Descriptor
    BOOL bInheritHandle;
} SECURITY_ATTRIBUTES;  */



위의 예제는 커널 오브젝트를 생성하고 사용하는 예제고
이미 생성되어있는 커널 오브젝트를 사용하려면 Create  대신 Open을 사용한다

HANDLE hFileMapping = OpenFileMapping(FILE_MAP_READ,FALSE,TEXT("FileMapping Test"));

접근이 거부될 경우 NULL을 반환하며 GetLastError을 호출하면 5(ERROR_ACCESS_DENIED)가 반환되고 이 핸들로 FILE_MAP_READ 외의 다른 권한을 필요한 API를 호출하게 되면 "접근 거부" 가 발생된다

일반 오브젝트와 커널 오브젝트를 구분하는 간단한 방법은 SECURIT_ATTRIBUTES와 같은 보안 특성을 지정하는 매개변수가 있는지 확인하는 것이다, 일반 오브젝트는 보안 특성을 지신 매개변수가 없다



# B. 프로세스의 커널 오브젝트 핸들 테이블


프로세스가 초기화되면 운영체제는 프로세스를 위해 커널 오브젝트 핸들 테이블을 할당하는데
이 핸들 테이블은 유일하게 커널 오브젝트에 의해서만 사용된다 

프로세스 오브젝트 핸들 테이블의 구조는 아래와 같은 형태를 가지고 있다
------------------------------------------------------------------------------------------
| 커널 오브젝트의 메모리 블록을 가르키는 포인터  | 엑세스 마스크(Access Mask) | 플래그(Flag)|
------------------------------------------------------------------------------------------
 
1. 만들어 보자, 커널오브젝트!

프로세스가 처음 생성되면 프로세스의 핸들 테이블은 비어는데 프로세스 내에 스레드가 Create*의 함수를 호출하면

 - 커널은 커널 오브젝트를 위한 메모리 블록을 할당하고
 - 프로세스의 핸들 테이블을 조사하여 비어있는 공간을 찾아내고
 - 해당 값을 설정한다
   (포인트 멤버는 커널 오브젝트의 자료 구조를 가르키는 내부적인 메모리주소,
    엑세스마스크는 "full access",
    플래그는  "설정" 상태로 초기화함)


HANDLE CreateThread(
PSECURITY_ATTRIBUTES psa,
size_t dwStackSize,
LPTHREAD_START_ROUTINE pfnStartAddress,
PVOID pvParam,
DWORD dwCreationFlags,
PDWORD pdwThreadId);

HANDLE CreateFile(
PCTSTR pszFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
PSECURITY_ATTRIBUTES psa,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplatefile);

HANDLE CreateFileMapping(
HANDLE hFile,
PSECURITY_ATTRIBUTES psa,
DWORD flProtect,
DWORD dwMaximumSizeHigh,
DWORD dwMaximumSizeLow,
PCTSTR pszName);

HANDLE CreateSemaphore(
PSECURITY_ATTRIBUTES psa,
LONG lInitialCount,
LONG lMaximumCount,
PCTSTR pszName); 
 
 
커널 오브젝트를 생성하는 모든 함수(Create* 류의 함수)는 프로세스별로 고유한 핸들 값을 반환하며 이 반환한 핸들값을 인자로 사용하고자하는 커널 오브젝트의 실제 주소를 얻어내고 그 후에 커널 오브젝트의 자료구조를 변경할수 있다. 또한 이러한 핸들 값을 4로 나누면(오른쪽으로 2비트 shift) 커널 오브젝트에 대한 정보를 저장하는 있는 프로세스 핸들 테이블의 인덱스 값을 얻을 수 있다.

커널 오브젝트 생성이 실패하면 몇몇 함수들은 NULL을, 또 몇몇은 INVALID_HANDLE_VALE를 반환한다
CreateMutex 실패시 NULL, CreateFile 실패시에는 INVALID_HANDLE_VALUE를 반환





2. 커널 오브젝트 삭제하기 


BOOL CloseHandle(HANDLE hobject); 함수를 사용해서 해당 커널 오브젝트의 카운트 값을 감소시키고
이 값이 0이 되면 커널 오브젝트를 파괴하고 메모리를 반환한다 

유효하지 않은 핸들을 CloseHandle 함수에 전달하면 프로세스는 정상적으로 수행되고 CloseHandle 함수는 FALSE를 반환하고 GetLastError를 호출하면 ERROR INVALID HANDLE 값을 반환한다.



# C. 프로세스간 커널 오브젝트의 공유

서로 다른 프로세스에서 각기 수행되는 스레드들 간에 동일 커널 오브젝트를 공유해야 하는 경우가 발생하는데
커널 오브젝트의 핸들은 프로세스 별로 고유한 값(안정성 때문에)이기 때문에 이러한 핸들 값을 공유하는 것은 간단하지 않다.

1. 오브젝트 핸들의 상속을 이용하는 방법

 오브젝트 핸들의 상속은 오브젝트를 공유하고자 하는 프로세스들이 페어런트-차일드(Parent-child) 관계를가질 때에만 사용될 수 있다. 

 - (1) parent 프로세스는 커널 오브젝트를 생성할때 이를 가르키는 핸들이 상속될수 있음을 시스템에 알려줘야함
   오브젝트 상속 ( x )   ->   오브젝트 핸들 상속 ( o )
 - (2) CreateProcess 함수를 이용하여서 parent 프로세스가 child 프로세스를 생성
 - (3) Child 프로세스에서 Parent 프로세스를 조사해서 상속가능한 핸들을 자신의 핸들 테이블에 복사해온다
         복사된 이후에 부모에서 생성한 커널오브젝트는 받아오지 못함
         (이후에는 _strscanf_s 와 같은 함수를 써서 child가 parent의 핸들 값을 가져올수 있음)
          또는 WaitForInputIdle 함수를 써서 parent에서 child로 send 나 post 할수 있다)

SECURITY_ATTRIBUTES sa;   
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE; // 상속 가능한 핸들   (1) 단계, FALSE는 상속 안함

HANDLE hMutex = CreateMutex(&sa,FALSE,NULL);
CreateProcess 함수 호출  // (2) 단계 

 
2. 명명된 오브젝트를 사용하는 방법 

모두는 아니지만 대부분의 커널 오브젝트는 이름을 가질 수 있다.

HANDLE CreateFileMapping(
HANDLE hFile,
PSECURITY_ATTRIBUTES psa,
DWORD flProtect,
DWORD dwMaximumSizeHigh,
DWORD dwMaximumSizeLow,
PCTSTR pszName);

위의 함수를 보면 마지막에 pszName 라는 매개변수를 가지는데 이 변수가 NULL이면 익명의 커널 오브젝트를 생성하게 되는것이고, 이 변수에 '\0'으로 끝나는 문자열을 가르키는 주소를 전달하면 최대 260의 오브젝트의 이름을 지정할수 있다. MS 에서는 오브젝트의 명명 규칙을 제공하고 있지 않아서 똑 같은 이름의 오브젝트가 있을수도 있고 
서로 다른 Kernel Object를 같은 이름으로 명명할 경우 NULL반환이나 ERROR_INVALID_HANDLE이 반환된다
//DWORD dwErrorCode = GetLastError();



[A 프로세스]
HANDLE hMutexProcessA = CreateMutex(NULL,FALSE,TEXT("Leo"));  
// Mutext Kernel  Object를 Leo로 명명하여 생성
[B 프로세스]
HANDLE hMutexProcessB = 
CreateMutex(NULL,FALSE,TEXT("Leo"));  

이렇게 같은 이름으로 생성하려고 하면 일단 같은 형태의 오브젝트인지 확인하고, B 프로세스가 오브젝트에 대한 최대 접근 권한을 가지고있는지 확인한다, 그리고 마지막으로 B 프로세스의 핸들 테이블에 비어있는 항목을 추가하고 이미 존재하고 있는 (Leo)오브젝트를 가르키도록 설정한다.
만일 타입이 일치하지 않거나 접근 권한이 없으면 CreatMutex는 실패하고 NULL을 반환한다
(만일 핸들에 대한 가용 접근 권한을 제한하고 싶으면 커널 오브젝트 생성 함수 사용시 끝에 EX가 붙은 확장 버전을 사용하면 된다)

만들어져 있는 오브젝트만 여는 Open* 류의 함수도 존재한다
Create* 류의 함수는 커널 오브젝트가 없는 경우 생성하고 
           만약 동일한 어플리케이션이 수행 중이라면 ERROR_ALREADY_EXISTS를 반환한다
Open* 류의 함수는 커널 오브젝트가 없는 경우 실패한다

HANDLE OpenMutex(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
PCTSTR pszName); 


마지막 매개변수 pszName 에 커널 오브젝트 네임을 넣어 주면 된다 



3. 오브젝트 핸들의 복사를 이용하는 방법

특정 프로세스 핸들테이블을 다른 프로세스 핸들테이블로 복사하는 DuplicateHandle  함수를 사용하는 것이다
아래는 A 프로세스의 커널 오브젝트를 B 프로세스에서 사용할수 있게 하는 코드이다.

[A 프로세스]
HANDLE hObjInProcessA = CreateMutex(NULL,FALSE,NULL);
//A 프로세스에서 접근 가능한 뮤텍스 오브젝트를 생성
HANDLE hProcessB = OpenProcess(PROCESS_ALL_ACCESS,FALSE,dwProcessIdB);
//B 프로세스 커널 오브젝트 핸들을 가져옴
HANDLE hObjInProcessB;
DuplicateHandle(GetCurrentProcess(),hObjInProcesssA,hProcessB,&hObInProcessB,0,
FALSE,DUPLICATE_SAME_ACCESS);
//hObjInProcessB 핸들 값을 B 프로세스에 전달하기 위해 프로세스간 통신 메커니즘을 사용한다
CloseHandle(hProcessA);
CloseHandle(hProcessB);