Getting a genuine Windows Subsystem (csrss.exe) process

In this article I will show you how to find the genuine process of the Windows subsystem, it is useful, for example, when you try to get a list of active processes (note, only processes that work in the Windows subsystem can be found in this way, but in addition to the Windows subsystem there are also subsystems POSIX and OS / 2, which, one might say, are no longer supported), listing the CSR_PROCESS structures , a list of which is in the CSRSS process . The easiest way to find the CSRSS process is to use PsActiveProcessHead (this is a pointer to the top of the doubly linked list that can be found in any EPROCESS structure ) and search for the first process called “csrss.exe”, well, because this function is mainly used in IPD / Anti-Rootkit modules, this algorithm is unreliable (in fact, it is unreliable in any situation). For example, a rootkit can modify (using DKOM ) the list of processes and avoid detection. Therefore, I want to show you a better option.

During system boot, after starting the CSRSS process , among many initialization tasks, CSRSS also creates an ALPC port called ApiPort , with which CSRSS implements its API. Immediately after creating the ApiPort port , CSRSS creates a CsrApiRequestThread thread which then callsNtAlpcSendWaitReceivePort and passes the port handle to it as a parameter for waiting on the port (waiting for clients to communicate).


As you can see in the picture, ApiPort is located under the \ Windows object directory. Since a new CSRSS process is created for each subsequent session, ApiPort for each session will already be created in the \ Sessions \ N \ Windows directory.
One of the distinctive architectural decisions, when developing the ALPC model, was that only the process creating the server port (server port) can get a handle to it and, naturally, it gets it by calling NtCreatePort. Therefore, the only process that has an open descriptor on ApiPort is CSRSS .

After all these discussions, you can describe the general steps to identify a genuine CSRSS process .
1. Somehow get an ApiPort object .
2. Get a process that has an open handle to the port.

STEP 1.

As I already mentioned, the ObOpenObjectByName procedure will not work for ALPC server ports (it is worth noting that all other types of ports are nameless). If you try to use ObOpenObjectByName with the ALPC server port , you will get the error code STATUS_NOT_IMPLEMENTED. But actually there is a function that can help us: ObReferenceObjectByName this function is not documented, here is its prototype:
NTKERNELAPI 
NTSTATUS 
ObReferenceObjectByName( 
__in PUNICODE_STRING ObjectName, 
__in ULONG Attributes, 
__in_opt PACCESS_STATE AccessState, 
__in_opt ACCESS_MASK DesiredAccess, 
__in POBJECT_TYPE ObjectType, 
__in KPROCESSOR_MODE AccessMode, 
__inout_opt PVOID ParseContext, 
__out PVOID *Object 
); 


Obviously, the last parameter gets the address of the object.
Sample code to get an ApiPort object :
UNICODE_STRING apiport;
PVOID pApiPort;
NTSTATUS ret;
RtlInitUnicodeString(&apiport,L"\\Windows\\ApiPort");
ret = ObReferenceObjectByName(&apiport,0,0,GENERIC_READ,*LpcPortObjectType,KernelMode,NULL,&pApiPort);
DbgPrint("ObOpenObjectByName returned: %x\nOBJECT: %p",ret,pApiPort);
if(!NT_SUCCESS(ret)) 
{ 
//cleanup and exit 
} 
//Do some work.... 
ObDereferenceObject(pApiPort);


Note!
LpcPortObjectType , LpcWaitablePortObjectType , AlpcPortObjectType - they all point to the same OBJECT_TYPE structure , so it doesn’t matter which one to use


STEP 2.
To understand what we need to do in the second step, first we need to dive into a little bit to study Object Manager - a.
Each object created by Object Manager has the following structure:


Object headers (OBJECT_HEADERS) consist of “Object Optional Headers” and “Object Optional Headers.” The Object Header is located immediately after the optional Object Headers. .

1.Object Header is represented by the _OBJECT_HEADER structure.
2. Additional Object Headers are represented by the following structures:
_OBJECT_HEADER_CREATOR_INFO ,
_OBJECT_HEADER_NAME_INFO ,
_OBJECT_HEADER_HANDLE_INFO ,
_OBJECT_HEADER_QUOTA_INFO ,
_OBJECT_HEADER_PROCESS .

