Kernel mode2013. 11. 26. 16:49

숨겨진 프로세스 탐지 방법을 찾던중 PspCidTable이 가지고 있는 핸들 정보를 이용하여 검색하는 방법을 구현하게 되었다.

인터넷에 검색해보면 몇몇 코드가 나오는데 대부분 XP에서 테스트가 된거라 손볼데가 많았다.


1. PspCidTable 주소 찾기

각 OS마다 PspCidTable 주소를 찾기 위해서 PsLookupProcessByProcessId 함수 내부를 패턴으로 검색해서 찾아야한다. Windows 8부터는 함수 내부에서 PspReferenceCidTableEntry 함수를 call하게 되고, 이곳에 PspCidTable 주소가 있다. 8 이전 OS에서는 PsLookupProcessByProcessId 함수 내부에서 바로 찾을 수 있다.

 

PsLookupProcessByProcessId() / PspReferenceCidTableEntry -> PspCidTable



2. Object Type과 PsProcessType 비교

_HANDLE_TABLE_ENTRY의 Object 필드가 올바른 오브젝트 주소인지 확인하기 위해 _OBJECT_HEADER의 Type이 PsProcessType이 맞는지 검증을 해야하는데, 전에 글에도 썼듯이 Windows 7부터는 Type(_OBJECT_HEADER)필드가 없고 TypeIndex 값만 존재한다.

ObTypeIndexTable 주소를 찾아 TypeIndex와 계산을 하면 _OBJECT_TYPE 주소가 나오는데, ObTypeIndexTable이 export 되지 않기 때문에 ObGetObjectType() 함수 내부에서 모두 패턴으로 찾아야 한다.

 

 ObGetObjectType() -> ObTypeIndexTable


VISTA이하는 해당 함수가 없으며, _OBJECT_HEADER의 Type필드 값이 바로 Object Type 주소이다.

7이상부터는 ObTypeIndexTable 주소를 미리 구한 뒤에, 아래와 같이 계산하여 PsProcessType 주소와 비교한다.


 Object Type Address = ObTypeIndexTable + (TypeIndex * Porinter Size)


※ 추가

위 계산 과정을 수행하는 ObGetObjectType()이라는 함수가 있으며, 인자로 Object 주소를 넣을 경우 Object Type 주소를 리턴한다.

_OBJECT_HEADER의 TypeIndex를 찾아 ObTypeIndexTable과 연산한 값을 리턴한다.


 

 

 ObGetObjectType 내부 코드


ex)



3. ObjectPointerBits로 주소 재구성

Windows 8 이상부터는 _HANDLE_TABLE_ENTRY 내에 Object 포인터가 바로 들어있지 않다.

8 / 8.1 x64 OS에서 _HANDLE_TABLE_ENTRY를 보면 아래와 같이 Unlocked, RefCnt, Attributes, ObjectPointerBits라는 4개의 필드가 8byte를 bit단위로 나누어 쓰고 있다.

(x86에서는 RefCnt 필드가 제일 마지막에 있지만 x64에서는 위로 올라와있다.)

 

Windows 7 _HANDLE_TABLE_ENTRY

 

Windows 8 _HANDLE_TABLE_ENTRY

 

 Windows 8.1 _HANDLE_TABLE_ENTRY


Windows 8 이전 OS에서는 Object 필드에 최하위 1bit인 Lock bit만 제거해주면 바로 Object 주소를 얻을 수 있었지만, 8 이상부터는 Object의 주소를 구하기 위해 4개의 필드를 이용하여 주소를 재구성 해주어야 한다.

주소를 재구성하는 방법은 한 중국 사이트에서 아래와 같은 공식(?)으로 재구성하는 것을 볼 수 있었다.


Object Address = ((ObjectPointerBits >> 19) & (~0xF) | 0xFFFFF00000000000)


어찌어찌 삽질을 해서 Windows 8 / 8.1 x64에서도 동작이 되도록 수정은 했지만 잘 이해가 가지 않는 부분이 있다.


3-1. SHR 19bit

Windows 8 x64 기준으로 언뜻보면 Unlocked, RefCnt 필드를 제거하고 Attributes, ObjectPointerBits의 값만 사용하기 위한 것으로 보이지만, 정확하게는 20bit를 Shift해야 RefCnt까지 제거된다.

물론 >> 20을 하면 주소 계산이 제대로 안된다. (Windows 8.1도 역시 16bit만..)


3-2. AND (~0xF)

Shift 이후에 ~0xF로 마지막 4bit를 날려버리는데, RefCnt의 1bit와 Attributes의 3bit를 날리는 것 같다.

어떻게 이런 계산 방식이 나오는건지 당최..


3-3. OR 0xFFFFF000`00000000

마지막에 0xFFFFF00000000000로 OR 연산을 해주는데, OS마다 상위 8byte가 제대로 나오지 않는 경우가 있다.


위의 의문점들의 답은 PspReferenceCidTableEntry() 함수 내부를 리버싱해보면 찾을 수 있다.

아래는 해당 함수 내부에서 주소를 재구성하는 코드다.

[Windows 8 x86]

nt!PspReferenceCidTableEntry:

...

814796c1 83e7f8          and edi, 0FFFFFFF8h

...


[Windows 8 x64]

nt!PspReferenceCidTableEntry

...

fffff802`a088017d 48c1ff13        sar     rdi,13h

fffff802`a0880181 4883e7f0        and     rdi,0FFFFFFFFFFFFFFF0h

...

[Windows 8.1 x86]

nt!PspReferenceCidTableEntry:

...

8131022c 83e3f8          and edi, 0FFFFFFF8h

...


[Windows 8.1 x64]

nt!PspReferenceCidTableEntry

...

fffff802`57e487cc 48c1ff10        sar     rdi,10h

fffff802`57e487d0 4883e7f0        and     rdi,0FFFFFFFFFFFFFFF0h

...


x64 코드에서는 SAR(부호비트유지)로 쉬프트를 하고 있었다.

코드에서는 ULONG_PTR 변수형을 사용하고 있어서 Shift(SHR)를 할 경우 밀린 bit만큼 0으로 채워져 0xFFFFF00000000000 값을 다시 OR 연산하는 과정이 필요했었던거로 보인다. 하지만 하드코딩된 값은 언제나 위험..

LONG_PTR로 변수 사용시 OR연산은 필요하지 않다.


x86 코드에서는 0xFFFFFFF8과 AND 연산을 통해 Unlocked, Attributes 3bit를 제거하여 Object 주소를 얻는다.


결국 x86이나 x64나 ObjectPointerBits로 주소 연산을 하는 것을 알 수 있다. 다만 x64에서 & (~0xF) 하는 부분이 좀 이상하긴 하지만 현재로서는 정확하게 설명을 못하겠다.


아래는 Windows 8 계열에서 다시 재구성해본 계산방식이다.

 LONG_PTR ObjectPointerBits = 0;

 PVOID pObj = NULL;


#ifdef _WIN64

 ObjectPointerBits = (*(PLONG_PTR)pHandleTableEntry & 0xFFFFFFFFFFF00000);

 pObj = ((ObjectPointerBits >> 16) & (~0xF));

#else

 pObj = (PVOID)((ULONG_PTR)pHandleTableEntry->Object & (~0x7));

#endif



참조 사이트

1. http://forum.sysinternals.com/hiding-a-process-pspcidtable_topic15362.html

2. http://bbs.pediy.com/showpost.php?p=1169355&postcount=1


Posted by hswang