핸들 테이블을 이용해 뭔가 해야 될 작업이 있어 다시 보던 중 잘못 알고있던게 있어 추가로 남긴다.
이전 글에 PspCidTable을 이용해서 프로세스 목록을 가져오는 관련 글을 포스팅 했었다.
PspCidTable을 참조하여 TableCode로 _HANDLE_TABLE_ENTRY 주소를 가져오고, Object 필드에 하위 1 락비트를 제거하여 Object 주소를 얻는 방식으로 동작을 했었다.
이번에는 임의의 PID로 EPROCESS를 구하고 ObjectTable(_HANDLE_TABLE)을 참조하여 위와 같은 방식으로 Object 필드의 하위 1 락비트를 제거하여 사용하려고 하였다.
근데 생각한거와는 달리 너무나도 엉뚱한 값들이 계속 튀어나와 오랜만에 해서 뭔가를 빼먹었나 싶어 계속 삽질을 하다보니, 하위 1비트를 제거했을때 나오는 주소가 Object 주소가 아닌 _OBJECT_HEADER의 주소였다.
PspCidTable(핸들 테이블)과 일반 프로세스의 핸들 테이블은 구조가 조금 다른 것으로 보인다.
뭔가 비밀이 있을 것 같은데 삽질을 오래했더니 시간이 없어 나중에 다시 확인해봐야겠다. ㅠㅠ
아래는 유명한 듀얼님의 Handle의 관한 글입니다.
원본 링크가 깨져 본의 아니게 전체 글을 가져왔네요.
HANDLE에 대한 탐구 - 첫번째 시간.
Intro
API로 프로그래밍을 하면서 수없이 만나게 되는 HANDLE,
하지만 대부분의 프로그래머들이 HANDLE에 대하여 제대로 알고 있지 못하다.
이 글은 HANDLE에 대해 알고 싶어 하는 모든분들에게 바칩니다.
글 내용 중 잘못되었거나, 빠진부분은 dual5651@hotmail.com으로
알려주시면 감사하겠습니다.
* 이글은 Windows XP를 대상으로 작성되어졌습니다.
HANDLE값은 무엇을 의미하는가?
HANDLE은 다음과 같이 define되어 있다.
#ifdef STRICT
typedef void *HANDLE;
#define DECLARE_HANDLE(name) struct name##__ { int unused; }; typedef struct name##__ *name
#else
typedef PVOID HANDLE;
#define DECLARE_HANDLE(name) typedef HANDLE name
#endif
HANDLE값은 무엇을 의미하는가?
Windows에서는 Process는 각각 HANDLE_TABLE이라는 구조체 형태를 가진,
ObjectTable에 의해 HANDLE을 관리한다. 그리고 Handle은 HandleTable에서의
Entry를 가르키는 색인값으로 사용되어 진다.
그럼으로 서로 다른 프로세스에서는 같은 HANDLE값이라도 다른 객체를 지시한다.
또 Kernel에서 생성된 HANDLE은 ObpKernelHandleTable이라는 특수한 HANDLE_TABLE
에 의해 관리되어 진다.
그럼 지금 부터 직접 접근하여 보자.
각 프로세스의 HANDLE_TABLE의 주소는 각 프로세스의 EPROCESS구조체에
저장되어 있다.
lkd> dt _EPROCESS
+0x000 Pcb : _KPROCESS
+0x06c ProcessLock : _EX_PUSH_LOCK
+0x070 CreateTime : _LARGE_INTEGER
+0x078 ExitTime : _LARGE_INTEGER
+0x080 RundownProtect : _EX_RUNDOWN_REF
+0x084 UniqueProcessId : Ptr32 Void
+0x088 ActiveProcessLinks : _LIST_ENTRY
+0x090 QuotaUsage : [3] Uint4B
+0x09c QuotaPeak : [3] Uint4B
+0x0a8 CommitCharge : Uint4B
+0x0ac PeakVirtualSize : Uint4B
+0x0b0 VirtualSize : Uint4B
+0x0b4 SessionProcessLinks : _LIST_ENTRY
+0x0bc DebugPort : Ptr32 Void
+0x0c0 ExceptionPort : Ptr32 Void
+0x0c4 ObjectTable : Ptr32 _HANDLE_TABLE //HANDLE_TABLE포인터
+0x0c8 Token : _EX_FAST_REF
.......................
} EPROCESS, *PEPROCESS;
이 포인터 변수는 EPROCESS의 시작위치로 부터 0xC4만큼 떨어져 있다.
이 사실을 기반으로 특정한 Process의 ObjectTable주소를 알아내는 함수를 다음과
같이 작성할 수 있다.
PHANDLE_TABLE GetObjectTable(DWORD Pid)
{
NTSTATUS status;
PEPROCESS Process;
PHANDLE_TABLE ObjectTable;
status = PsLookupProcessByProcessId(Pid,&Process);
if(NT_SUCCESS(status))
{
ObjectTable = (PHANDLE_TABLE)(*(ULONG*)((ULONG)Process + 0xC4));
DbgPrint(\"Pid : %X PEPROCESS : %X ObjectTable :
%X\\n\",Pid,Process,ObjectTable);
ObDereferenceObject(Process);
return ObjectTable;
}
else
{
DbgPrint(\"PsLookup error\\n\");
}
ObDereferenceObject(Process);
return 0;
}
이 HANDLE_TABLE은 다음과 같은 형태를 갖는다.
typedef struct _HANDLE_TABLE
{
ULONG TableCode; //PHANDLE_TABLE_ENTRY**
PEPROCESS QuotaProcess;
PVOID UniqueProcessId;
EX_PUSH_LOCK HandleTableLock [4];
LIST_ENTRY HandleTableList;
EX_PUSH_LOCK HandleContentionEvent;
PHANDLE_TRACE_DEBUG_INFO DebugInfo;
LONG ExtraInfoPages;
ULONG FirstFree;
ULONG LastFree;
ULONG NextHandleNeedingPool;
LONG HandleCount;
ULONG Flags;
} HANDLE_TABLE, *PHANDLE_TABLE;
TableCode :
HADLE_TABLE_ENTRY의 주소를 갖는다.
이 ENTRY에 해당 Process들이 사용하고 있는 Object들이 있다.
QuotaProcess :
System의 경우 0을 가지며,
그외의 Process들은 자신의 PEPROCESS값을 갖는다.
UniqueProcessId :
이 HANDLE_TABLE의 소유주의 ProcessId값이다.
HandleTableLock :
EX_PUSH_LOCK 4개가 연결된 배열로 되어 있다.
EX_PUSH_LOCK은 다음과 같은 형태를 갖는다.
typedef struct _EX_PUSH_LOCK
{
union
{
struct
{
ULONG Waiting:1;
ULONG Exclusive:1;
ULONG Shared:30;
};
ULONG Value;
PVOID Ptr;
};
} EX_PUSH_LOCK, *PEX_PUSH_LOCK;
HandleTableList :
다음 HandleTable로 연결된 ListEntry이다.
PHANDLE_TRACE_DEBUG_INFO :
해당 HANDLE_TABLE의 HANDLE Trace Debug정보를 갖는 구조체의 포인터이다.
Windows가 Debug모드로 시작되었을때만 사용하는 듯 하다.
HANDLE_TRACE_DEBUG_INFO는 다음과 같은 형태를 갖는다.
typedef struct _HANDLE_TRACE_DB_ENTRY
//HANDLE_TRACE_DEBUG_INFO가 사용하는 구조체
{
CLIENT_ID ClientId;
HANDLE Handle;
ULONG Type;
PVOID StackTrace[16];
} HANDLE_TRACE_DB_ENTRY; *PHANDLE_TRACE_DB_ENTRY;
typedef struct _HANDLE_TRACE_DEBUG_INFO
{
ULONG CurrentStackIndex;
HANDLE_TRACE_DB_ENTRY TraceDb[4096];
} HANDLE_TRACE_DEBUG_INFO, *PHANDLE_TRACE_DEBUG_INFO;
ExtraInfoPages :
부가적인 정보에 대한 Page의 Offset값을 갖는것으로 보이나,
주로 0으로 Set되어 있다.
FirstFree :
HandleTable에서 가장 처음으로 사용가능한(비어있는) Entry의 주소이며,
이 값이 바로 다음으로 생성될 HANDLE이다.
LastFree :
NextHandleNeedingPool :
다음번 핸들이 필요로 하는 Pool의 Size값이다.
HandleCount :
이 HandleTable에서 사용하고 있는 Handle의 총갯수이다.
Flags :
이 HandleTable의 Flag값이다.
TableCode가 Entry의 주소를 가르킨다고 하였는데,
HANDLE_TABLE_ENTRY는 다음과 같은 형태를 갖는다.
typedef struct _HANDLE_TABLE_ENTRY_INFO {
ULONG AuditMask;
} HANDLE_TABLE_ENTRY_INFO, *PHANDLE_TABLE_ENTRY_INFO;
typedef struct _HANDLE_TABLE_ENTRY {
union {
PVOID Object; //이 Handle이 가르키는 Object
ULONG ObAttributes; //Handle의 속성
PHANDLE_TABLE_ENTRY_INFO InfoTable;
ULONG Value;
} u1;
union {
ULONG GrantedAccess; //Handle이 Object에 접근하는 접근권한
USHORT GrantedAccessIndex; //Access Index
LONG NextFreeTableEntry; //다음번 사용가능한 Table의 Entry
} u2;
USHORT CreatorBackTraceIndex;
} HANDLE_TABLE_ENTRY, *PHANDLE_TABLE_ENTRY;
System의 HANDLE_TABLE을 WinDbg를 이용하여 살펴 보면서,
직접확인 하여 보자.
위에서 작성했던 GetObjectTable()함수를 이용하여 System Process의
ObjectTable을 구했더니 0xE1001D28 이었다.
이 주소를 기반으로 다음과 같이 WinDbg를 이용하여 표시해 보았다.
lkd> dt _HANDLE_TABLE 0xE1001D28
+0x000 TableCode : 0xe1002000
+0x004 QuotaProcess : (null)
+0x008 UniqueProcessId : 0x00000004
+0x00c HandleTableLock : [4] _EX_PUSH_LOCK
+0x01c HandleTableList : _LIST_ENTRY [ 0xe13f4144 - 0x80563ec8 ]
+0x024 HandleContentionEvent : _EX_PUSH_LOCK
+0x028 DebugInfo : (null)
+0x02c ExtraInfoPages : 0
+0x030 FirstFree : 0x698
+0x034 LastFree : 0
+0x038 NextHandleNeedingPool : 0x800
+0x03c HandleCount : 255
+0x040 Flags : 0
+0x040 StrictFIFO : 0y0
현재 이 ObjectTable의 TableCode값은 0xe1002000임을 알 수 있다.
0xe1002000의 메모리를 표시하여 보자.
e1002000 00000000 fffffffe 817bc9e9 001f0fff 817bc329 00000000 e1288de9 000f003f
e1002020 e1007149 00000000 e1291641 0002001f e1291571 00020019 e12870f1 00020019
e1002040 e12915d9 00020019 e12929f9 00020019 e1292991 00020019 e12913d1 00020019
e1002060 e12928f9 00020019 e1291339 00020019 e1293341 0002001f e1292651 00020019
e1002080 817b6209 001f0003 00000000 00000050 00000000 000000b4 00000000 00000058
e10020a0 00000000 00000054 00000000 0000005c 00000000 00000048 00000000 0000004c
e10020c0 00000000 00000044 00000000 00000060 00000000 00000064 00000000 00000068
e10020e0 00000000 0000006c 00000000 00000070 00000000 00000074 00000000 00000078
e1002100 00000000 0000007c 815b1331 02000003 00000000 00000080 817cc981 00000000
e1002120 8179fc39 001f03ff 00000000 000000bc 00000000 000000a4 00000000 00000094
e1002140 817da6c9 001f0003 00000000 0000009c e1302de9 000f000f 00000000 00000098
e1002160 00000000 000000ac 00000000 000000b0 e12fd2f9 000f000f 00000000 000000c0
e1002180 00000000 000000c4 00000000 000000c8 00000000 000000cc 00000000 000000d0
e10021a0 00000000 000000d4 00000000 000000d8 00000000 000000dc 00000000 000000e0
e10021c0 00000000 000000e4 00000000 000000e8 00000000 000000ec 00000000 000000f0
e10021e0 00000000 000000f4 00000000 000000f8 00000000 000000fc 00000000 00000100
e1002200 00000000 00000104 00000000 00000108 00000000 0000010c 00000000 00000110
e1002220 00000000 00000114 00000000 00000118 00000000 0000011c 00000000 00000120
e1002240 00000000 00000124 00000000 00000128 00000000 0000012c 00000000 00000130
e1002260 00000000 00000134 00000000 00000138 00000000 0000013c 00000000 0000014c
e1002280 00000000 00000288 00000000 00000140 00000000 00000144 00000000 00000148
e10022a0 00000000 00000088 00000000 00000150 00000000 00000154 00000000 00000158
e10022c0 00000000 0000015c 00000000 00000160 00000000 00000164 00000000 00000168
e10022e0 00000000 0000016c e1407499 00020019 815632b9 001f01ff 8157e7c1 001f03ff
e1002300 00000000 00000170 00000000 000006f8 e140a5e1 00020019 00000000 00000194
e1002320 81563f79 0012019f 00000000 00000180 00000000 0000018c 00000000 00000198
하나의 Object에 대하여 ObjectHeader와 GrantedAccess의 쌍으로 이루어져
있음을 볼 수 있다.
재밌는 것은 이 HandleTable의 첫번쨰 Entry는 ObjectHeader의 Pointer값으로 0을,
GrantedAccess값으로 0xfffffffe을 가짐으로 써, HandleTable의 시작지점임을
나타낸다. 이것은 보는 관점에 따라 여러가지의 의미가 있는데, HANDLE을 가지고 보자면,
0번 Handle은 존재할 수 없고, 4번 Handle이 처음이라는 것을 의미한다.
각 Handle의 Entry가 무언인지는 어떻게 알 수 있을까?
필자는 다음과 같은 식을 이용하여 계산한다.
EntryAddress = ObjectTable->TableCode + (Handle / 4) * 8
예를 들어, System Process의 0x350이라는 Handle에 대한 Entry의 주소는 다음과
같을 것이다.
EntryAddress = 0xe1002000 + (0x350/4) * 8
= 0xe10026A0
* Handle값은 위의 식에서 알 수 있다 싶이 늘 4의 배수이다.
이 Entry에 대한 정보를 WinDbg로 표시하여 보면,
lkd> dt _HANDLE_TABLE_ENTRY 0xe10026A0
+0x000 Object : 0xe138cd19
+0x000 ObAttributes : 0xe138cd19
+0x000 InfoTable : 0xe138cd19 _HANDLE_TABLE_ENTRY_INFO
+0x000 Value : 0xe138cd19
+0x004 GrantedAccess : 0x20019
+0x004 GrantedAccessIndex : 0x19
+0x006 CreatorBackTraceIndex : 2
+0x004 NextFreeTableEntry : 131097
다음과 같이 올바른 Entry를 구했음을 알 수 있다.
(이건 어떻게 글을 읽는 독자에게 맞는지 보여줄 방법이 없다.
그냥 믿으세요 :p )
여기서 Object값은 이 Handle에 의해 참조되는 Object이다.
각 Object는 다음과 같은 형태를 갖는다.
typedef struct _OBJECT_TYPE_INFO {
UNICODE_STRING ObjectTypeName; //Object의 Type이름의 포인터
UCHAR Unknown[0x58];
WCHAR ObjectTypeNameBuffer[1];
} OBJECT_TYPE_INFO, *POBJECT_TYPE_INFO;
typedef struct _OBJECT_TYPE_INITIALIZER
{
USHORT Length;
UCHAR UseDefaultObject;
UCHAR CaseInsensitive;
ULONG InvalidAttributes;
GENERIC_MAPPING GenericMapping;
ULONG ValidAccessMask;
UCHAR SecurityRequired;
UCHAR MaintainHandleCount;
UCHAR MaintainTypeList;
POOL_TYPE PoolType;
ULONG DefaultPagedPoolCharge;
ULONG DefaultNonPagedPoolCharge;
PVOID DumpProcedure;
PVOID OpenProcedure;
PVOID CloseProcedure;
PVOID DeleteProcedure;
PVOID ParseProcedure;
PVOID SecurityProcedure;
PVOID QueryNameProcedure;
PVOID OkayToCloseProcedure;
} OBJECT_TYPE_INITIALIZER, *POBJECT_TYPE_INITIALIZER;
typedef struct _OBJECT_TYPE {
ERESOURCE Mutex;
LIST_ENTRY TypeList;
UNICODE_STRING Name;
PVOID DefaultObject;
ULONG Index;
ULONG TotalNumberOfObjects;
ULONG TotalNumberOfHandles;
ULONG HighWaterNumberOfObjects;
ULONG HighWaterNumberOfHandles;
OBJECT_TYPE_INITIALIZER TypeInfo;
ULONG Key;
ERESOURCE ObjectLocks[4];
} OBJECT_TYPE, *POBJECT_TYPE;
typedef struct _OBJECT_HEADER
{
LONG PointerCount; //이 Object를 가르키는 Pointer의 수
union
{
LONG HandleCount; //이 Object를 대상으로 하는 Handle의 수
PVOID NextToFree;
};
POBJECT_TYPE Type; //Object의 Type
UCHAR NameInfoOffset;
UCHAR HandleInfoOffset;
UCHAR QuotaInfoOffset;
UCHAR Flags;
union
{
POBJECT_CREATE_INFORMATION ObjectCreateInfo;
PVOID QuotaBlockCharged;
};
PSECURITY_DESCRIPTOR SecurityDescriptor;
QUAD Body;
} OBJECT_HEADER, *POBJECT_HEADER,**PPOBJECT_HEADER;
PointerCount와 HandleCount로 이 Object가 몇번이나 참조되었는지,
핸들에 의해 사용되고 있는지를 알 수 있는데,
참조한 Object의 경우 ObDereferenceObject()함수를 사용하여,
반환을 하게 되는데, 이떄 ObDereferenceObject()함수는 PointerCount만 1감소시킨다.
얻은 Handle을 반환하고자 할 경우 ZwClose()함수를 사용하는데,
이때 ZwClose()함수는 PointerCount와 HandleCount을 각각 1씩 감소 시킨다.
이 HandleCount와 PointerCount가 0이 되었을떄 객체에 대한 완전한 반환이 이루어진다.
0x350이라는 Handle값의 ObjectHeader값으로 0xE138CD19라고 표시되는데,
실제 ObjectHeader의 주소는 이 값에서 1을 뺀값이 올바르다.
그럼으로 ObjectHeader의 주소는 0xE138CD18이다.
이 ObjectHeader에 대한 정보를 WinDbg로 표시하여 보면 다음과 같다.
lkd> dt _OBJECT_HEADER 0xE138CD18
+0x000 PointerCount : 1
+0x004 HandleCount : 1
+0x004 NextToFree : 0x00000001
+0x008 Type : 0x817b3bf8 _OBJECT_TYPE
+0x00c NameInfoOffset : 0 \'\'
+0x00d HandleInfoOffset : 0 \'\'
+0x00e QuotaInfoOffset : 0 \'\'
+0x00f Flags : 0x2 \'\'
+0x010 ObjectCreateInfo : 0x00000001 _OBJECT_CREATE_INFORMATION
+0x010 QuotaBlockCharged : 0x00000001
+0x014 SecurityDescriptor : (null)
+0x018 Body : _QUAD
lkd> dt _OBJECT_TYPE 0x817b3bf8
+0x000 Mutex : _ERESOURCE
+0x038 TypeList : _LIST_ENTRY [ 0x817b3c30 - 0x817b3c30 ]
+0x040 Name : _UNICODE_STRING \"Key\"
+0x048 DefaultObject : 0x80562240
+0x04c Index : 0x14
+0x050 TotalNumberOfObjects : 0x29b
+0x054 TotalNumberOfHandles : 0x299
+0x058 HighWaterNumberOfObjects : 0x2cd
+0x05c HighWaterNumberOfHandles : 0x2cb
+0x060 TypeInfo : _OBJECT_TYPE_INITIALIZER
+0x0ac Key : 0x2079654b
+0x0b0 ObjectLocks : [4] _ERESOURCE
'Kernel mode' 카테고리의 다른 글
windbg - registers are not yet known (0) | 2014.05.12 |
---|---|
[펌] 커널에서 프로세스 풀패스 얻는 법 (0) | 2014.03.06 |
[링크] Windows NT드라이버 개발자들을 위한 정보 - 피해야 할 사항들 (0) | 2014.01.21 |
PspCidTable, ObTypeIndexTable, _HANDLE_TABLE_ENTRY -> ObjectPointerBits (0) | 2013.11.26 |
ConfigFlags (0) | 2013.11.15 |