To determine which additional headers are present, use the _OBJECT_HEADER-> InfoMask structure field . In general, InfoMask is a bitmask where bits detect the presence of additional headers.

0x1 _OBJECT_HEADER_CREATOR_INFO
0x2 _OBJECT_HEADER_NAME_INFO
0x4 _OBJECT_HEADER_HANDLE_INFO
0x8 _OBJECT_HEADER_QUOTA_INFO
0x10 _OBJECT_HEADER_PROCESS_INFO

InfoMask is also used to calculate the offset in the array (not exported) of ObpInfoMaskToOffset , which is used to get the offset of the desired additional header relative to the beginning of the object body. The code that simulates the offset calculation algorithm is as follows:
/******/
BYTE HeaderOffset =
ObpInfoMaskToOffset[_OBJECT_HEADER->InfoMask & (HeaderBit | (HeaderBit-1))]. 
/******/

In addition, additional headers are arranged strictly as shown.


It turns out the problem is that ObpInfoMaskToOffset is not exported, which means that we either need to get it using the Pattern search or we can implement our own function for calculating the displacement, based on the knowledge that we already have (there are 3 more ways: implement our own array ObpInfoMaskToOffset ).

I chose the second method.
enum OBJ_HEADER_INFO_FLAG
{
  HeaderCreatorInfoFlag = 0x1,
  HeaderNameInfoFlag = 0x2,
  HeaderHandleInfoFlag= 0x4,
  HeaderQuotaInfoFlag= 0x8,
  HeaderProcessInfoFlag= 0x10
};
BYTE GetObjectHeaderOffset( 
BYTE InfoMask,OBJ_HEADER_INFO_FLAG Flag)
{
BYTE OffsetMask,HeaderOffset=0;
if( (InfoMask & Flag) == 0 )
  return 0;
OffsetMask = InfoMask & ( Flag | (Flag - 1) );
if((OffsetMask & HeaderCreatorInfoFlag) != 0)
   HeaderOffset += (BYTE)sizeof(OBJECT_HEADER_CREATOR_INFO);
if((OffsetMask & HeaderNameInfoFlag) != 0)
   HeaderOffset += (BYTE)sizeof(OBJECT_HEADER_NAME_INFO);
if((OffsetMask & HeaderHandleInfoFlag) != 0)
  HeaderOffset += (BYTE)sizeof(OBJECT_HEADER_HANDLE_INFO);
if((OffsetMask & HeaderQuotaInfoFlag) != 0)
  HeaderOffset += (BYTE)sizeof(OBJECT_HEADER_QUOTA_INFO);
if((OffsetMask & HeaderProcessInfoFlag) != 0)
   HeaderOffset += (BYTE)sizeof(OBJECT_HEADER_PROCESS_INFO);
return HeaderOffset;
}
POBJECT_HEADER_HANDLE_INFO GetObjectHeaderHandleInfo(POBJECT_HEADER pObjHeader)
{
//DbgPrint("->GetObjectHeaderHandleInfo pObjHeader: %p",pObjHeader);
BYTE HeaderOffset = GetObjectHeaderOffset(pObjHeader->InfoMask,HeaderHandleInfoFlag);
if(HeaderOffset == 0)
   return NULL;
//DbgPrint("->GetObjectHeaderHandleInfo HeaderOffset: %d",HeaderOffset);
return (POBJECT_HEADER_HANDLE_INFO)((ULONGLONG)pObjHeader-(ULONGLONG)HeaderOffset);
}

