숨겨진 프로세스 탐지 방법을 찾던중 PspCidTable이 가지고 있는 핸들 정보를 이용하여 검색하는 방법을 구현하게 되었다.
인터넷에 검색해보면 몇몇 코드가 나오는데 대부분 XP에서 테스트가 된거라 손볼데가 많았다.
1. PspCidTable 주소 찾기
각 OS마다 PspCidTable 주소를 찾기 위해서 PsLookupProcessByProcessId 함수 내부를 패턴으로 검색해서 찾아야한다. Windows 8부터는 함수 내부에서 PspReferenceCidTableEntry 함수를 call하게 되고, 이곳에 PspCidTable 주소가 있다. 8 이전 OS에서는 PsLookupProcessByProcessId 함수 내부에서 바로 찾을 수 있다.
|
PsLookupProcessByProcessId() / PspReferenceCidTableEntry -> PspCidTable |
_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
'Kernel mode' 카테고리의 다른 글
HANDLE에 관하여 (0) | 2014.02.17 |
---|---|
[링크] Windows NT드라이버 개발자들을 위한 정보 - 피해야 할 사항들 (0) | 2014.01.21 |
ConfigFlags (0) | 2013.11.15 |
Prefetch / Process Carving : 종료된 프로세스 탐지 (0) | 2013.10.31 |
Windows Kernel (0) | 2013.08.20 |