Kernel Object
커널 오브젝트는 커널에 의해 할당된 메모리 블록으로, 커널에 의해서만 접근 가능한 객체이다. 이를 이용하여 시스템에서 사용되는 여러 자원들을 효과적으로 관리할 수 있다.
커널 오브젝트는 파일 오브젝트, Mutex 오브젝트, Process 오브젝트 등 여러 종류의 형태로 존재한다. 각 오브젝트는 보안과 안정성을 위해 커널에 의해서만 접근 가능하며, 이를 생성 및 조작하기 위해서는 Windows에서 제공하는 API를 이용해야 한다.
각 커널 오브젝트는 형태에 따라 서로 다른 정보를 가진다. 그럼에도 불구하고 다음의 두 정보는 커널 오브젝트 마다 공통적으로 사용된다.
1. 사용 카운트
: 해당 커널 오브젝트를 사용 중인 프로세스의 수. 0 이상의 정수값을 갖는다.
- 커널 오브젝트를 최초 생성할 경우 사용 카운트값으로 1을 갖는다.
- 커널 오브젝트를 사용하는 프로세스의 수가 증가할 때마다 사용 카운트값이 1씩 증가한다.
- 커널 오브젝트의 사용을 종료(프로세스의 종료 또는 CloseHandle(hObj) 호출)할 때마다 사용 카운트 값이 1씩 감소한다.
- 커널 오브젝트의 사용 카운트값이 0이 되면 해당 오브젝트가 삭제된다. (해당 커널 오브젝트에 대한 사용이 모두 종료되어야 삭제된다.)
2. 보안 디스크립터(SECURITY_ATTRIBUTES 구조체)
: 커널 오브젝트에 대한 소유자, 접근 권한에 대한 정보
커널 오브젝트와 유저 오브젝트/GDI 오브젝트를 구분하는 팁으로, 오브젝트 생성 API에 보안 특성을 지정하는 매개변수가 존재하는지를 확인하는 방법이 있다고 한다.
Handle & Process Handle Table
Windows API를 이용해 커널 오브젝트를 생성하는 데 성공하면 커널 오브젝트를 조작하는 데 사용 가능한 핸들(handle) 값을 얻을 수 있다. 이 핸들값을 다른 Windows API 매개변수로 전달하여 커널 오브젝트를 사용할 수 있다.
생성된 커널 오브젝트에 대한 핸들값은 각 프로세스마다 고유하다. 각 프로세스는 자신만의 독립된 프로세스 핸들 테이블을 갖기 때문에, 프로세스 내에서 생성된 핸들값은 해당 프로세스 내에서 생성된 모든 스레드에서 사용 가능하지만 다른 프로세스에서는 사용할 수 없다. 다음은 프로세스 핸들 테이블의 구조이다.
커널 오브젝트에 대한 핸들값은 프로세스 핸들 테이블에 매핑된 인덱스값과도 관련이 있는데, 가령 프로세스 A에서 프로세스 핸들 테이블 내 인덱스 2번을 가리키는 핸들값을 프로세스 B에서 동일하게 사용할 경우 프로세스 B의 프로세스 핸들 테이블 내 인덱스 2번에 다른 타입의 커널 오브젝트가 참조되어 있으면 잘못된 오브젝트를 참조하게 된다.
How to share Handles
앞서 설명한 바와 같이 두 프로세스 간 커널 오브젝트를 공유해야 할 경우 단순히 핸들값을 공유하는 방법은 옳지 않다. 그렇기에 이후 내용에서 서로 다른 두 프로세스 간에 커널 오브젝트를 공유하기 위한 방안을 정리하였다.
1. Kernel Object Handle 상속
이 방법은 커널 오브젝트를 공유하고자 하는 두 프로세스가 Parent - Child 관계일 경우 사용 가능하다. 이 방법은 크게 다음과 같은 단계로 진행된다.
- Parent process에서 상속하고자 하는 핸들 설정: 핸들 생성 또는 핸들 속성 변경 시
- Child process 생성 시 child process에게 핸들을 상속할 것인지 유무 설정
위 두 단계를 거치고 나면 Parent process의 process handle table 내 상속된 핸들에 대한 레코드가 새로 생성된 Child process의 process handle table 내 동일한 인덱스로 복사된다.
이러한 메커니즘으로 인해 핸들 상속 후 Parent process에서 대상 커널 오브젝트에 대해 사용 중이던 핸들의 사용을 종료하더라도, Child process의 process handle table 내에 커널 오브젝트에 대한 정보가 남아있으므로 Child process에서 상속된 핸들을 통해 여전히 커널 오브젝트에 접근할 수 있다. 또한 상속을 통해 대상 커널 오브젝트를 사용하는 프로세스의 수가 1 증가하였으므로 대상 커널 오브젝트의 사용 카운트값도 1 증가하게 된다.
2. Kernel Object의 이름 이용
이 방법은 커널 오브젝트 생성 시 고유한 이름을 부여하여, 다른 프로세스에서 커널 오브젝트의 이름을 이용해 대상 오브젝트에 대한 핸들을 얻는 방식이다. 이 방식이 가능한 이유는 커널에 의해 할당된 커널 오브젝트들은 타입에 상관없이 동일한 네임스페이스를 공유하기 때문에 서로 다른 프로세스라 하더라도 공유 중인 네임스페이스 내 정의된 커널 오브젝트의 이름을 이용하여 공유할 수 있다. 따라서 하나의 네임스페이스 내에서 명명되는 커널 오브젝트들은 고유한 이름을 가져야 하며, 다른 타입의 커널 오브젝트라 하더라도 서로 다른 이름을 가져야 한다.
만일 명명하고자 하는 커널 오브젝트의 이름이 이미 네임스페이스 내에서 사용 중인 경우 커널 오브젝트의 생성은 실패할 것이다. 이러한 메커니즘을 악용하면, 공유 중인 네임스페이스를 통해 공격 대상 프로세스에서 사용할 커널 오브젝트의 이름을 수집한 후 미리 해당 이름을 갖는 커널 오브젝트를 생성하여 공격 대상 프로세스의 정상 구동을 방해하는 공격(DoS 공격의 일종)에 취약해진다.
따라서 이를 막고자 Private 네임스페이스를 사용하여, 다른 프로세스에서 사용 중인 이름과 충돌되거나 사용되는 이름이 탈취되는 것으로부터 안전해질 수 있다. 이 때 Boundary descriptor를 함께 사용하여 private 네임스페이스에 대한 접근 권한을 설정하는 것으로 private 네임스페이스 자체에 대한 보안을 강화할 수 있다.
추가로, 터미널 서비스를 사용하는 경우에는 모든 클라이언트 세션으로부터 접근 가능한 Global 네임스페이스가 있고, 각 클라이언트 세션별 고유 네임스페이스(Local)가 존재한다. 기본적으로 터미널 서비스에서 사용되는 커널 오브젝트는 Global 네임스페이스 내 명명되고, 각 터미널 서비스 내 애플리케이션에서 사용되는 커널 오브젝트는 Local 네임스페이스에 명명된다. 그렇기 때문에 서로 다른 클라이언트 세션에서 동일한 애플리케이션을 실행하여도 충돌이 발생하지 않을 수 있다.
3. Kernel Object Handle 복사
이 방법은 source process의 process handle table 내에 기록된 커널 오브젝트에 대한 참조를 target process의 process handle table로 복사하는 것이다. 이를 위해 DuplicateHandle API를 사용하면 간편하게 복사할 수 있다.
BOOL DuplicateHandle(
[in] HANDLE hSourceProcessHandle,
[in] HANDLE hSourceHandle,
[in] HANDLE hTargetProcessHandle,
[out] LPHANDLE lpTargetHandle,
[in] DWORD dwDesiredAccess,
[in] BOOL bInheritHandle,
[in] DWORD dwOptions
);
이 함수를 호출하면 공유하고자 하는 target process에서 사용 가능한 커널 오브젝트의 핸들값을 얻을 수 있다. 만일 source process에서 위 함수를 사용하여 target process를 위한 핸들값을 구했다면 IPC와 같은 프로세스 간 통신 방법으로 target process에 핸들값을 전달하여 커널 오브젝트를 사용하도록 할 수 있다.
- 참고: 제프리 리처의 Windows via C/C++
'OS' 카테고리의 다른 글
[Windows] Session 0 isolation (0) | 2024.01.23 |
---|---|
[Windows] Process 생성 및 종료 과정 (0) | 2023.12.28 |