Опять случился большой перерыв между публикациями в блоге, но, благодаря настойчивости dx'a, который в данный момент занят улучшением его библиотеки для работы с PE-файлами и продажей новой версии обфускатора PHP, в блоге все-таки появится новый пост. На этот раз он будет посвящен реализации очередного тривиального драйвера, который будет получать уведомления о новых процессах, запускаемых в системе и понижать уровень целостности (integrity level) заданного процесса до низкого.
В качестве заготовки я воспользуюсь фрагментами кода из моей старой публикации. Но давайте по порядку. Для начала нам необходимо как-то получать уведомления о процессах, запускаемых в ОС. Самый простой способ - воспользоваться функцией PsSetCreateProcessNotifyRoutineEx (я умышленно использую функцию, которая появилась в Windows только начиная с Vista, серьезно, сколько можно заниматься поддержкой старых, неактуальных версий ОС, даже поддержка относительно новой Windows 7 заканчивается в 2015 году, а уж на XP закладываться... let it go).
Итак, давайте уже напишем немного кода:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
NTSTATUS DeployProcessHook() { NTSTATUS Status = STATUS_UNSUCCESSFUL; LOG("DeployProcessHook"); for (;;) { KeInitializeMutex(&NameMutex, 0); Status = PsSetCreateProcessNotifyRoutineEx(HookRoutine, FALSE); if (!NT_SUCCESS(Status)) break; RtlInitUnicodeString(&ProcessName, NULL); break; } LOG("DeployProcessHook - Status=%08X", Status); return Status; } NTSTATUS RemoveProcessHook() { NTSTATUS Status = STATUS_UNSUCCESSFUL; LOG("RemoveProcessHook"); for (;;) { Status = PsSetCreateProcessNotifyRoutineEx(HookRoutine, TRUE); if (!NT_SUCCESS(Status)) break; KeWaitForMutexObject(&NameMutex, Executive, KernelMode, FALSE, NULL); RtlFreeUnicodeString(&ProcessName); KeReleaseMutex(&NameMutex, FALSE); break; } LOG("RemoveProcessHook - Status=%08X", Status); return Status; } |
Что делает этот код: в процедуре DeployProcessHook мы инициализируем глобальный мьютекс (он будет использоваться при работе с переменной ProcessName, чтобы не возникало конфликтов), регистрируем процедуру HookRoutine, чтобы она получала уведомления о новых процессах и инициализируем глобальную переменную ProcessName, которая будет содержать название процесса, для которого будет выставляться low integrity level. В RemoveProcessHook мы совершаем противоположные действия.
Теперь рассмотрим HookRoutine, которая получает информацию о создаваемых процессах и принимает решение об установке low integrity.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
VOID HookRoutine(PEPROCESS Process, HANDLE ProcessId, PPS_CREATE_NOTIFY_INFO CreateInfo) { NTSTATUS Status; LARGE_INTEGER Timeout; UNICODE_STRING TargetProcessName, FullPathName, FileName; LOG("HookRoutine"); /* Таймаут на 500 ms */ Timeout.QuadPart = 5000000L; Timeout.QuadPart = -(Timeout.QuadPart); for (;;) { /* Игнорируем следующие ситуации: процесс завершает работу, у процесса отсутствует имя, не задано имя для применения фильтра */ if (CreateInfo == NULL || !CreateInfo->FileOpenNameAvailable || ProcessName.Length == 0) break; /* Делаем локальную копию глобальной переменной, чтобы минимизировать время нахождения под мьютексом */ Status = KeWaitForMutexObject(&NameMutex, UserRequest, KernelMode, FALSE, &Timeout); if (Status != STATUS_SUCCESS) { LOG("KeWaitForMutexObject - Status=%08X", Status); break; } RtlCreateUnicodeString(&TargetProcessName, ProcessName.Buffer); KeReleaseMutex(&NameMutex, FALSE); /* Получаем имя процесса из полного пути и понижаем уровень целостности если это нужный процесс */ RtlCreateUnicodeString(&FullPathName, CreateInfo->ImageFileName->Buffer); GetFileName(&FullPathName, &FileName); LOG("TargetProcessName=%wZ, FullPathName=%wZ, FileName=%wZ", &ProcessName, &FullPathName, &FileName); if (RtlCompareUnicodeString(&ProcessName, &FileName, FALSE) == 0) SetLowIntegrity(Process); RtlFreeUnicodeString(&TargetProcessName); RtlFreeUnicodeString(&FullPathName); break; } } |
Логика работы крайне примитивна и едва ли нуждается в детальном пояснении. Таймаут на ожидание мьютекса я поставил на случай, если драйвер будет обрабатывать пользовательский IOCTL неоправданно долго (даже не уверен, имеет ли смысл эта конструкция здесь).
Буду слегка непоследовательным и приведу код одной вспомогательной функции, которая использовалась выше, а потом вернусь к основной логике.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
VOID GetFileName(PUNICODE_STRING pSourceString, PUNICODE_STRING pFileName) { UNICODE_STRING current = *pSourceString; UNICODE_STRING remaining; for (;;) { FsRtlDissectName(current, pFileName, &remaining); if (remaining.Length == 0) break; current = remaining; } } |
GetFileName необходима для ивзлечения имени запускаемого файла из полного пути, который мы получаем из структуры PS_CREATE_NOTIFY_INFO, находясь в HookRoutine.
Возвращаемся к основной логике:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
VOID SetLowIntegrity(PEPROCESS Process) { PACCESS_TOKEN pAccessToken; NTSTATUS Status; HANDLE hToken = NULL; LOG("SetLowIntegrity"); Status = STATUS_SUCCESS; for (;;) { if (!NT_SUCCESS(Status)) { LOG("SetLowIntegrity - something went wrong"); break; } pAccessToken = PsReferencePrimaryToken(Process); Status = ObOpenObjectByPointer(pAccessToken, 0, NULL, TOKEN_QUERY, NULL, KernelMode, &hToken); if (!NT_SUCCESS(Status)) continue; Status = TuneTokenIntegrity(hToken); if (!NT_SUCCESS(Status)) continue; break; } if (hToken != NULL) ZwClose(hToken); PsDereferencePrimaryToken(pAccessToken); LOG("SetLowIntegrity - Status=%08X", Status); } NTSTATUS TuneTokenIntegrity(HANDLE Token) { PSID lowMandatoryLevelSid; TOKEN_MANDATORY_LABEL mandatoryLabel; static SID_IDENTIFIER_AUTHORITY mandatoryLabelAuthority = SECURITY_MANDATORY_LABEL_AUTHORITY; UCHAR lowMandatoryLevelSidBuffer[FIELD_OFFSET(SID, SubAuthority) + sizeof(ULONG)]; lowMandatoryLevelSid = (PSID)lowMandatoryLevelSidBuffer; RtlInitializeSid(lowMandatoryLevelSid, &mandatoryLabelAuthority, 1); *RtlSubAuthoritySid(lowMandatoryLevelSid, 0) = SECURITY_MANDATORY_LOW_RID; mandatoryLabel.Label.Sid = lowMandatoryLevelSid; mandatoryLabel.Label.Attributes = SE_GROUP_INTEGRITY; return ZwSetInformationToken(Token, TokenIntegrityLevel, &mandatoryLabel, sizeof(TOKEN_MANDATORY_LABEL)); } |
SetLowIntegrity получает хендл токена запускаемого процесса и передает в TuneTokenIntegrity, которая, в свою очередь, выставляет low integrity level. Кстати, много полезных примеров по взаимодействию с процессами можно посмотреть в исходном коде Process Hacker, собственно, код TuneTokenIntegrity подсмотрен в них.
Осталась последняя вспомогательная функция, которая устанавливает значение глобальной переменной ProcessName, которую я упоминал ранее.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
BOOLEAN SetTargetProcessName(UNICODE_STRING Name) { BOOLEAN Status = TRUE; LOG("SetTargetProcessName"); KeWaitForMutexObject(&NameMutex, UserRequest, KernelMode, FALSE, NULL); for (;;) { RtlFreeUnicodeString(&ProcessName); if(!RtlCreateUnicodeString(&ProcessName, Name.Buffer)) { Status = FALSE; LOG("SetTargetProcessName - RtlCreateUnicodeString failed"); break; } break; } KeReleaseMutex(&NameMutex, FALSE); return Status; } |
Возможно у кого-то возник вопрос, что такое LOG. Это просто вспомогательный макрос для логирования. Единственная цель - возможность отключить логирование, не комментируя кучи строк.
1 2 3 4 5 |
#ifdef _DEBUG #define LOG DbgPrint #else #define LOG(...) #endif |
Также приведу код DriverEntry и прочих обязательных процедур, а также простой программы для взаимодействия с драйвером. Комментировать не буду, так как примитивно, да и код упоминался в прошлой статье.
DriverEntry и прочее:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 |
#include <Ntifs.h> #include <Ntddk.h> #include "IntegrityLevel.h" #ifdef _DEBUG #define LOG DbgPrint #else #define LOG(...) #endif #define MAX_PATH 260 #define DEV_NAME L"ILDriver" #define SYMLINK_NAME L"\\??\\" DEV_NAME #define IOCTL_SET_PROCESS_NAME CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS) typedef struct _ILDeviceExt { PDEVICE_OBJECT DeviceObject; UNICODE_STRING SymLink; } IL_DEVICE_EXT, *PIL_DEVICE_EXT; #pragma code_seg("INIT") VOID DriverUnload(PDRIVER_OBJECT pDriverObject); NTSTATUS DispatchRoutine(PDEVICE_OBJECT DeviceObject, PIRP Irp); NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { NTSTATUS Status; PDEVICE_OBJECT DeviceObject; UNICODE_STRING DeviceName; PIL_DEVICE_EXT DeviceExtension; UNICODE_STRING SymLink; UINT16 i; LOG("DriverEntry"); Status = STATUS_SUCCESS; DeviceObject = NULL; SymLink.Length = 0; for (;;) { if (!NT_SUCCESS(Status)) { LOG("DriverEntry - something went wrong"); if (DeviceObject != NULL) IoDeleteDevice(DeviceObject); if (SymLink.Length != 0) IoDeleteSymbolicLink(&SymLink); break; } for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++) DriverObject->MajorFunction[i] = DispatchRoutine; DriverObject->MajorFunction[i] = NULL; DriverObject->DriverUnload = DriverUnload; RtlInitUnicodeString(&DeviceName, L"\\Device\\" DEV_NAME); Status = IoCreateDevice ( DriverObject, sizeof(IL_DEVICE_EXT), &DeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &DeviceObject ); if (!NT_SUCCESS(Status)) continue; DeviceExtension = (PIL_DEVICE_EXT)DeviceObject->DeviceExtension; DeviceExtension->DeviceObject = DeviceObject; LOG("DriverEntry - DeviceObject=%p, DeviceExtension=%p", DeviceObject, DeviceExtension); RtlInitUnicodeString(&SymLink, SYMLINK_NAME); DeviceExtension->SymLink = SymLink; Status = IoCreateSymbolicLink(&SymLink, &DeviceName); if (!NT_SUCCESS(Status)) continue; Status = DeployProcessHook(); if (!NT_SUCCESS(Status)) continue; break; } LOG("DriverEntry - status=%08X", Status); return Status; } #pragma code_seg() NTSTATUS DispatchRoutine(PDEVICE_OBJECT DeviceObject, PIRP Irp) { PIL_DEVICE_EXT DeviceExtension; PIO_STACK_LOCATION pIrpStack; BOOLEAN IoResult; NTSTATUS Status; ULONG outSize, inSize; CHAR ApplicationNameRaw[MAX_PATH]; ANSI_STRING ApplicationNameANSI; UNICODE_STRING ApplicationNameUnicode; LOG("DispatchRoutine"); pIrpStack = IoGetCurrentIrpStackLocation(Irp); Irp->IoStatus.Information = 0; Irp->IoStatus.Status = STATUS_SUCCESS; DeviceExtension = (PIL_DEVICE_EXT)DeviceObject->DeviceExtension; switch (pIrpStack->MajorFunction) { case IRP_MJ_CREATE: LOG("IRP_MJ_CREATE"); break; case IRP_MJ_CLOSE: LOG("IRP_MJ_CLOSE"); break; case IRP_MJ_CLEANUP: LOG("IRP_MJ_CLEANUP"); break; case IRP_MJ_DEVICE_CONTROL: LOG("IRP_MJ_DEVICE_CONTROL"); switch (pIrpStack->Parameters.DeviceIoControl.IoControlCode) { case IOCTL_SET_PROCESS_NAME: { LOG("IOCTL_SET_PROCESS_NAME"); outSize = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength; inSize = pIrpStack->Parameters.DeviceIoControl.InputBufferLength; if (outSize != sizeof(BOOLEAN) || inSize != MAX_PATH * sizeof(WCHAR)) { Irp->IoStatus.Status = STATUS_INVALID_PARAMETER; break; } RtlCopyMemory(ApplicationNameRaw, Irp->AssociatedIrp.SystemBuffer, MAX_PATH); ApplicationNameRaw[MAX_PATH - 1] = L'\0'; RtlInitUnicodeString(&ApplicationNameUnicode, ApplicationNameRaw); LOG("UnicodeName=%wZ", &ApplicationNameUnicode); IoResult = SetTargetProcessName(ApplicationNameUnicode); RtlCopyMemory(Irp->AssociatedIrp.SystemBuffer, &IoResult, sizeof(BOOLEAN)); Irp->IoStatus.Information = sizeof(BOOLEAN); } break; } break; default: LOG("Not implemented major function call - %02X", pIrpStack->MajorFunction); Irp->IoStatus.Status = STATUS_NOT_IMPLEMENTED; break; } IoCompleteRequest(Irp, IO_NO_INCREMENT); return Irp->IoStatus.Status; } VOID DriverUnload(PDRIVER_OBJECT pDriverObject) { PDEVICE_OBJECT NextDevObj; NTSTATUS Status; LOG("DriverUnload"); for (NextDevObj = pDriverObject->DeviceObject; NextDevObj != NULL; NextDevObj = NextDevObj->NextDevice) { PIL_DEVICE_EXT DeviceExtension = (PIL_DEVICE_EXT)NextDevObj->DeviceExtension; UNICODE_STRING * Link = &(DeviceExtension->SymLink); Status = IoDeleteSymbolicLink(Link); if (!NT_SUCCESS(Status)) LOG("DriverUnload - IoDeleteSymbolicLink=%p, Status=%08X", Link, Status); IoDeleteDevice(DeviceExtension->DeviceObject); } (void)RemoveProcessHook(); } |
Простое приложение:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
#include <Windows.h> #include <stdio.h> #define SYMLINK_NAME L"\\\\.\\ILDriver" #define IOCTL_SET_PROCESS_NAME CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS) int wmain(int argc, wchar_t *argv[], wchar_t *envp[]) { HANDLE DrvHandle; DWORD Size; WCHAR In[MAX_PATH]; BOOLEAN Out; if (argc != 2) { printf("Usage: %ls program_name.exe\n", argv[0]); return -1; } wcscpy_s(In, sizeof(In) / sizeof(WCHAR), argv[1]); DrvHandle = CreateFile ( SYMLINK_NAME, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0 ); if (DrvHandle == INVALID_HANDLE_VALUE) { printf("Can't open handle\n"); return -1; } if ( !DeviceIoControl ( DrvHandle, IOCTL_SET_PROCESS_NAME, &In, sizeof(In), &Out, sizeof(BOOLEAN), &Size, NULL ) ) { CloseHandle(DrvHandle); printf("DeviceIoControl failed\n"); return -1; } printf("DeviceIoControl: %d\n", Out); CloseHandle(DrvHandle); Sleep(1000); return 0; } |
Упомяну несколько важных моментов: при линкове драйвера необходимо указать параметр /integritycheck. При отсутствии параметра драйвер не удастся загрузить, также не забывайте, что для тестирования необходимо включить TESTSIGNING и подписать драйвер каким-нибудь сертификатом, например, так:
1 |
signtool sign /v /f kaimi.io.pfx /p kaimi driver.sys |
Подробнее про процесс подписи можно почитать в MSDN. В проект для MSVC 2013, который можно скачать по ссылке в конце статьи, я включил тестовый сертификат и добавил post-build event для подписывания драйвера.
Наконец, загрузим драйвер в тестовой среде и посмотрим, работает ли он. По умолчанию Notepad запускается с medium integrity level:
И с low integrity, при наличии нашего драйвера в системе:
Вроде все работает.
Проект для MSVC 2013: скачать
Тащемто в xp нет IL.
Мимокэп.
Так-то да, но можно было какую-нибудь другую "полезную" нагрузку впихнуть и обеспечить совместимость с XP...
Чё, как жизнь вообще, нормально всё?
http://i.imgur.com/B92NtLj.jpg
Санкции отменили? :-)
Работаем над этим
Ну наконец-то статья! Да ещё и про драйвер, щикарно.
Чуть не забыл. Пусть клик по картинке в заголовке сайта ведёт на главную.
PS: чуть кондратий не схватил:
http://kaimi.io/wp-comments-post.php
С заголовком сайта сделал полумеру, на саму картинку ничего не повесить, т.к. она через css прописана.