Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
В Windows все взаимодействие программного кода с системой построено на хендлах. Можно атаковать компоненты системы, ковырять ядро, но что, если атаковать сами дескрипторы? В этой статье я разобрал несколько вариантов атак на них.
Хендлы (они же дескрипторы) в Windows — это один из важных камней в фундаменте системы. На хендлах построено и основывается все взаимодействие из программного кода с компонентами Windows. Именно благодаря дескрипторам ядро понимает, к какому объекту мы хотим обратиться и с каким уровнем доступа. Хендл обычно можно получить с помощью стандартных функций. На файл — через CreateFile(), на LSA — через LsaOpenPolicy(). В Windows очень много разных объектов, с которыми можно взаимодействовать. Ты можешь открыть список WinAPI, ткнуть на любой интересующий компонент и убедиться, что самой основной и важной апихой будет API для получения хендла на нужный объект. Не получил хендл — не смог взаимодействовать с системой.
Знаешь ли ты, что есть атаки и на сами хендлы?
warning
Статья имеет ознакомительный характер и предназначена для специалистов по безопасности, проводящих тестирование в рамках контракта. Автор и редакция не несут ответственности за любой вред, причиненный с применением изложенной информации. Распространение вредоносных программ, нарушение работы систем и нарушение тайны переписки преследуются по закону.
Во‑первых, следует определиться с тем, что мы ищем. Нас интересуют далеко не все хендлы. Конечно, приоритетнее всего процессы и потоки. Взаимодействие с ними позволит запустить шелл‑код. Впрочем, тут опять не все так гладко. Полученный хендл должен иметь хотя бы право PROCESS_VM_WRITE
(для процесса) или THREAD_SET_CONTEXT
(для потока). Кстати, нам подойдут и хендлы на секции памяти и — в исключительных случаях — на конкретные файлы или ключи реестра.
Перед тем как приступить, можем глянуть хендлы конкретных процессов в системе. Это позволит наметить дальнейший вектор развития атаки. Удобнее всего, как мне кажется, использовать утилиту Process Hacker.
Изучение хендлов процесса
Есть также System Explorer и Sysinternals, но я ими пользовался не очень много.
Вывод инструмента Handle из пакета Sysinternals
Это самая первая атака на хендлы. Основана она по большей части на функции DuplicateHandle() и ее более низкоуровневых аналогах вроде NtDuplicateObject().
Взглянем на прототип функции.
BOOL DuplicateHandle( [in] HANDLE hSourceProcessHandle, [in] HANDLE hSourceHandle, [in] HANDLE hTargetProcessHandle, [out] LPHANDLE lpTargetHandle, [in] DWORD dwDesiredAccess, [in] BOOL bInheritHandle, [in] DWORD dwOptions);
hSourceProcessHandle
— хендл процесса, из которого требуется скопировать хендл. Должен иметь маску доступа PROCESS_DUP_HANDLE
; hSourceHandle
— хендл, который нужно скопировать; hTargetProcessHandle
— процесс, в который нужно скопировать хендл; lpTargetHandle
— куда класть скопированный хендл; dwDesiredAccess
— флаги доступа; bInheritHandle
— наследуемый ли дескриптор, то есть можно ли его передавать в дочерние процессы; dwOptions
— дополнительные параметры. Подробнее каждый аргумент можно изучить в официальной документации.
Итак, вся атака заключается в том, что мы находим процессы, на которые можем запросить хендл с маской PROCESS_DUP_HANDLE
, затем копируем их хендлы, изучаем, на что они указывают, после чего эксплуатируем! Например, на процесс victim.exe
из нашего процесса target.exe
можно успешно получить хендл с нужной маской. После копирования дескрипторов из целевого процесса обнаруживаем, что у нас есть дескриптор процесса lsass.exe
! Как следствие, успешно дампим процесс.
Конечно, в целевом процессе может быть указана плохая маска доступа, которая не подходит для дампа процесса. Тем не менее, если у нас есть хендл, мы можем распарсить его и извлечь маску доступа, используемую для получения этого хендла.
Здесь нам поможет функция NtQueryObject() и передача структуры PUBLIC_OBJECT_BASIC_INFORMATION.
#include <iostream>#include <windows.h>#include <winternl.h>#pragma comment(lib, "ntdll.lib")void ParseAccessMask(ACCESS_MASK accessMask) { if ((accessMask & GENERIC_READ) == GENERIC_READ) std::cout << "GENERIC_READ "; if ((accessMask & GENERIC_WRITE) == GENERIC_WRITE) std::cout << "GENERIC_WRITE "; if ((accessMask & GENERIC_EXECUTE) == GENERIC_EXECUTE) std::cout << "GENERIC_EXECUTE "; if ((accessMask & GENERIC_ALL) == GENERIC_ALL) std::cout << "GENERIC_ALL "; if ((accessMask & PROCESS_CREATE_PROCESS) == PROCESS_CREATE_PROCESS) std::cout << "PROCESS_CREATE_PROCESS "; if ((accessMask & PROCESS_TERMINATE) == PROCESS_TERMINATE) std::cout << "PROCESS_TERMINATE "; if ((accessMask & PROCESS_SUSPEND_RESUME) == PROCESS_SUSPEND_RESUME) std::cout << "PROCESS_SUSPEND_RESUME "; if ((accessMask & PROCESS_QUERY_INFORMATION) == PROCESS_QUERY_INFORMATION) std::cout << "PROCESS_QUERY_INFORMATION "; if ((accessMask & PROCESS_SET_INFORMATION) == PROCESS_SET_INFORMATION) std::cout << "PROCESS_SET_INFORMATION "; std::cout << std::endl;}int main() { HANDLE hProcess = GetCurrentProcess(); ULONG returnLength = 0; PUBLIC_OBJECT_BASIC_INFORMATION obi; NTSTATUS status = NtQueryObject( hProcess, ObjectBasicInformation, &obi, sizeof(obi), &returnLength); if (NT_SUCCESS(status)) { std::cout << "Granted Access: " << std::hex << obi.GrantedAccess << std::endl; ParseAccessMask(obi.GrantedAccess); } else { std::cerr << "NtQueryObject failed with status: " << std::hex << status << std::endl; } CloseHandle(hProcess); return 0;}
Пример вывода
Есть проблема: мы парсим маски, совершенно забывая о том, что маски доступа могут иметь перекрывающиеся значения. Например, у процесса есть PROCESS_VM_READ
, у потока — THREAD_SET_CONTEXT
, а эти оба значения указывают на 0x0010
, поэтому нашу функцию парсинга маски доступа нужно модифицировать, научив распознавать переданный хендл.
Далеко ходить не надо — используем ту же функцию NtQueryObject()
, только будем передавать ей структуру PUBLIC_OBJECT_TYPE_INFORMATION.
typedef struct __PUBLIC_OBJECT_TYPE_INFORMATION { UNICODE_STRING TypeName; ULONG Reserved[22];} PUBLIC_OBJECT_TYPE_INFORMATION, *PPUBLIC_OBJECT_TYPE_INFORMATION;
Здесь будем отталкиваться только от поля TypeName
. Именно там содержится тип объекта, на который указывает хендл.
Источник: xakep.ru