As you can see, I also implemented GetObjectHeaderHandleInfo (), this should hint that we need a _OBJECT_HEADER_HANDLE_INFO structure which looks like this:
typedef struct _OBJECT_HANDLE_COUNT_ENTRY // 3 elements, 0x10 bytes (sizeof)
{
/*0x000*/ PEPROCESS Process;
struct // 2 elements, 0x4 bytes (sizeof)
{
/*0x008*/ ULONG32 HandleCount : 24; // 0 BitPosition
/*0x008*/ ULONG32 LockCount : 8; // 24 BitPosition
};
  ULONG32 Reserved;
}OBJECT_HANDLE_COUNT_ENTRY, *POBJECT_HANDLE_COUNT_ENTRY;
typedef struct _OBJECT_HANDLE_COUNT_DATABASE // 2 elements, 0x18 bytes (sizeof)
{
/*0x000*/ ULONG32 CountEntries;
/*0x004*/ UINT8 Reserved[0x4];
/*0x008*/ struct _OBJECT_HANDLE_COUNT_ENTRY HandleCountEntries[1];
}OBJECT_HANDLE_COUNT_DATABASE, *POBJECT_HANDLE_COUNT_DATABASE;
typedef struct _OBJECT_HEADER_HANDLE_INFO // 2 elements, 0x10 bytes (sizeof)
{
union // 2 elements, 0x10 bytes (sizeof)
{
/*0x000*/ struct _OBJECT_HANDLE_COUNT_DATABASE* HandleCountDataBase;
/*0x000*/ struct _OBJECT_HANDLE_COUNT_ENTRY SingleEntry; // 3 elements, 0x10 bytes (sizeof)
};
}OBJECT_HEADER_HANDLE_INFO, *POBJECT_HEADER_HANDLE_INFO;


_OBJECT_HEADER_HANDLE_INFO Содержит один элемент, которое представляет собой union, а union состоит из двух полей HandleCountDataBase, SingleEntry (_OBJECT_HEADER->Flag определяет, какое поле использовать). В частности OB_FLAG_SINGLE_HANDLE_ENTRY (0x40) флаг определяет, что нужно исспользовать поле SingleEntry , а в остальных случиях исспользуется HandleCountDataBase(как правило, для объектов портов ALPC всегда используется HandleCountDataBase ).Для серверных портов ALPC HandleCountDataBase всегда содержит два элемента (то есть CountEntries == 2)и второй элемент всегда обнулён.

Так вот финальная часть кода:
              UNICODE_STRING apiport;
 POBJECT_HEADER pApiPortHeader;
 POBJECT_HEADER_HANDLE_INFO pHandleInfo;
 PVOID pApiPort;
 NTSTATUS ret;
                PEPROCESS procCSRSS;
 RtlInitUnicodeString(&apiport,L"\\Windows\\ApiPort");
 ret = ObReferenceObjectByName(&apiport,0,0,GENERIC_READ,*LpcPortObjectType,KernelMode,NULL,&pApiPort);
 DbgPrint("ObOpenObjectByName returned: %x\nOBJECT: %p \nIndex: %d",ret,pApiPort,(*LpcPortObjectType)->Index);
 if(!NT_SUCCESS(ret))
 DbgPrint("Can not reference ApiPort: %x",ret);
 /*Get the object header form object pointer 
 ---------OBJECT_HEADER---------
 |   |
 |   |
 |   |
 /0x30/_________________________
 | QUAD(OBJECT)       |
 ...............................
 */
 pApiPortHeader = OBJECT_TO_OBJECT_HEADER(pApiPort);
 pHandleInfo = GetObjectHeaderHandleInfo(pApiPortHeader);
 DbgPrint("Handle Info:%p\nOBject:%p\nSIZEOF:%d",pHandleInfo,pApiPortHeader,sizeof(OBJECT_HEADER_NAME_INFO));
 if(pHandleInfo != NULL)
 {
 if(pApiPortHeader->Flags & OB_FLAG_SINGLE_HANDLE_ENTRY)
 procCSRSS = pHandleInfo->SingleEntry.Process;
 else
 procCSRSS = pHandleInfo->HandleCountDataBase->HandleCountEntries[0].Process;
 DbgPrint("CSRSS: %p",procCSRSS);
 }
 else
 {
 DbgPrint("Can not obtain Handle Info Header for ApiPort");
 }
 ObDereferenceObject(pApiPort);

All this fuss with Object & Object Headers could be circumvented, simply using the field ALPC_PORT-> OwnerProcess , ( we get the pointer to ALPC_PORT from ObReferenceObjectByName ), by the time of writing this article, I needed a way to get a list of processes that have open handles to the object and I didn’t even I bothered to look in detail at the structure of ALPC_PORT , however, please do not hit me in the kidneys, because I believe that material about Object & Object Headers was still useful.

That's all!

LINKS
Windows 7 Object Headers

Also popular now: