본문 바로가기

C, C++/WINDOWS VIA C,C++

[WINDOWS VIA C/C++] 03.커널 오브젝트

https://www.hanbit.co.kr/store/books/look.php?p_code=B2974835990

 

제프리 리처의 Windows via C/C++(복간판)

이 책은 윈도우 XP, 윈도우 비스타, 윈도우 서버 2008까지 내용을 포괄한다. 이미 윈도우 10이 출시된 지 오래지만 윈도우의 기본 구조는 변하지 않아 아직까지도 이 책은 윈도우 시스템 프로그래

www.hanbit.co.kr

해당 책을 읽고 학습 목적으로 간단하게 정리한 글입니다.


Chapter 03: 커널 오브젝트 

개요

이번 챕터에서는 커널 오브젝트, 핸들, 이를 다루는 API에 대해서 알아본다.


커널 오브젝트란?

  • 윈도우 개발자는 파일 오브젝트, 뮤텍스 오브젝트, 파이프 오브젝트, 스레드 오브젝트 등 여러 커널 오브젝트를 생성하고 다룬다.
  • 커널 오브젝트의 security descriptor, usage count 등은 공통적으로 존재하지만, 대부분 값은 커널 오브젝트 마다 상이하게 존재한다.
  • 커널 오브젝트는 이름 그대로 커널 소유이기 때문에 커널에 의해서만 접근할 수 있고, 메모리를 직접 접근해 수정하는 행위는 불가하다.
  • 커널 오브젝트에 해당하는 핸들 값은 프로세스마다 독립적이다. 따라서 핸들 값을 다른 프로세스에게 전달하는 행위는 정의되지 않은 행동이다.

Usage Count

커널 오브젝트는 커널의 소유이므로 프로세스가 커널을 통해 커널 오브젝트를 생성한 후 해당 프로세스가 종료되어도 반드시 커널 오브젝트가 프로세스와 함께 사라지지 않는다.

 

커널 오브젝트는 내부적으로 Usage Count라는 자신을 가리키는 프로세스의 수를 기억한다.  

이 값이 0이 되어야 커널 오브젝트는 비로소 삭제된다.

 

따라서 내가 생성(요청)했더라도, 다른 프로세스도 가리키고 있다면 내가 종료되어도 커널 오브젝트는 살아있다.


Security (Descriptor)

커널 오브젝트는 security descriptor를 통해 보호가 가능하다.

이는 커널 오브젝트의 소유, 접근, 제한 등 정보가 있다.

private namespace 등의 기능과 함께 연계된다.

 

https://learn.microsoft.com/en-us/previous-versions/windows/desktop/legacy/aa379560(v=vs.85)

 

SECURITY_ATTRIBUTES structure (Windows)

Ask Learn Ask Learn Focus mode Language Table of contents Read in English Add Add to plan Share via Facebook x.com LinkedIn Email Print Note Access to this page requires authorization. You can try signing in or changing directories. Access to this page req

learn.microsoft.com

 

 

대부분 현재 프로세스의 보안 토큰 기반인 기본 security descriptor를 사용하고 이 경우 security descriptor를 필요로 하는 함수의 인자에는 NULL을 넘겨주면 된다.

 

필요한 경우 security attributes구조체를 직접 초기화 해 인자로 넘겨줄 수 있다.

 

보안 접근 플래그를 간과해서는 안되며, UAC(User Access Control) 등 제약사항에 대한 대응을 위해 플래그에 대한 고려가 필요하다.

 

 어떤 오브젝트가 커널 오브젝트인지 궁금하다면, 해당 오브젝트를 생성하는 함수에 Security Attributes인자가 포함되는지를 확인하면 된다. 대표적으로 GDI 오브젝트는 커널 오브젝트가 아니다.


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

생성

OS는 프로세스 초기화 이후 커널 오브젝트 핸들 테이블을 할당한다.

 

프로세스 초기에는 커널 오브젝트가 생성되지 않아 테이블은 비어있지만

스레드가 커널 오브젝트 생성 함수를 호출한다면, 커널은 커널 오브젝트 생성 뒤 프로세스의 핸들 테이블에 추가한다.

 

커널 오브젝트를 생성하는 함수들은 HANDLE을 반환하며 프로세스마다 고유하기 때문에 내부 thread간에는 공통적으로 사용 가능하다.

 

커널 오브젝트 생성 함수 중 어떤 함수는 실패 시 return값이 NULL(0)이지만, 어떤 함수는 -1(INVALID_HANDLE_VALUE) 이기 때문에 주의가 필요하다.

 

삭제

CloseHandle을 사용하면 커널에게 해당 오브젝트를 사용하지 않음을 알린다.

이를 통해 커널은 해당하는 오브젝트의 UsageCount를 1 감소시키고  값이 0이라면 오브젝트를 파괴한다.

 

https://learn.microsoft.com/ko-kr/windows/win32/api/handleapi/nf-handleapi-closehandle

 

CloseHandle 함수(handleapi.h) - Win32 apps

열린 개체 핸들을 닫습니다.

learn.microsoft.com

애플리케이션이 종료된다면  closehandle을 하지 않았더라도 프로세스가 종료되며 자연스럽게 핸들은 반환되지만,

실행 중에는 핸들이 남기 때문에 서버 같은 지속적인 프로그램에서 누수가 발생하는 경우 핸들 값이 계속해서 증가할 수 있다.

작업관리자 자세히에서는 프로세스가 사용하는 핸들의 개수를 확인할 수 있다.

이를 통해 누수를 확인해볼 수 있다.


프로세스 간 커널 오브젝트 공유

