В этой статье я покажу вам, как найти подлинный процесс подсистемы Windows, это полезно, например, когда вы пытаетесь получить список активных процессов (заметьте, только процессы, которые работают в подсистеме Windows могут быть найдены таким образом, а кроме подсистемы Windows есть еще подсистемы POSIX и OS/2, которые уже, можно сказать, уже и не поддерживаються), перечисляя структуры CSR_PROCESS, список которых находится в процессе CSRSS. Самый простой способ нахождения процесса CSRSS это исспользование PsActiveProcessHead (это указатель на вершину двусвязного списка, который можно найти в любой структуре EPROCESS) и поиск первого процесса под названием «csrss.exe», ну, потому что эта функция используется в основном в IPD / Anti-Rootkit модулях, то этот алгоритм является ненадежным (на самом деле он ненадежен в любой ситуации). Например руткит может изменить (с помощью DKOM) список процессов и избежать обнаружения. Поэтому я хочу вам показать вариант получше.
Во время загрузки системы, после запуска процесса CSRSS, среди множества инициализационных задач, CSRSS также создает ALPC порт под названиемApiPort, с помощью которого CSRSS реализует свой API. Сразу после создания порта ApiPort, CSRSS создает поток CsrApiRequestThread который затем вызывает NtAlpcSendWaitReceivePort и передает ему хэндл порта в качестве параметра для осуществления ожидания на порте(ждет клиентов для коммуникации).
Как видно на картинке, ApiPort находится под каталогом объектов Windows. Так как для каждой последующей сессии создается новый процесс CSRSS , то ApiPort для каждой сессии будет уже создан в каталоге SessionsNWindows.
Одной из отличительных архитектурных решений, при разработке ALPC модели, было то, что только процесс создающий серверный порт(server port) может получить хэндл на него и, естественно, получает он его вызовом NtCreatePort . Поэтому единственный процесс, который имеет открытый дескриптор на ApiPort является CSRSS.
После всех этих обсуждений, можно описать общие шаги выявления подлинного процесса CSRSS.
1.Каким-то образом получить объект ApiPort .
2.Получить процесс, который имеет открытый дескриптор к порту.
ШАГ 1.
Как я уже упоминал, процедура ObOpenObjectByName не будет работать для портов сервера ALPC (стоит заметить, что все другие типы портов безымянны). Если попытаться использовать ObOpenObjectByName с портом сервера ALPC, вы получите код ошибки STATUS_NOT_IMPLEMENTED. Но на самом деле есть функция, которая может помочь нам: ObReferenceObjectByName эта функция не документирована, вот ее прототип:
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
);
Очевидно, что последний параметр получает адрес объекта.
Пример кода для получения объекта ApiPort :
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: %xnOBJECT: %p",ret,pApiPort);
if(!NT_SUCCESS(ret))
{
//cleanup and exit
}
//Do some work....
ObDereferenceObject(pApiPort);
Примечание!
LpcPortObjectType, LpcWaitablePortObjectType, AlpcPortObjectType — все они указывают на одну и ту же структуру OBJECT_TYPE, поэтому не имеет значения, какую из них использовать
ШАГ 2.
Чтобы понять, что нам нужно сделать во втором шаге, сначала мы должны немного погрузиться в изучение Object Manager -а.
Каждый объект, созданный Object Manager имеет следующую структуру:
Загаловоки объекта(OBJECT_HEADERS) состоят из «Дополнительных Заголовков Объекта»(Object Optional Headers) и «Общего Заголовка Объекта»(Object Header).Object Header находится сразу после дополнительных заголовков(Object Optional Headers).
1.Object Header представляется структурой _OBJECT_HEADER.
2.Дополнительные Заголовки Объекта представляются следующими структурами:
_OBJECT_HEADER_CREATOR_INFO,
_OBJECT_HEADER_NAME_INFO,
_OBJECT_HEADER_HANDLE_INFO,
_OBJECT_HEADER_QUOTA_INFO,
_OBJECT_HEADER_PROCESS_INFO.
Чтобы определить, какие дополнительные заголовки присутствуют, используется поле структуры _OBJECT_HEADER->InfoMask. В общем, InfoMask это битовая маска, где биты выявляют присутствие дополнительных заголовков.
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 также используется для расчета смещения в массиве(не экспортируемом) ObpInfoMaskToOffset, который используется для получения смещения желаемого дополнительного заголовка относительно начала тела объекта.Код, который имитирует алгоритм вычисления смещения, выглядит следующим образом:
/******/
BYTE HeaderOffset =
ObpInfoMaskToOffset[_OBJECT_HEADER->InfoMask & (HeaderBit | (HeaderBit-1))].
/******/
Кроме того, дополнительные заголовки расположены строго, как показано на рисунке.
Получается проблема в том, что ObpInfoMaskToOffset не экспортируется, это означает, что либо мы должны получить его с помощью Pattern-поиска или мы можем реализовать нашу собственную функцию для расчета смещения, основываясь на знаниях, которые у нас уже есть (есть еще 3 способ: реализовать наш собственный массив ObpInfoMaskToOffset).
Я выбрал второй способ.
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);
}
Как вы можете видеть, я также реализовал GetObjectHeaderHandleInfo() это должно намекать на то, что нам нужна структура _OBJECT_HEADER_HANDLE_INFO которая имеет следующий вид:
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: %xnOBJECT: %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:%pnOBject:%pnSIZEOF:%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);
Вот и все!
LINKS
Windows 7 Object Headers
Автор: KeMmIo