- некоторые объекты ядра могут быть открыты на основе 2 вещей:
- дескриптор безопасности объекта
- токен вызывающего абонента (токен потока, если существует, в противном случае обрабатывает токен)
обычно нить может открывать собственную ручку, но могут быть исключения, одна - нить, созданная системой, для сигналов управления консоли.
минимальный код для воспроизведения ( c ++ ):
HANDLE g_hEvent;
BOOL WINAPI HandlerRoutine(DWORD dwCtrlType)
{
if (CTRL_C_EVENT == dwCtrlType)
{
if (HANDLE hThread = OpenThread(THREAD_QUERY_INFORMATION,
FALSE, GetCurrentThreadId()))
{
CloseHandle(hThread);
}
else GetLastError();
SetEvent(g_hEvent);
}
return TRUE;
}
и из консольного приложения вызовите
if (g_hEvent = CreateEvent(0, TRUE, FALSE, 0))
{
if (SetConsoleCtrlHandler(HandlerRoutine, TRUE))
{
// send ctrl+c, for not manually do this
if (GenerateConsoleCtrlEvent (CTRL_C_EVENT, 0))
{
WaitForSingleObject(g_hEvent, INFINITE);
}
SetConsoleCtrlHandler(HandlerRoutine, FALSE);
}
CloseHandle(g_hEvent);
}
может в тестовом виде, что
OpenThread(THREAD_QUERY_INFORMATION, FALSE, GetCurrentThreadId())
ошибка с ошибкой - ERROR_ACCESS_DENIED
почему это произошло? нужно искать дескриптор безопасности потока. простой код для этого может выглядеть так:
void DumpObjectSD(HANDLE hObject = GetCurrentThread())
{
ULONG cb = 0, rcb = 0x40;
static volatile UCHAR guz;
PVOID stack = alloca(guz);
PSECURITY_DESCRIPTOR psd = 0;
do
{
if (cb < rcb)
{
cb = RtlPointerToOffset(psd = alloca(rcb - cb), stack);
}
if (GetKernelObjectSecurity(hObject,
OWNER_SECURITY_INFORMATION|DACL_SECURITY_INFORMATION|LABEL_SECURITY_INFORMATION,
psd, cb, &rcb))
{
PWSTR sz;
if (ConvertSecurityDescriptorToStringSecurityDescriptor(psd, SDDL_REVISION_1,
OWNER_SECURITY_INFORMATION|DACL_SECURITY_INFORMATION|LABEL_SECURITY_INFORMATION, &sz, &rcb))
{
DbgPrint("%S\n", sz);
LocalFree(sz);
}
break;
}
} while (GetLastError() == ERROR_INSUFFICIENT_BUFFER);
}
и вызовите его из потока обработчика консоли и из обычного (первого потока) для сравнения.
SD обычного потока процесса может выглядеть следующим образом:
для невысокого процесса:
O:S-1-5-21-*
D:(A;;0x1fffff;;;S-1-5-21-*)(A;;0x1fffff;;;SY)(A;;0x121848;;;S-1-5-5-0-LogonSessionId)
S:AI(ML;;NWNR;;;ME)
или для повышенных прав (работает от имени администратора)
O:BA
D:(A;;0x1fffff;;;BA)(A;;0x1fffff;;;SY)(A;;0x121848;;;S-1-5-5-0-LogonSessionId)
S:AI(ML;;NWNR;;;HI)
но когда это вызывается из потока обработчика (автоматически созданного системой) - мы получили еще один dacl:
для не повышенных:
O:BA
D:(A;;0x1fffff;;;S-1-5-21-*)(A;;0x1fffff;;;SY)(A;;0x121848;;;S-1-5-5-0-LogonSessionId)
S:AI(ML;;NWNR;;;SI)
для повышенных:
O:BA
D:(A;;0x1fffff;;;BA)(A;;0x1fffff;;;SY)(A;;0x121848;;;S-1-5-5-0-LogonSessionId)
S:AI(ML;;NWNR;;;SI)
отличается здесь в SYSTEM_MANDATORY_LABEL
S:AI(ML;;NWNR;;;SI)
"ML"
здесь SDDL_MANDATORY_LABEL
(SYSTEM_MANDATORY_LABEL_ACE_TYPE
)
Обязательные права на этикетку:
"NW"
- SDDL_NO_WRITE_UP
(SYSTEM_MANDATORY_LABEL_NO_WRITE_UP
)
"NR"
- SDDL_NO_READ_UP
(SYSTEM_MANDATORY_LABEL_NO_READ_UP
)
и точка main - метка значение (sid):
поток обработчика всегда имеет "SI"
- SDDL_ML_SYSTEM
- уровень целостности системы.
в то время как обычные темы имеют "ME"
- SDDL_MLMEDIUM
- средний уровень целостности или
"HI"
- SDDL_ML_HIGH
- высокий уровень целостности при запуске от имени администратора
Итак - поскольку этот поток имеет более высокий уровень целостности (Система), чем обычный уровень целостности процесса в токене (Высокий уровень целостности или ниже, если не системный процесс) и без прав на чтение и запись - мы не можем открыть этот поток с доступ на чтение или запись, только с доступом на исполнение.
мы можем сделать следующий тест в HandlerRoutine
- попробуйте открыть поток с MAXIMUM_ALLOWED
и ищите предоставленный доступ с NtQueryObject
(используйте ObjectBasicInformation
)
if (HANDLE hThread = OpenThread(MAXIMUM_ALLOWED, FALSE, GetCurrentThreadId()))
{
OBJECT_BASIC_INFORMATION obi;
if (0 <= ZwQueryObject(hThread, ObjectBasicInformation, &obi, sizeof(obi), 0))
{
DbgPrint("[%08x]\n", obi.GrantedAccess);
}
CloseHandle(hThread);
}
мы получили здесь: [00101800]
что означает:
SYNCHRONIZE | THREAD_RESUME | THREAD_QUERY_LIMITED_INFORMATION
также мы можем запросить ObjectTypeInformation
и получить GENERIC_MAPPING
для объекта потока.
OBJECT_BASIC_INFORMATION obi;
if (0 <= ZwQueryObject(hThread, ObjectBasicInformation, &obi, sizeof(obi), 0))
{
ULONG rcb, cb = (obi.TypeInfoSize + __alignof(OBJECT_TYPE_INFORMATION) - 1) & ~(__alignof(OBJECT_TYPE_INFORMATION) - 1);
POBJECT_TYPE_INFORMATION poti = (POBJECT_TYPE_INFORMATION)alloca(cb);
if (0 <= ZwQueryObject(hThread, ObjectTypeInformation, poti, cb, &rcb))
{
DbgPrint("a=%08x\nr=%08x\nw=%08x\ne=%08x\n",
poti->GenericMapping.GenericAll,
poti->GenericMapping.GenericRead,
poti->GenericMapping.GenericWrite,
poti->GenericMapping.GenericExecute);
}
}
и получил
a=001fffff
r=00020048
w=00020437
e=00121800
так что мы вообще можем открыть этот поток с доступом GenericExecute
, кроме 00020000
(READ_CONTROL
), потому что этот доступ в GenericRead и GenericWrite и политике - без чтения / записи.
однако почти для всех API, где требуется дескриптор (поток или универсальный), мы можем использовать GetCurrentThread()
- псевдо-дескриптор для вызывающего потока. конечно это можно использовать только для текущего потока. так что мы можем назвать, например,
FILETIME CreationTime, ExitTime, KernelTime, UserTime;
GetThreadTimes(GetCurrentThread(), &CreationTime, &ExitTime, &KernelTime, &UserTime);
CloseHandle(GetCurrentThread());
также допустимый вызов - Вызов функции CloseHandle с этим дескриптором не имеет никакого эффекта. (просто ничего не будет). и этот псевдо-дескриптор имеет GENERIC_ALL
предоставленный доступ.
чтобы ваша подпрограмма OpenThread
могла проверить идентификатор потока - если он равен GetCurrentThreadId()
- просто вернуть GetCurrentThread()
.
также мы можем позвонить
DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &hThread, 0, 0, DUPLICATE_SAME_ACCESS);
это также будет хорошо работать для этой темы. однако обычного использования GetCurrentThread()
достаточно