// 테이블 3개가 존재한다는 말은 2000 구조같은데... xp는 어떨지...
Win32 개발자들은 핸들이 뭔지는 알고 있을 것이다.
그리고 이 핸들이 윈도우핸들(HWND)이 아님을 알고 있을것이다. 이 핸들이란건 특정 동작을 하기 위한 핸들링 용도로 쓰이고 있으며 대표적으로 파일을 다루는 WINAPI인 CreateFile(), ReadFile, WriteFile()함수들을 예로 들어 볼 수 있다.
CreateFile()함수 성공시에 리턴값으로 핸들을 받게 되는데 이 핸들은 나중에 특정 파일을 핸들링 하기 위해 그 정보를 유지 해야 하는데 그 정보는 커널영역에 오브젝트 형태로 존재하게 되며 보통은 커널 오브젝트라고 부른다.
그럼 핸들값이 의미하는 숫자는 무엇일까?
이 핸들값은 커널오브젝트를 찾기 위한 일종의 인덱스 역활을 하는 값이다.
밑에 그림을 보도록 하자.
위 그림은 유저영역에 존재하는 HANDLE을 통해 실제 객체를 나타내는 커널 오브젝트를 찾아가는 그림이다. 그림에선 중간에 HANDLE TALBLE이란게 존재하여 핸들값을 핸들테이블에서 인덱스로 사용하여 커널 오브젝트의 포인터를 찾는다.
자 이제 핸들테이블에 대해 알아보자.
핸들테이블이란?
현 프로세스에서 생성된 핸들들에 대해서 커널 객체를 찾아가기 위한 인터페이스 역할을 하는 배열 형태로 된 테이블 영역이다. 이 핸들 테이블은 프로세스당 하나가 할당되며 프로세스간 독립적으로 핸들 테이블을 관리하게 되는데 우리가 CreateFile()로 생성된 핸들은 CloseHandle()를 사용하지 않고 프로세스를 죽였을때 운영체제는 이 핸들테이블에서 할당된 정보를 보고 모든 핸들을 반환하게 된다.
밑에 그림은 객체의 포인터를 찾기 위한 핸들테이블 구조를 보여준다.
핸들테이블은 세개의 영역이 존재하는데 Top, Middle, entry가 존재함을 그림을 통해
알수가있다.
그럼 위 테이블 구조를 어떤식으로 접근하여 실제 커널 오브젝트를 찾을 것인가? 아까 핸들값이 인덱스 역할을 하는 값이라고 설명하였다. 이 핸들값을 바이트 단위로 3등분 한후 핸들 테이블의 각 레벨의 배열을 인덱스로 사용하고 찾아간다.
밑에 그림은 핸들값이 0x4일때 이걸 세등분 한 후 핸들 테이블을 인덱스 삼아
실제 오브젝트를 찾아가는 그림이다.
핸들값은 상위 6비트와 하위 2비트는 핸들테이블을 인덱싱하는 부분에선 해당되지 않는 비트이며 중간에 있는 총 3개의 8비트가 우리가 커널 오브젝트를 찾기 위한 핸들테이블을 접근하는 인덱스 번호가 되겠다.
Table ptr은 Top레벨의 주소를 가리킨다.여기서부터 핸들값의 인덱스가 적용된다. 우리가 보고자 하는 0x4값은 각각 Top:0, Mid:0, Entry:1값을 가진다. 핸들 테이블에 이 값들을 적용하면 Top[0], Mid[0], Entry[1]이 되며. 처음 Top[0]에는 Mid의 첫번째 배열 주소를 가지키며 Mid[0]은 Entry의 주소를 가리키고 있다. 마지막 Entry는 실제 커널 오브젝트를 가리키는 주소를 가지고 있게된다.
코드로 함 살펴보기로 하자.
lkd> dt nt!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x0c4 ObjectTable : Ptr32 _HANDLE_TABLE
커널에서 프로세스를 표현하는 EPROCESS에 보면 _HANDLE_TABLE ObjectTable가 존재한다.이 필드는 HANDLE_TABLE이란 구조체의 포인터를 가리키며 이 필드가 실제 핸들 테이블의 정보를 가지고 있는 셈이다. 윈도우 XP에서 ObjectTable의 위치는 0x0c4로 되어있다.
자 이제 HANDLE_TABLE의 구조를 살펴 봐야 한다.
lkd> dt nt!_HANDLE_TABLE
+0x000 TableCode : Uint4B
+0x004 QuotaProcess : Ptr32 _EPROCESS
+0x008 UniqueProcessId : Ptr32 Void
+0x00c HandleTableLock : [4] _EX_PUSH_LOCK
+0x01c HandleTableList : _LIST_ENTRY
+0x024 HandleContentionEvent : _EX_PUSH_LOCK
+0x028 DebugInfo : Ptr32 _HANDLE_TRACE_DEBUG_INFO
+0x02c ExtraInfoPages : Int4B
+0x030 FirstFree : Uint4B
+0x034 LastFree : Uint4B
+0x038 NextHandleNeedingPool : Uint4B
+0x03c HandleCount : Int4B
+0x040 Flags : Uint4B
+0x040 StrictFIFO : Pos 0, 1 Bit
대충 보면 몇몇 필드는 변수명으로 그 의미를 파악할 수 있다. 여기서 HandleCount는 현재 프로세스에 할당된 핸들 총갯수 즉 핸들 테이블에 기록된 핸들의 정보수를 나타낸다.
UniqueProcessId는 이 핸들 테이블을 소유한 프로세스의 ID를 뜻하며 실제적인 핸들 테이블을 나타내는 Table 필드가 우리가 알고자 하는 핸들 테이블을 가리키는 필드이다.
lkd> dt nt!_HANDLE_TABLE_ENTRY
+0x000 Object : Ptr32 Void
+0x000 ObAttributes : Uint4B
+0x000 InfoTable : Ptr32 _HANDLE_TABLE_ENTRY_INFO
+0x000 Value : Uint4B
+0x004 GrantedAccess : Uint4B
+0x004 GrantedAccessIndex : Uint2B
+0x006 CreatorBackTraceIndex : Uint2B
+0x004 NextFreeTableEntry : Int4B
HANDLE_TABLE_ENTRY 구조체는 커널 오브젝트를 가리키는 Object필드와 핸들테이블 액세스 관련 필드인 GrantedAccess로 구성되어 있으며 Object가 OBJECT_HEADER를 가리키며 OBJECT_HEADER + OBJECT BODY 형태로 되어있다. 구조는 밑에와 같이 생겼으며 Body로 된 부분은 커널 객체마다 다른 구조로 되어 있다.
lkd> dt nt!_OBJECT_HEADER
+0x000 PointerCount : Int4B
+0x004 HandleCount : Int4B
+0x004 NextToFree : Ptr32 Void
+0x008 Type : Ptr32 _OBJECT_TYPE
+0x00c NameInfoOffset : UChar
+0x00d HandleInfoOffset : UChar
+0x00e QuotaInfoOffset : UChar
+0x00f Flags : UChar
+0x010 ObjectCreateInfo : Ptr32 _OBJECT_CREATE_INFORMATION
+0x010 QuotaBlockCharged : Ptr32 Void
+0x014 SecurityDescriptor : Ptr32 Void
+0x018 Body : _QUAD
이 Body부분은 운영체제에 지시한 특정 명령에 동작을 나타내는 정보를 가지고 있는 실제 커널 오브젝트를 뜻한다.
이 객체는 CreateFile()과 같은 커널에서 수행되어야 될 명령을 오브젝트 형태로 커널에서 관리하기 위해서 만들어진 구조체이며 유저영역에선 핸들을 사용하여 커널 오브젝트에 접근하게 된다.
커널오브젝트는 스스로 제거될 수 있도록 참조 개수를 유지 하게 되는데. HandleCount, PointerCount란 필드를 통해서 관리된다. HandleCount는 우리가 CreateFile()로 생성되었을때 리턴된 핸들의 갯수를 뜻하며 PointerCount는 핸들이 아닌 직접 객체의 포인터를 얻어왔을때 증가하게된다. 커널에선 ReferenceObjectByPointer(), ObDereferenceObject() 사용시에 PointerCount가 증감된다.