단일 머신에서의 두 프로세스 간, 서로 다른 머신 사이, 서로 다른 프로세스의 스레드 간 동기화

에는 커널 오브젝트 공유가 필요할 수 있다.

 

프로세스의 보안을 위해 핸들을 프로세스마다 고유한 값을 가지게 설계했기 때문에 핸들 공유는 간단하지 않다.


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

프로세스가 parent-child 관계를 가진다면 핸들을 상속해 공유할 수 있다.

커널오브젝트 생성 시 넘겨주는 SecurityAttributes에 있는 bInheritHandle 인자값을 TRUE로 한다면 이를 통해 생성되는 커널 오브젝트의 상속 가능한 핸들을 반환한다. (핸들 테이블에 상속 가능 플래그가 켜짐)

 

이후 CreateProcess의 인자 bInheritHandles를 TRUE로 설정한 뒤 프로세스를 생성하면

부모의 핸들 중 상속 가능한 핸들이 자식 프로세스의 핸들 테이블에 복사된다.

 

같은 커널 오브젝트를 가리키는 프로세스가 늘어났으므로 usage count가 증가한다.

또한 child process는 상속된 핸들이 어떤 커널 오브젝트에 대한 핸들인지 알 수 없다.

 

따라서 알려야 한다면 CreateProcess시 명령행 인자로 넘기거나, 환경 변수를 추가하는 등의 행동이 필요하다.

 

SetHandleInformation 함수를 통해 핸들의 플래그 값을 변경할 수 있다.

https://learn.microsoft.com/ko-kr/windows/win32/api/handleapi/nf-handleapi-sethandleinformation

 

SetHandleInformation 함수(handleapi.h) - Win32 apps

개체 핸들의 특정 속성을 설정합니다.

learn.microsoft.com

대응하는 GetHandleInformation 함수도 있다.


2. Named Object를 사용하는 방법

커널 오브젝트의 일부는 이름을 가진채 생성될 수 있다.

가능한 함수는 이름을 지정할 수 있는 인자가 존재한다. (NULL이라면 이름 없이 생성)

 

커널은 이름이 중복되지 않게 관리하며, 이름이 중복된다면 NULL이 반환된다.

타입에 따라 분리해 관리하는 것이 아닌 전체적으로 관리하므로 주의가 필요하다.

 

서로 다른 타입의 커널 오브젝트를 생성하며 이름이 중복된다면 NULL이 반환되며 errorCode에서 확인할 수 있다.

 

만약 같은 타입의 커널 오브젝트를 생성하려고 한다면 이미 만들어져 있는 커널 오브젝트를 가리키는 새로운 HANDLE이 반환된다.

 

이러한 경우 Create가 아닌 Open계열 함수를 사용할 수도 있다. 이 경우에는 커널 오브젝트가 존재하지 않는다면 실패한다.


터미널 네임 스페이스

터미널 서비스의 경우에는 머신이 다수의 네임스페이스를 갖는다.

모든 세션이 공유하는 전역 네임스페이스, 세션마다 가지는 고유 네임스페이스가 존재한다.

 

따라서 동일 애플리케이션이 다른 세션에서 실행된다면 서로 영향을 미치지 않는다.


Private namespace

윈도우 비스타 이전에는 권한 낮은 프로세스더라도 동일 이름으로 오브젝트를 생성하는 것이 가능했다.

이를 이용해 프로그램에서 사용되는 이름을 먼저 선점해 오브젝트를 만들어 프로그램을 먹통으로 만드는 DOS공격이 가능했다.

 

따라서 공유가 필요없다면 이름이 없는 오브젝트를 만드는 것이 안전하다.

 

Named Kernel Object가 이름이 충돌되지 않으면서 안전하기를 원하면 Private namespace를 사용할 수 있다.

 

서버 프로세스는 namespace이름을 보호하기 위해 boundary descriptor를 만들고 이를 통해 private namespace 생성이 필요하다.

 

boundary descriptor는 사용자 그룹을 나타내는 SID값을 가진다.

윈도우는 해당 값을 통해 SID에 해당하는 권한을 가진 애플리케이션만 boundary 내에 namespace를 생성할 수 있도록 한다.

 

악의적인 애플리케이션은 유저 그룹에 속하지 않아 boundary내의 private namespace를 생성하거나 열 수 없다.

 

private namespace는 커널 오브젝트를 담는 디렉터리와 비슷하다.

security descriptor를 가지며 CreatePrivateNamespace 호출 시 사용된다.

다만 상위 디렉터리가 없으며 이름도 없다.


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

DuplicateHandle 함수를 사용해 HANDLE을 복사할 수 있다.

https://learn.microsoft.com/ko-kr/windows/win32/api/handleapi/nf-handleapi-duplicatehandle

 

DuplicateHandle 함수(handleapi.h) - Win32 apps

개체 핸들을 복제합니다.

learn.microsoft.com

프로세스의 자체의 핸들과 커널 오브젝트 핸들을 활용한다.

 

함수는 동일한 커널 오브젝트를 가리키는 새로운 핸들을 반환한다.

그리고 옵션의 값을 통해서 새로운 핸들의 속성 값을 지정할 수 있다.

 

DuplicateHandle 사용 시 phTargetHandle인자는 새롭게 생성된 핸들 값이 들어간다.

다만 hTargetProcessHandle이 다른 프로세스인 경우 다른 프로세스에 사용되는 핸들 값이 들어가기 때문에 주의가 필요하다.

 

또한 복제를 받은 프로세스는 새로운 핸들이 무엇인지 알 수 없기 때문에 IPC 등으로 통보가 필요하다.

 

추가적으로 GetCurrentProcess는 pseudo handle을 반환한다는 것을 염두하자.