📜 ⬆️ ⬇️

Getting Genuine Windows Subsystem (csrss.exe) Process

In this article, I will show you how to find the true process of the Windows subsystem, this is useful, for example, when you are trying to get a list of active processes (note that only processes that work in the Windows subsystem can be found this way, and besides the Windows subsystem there are also subsystems POSIX and OS / 2, which can already be said to be no longer supported), listing the CSR_PROCESS structures, the list of which is in the process of CSRSS . The easiest way to find the CSRSS process is to use PsActiveProcessHead (this is a pointer to the top of a 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, then this algorithm is unreliable (in fact, it is unreliable in any situation). For example, a rootkit may change (with the help of DKOM ) a list of processes and avoid detection. Therefore, I want to show you a better option.

During system startup, after starting the CSRSS process, among a variety of 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 stream , which then calls NtAlpcSendWaitReceivePort and sends it a port handle as a parameter to wait on the port (waiting for clients to communicate).


As you can see in the picture, ApiPort is located under the object directory \ Windows . Since for each subsequent session a new CSRSS process is created, ApiPort for each session will already be created in the \ Sessions \ N \ Windows directory.
One of the distinctive architectural solutions in the development of the ALPC model was that only the process that creates the server port can receive a handle on it and, naturally, it receives 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 the true CSRSS process.
1. Some way to 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 receive the error code STATUS_NOT_IMPLEMENTED . But in fact 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.
Example 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, we first have to dive a little into the Object Manager's study.
Each object created by Object Manager has the following structure:


Object object headers (OBJECT_HEADERS) consist of “Object Optional Headers” and “Object General Header” (Object Header) .Object Header is immediately after the additional 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_INFO .

To determine which additional headers are present, use the structure field _OBJECT_HEADER-> InfoMask . 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) ObpInfoMaskToOffset , which is used to obtain 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 in the figure.


The problem is that ObpInfoMaskToOffset is not exported, which means that we must either get it using Pattern-search or we can implement our own function for calculating the offset based on the knowledge that we already have (there are 3 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 Contains one element, which is a union, and the union consists of two fields HandleCountDataBase , SingleEntry (_OBJECT_HEADER-> Flag determines which field to use). In particular, the OB_FLAG_SINGLE_HANDLE_ENTRY (0x40) flag specifies that the SingleEntry field should be used , and in the remaining cases, the HandleCountDataBase is used (as a rule, the ALPC port objects always use the HandleCountDataBase ). For the ALPC server ports, the HandleCountDataBase always contains two elements, for example, to use one of the same elements. ) and the second element is always zeroed out.

So the final part of the code:
  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 fussing with Object & Object Headers could be bypassed by simply using the ALPC_PORT-> OwnerProcess field , (we get the pointer to ALPC_PORT from ObReferenceObjectByName ) just by the time of writing this article I needed a way to get a list of processes that have open handles on the object and I don’t even I bothered to look at the structure of ALPC_PORT in detail , nevertheless, I ask you not to beat me in the kidneys, since I think that the material about Object & Object Headers was still useful.

That's all!

LINKS
Windows 7 Object Headers

Source: https://habr.com/ru/post/153391/


All Articles