Хабрахабр

Как PROCESS_DUP_HANDLE превращается в PROCESS_ALL_ACCESS

В MSDN'овской статье Process Security and Access Rights есть интересная ремарка:

This creates a handle that has maximum access to process B. … if process A has a handle to process B with PROCESS_DUP_HANDLE access, it can duplicate the pseudo handle for process B.

Если вольно перевести это на русский, то тут говорится, что имея описатель на процесс с правом доступа PROCESS_DUP_HANDLE мы можем, используя функцию DuplicateHandle(...), получить описатель с максимально разрешенными масками доступа на этот процесс.

Демонстрация

Исходный код, эксплуатирующий эту особенность, достаточно простой:

#include <Windows.h> int wmain(int argc, PWSTR argv[])
CloseHandle(ProcessDuplicateHandle); } return 0;
}

Затем утилита открывает указанный процесс с правом PROCESS_DUP_HANDLE. В результате компиляции и линковки получаем тестовую утилиту, которая в качестве аргумента принимает идентификатор целевого процесса (PID). Тем самым мы моделируем необходимое условие наличия описателя на процесс с правом PROCESS_DUP_HANDLE (== 0x40).

В качестве демонстрации я буду трассировать собранную утилиту в WinDbg:

0:000> lsa @$ip 0,3
> 13: if (ProcessDuplicateHandle) 14: { 15: if (DuplicateHandle(ProcessDuplicateHandle, 0:000> !handle @@C++(ProcessDuplicateHandle) 3
Handle 80 Type Process Attributes 0 GrantedAccess 0x40: None DupHandle HandleCount 9 PointerCount 260518

А затем легким движением руки вызовом DuplicateHandle(...) получаем второй описатель на тот же процесс, но уже с максимально широкими правами:

0:000> lsa @$ip 0,3
> 23: CloseHandle(ProcessAllAccessHandle); 24: } 25: CloseHandle(ProcessDuplicateHandle);
0:000> !handle @@C++(ProcessAllAccessHandle) 3
Handle 84 Type Process Attributes 0 GrantedAccess 0x1fffff: Delete,ReadControl,WriteDac,WriteOwner,Synch Terminate,CreateThread,,VMOp,VMRead,VMWrite,DupHandle,CreateProcess,SetQuota,SetInfo,QueryInfo,SetPort HandleCount 10 PointerCount 292877

К сожалению, WinDbg не выводит PID целевого процесса. Ключевой момент — значение GrantedAccess, которое у нового описателя равно 0x1fffff, что соответствует PROCESS_ALL_ACCESS. Но что бы убедиться, что описатель получен на нужный процесс, можно посмотреть на описатели Process Explorer'ом (предварительно уточнив в отладчике указанный в аргументах командной строки PID):

0:000> dx argv[1]
argv[1] : 0x1b7c2e2412c : "21652" [Type: wchar_t *]

На скриншоте утилита открывает описатели на запущенный notepad.exe.

Почему так происходит?

Проверяется только то, что переданные в функцию DuplicateHandle(...) описатели процессов имеют разрешенную маску доступа PROCESS_DUP_HANDLE. Во первых потому, что при дублировании описателя, если не расширяется маска доступа к объекту (а у нас специально указан флаг операции DUPLICATE_SAME_ACCESS), не происходит проверки того, что процесс (в котором будет создан продублированный описатель) имеет доступ к этому объекту. А далее копирование описателя между процессами происходит без проверки прав доступа (повторюсь: если у нового описателя маска разрешенных прав не шире, чем у исходного дублируемого описателя).

Существуют два документированных псевдо-описателя с константными значениями, которые физически отсутствую в таблице описателей процесса. А затем нужно отметить, что вызов GetCurrentProcess() возвращает константу, тот самый псевдо-описатель (pseudo handle), упомянутый в самом начале этой публикации. Но эти описатели обрабатываются всеми функциями ядра (наряду с обычными описателями из таблицы описателей процесса):

Именно значение NtCurrentProcess (== -1) возвращает вызов GetCurrentProcess().

Получается такая ссылка на самого себя, но через описатель:
Этот псевдо-описатель в рамках конкретного процесса означает объект этого самого процесса с правами PROCESS_ALL_ACCESS (на самом деле есть нюансы, но статья не о них).

А для целевого процесса (того, на который у нас сохранен описатель в переменной ProcessDuplicateHandle) значение -1 и будет ссылаться на этот самый целевой процесс с правами PROCESS_ALL_ACCESS. То есть наш вызов DuplicateHandle(ProcessDuplicateHandle, GetCurrentProcess(), ...) будет трактован так: из открытого (целевого) процесса дублируй описатель со значением -1. Поэтому в результате мы получаем описатель на целевой процесс с максимальными правами.

Вместо эпилога

Повторю мысль, написанную в самом начале: если кто-то получает на процесс описатель с правом PROCESS_DUP_HANDLE, то в рамках модели безопасности Windows он сможет получить другой описатель на этот же процесс, но с правами PROCESS_ALL_ACCESS (и сделать с процессом все, что ему заблагорассудится).

Приглашаю всех желающих пройти опрос, что бы узнать насколько подобные публикации могут быть интересны/полезны аудитории. Спасибо всем, кто дочитал публикацию до конца.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть