Учитывая популярность объектов GDI Bitmap для эксплуатации уязвимостей ядра - из-за того, что практически любой вид уязвимости, связанной с повреждением памяти (за исключением записи NULL), может быть использован для надежного получения произвольных примитивов чтения/записи в памяти ядра путем злоупотребления Bitmap - Microsoft решила убить технику эксплуатации, основанную на bitmap. Для этого в Windows 10 Fall Creators Update (также известном как Windows 10 1709) была введена функция изоляции типов, предотвращение эксплуатации в подсистеме Win32k, которая разделяет структуру памяти объектов SURFACE, внутреннее представление bitmap в ядре. В этом посте подробно рассказывается о том, как реализована изоляция типов.
Примечание
Первоначально этот анализ проводился с использованием
Контекст
С середины 2015 года Bitmaps [1], тип объектов GDI, был предпочтительным выбором разработчиков эксплойтов при эксплуатации уязвимостей ядра в Windows. Оказалось, что структура данных, представляющая этот тип объекта в ядре Windows, имеет несколько очень удобных элементов, которые при повреждении из-за уязвимости памяти могут предоставить злоумышленнику полноценный доступ для чтения/записи к адресному пространству ядра.
На стороне ядра Bitmap представлен объектом
BASEOBJECT является общим для нескольких типов объектов и определяется следующим образом:
Но нас интересует специфическая для SURFACE структура, которая называется SURFOBJ, и определяется она следующим образом:
Двумя наиболее интересными членами этой структуры являются
Есть два основных способа использовать объекты SURFACE в целях эксплуатации, повредив элементы, упомянутые ранее:
Первый метод обеспечивает полные возможности чтения/записи. Его недостаток в том, что для него требуется «хорошая» уязвимость (write-what-where), которая должна позволять перезаписывать указатель
Второй метод, хотя поначалу менее мощный, поскольку он изначально обеспечивает доступ для чтения/записи к памяти, расположенной сразу после конца буфера данных пикселей, имеет то преимущество, что его можно использовать даже с ограниченными уязвимостями; простые произвольные декременты/инкременты или запись непроизвольных значений сделают свое дело. Стратегия эксплуатации при перезаписи члена
Как вы могли заметить, этот второй подход к эксплуатации был возможен, потому что до сих пор буфер пиксельных данных Bitmap обычно прилегал к заголовку SURFACE; весь объект SURFACE был создан с помощью единственного выделения памяти с размером, достаточно большим, чтобы вместить как заголовок SURFACE, так и буфер данных пикселей. Это не обязательно должно было быть так, но это было реализовано именно так. Это позволило разработчикам эксплойтов получить выгодные схемы памяти, где за буфером пиксельных данных одного объекта SURFACE может следовать заголовок другого.
Учитывая этот второй подход к эксплуатации, который позволяет превратить практически любой вид уязвимости повреждения памяти (кроме записи NULL) в произвольную операцию чтения/записи над памятью ядра путем злоупотребления объектами GDI Bitmap, Microsoft решила избавиться от нее. Для этого в Windows 10 Fall Creators Update появилась функция Type Isolation, предотвращение эксплуатации в подсистеме Win32k, которая разделяет структуру памяти для объектов SURFACE.
Type Isolation
Структура данных
Изоляция типов реализуется с помощью ряда связанных структур. В этом смягчении участвуют 4 основные структуры данных (ниже вы можете найти симпатичную маленькую диаграмму, которая все объясняет в графическом виде):
Статическая переменная
Далее следуют реконструированные определения четырех упомянутых структур данных, а также их размеры и смещения их полей:
CTypeIsolation (size = 0x20 bytes)
CSectionEntry (size = 0x28 bytes)
CSectionBitmapAllocator (size = 0x28 bytes)
RTL_BITMAP (size = 0x10 bytes)
Следующая диаграмма пытается прояснить отношения между всеми задействованными структурами данных.
На этом рисунке представлено гипотетическое состояние структур Type Isolation с 3 экземплярами
Также представлены 0x28 Views размером 0x1000, поддерживающие первый
Также обратите внимание, что на этом рисунке не изображен объект Section, поддерживающий объекты Views, для простоты, поскольку прямой доступ к секции не осуществляется (все обращения выполняются через Views).
В качестве отдельного замечания статическая переменная
Инициализация
Инициализация структур изоляции типов происходит внутри
В частности,
В свою очередь,
Затем он отображает View этого раздела. Указатель на представление также сохраняется в структуре CSectionEntry.
Наконец,
Как и ожидалось,
Помимо выделения этой структуры RTL_BITMAP,
Указатели XORed на объекты View и RTL_BITMAP хранятся в структуре CSectionBitmapAllocator.
Начиная с Windows 10 Fall Creators Update,
Буфер данных пикселей выделяется в пуле PagedPoolSession простым способом, вызывая оболочку
С другой стороны, заголовок SURFACE теперь выделяется из структур CTypeIsolation, описанных ранее, путем вызова
Покопавшись в функции
Если
Таким образом, нам нужно отобразить индекс бита очистки в RTL_BITMAP, возвращаемый функцией
Этот
После успешной фиксации 0x1000-байтовое представление инициализируется значением 0 (эта операция записи завершает фактическую фиксацию), и поле num_commited_views CSectionBitmapAllocator обновляется соответствующим образом.
Либо, если новый View должно быть зафиксировано или нет, индекс бита очистки RTL_BITMAP затем устанавливается в 1 путем вызова
Теперь, когда мы сопоставили наш бит очистки с соответствующим View, нам нужно выбрать блок размером 0x280 байт в этом представлении. Каждое представление может содержать 6 заголовков SURFACE (0x1000 / 0x280 == 6). Для этого выполняется следующий расчет:
Значение, которое RBX получает по адресу 0x1C00399ED, является адресом недавно выделенного заголовка SURFACE, и это значение будет возвращено
Для полноты и, как и было обещано, вот что происходит, когда
Этот переход приводит нас сюда, где он проверяет, есть ли CSectionEntry->next == CTypeIsolation, что означает, что мы достигли конца списка объектов CSectionEntry. Если это не так, он зацикливается и повторяет процесс со следующим объектом CSectionEntry.
В противном случае, если мы достигли конца списка объектов CSectionEntry, не найдя пустой слот (то есть каждый CSectionEntry содержит максимум заголовков 0xF0 SURFACE), будет достигнут следующий код. Как показано ниже, он создает новый CSectionEntry и вызывает
Наконец, вновь созданный CSectionEntry вставляется в конец двусвязного списка, как подробно описано ниже. Обратите внимание, что перед работой с указателями списка выполняется проверка целостности: код проверяет, указывает ли следующий указатель CTypeIsolation->previous на заголовок CTypeIsolation.
После этого он берет заголовок CTypeIsolation и начинает обход двусвязного списка объектов CSectionEntry, пытаясь определить, какой CSectionEntry содержит заголовок SURFACE, который он пытается освободить. Для этого он просто проверяет, есть ли
Когда эти условия выполнены, то есть мы обнаружили, что CSectionEntry, содержащий заголовок SURFACE, должен быть освобожден, индекс этой SURFACE в его View контейнере вычисляется (называемый здесь index_within_view), беря 3 младших полубайта адреса SURFACE и разделив их на 0x280:
Затем адрес SURFACE необходимо отобразить в битовый индекс, который представляет его в RTL_BITMAP. Чтобы получить соответствующий битовый индекс, он получает view_index (то есть, в котором 0x1000-байтовом View находится этот объект SURFACE), а затем просто выполняет этот расчет:
Значение рассчитанного битового индекса проверяется с помощью
Расширение Windbg
Во время реверс-инжиниринга Win32k Type Isolation я разработал небольшое расширение WinDbg, которое помогает мне определять состояние структур Type Isolation.
Расширение доступно на: https://github.com/fdfalcon/TypeIsolationDbg
Расширение WinDbg предоставляет следующие команды:
Заключение
Изоляции типов, реализованная в компоненте Win32k Windows 10 1709, изменяет способ выделения объектов Bitmap GDI в пространстве ядра: заголовок SURFACE выделяется в представлении раздела, а буфер данных пикселей выделяется в пуле PagedPoolSession. Это определенно исключает обычную технику эксплуатации, заключающуюся в использовании Bitmap в качестве целей для ограниченных уязвимостей, связанных с повреждением памяти, поскольку больше невозможно сделать выровненный спрей из соседних Bitmap, где за концом буфера данных пикселей сразу же следует заголовок следующего объекта SURFACE
Между тем, разработчики эксплойтов уже перешли на другие полезные объекты ядра, такие как Palettes [5] [6] [7].
Любопытно, что объект CSectionBitmapAllocator сохраняет как указатель на представления разделов, так и указатель на RTL_BITMAP, запутанный с помощью операции XOR, однако родительская структура CSectionEntry сохраняет тот же указатель на представления в обычном виде.
Ссылки
[1] - https://msdn.microsoft.com/en-us/library/dd183377(v=vs.85).aspx
[2] - https://www.coresecurity.com/system...6/10/Abusing-GDI-Reloaded-ekoparty-2016_0.pdf
[3] - https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/section-objects-and-views
[4] - https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/managing-memory-sections
[5] - https://sensepost.com/blog/2017/abusing-gdi-objects-for-ring0-primitives-revolution/
[6] - https://labs.bluefrostsecurity.de/f...ring0_exploit_primitives_Evolution_Slides.pdf
[7] - http://theevilbit.blogspot.com/2017/10/abusing-gdi-objects-for-kernel.html
От ТС
Оригинал доступен тут - https://blog.quarkslab.com/reverse-engineering-the-win32k-type-isolation-mitigation.html
Очень интересная и подробная статья, и не менее интересное расширение для WinDbg
Перевод:
Azrv3l cпециально для xss.pro
Примечание
Первоначально этот анализ проводился с использованием
win32kbase.sys версии 10.0.16288.1 в Windows 10 Fall Creators Update x64, которая была одной из последних сборок Insider Preview перед выпуском общедоступной Windows 10 1709 в октябре 2017 года. Выполнение бинарного сравнения между указанной версией и последняя версия win32kbase.sys 10.0.16299.125, доступная на момент написания этой статьи (конец января 2018 г.), показывает, что функции, относящиеся к изоляции типов, остаются неизменными.Контекст
С середины 2015 года Bitmaps [1], тип объектов GDI, был предпочтительным выбором разработчиков эксплойтов при эксплуатации уязвимостей ядра в Windows. Оказалось, что структура данных, представляющая этот тип объекта в ядре Windows, имеет несколько очень удобных элементов, которые при повреждении из-за уязвимости памяти могут предоставить злоумышленнику полноценный доступ для чтения/записи к адресному пространству ядра.
На стороне ядра Bitmap представлен объектом
SURFACE со следующей структурой:
C++:
typedef struct _SURFACE {
BASEOBJECT BaseObject;
SURFOBJ surfobj;
[...]
}
BASEOBJECT является общим для нескольких типов объектов и определяется следующим образом:
C++:
typedef struct _BASEOBJECT {
HANDLE hHmgr;
ULONG ulShareCount;
USHORT cExclusiveLock;
USHORT BaseFlags;
PVOID Tid;
} BASEOBJECT, *PBASEOBJECT;
Но нас интересует специфическая для SURFACE структура, которая называется SURFOBJ, и определяется она следующим образом:
C++:
typedef struct _SURFOBJ {
DHSURF dhsurf;
HSURF hsurf;
DHPDEV dhpdev;
HDEV hdev;
SIZEL sizlBitmap;
ULONG cjBits;
PVOID pvBits;
PVOID pvScan0;
LONG lDelta;
ULONG iUniq;
ULONG iBitmapFormat;
USHORT iType;
USHORT fjBitmap;
} SURFOBJ, *PSURFOBJ;
Двумя наиболее интересными членами этой структуры являются
pvScan0, который указывает на буфер, содержащий данные пикселей bitmap, и sizlBitmap, который содержит размеры (ширину и высоту) bitmap.Есть два основных способа использовать объекты SURFACE в целях эксплуатации, повредив элементы, упомянутые ранее:
- И
GetBitmapBits, иSetBitmapBitsGDI API работают с буфером данных пикселей, на который указывает членpvScan0структуры SURFOBJ. Перезапись этого указателя обеспечивает произвольную перезапись памяти ядра из пользовательского режима. - Поле sizlBitmap структуры SURFOBJ содержит свойства ширины и высоты bitmap. Перезаписывая
sizlBitmap.cxилиsizlBitmap.cy, можно «увеличить» буфер данных пикселей. Это обеспечивает доступ для чтения / записи к памяти ядра за пределами буфера данных пикселей.
Первый метод обеспечивает полные возможности чтения/записи. Его недостаток в том, что для него требуется «хорошая» уязвимость (write-what-where), которая должна позволять перезаписывать указатель
pvScan0 произвольным значением.Второй метод, хотя поначалу менее мощный, поскольку он изначально обеспечивает доступ для чтения/записи к памяти, расположенной сразу после конца буфера данных пикселей, имеет то преимущество, что его можно использовать даже с ограниченными уязвимостями; простые произвольные декременты/инкременты или запись непроизвольных значений сделают свое дело. Стратегия эксплуатации при перезаписи члена
sizlBitmap структуры SURFOBJ состоит в том, чтобы сделать два объекта SURFACE (назовем их SURFACE1 и SURFACE2) смежными в памяти; из-за повреждения sizlBitmap SURFACE1 его буфер данных пикселей "увеличивается", таким образом перекрываясь с соседним объектом SURFACE2. С этого момента дальнейшие операции над (теперь увеличенным) SURFACE1 могут произвольно перезаписывать элементы заголовка SURFACE2, эффективно преобразовывая ограниченное чтение/запись за пределами пиксельного буфера SURFACE1 в полностью произвольные возможности чтения / записи.Как вы могли заметить, этот второй подход к эксплуатации был возможен, потому что до сих пор буфер пиксельных данных Bitmap обычно прилегал к заголовку SURFACE; весь объект SURFACE был создан с помощью единственного выделения памяти с размером, достаточно большим, чтобы вместить как заголовок SURFACE, так и буфер данных пикселей. Это не обязательно должно было быть так, но это было реализовано именно так. Это позволило разработчикам эксплойтов получить выгодные схемы памяти, где за буфером пиксельных данных одного объекта SURFACE может следовать заголовок другого.
Учитывая этот второй подход к эксплуатации, который позволяет превратить практически любой вид уязвимости повреждения памяти (кроме записи NULL) в произвольную операцию чтения/записи над памятью ядра путем злоупотребления объектами GDI Bitmap, Microsoft решила избавиться от нее. Для этого в Windows 10 Fall Creators Update появилась функция Type Isolation, предотвращение эксплуатации в подсистеме Win32k, которая разделяет структуру памяти для объектов SURFACE.
Type Isolation
Структура данных
Изоляция типов реализуется с помощью ряда связанных структур. В этом смягчении участвуют 4 основные структуры данных (ниже вы можете найти симпатичную маленькую диаграмму, которая все объясняет в графическом виде):
- CTypeIsolation
- CSectionEntry
- CSectionBitmapAllocator
- RTL_BITMAP
PagedPoolSession с помощью ExAllocatePoolWithTag. Все они имеют новый 4-байтовый тег пула, который называется «Uiso». Кроме того, тег пула, используемый для буфера пиксельных данных объектов SURFACE, изменился с «Gh? 5» на «Gpbm».Статическая переменная
win32kbase!GpTypeIsolation является указателем на другой указатель, который, в свою очередь, указывает на глобальную структуру CTypeIsolation. Этот CTypeIsolation является главой кругового двусвязного списка объектов CSectionEntry. Каждый CSectionEntry управляет 0xF0 заголовками SURFACE. Каждому CSectionEntry принадлежит объект CSectionBitmapAllocator, который поддерживает синхронизацию двух основных объектов: массив из 0x28 Views над Section [3], каждый из которых может содержать 6 заголовков SURFACE, и карта битов (RTL_BITMAP), которая отслеживает занятое или свободное состояние каждого из 0x28 * 6 == 0xF0 доступных слотов в представлениях. Двусвязный список объектов CSectionEntry может увеличиваться по мере необходимости.Далее следуют реконструированные определения четырех упомянутых структур данных, а также их размеры и смещения их полей:
CTypeIsolation (size = 0x20 bytes)
C++:
typedef struct _CTYPEISOLATION {
PCSECTIONENTRY next; // + 0x00
PCSECTIONENTRY previous; // + 0x08
PVOID pushlock; // + 0x10
ULONG64 size; // + 0x18
} CTYPEISOLATION, *PCTYPEISOLATION;
CSectionEntry (size = 0x28 bytes)
Код:
typedef struct _CSECTIONENTRY CSECTIONENTRY, *PCSECTIONENTRY;
struct _CSECTIONENTRY {
CSECTIONENTRY *next; // + 0x00
CSECTIONENTRY *previous; // + 0x08
PVOID section; // + 0x10
PVOID view; // + 0x18
PCSECTIONBITMAPALLOCATOR bitmap_allocator; // + 0x20
};
CSectionBitmapAllocator (size = 0x28 bytes)
C++:
typedef struct _CSECTIONBITMAPALLOCATOR {
PVOID pushlock; // + 0x00
ULONG64 xored_view; // + 0x08
ULONG64 xor_key; // + 0x10
ULONG64 xored_rtl_bitmap; // + 0x18
ULONG bitmap_hint_index; // + 0x20
ULONG num_commited_views; // + 0x24
} CSECTIONBITMAPALLOCATOR, *PCSECTIONBITMAPALLOCATOR;
RTL_BITMAP (size = 0x10 bytes)
C++:
typedef struct _RTL_BITMAP {
ULONG64 size; // + 0x00
PVOID bitmap_buffer; // + 0x08
} RTL_BITMAP, *PRTL_BITMAP;
Следующая диаграмма пытается прояснить отношения между всеми задействованными структурами данных.
На этом рисунке представлено гипотетическое состояние структур Type Isolation с 3 экземплярами
CSectionEntry, каждый со своими связанными экземплярами CSectionBitmapAllocator и RTL_BITMAP. Поскольку каждый экземпляр CSectionEntry управляе0xF0 т заголовками SURFACE, член size объекта CTypeIsolation устанавливается в 0xF0 * 3 == 0x2D0.Также представлены 0x28 Views размером 0x1000, поддерживающие первый
CSectionEntry. В этом случае фиксируются только 2 из 0x28 Views; остальные остаются неотображенными до тех пор, пока они не понадобятся. Первый View заполнен: все 6 слотов размером 0x280 используются заголовками SURFACE (запасные байты 0x100 в конце страницы здесь не показаны). Второй View заполнен только наполовину: используются 3 слота размером 0x280 байт, а последние 3 слота остаются неиспользованными. В то же время статус занятости каждого слота синхронизируется с картой битов в RTL_BITMAP, принадлежащей тому же CSectionEntry. В этой гипотетической ситуации, когда первые 9 слотов используются, а остальные свободны, карта битов будет выглядеть так: 11111111 00000001 00000000 00000000 ....Также обратите внимание, что на этом рисунке не изображен объект Section, поддерживающий объекты Views, для простоты, поскольку прямой доступ к секции не осуществляется (все обращения выполняются через Views).
В качестве отдельного замечания статическая переменная
win32kbase!SURFACE::tSize, которая хранит размер заголовка SURFACE в байтах, имеет значение 0x278. Однако во всем проанализированном здесь коде вычисления выполняются для размера 0x280 байт на заголовок SURFACE, вероятно, просто для целей выравнивания.
Код:
.data:00000001C0196110 ; Exported entry 387. ?tSize@SURFACE@@0_KA
.data:00000001C0196110 public private: static unsigned __int64 SURFACE::tSize
.data:00000001C0196110 private: static unsigned __int64 SURFACE::tSize dq 278h
Инициализация
Инициализация структур изоляции типов происходит внутри
win32kbase!HmgCreate(), которая вызывается во время инициализации драйвера win32kbase.sys. Он начинается с выделения указателя на будущую головную структуру NSInstrumentation::CTypeIsolation и сохранения его в глобальной переменной win32kbase!GpTypeIsolation. Затем он вызывает метод CTypeIsolation::Create(), который выделяет головную структуру CTypeIsolation.
Код:
HmgCreate+397 mov edx, 'osiU'
HmgCreate+39C mov rcx, r14 ; size = 8 (ptr to CTypeIsolation)
HmgCreate+39F call Win32AllocPool
HmgCreate+3A4 mov cs:uchar * * gpTypeIsolation, rax
HmgCreate+3AB test rax, rax
HmgCreate+3AE jz short loc_1C0012561
HmgCreate+3B0 xor ecx, ecx
HmgCreate+3B2 mov [rax], rcx
HmgCreate+3B5 call TypeIsolationFactory<NSInstrumentation::CTypeIsolation<163840,640>>::Create(uchar * *)
CTypeIsolation::Create() выделяет 0x20 байт для объекта CTypeIsolation, а затем вызывает CTypeIsolation::Initialize() для его инициализации. Если все прошло нормально, адрес объекта CTypeIsolation сохраняется в указателе, на который ссылается win32kbase!GpTypeIsolation.
Код:
.text:00000001C001263C public: static bool TypeIsolationFactory<class NSInstrumentation::CTypeIsolation<163840, 640>>::Create(unsigned char * *) proc near
[...]
.text:00000001C001264D mov edx, 20h ; NumberOfBytes
.text:00000001C0012652 mov r8d, 'osiU' ; Tag
.text:00000001C0012658 lea ecx, [rdx+1] ; PoolType
.text:00000001C001265B call cs:__imp_ExAllocatePoolWithTag ; allocates a NSInstrumentation::CTypeIsolation object
.text:00000001C0012661 mov rbx, rax ; rbx = CTypeIsolation object
.text:00000001C0012664 test rax, rax
.text:00000001C0012667 jz short loc_1C0012699
.text:00000001C0012669 and qword ptr [rax+10h], 0 ; CTypeIsolation->pushlock = NULL
.text:00000001C001266E mov rcx, rax
.text:00000001C0012671 and dword ptr [rax+18h], 0 ; CTypeIsolation->size = 0
.text:00000001C0012675 mov [rax+8], rax ; CTypeIsolation->previous = this
.text:00000001C0012679 mov [rax], rax ; CTypeIsolation->next = this
.text:00000001C001267C call NSInstrumentation::CTypeIsolation<163840,640>::Initialize(void)
.text:00000001C0012681 test al, al
.text:00000001C0012683 jz loc_1C00BA344
.text:00000001C0012689 mov [rdi], rbx ; *win32kbase!gpTypeIsolation = CTypeIsolation
В частности,
CTypeIsolation::Initialize() создает структуру CSectionEntry, вызывая CSectionEntry::Create(), и назначает ее следующим и предыдущим членам объекта CTypeIsolation:
Код:
.text:00000001C0039A34 private: bool NSInstrumentation::CTypeIsolation<163840, 640>::Initialize(void) proc near
[...]
.text:00000001C0039A5E call NSInstrumentation::CSectionEntry<163840,640>::Create(void)
.text:00000001C0039A63 test rax, rax ; rax == CSectionEntry object
.text:00000001C0039A66 jz short loc_1C0039A92
.text:00000001C0039A68 mov rcx, [rbx+8] ; rcx = CTypeIsolation->previous
.text:00000001C0039A6C mov dword ptr [rbx+18h], 0F0h ; CTypeIsolation->size = 0xF0
.text:00000001C0039A73 cmp [rcx], rbx ; CTypeIsolation->previous->next == CTypeIsolation?
.text:00000001C0039A76 jnz FatalListEntryError_10
.text:00000001C0039A7C mov [rax], rbx ; CSectionEntry->next= CTypeIsolation
.text:00000001C0039A7F mov [rax+8], rcx ; CSectionEntry->previous = CTypeIsolation->previous
.text:00000001C0039A83 mov [rcx], rax ; *CTypeIsolation->previous->next = CSectionEntry
.text:00000001C0039A86 mov [rbx+8], rax ; CTypeIsolation->previous = CSectionEntry
В свою очередь,
CSectionEntry::Create() вызывает CSectionEntry::Initialize(), который создает объект Section, вызывая nt!MmCreateSection(). Размер этого раздела составляет 0x28000 байт; доступ к этому разделу будет осуществляться через 0x28 просмотров, каждое размером 0x1000 байт. Указатель на этот объект раздела хранится в структуре CSectionEntry.
Код:
.text:00000001C0099E5C lea r9, [rbp+arg_0] ; MaximumSize
.text:00000001C0099E60 xor eax, eax
.text:00000001C0099E62 mov rdi, rcx ; rdi = CSectionEntry object
.text:00000001C0099E65 and [r11-10h], rax
.text:00000001C0099E69 lea rcx, [rbp+SectionHandle] ; SectionHandle
.text:00000001C0099E6D and [r11-18h], rax
.text:00000001C0099E71 xor r8d, r8d ; ObjectAttributes
.text:00000001C0099E74 mov [rbp+arg_0], rax
.text:00000001C0099E78 mov edx, 0F001Fh ; DesiredAccess = SECTION_ALL_ACCESS
.text:00000001C0099E7D mov [rsp+40h+var_18], SEC_RESERVE ; AllocationAttributes
.text:00000001C0099E85 mov [rsp+40h+var_20], PAGE_READWRITE ; SectionPageProtection
.text:00000001C0099E8D mov dword ptr [rbp+arg_0], 28000h ; size for the Section
.text:00000001C0099E94 call cs:__imp_MmCreateSection
Затем он отображает View этого раздела. Указатель на представление также сохраняется в структуре CSectionEntry.
Код:
.text:00000001C0099EB8 mov [rdi+10h], rcx ; CSectionEntry->section = section
.text:00000001C0099EBC test rcx, rcx
.text:00000001C0099EBF jz short loc_1C0099F0F
.text:00000001C0099EC1 and [rbp+arg_0], 0
.text:00000001C0099EC6 lea rbx, [rdi+18h] ; rbx = ptr to output view
.text:00000001C0099ECA mov rdx, rbx
.text:00000001C0099ECD lea r8, [rbp+arg_0]
.text:00000001C0099ED1 call cs:__imp_MmMapViewInSessionSpace ; populates CSectionEntry->view
Наконец,
CSectionEntry::Initialize() создает объект CSectionBitmapAllocator, вызывая CSectionBitmapAllocator::Create(). Указатель на этот объект хранится в структуре CSectionEntry.
Код:
.text:00000001C0099EED mov rcx, [rbx] ; rcx = CSectionEntry->view
.text:00000001C0099EF0 call NSInstrumentation::CSectionBitmapAllocator<163840,640>::Create(uchar * const)
.text:00000001C0099EF5 test rax, rax ; rax = CSectionBitmapAllocator
.text:00000001C0099EF8 mov [rdi+20h], rax ; CSectionEntry->bitmap_allocator = CSectionBitmapAllocator
Как и ожидалось,
CSectionBitmapAllocator::Create() вызывает CSectionBitmapAllocator::Initialize(). Этот метод выделяет буфер пула размером 0x30, который используется для хранения структуры RTL_BITMAP. Обратите внимание, что в этом контексте мы говорим не об объектах GDI Bitmap, а о карте битов общего назначения, которая обычно используется для отслеживания набора повторно используемых элементов. Первые 0x10 байтов этого буфера пула используются для хранения заголовка битовой карты, а оставшиеся 0x20 байтов используются для хранения самой карты битов. Буфер размером 0x20 байт может содержать 0x100 бит, однако только 0xF0 указывается как количество бит при вызове nt!RtlInitializeBitMap, чтобы соответствовать количеству слотов SURFACE, которые обрабатываются CSectionEntry. Затем все биты в битовой карте инициализируются до 0 путем вызова nt!RtlClearAllBits.
Код:
.text:00000001C009E324 allocate_rtl_bitmap proc near
[...]
.text:00000001C009E333 mov ecx, 21h ; PoolType = PagedPoolSession
.text:00000001C009E338 cmp edx, edi
.text:00000001C009E33A mov r8d, 'osiU' ; Tag = 'Uiso'
.text:00000001C009E340 cmovnb edi, edx ; edi = 0xF0
.text:00000001C009E343 mov edx, edi
.text:00000001C009E345 shr edx, 3 ; edx = 0x1e
.text:00000001C009E348 add edx, 7 ; edx = 0x25
.text:00000001C009E34B and edx, 0FFFFFFF8h ; edx = 0x20
.text:00000001C009E34E add edx, 10h ; NumberOfBytes = 0x30
.text:00000001C009E351 call cs:__imp_ExAllocatePoolWithTag ; allocs 0x30 bytes for a RTL_BITMAP
.text:00000001C009E357 mov rbx, rax
.text:00000001C009E35A test rax, rax
.text:00000001C009E35D jz short loc_1C009E386
.text:00000001C009E35F lea rdx, [rax+10h] ; BitMapBuffer (0x30 - 0x10 bytes)
.text:00000001C009E363 mov r8d, edi ; SizeOfBitMap (number of bits) = 0xF0
.text:00000001C009E366 mov rcx, rax ; BitMapHeader
.text:00000001C009E369 call cs:__imp_RtlInitializeBitMap
.text:00000001C009E36F mov rcx, rbx ; BitMapHeader
.text:00000001C009E372 call cs:__imp_RtlClearAllBits
Помимо выделения этой структуры RTL_BITMAP,
CSectionBitmapAllocator::Initialize() также генерирует 64-битное случайное число, которое используется в качестве ключа XOR для кодирования указателей на объекты View и RTL_BITMAP, которые были ранее выделены:
Код:
.text:00000001C002DE38 private: bool NSInstrumentation::CSectionBitmapAllocator<163840, 640>::Initialize(unsigned char *) proc near
[...]
.text:00000001C002DE48 rdtsc ; source for RtlRandomEx
.text:00000001C002DE4A shl rdx, 20h
.text:00000001C002DE4E lea rcx, [rsp+28h+arg_0]
.text:00000001C002DE53 or rax, rdx
.text:00000001C002DE56 mov [rsp+28h+arg_0], eax
.text:00000001C002DE5A call cs:__imp_RtlRandomEx ; get a 32-bit random number
.text:00000001C002DE60 mov eax, eax
.text:00000001C002DE62 lea rcx, [rsp+28h+arg_0]
.text:00000001C002DE67 shl rax, 20h ; shift eax to the higher part of RAX
.text:00000001C002DE6B mov [rbx+10h], rax ; CSectionBitmapAllocator->xor_key = random
.text:00000001C002DE6F call cs:__imp_RtlRandomEx ; get another 32-bit random number
.text:00000001C002DE75 mov eax, eax
.text:00000001C002DE77 or [rbx+10h], rax ; CSectionBitmapAllocator->xor_key |= another_random
Указатели XORed на объекты View и RTL_BITMAP хранятся в структуре CSectionBitmapAllocator.
Код:
.text:00000001C002DEB8 mov rdx, [rbx+10h] ; rdx = CSectionBitmapAllocator->xor_key
.text:00000001C002DEBC mov rcx, rdx
.text:00000001C002DEBF xor rcx, rax ; rcx = CSectionBitmapAllocator->xor_key ^ RTL_BITMAP
.text:00000001C002DEC2 mov al, 1
.text:00000001C002DEC4 xor rdx, rdi ; rdx = CSectionBitmapAllocator->xor_key ^ CSectionEntry->view
.text:00000001C002DEC7 mov [rbx+18h], rcx ; CSectionBitmapAllocator->xored_rtl_bitmap = CSectionBitmapAllocator->xor_key ^ RTL_BITMAP
.text:00000001C002DECB mov [rbx+8], rdx ; CSectionBitmapAllocator->xored_view = CSectionBitmapAllocator->xor_key ^ CSectionEntry->view
Allocation
Системный вызовwin32kfull!NtGdiCreateBitmap() отвечает за создание объектов GDI Bitmap. win32kfull!NtGdiCreateBitmap() вызывает win32kbase!GreCreateBitmap(), который, в свою очередь, вызывает win32kbase!SURFMEM::bCreateDIB(). Задача win32kbase!SURFMEM::bCreateDIB() - выделить память для объекта SURFACE. В предыдущих версиях Windows буфер пиксельных данных Bitmap обычно прилегал к заголовку SURFACE; это не обязательно должно было быть таким, но это было сделано именно так. Это сделало возможным «расширить» буфер данных пикселей, повредив член sizlBitmap заголовка SURFACE, как объяснялось ранее, и сделав его перекрывающимся с заголовком SURFACE соседнего Bitmap.Начиная с Windows 10 Fall Creators Update,
win32kbase!SURFMEM::bCreateDIB гарантирует, что заголовок SURFACE и буфер пиксельных данных выделяются отдельно.Буфер данных пикселей выделяется в пуле PagedPoolSession простым способом, вызывая оболочку
nt!ExAllocatePoolWithTag:
Код:
SURFMEM::bCreateDIB+10B sub r15d, r12d ; alloc_size = requested_size - sizeof(SURFACE)
SURFMEM::bCreateDIB+10E jz short loc_1C0038F91
SURFMEM::bCreateDIB+110 call cs:__imp_IsWin32AllocPoolImplSupported
SURFMEM::bCreateDIB+116 test eax, eax
SURFMEM::bCreateDIB+118 js loc_1C00C54D6
SURFMEM::bCreateDIB+11E mov r8d, 'mbpG' ; Tag = 'Gpbm'
SURFMEM::bCreateDIB+124 mov edx, r15d ; NumberOfBytes = requested_size - sizeof(SURFACE)
SURFMEM::bCreateDIB+127 mov ecx, 21h ; PoolType = PagedPoolSession
SURFMEM::bCreateDIB+12C call cs:__imp_Win32AllocPoolImpl ; <<< allocation! only for the pixel_data_buffer
С другой стороны, заголовок SURFACE теперь выделяется из структур CTypeIsolation, описанных ранее, путем вызова
CTypeIsolation::AllocateType(). Чтобы быть точным, это распределение возвращает буфер, расположенный в представлении объекта раздела:
Код:
SURFMEM::bCreateDIB+16C mov rax, cs:uchar * * gpTypeIsolation
SURFMEM::bCreateDIB+173 mov rcx, [rax]
SURFMEM::bCreateDIB+176 test rcx, rcx
SURFMEM::bCreateDIB+179 jz loc_1C00C579D
SURFMEM::bCreateDIB+17F call NSInstrumentation::CTypeIsolation<163840,640>::AllocateType(void)
SURFMEM::bCreateDIB+184 mov rsi, rax ; rsi = buffer for the SURFACE header
SURFMEM::bCreateDIB+187 test rax, rax ; the returned buffer is a View of a Section object
SURFMEM::bCreateDIB+18A jz loc_1C00C5791
CTypeIsolation::AllocateType(), мы можем увидеть, как работает алгоритм распределения.CTypeIsolation::AllocateType() просматривает список объектов CSectionEntry; для каждого CSectionEntry он проверяет, содержит ли его CSectionBitmapAllocator бит очистки в своей поддерживающей структуре RTL_BITMAP, вызывая nt!RtlFindClearBits. Он использует поле bitmap_hint_index класса CSectionBitmapAllocator, чтобы ускорить поиск.
Код:
.text:00000001C0039863 mov r8d, ebp ; HintIndex = 0
.text:00000001C0039866 cmp eax, 0F0h ; bitmap_hint_index >= RTL_BITMAP->size?
.text:00000001C003986B jnb short loc_1C0039870
.text:00000001C003986D mov r8d, eax ; HintIndex = bitmap_hint_index
.text:00000001C0039870
.text:00000001C0039870 loc_1C0039870: ; CODE XREF: NSInstrumentation::CTypeIsolation<163840,640>::AllocateType(void)+6Bj
.text:00000001C0039870 mov rcx, [rsi+18h] ; rcx = CSectionBitmapAllocator->xored_rtl_bitmap
.text:00000001C0039874 mov edx, 1 ; NumberToFind
.text:00000001C0039879 xor rcx, [rsi+10h] ; BitMapHeader = CSectionBitmapAllocator->xored_rtl_bitmap
.text:00000001C0039879 ; ^ CSectionBitmapAllocator->xor_key
.text:00000001C003987D call cs:__imp_RtlFindClearBits
.text:00000001C0039883 mov r12d, eax ; r12 = free_bit_index
.text:00000001C0039886 cmp eax, 0FFFFFFFFh ; free_bit_index == -1?
.text:00000001C0039889 jz short loc_1C00398D6 ; if so, RTL_BITMAP is full, check another CSectionEntry
Если
nt!RtlFindClearBits возвращает -1, указывая, что все биты в RTL_BITMAP установлены в 1 (то есть RTL_BITMAP заполнен), то он пытается повторить операцию со следующим CSectionEntry в списке. Мы рассмотрим этот случай позже. В противном случае, если nt!RtlFindClearBits возвращает значение, отличное от -1, это означает, что RTL_BITMAP имеет по крайней мере 1 очищающий бит, и, следовательно, что память раздела в текущем CSectionEntry имеет по крайней мере 1 свободный слот для заголовка SURFACE.Таким образом, нам нужно отобразить индекс бита очистки в RTL_BITMAP, возвращаемый функцией
nt!RtlFindClearBits(), в соответствующий адрес памяти свободного слота в представлении сечения. Для этого индекс бита очистки делится на 6, поскольку каждое представление раздела размером 0x1000 байт может содержать 6 заголовков SURFACE размером 0x280. Результатом является индекс, который я называю view_index в приведенных ниже фрагментах дизассемблера. Этот view_index будет находиться в диапазоне [0, 0x27], поскольку размер каждого раздела составляет 0x28000 байт, и поэтому его можно разделить на 0x28 Views размером 0x1000, и он используется для адресации одного из 0x28 возможных представлений раздела.Этот
view_index сравнивается со счетчиком фактически зафиксированных Views для текущего раздела, который содержится в поле num_commited_views объекта CSectionBitmapAllocator. Как объясняется в MSDN [4], «физическая память не выделяется для представления до тех пор, пока не будет осуществлен доступ к диапазону виртуальной памяти». Если view_index меньше, чем количество зафиксированных представлений, тогда нам не нужно фиксировать новое представление, и мы можем сразу перейти к выделению. В противном случае адрес соответствующего представления вычисляется (first_view + view_index * 0x1000) и фиксируется в физической памяти путем вызова nt!MmCommitSessionMappedView.
Код:
.text:00000001C003988B mov eax, 0AAAAAAABh
.text:00000001C0039890 mul r12d
.text:00000001C0039893 mov eax, [rsi+24h] ; eax = CSectionBitmapAllocator->num_commited_views
.text:00000001C0039896 mov r15d, edx ; HI_DWORD(free_bit_index * 0xaaaaaaab) / 4 == free_bit_index / 6
.text:00000001C0039899 shr r15d, 2 ; r15d = view_index = free_bit_index / 6 (6 SURFACE headers fit in 0x1000 bytes)
.text:00000001C003989D cmp r15d, eax ; view_index < num_commited_views ?
.text:00000001C00398A0 jb loc_1C003998A ; if so, no need to commit a new 0x1000-byte chunk from the View
.text:00000001C00398A6 cmp eax, 28h ; num_commited_views >= MAX_VIEW_INDEX ?
.text:00000001C00398A9 jnb loc_1C003998A
.text:00000001C00398AF mov rbp, [rsi+8]
.text:00000001C00398AF ; rbp = CSectionBitmapAllocator->xored_view
.text:00000001C00398B3 mov edx, r15d ; edx = view_index
.text:00000001C00398B6 xor rbp, [rsi+10h] ; CSectionBitmapAllocator->xored_view ^ CSectionBitmapAllocator->xor_key
.text:00000001C00398BA shl edx, 0Ch ; view_index * 0x1000
.text:00000001C00398BD add rbp, rdx ; rbp = view + view_index * 0x1000
.text:00000001C00398C0 mov edx, 1000h ; edx = size to commit
.text:00000001C00398C5 mov rcx, rbp ; rcx = addr of view to commit
.text:00000001C00398C8 call cs:__imp_MmCommitSessionMappedView
После успешной фиксации 0x1000-байтовое представление инициализируется значением 0 (эта операция записи завершает фактическую фиксацию), и поле num_commited_views CSectionBitmapAllocator обновляется соответствующим образом.
Код:
.text:00000001C0039975 loc_1C0039975: ; CODE XREF: NSInstrumentation::CTypeIsolation<163840,640>::AllocateType(void)+D0j
.text:00000001C0039975 xor edx, edx ; Val
.text:00000001C0039977 mov r8d, 1000h ; Size
.text:00000001C003997D mov rcx, rbp ; Dst
.text:00000001C0039980 call memset ; this memset actually commits the memory
.text:00000001C0039985 inc dword ptr [rsi+24h] ; CSectionBitmapAllocator->num_commited_views++
.text:00000001C0039988 xor ebp, ebp
Либо, если новый View должно быть зафиксировано или нет, индекс бита очистки RTL_BITMAP затем устанавливается в 1 путем вызова
nt!RtlSetBit(), чтобы пометить этот бит как занятый. Как ни странно, код вызывает nt!RtlTestBit() перед установкой бита в 1, но возвращаемое значение вообще не проверяется. Кроме того, поле bitmap_hint_index CSectionBitmapAllocator увеличивается на 1, сбрасывая его в 0, если он превышает максимальное значение 0xF0 - 1.
Код:
.text:00000001C003998A mov rcx, [rsi+18h] ; rcx = CsectionBitmapAllocator->xored_rtl_bitmap
.text:00000001C003998E mov edx, r12d ; BitNumber = free bit index
.text:00000001C0039991 xor rcx, [rsi+10h] ; BitMapHeader = CSectionBitmapAllocator->xored_rtl_bitmap
.text:00000001C0039991 ; ^ CSectionBitmapAllocator->xor_key
.text:00000001C0039995 call cs:__imp_RtlTestBit ; [!] return value not checked
.text:00000001C003999B mov rcx, [rsi+18h] ; rcx = CsectionBitmapAllocator->xored_rtl_bitmap
.text:00000001C003999F mov edx, r12d ; BitNumber
.text:00000001C00399A2 xor rcx, [rsi+10h] ; BitMapHeader = xored_rtl_bitmap ^ xor_key
.text:00000001C00399A6 call cs:__imp_RtlSetBit
.text:00000001C00399AC inc dword ptr [rsi+20h] ; CSectionBitmapAllocator->bitmap_hint_index++
.text:00000001C00399AF cmp dword ptr [rsi+20h], 0F0h ; CSectionBitmapAllocator->bitmap_hint_index >= bitmap size?
.text:00000001C00399B6 jnb short loc_1C0039A27
[...]
.text:00000001C0039A27 loc_1C0039A27: ; CODE XREF: NSInstrumentation::CTypeIsolation<163840,640>::AllocateType(void)+1B6j
.text:00000001C0039A27 mov [rsi+20h], ebp ; CSectionBitmapAllocator->bitmap_hint_index = 0
.text:00000001C0039A2A jmp short loc_1C00399B8
Теперь, когда мы сопоставили наш бит очистки с соответствующим View, нам нужно выбрать блок размером 0x280 байт в этом представлении. Каждое представление может содержать 6 заголовков SURFACE (0x1000 / 0x280 == 6). Для этого выполняется следующий расчет:
free_bit_index - view_index * 6, что просто равно free_bit_index % 6.
Код:
.text:00000001C00399B8 mov rax, [rsi+10h] ; rax = CSectionBitmapAllocator->xor_key
.text:00000001C00399BC mov ecx, r15d ; ecx = view_index
.text:00000001C00399BF mov rsi, [rsi+8] ; rsi = CSectionBitmapAllocator->xored_view
.text:00000001C00399C3 xor edx, edx
.text:00000001C00399C5 shl ecx, 0Ch ; ecx = view_index * 0x1000
.text:00000001C00399C8 xor rsi, rax ; rsi = xored_view ^ xor_key
.text:00000001C00399CB add rsi, rcx ; rsi = view + view_index * 0x1000
.text:00000001C00399CE mov rcx, rbx ; rcx = CSectionBitmapAllocator->pushlock
.text:00000001C00399D1 call cs:__imp_ExReleasePushLockExclusiveEx
.text:00000001C00399D7 call cs:__imp_KeLeaveCriticalRegion
.text:00000001C00399DD lea eax, [r15+r15*2] ; r15 == view_index
.text:00000001C00399E1 add eax, eax
.text:00000001C00399E3 sub r12d, eax ; r12d = free_bit_index - view_index * 6 == free_bit_index % 6
.text:00000001C00399E6 lea ebx, [r12+r12*4]
.text:00000001C00399EA shl ebx, 7 ; ebx = r12 * 0x5 * 0x80 == r12 * 0x280
.text:00000001C00399ED add rbx, rsi ; rbx += view + view_index * 0x1000
Значение, которое RBX получает по адресу 0x1C00399ED, является адресом недавно выделенного заголовка SURFACE, и это значение будет возвращено
CTypeIsolation::AllocateType().Для полноты и, как и было обещано, вот что происходит, когда
nt!RtlFindClearBits() возвращает -1, что означает, что RTL_BITMAP текущего CSectionEntry заполнен. В этом случае выполняется следующий условный переход
Код:
.text:00000001C0039870 mov rcx, [rsi+18h] ; rcx = CSectionBitmapAllocator->xored_rtl_bitmap
.text:00000001C0039874 mov edx, 1 ; NumberToFind
.text:00000001C0039879 xor rcx, [rsi+10h] ; BitMapHeader = xored_rtl_bitmap ^ xor_key
.text:00000001C003987D call cs:__imp_RtlFindClearBits
.text:00000001C0039883 mov r12d, eax ; r12 = free_bit_index
.text:00000001C0039886 cmp eax, 0FFFFFFFFh ; free_bit_index == -1?
.text:00000001C0039889 jz short loc_1C00398D6 ; if so, RTL_BITMAP is full, check another CSectionEntry
Этот переход приводит нас сюда, где он проверяет, есть ли CSectionEntry->next == CTypeIsolation, что означает, что мы достигли конца списка объектов CSectionEntry. Если это не так, он зацикливается и повторяет процесс со следующим объектом CSectionEntry.
Код:
.text:00000001C00398D6 loc_1C00398D6: ; CODE XREF: NSInstrumentation::CTypeIsolation<163840,640>::AllocateType(void)+89j
.text:00000001C00398D6 lea rcx, [rsp+48h+arg_0]
.text:00000001C00398DB call NSInstrumentation::CAutoExclusiveCReaderWriterLock<NSInstrumentation::CPlatformReaderWriterLock>::~CAutoExclusiveCReaderWriterLock<NSInstrumentation::CPlatformReaderWriterLock>(void)
.text:00000001C00398E0 loc_1C00398E0: ; CODE XREF: NSInstrumentation::CTypeIsolation<163840,640>::AllocateType(void)+1F0j
.text:00000001C00398E0 mov r14, [r14] ; r14 = CSectionEntry->next
.text:00000001C00398E3 mov ebp, 0
.text:00000001C00398E8 cmp r14, r13 ; CSectionEntry->next == CTypeIsolation ?
.text:00000001C00398EB jnz loc_1C0039843 ; if not, keep traversing the list
В противном случае, если мы достигли конца списка объектов CSectionEntry, не найдя пустой слот (то есть каждый CSectionEntry содержит максимум заголовков 0xF0 SURFACE), будет достигнут следующий код. Как показано ниже, он создает новый CSectionEntry и вызывает
CSectionBitmapAllocator::Allocate() для члена CSectionBitmapAllocator этого нового CSectionEntry. Как и ожидалось, CSectionBitmapAllocator::Allocate() в основном дублирует процедуру, описанную ранее: он находит чистый бит в RTL_BITMAP, он фиксирует представление размером 0x1000 байт, соответствующее указанному свободному биту, он отмечает этот бит как занятый в RTL_BITMAP и, наконец, он возвращает адрес вновь созданного заголовка SURFACE в зафиксированном представлении.
Код:
.text:00000001C00398F1 loc_1C00398F1: ; CODE XREF: NSInstrumentation::CTypeIsolation<163840,640>::AllocateType(void)+3Dj
.text:00000001C00398F1 xor edx, edx ; if we land here, that means that we finished traversing
.text:00000001C00398F1 ; the list of CSectionEntry, without finding an empty slot
.text:00000001C00398F3 mov rcx, rdi
.text:00000001C00398F6 call cs:__imp_ExReleasePushLockSharedEx
.text:00000001C00398FC call cs:__imp_KeLeaveCriticalRegion
.text:00000001C0039902 call NSInstrumentation::CSectionEntry<163840,640>::Create(void)
.text:00000001C0039907 mov rdi, rax ; rdi = new CSectionEntry
.text:00000001C003990A test rax, rax
.text:00000001C003990D jz short loc_1C003996D
.text:00000001C003990F mov rcx, [rax+20h] ; rcx = CSectionEntry->bitmap_allocator
.text:00000001C0039913 call NSInstrumentation::CSectionBitmapAllocator<163840,640>::Allocate(void) ; *** do the actual SURFACE header allocation
.text:00000001C0039918 mov rbp, rax ; rbp = return value, allocated SURFACE header
Наконец, вновь созданный CSectionEntry вставляется в конец двусвязного списка, как подробно описано ниже. Обратите внимание, что перед работой с указателями списка выполняется проверка целостности: код проверяет, указывает ли следующий указатель CTypeIsolation->previous на заголовок CTypeIsolation.
Код:
.text:00000001C0039939 mov rcx, [r13+8] ; rcx = CTypeIsolation->previous
.text:00000001C003993D cmp [rcx], r13 ; CTypeIsolation->previous->next == CTypeIsolation ?
.text:00000001C0039940 jnz FatalListEntryError_9 ; if not, the list is corrupted
.text:00000001C0039946 mov [rdi+8], rcx ; CSectionEntry->previous = CTypeIsolation->previous
.text:00000001C003994A xor edx, edx
.text:00000001C003994C mov [rdi], r13 ; CSectionEntry->next = CTypeIsolation
.text:00000001C003994F mov [rcx], rdi ; CTypeIsolation->previous->next = CSectionEntry
.text:00000001C0039952 mov rcx, rbx
.text:00000001C0039955 add dword ptr [r13+18h], 0F0h ; CTypeIsolation->size += 0xF0
.text:00000001C003995D mov [r13+8], rdi ; CTypeIsolation->previous = CSectionEntry
Deallocation
Освобождение объектов SURFACE выполняется в функцииwin32kbase!SURFACE::Free(). Эта функция начинается с освобождения выделения пула, который содержит буфер данных пикселей:
Код:
.text:00000001C002DC9A cmp byte ptr [rbp+270h], 0 ; boolean is_kernel_mode_pixel_data_buffer
.text:00000001C002DCA1 loc_1C002DCA1: ; DATA XREF: .rdata:00000001C017D540o
.text:00000001C002DCA1 mov [rsp+48h+arg_8], rbx
.text:00000001C002DCA6 jz short loc_1C002DCCC ; if byte[SURFACE+0x270] == 0, the pixel data buffer is not freed
.text:00000001C002DCA8 mov rbx, [rbp+48h] ; rbx = SURFACE->pvScan0
.text:00000001C002DCAC test rbx, rbx
.text:00000001C002DCAF jz short loc_1C002DCCC
.text:00000001C002DCB1 call cs:__imp_IsWin32FreePoolImplSupported
.text:00000001C002DCB7 test eax, eax
.text:00000001C002DCB9 js short loc_1C002DCC4
.text:00000001C002DCBB mov rcx, rbx
.text:00000001C002DCBE call cs:__imp_Win32FreePoolImpl ; frees the pixel data buffer
После этого он берет заголовок CTypeIsolation и начинает обход двусвязного списка объектов CSectionEntry, пытаясь определить, какой CSectionEntry содержит заголовок SURFACE, который он пытается освободить. Для этого он просто проверяет, есть ли
CSectionEntry->view <= SURFACE <= CSectionEntry-> view + 0x28000. Обратите внимание, что при этой проверке может быть ошибка, поскольку, вероятно, это должно быть CSectionEntry->view <= SURFACE <CSectionEntry-> view + 0x28000 (< вместо <= во втором сравнении).
Код:
.text:00000001C002DCCC mov rax, cs:uchar * * gpTypeIsolation
.text:00000001C002DCD3 mov rsi, [rax] ; rsi = CTypeIsolation head
[...]
.text:00000001C002DD08 mov rbx, [rsi] ; rbx = CTypeIsolation->next
.text:00000001C002DD0B cmp rbx, rsi ; next == CTypeIsolation ?
.text:00000001C002DD0E jz loc_1C002DDFF ; if so, there's no CSectionEntry
.text:00000001C002DD14 mov r12, 0CCCCCCCCCCCCCCCDh
.text:00000001C002DD1E xchg ax, ax
.text:00000001C002DD20 loc_1C002DD20: ; CODE XREF: SURFACE::Free(SURFACE *)+C5j
.text:00000001C002DD20 mov r14, [rbx+20h] ; r14 = CSectionEntry->bitmap_allocator
.text:00000001C002DD24 mov r8, [r14+10h] ; r8 = bitmap_allocator->xor_key
.text:00000001C002DD28 mov rax, r8
.text:00000001C002DD2B xor rax, [r14+8] ; rax = xor_key ^ xored_view
.text:00000001C002DD2F cmp rbp, rax ; SURFACE < view?
.text:00000001C002DD32 jb short loc_1C002DD3F ; ...if so, skip to the next CSectionEntry
.text:00000001C002DD34 add rax, 28000h ; view += section_size
.text:00000001C002DD3A cmp rbp, rax ; SURFACE <= end of last view?
.text:00000001C002DD3D jbe short loc_1C002DD4C ; if so, we found the view containing the SURFACE header
Когда эти условия выполнены, то есть мы обнаружили, что CSectionEntry, содержащий заголовок SURFACE, должен быть освобожден, индекс этой SURFACE в его View контейнере вычисляется (называемый здесь index_within_view), беря 3 младших полубайта адреса SURFACE и разделив их на 0x280:
Код:
.text:00000001C002DD4C loc_1C002DD4C: ; CODE XREF: SURFACE::Free(SURFACE *)+BDj
.text:00000001C002DD4C mov rcx, rbp ; rcx = SURFACE header
.text:00000001C002DD4F mov rax, r12
.text:00000001C002DD52 and ecx, 0FFFh
.text:00000001C002DD58 mul rcx
.text:00000001C002DD5B mov r15, rdx
.text:00000001C002DD5E shr r15, 9 ; r15 = (SURFACE & 0xfff) / 0x280 == index_within_view
.text:00000001C002DD62 lea rax, [r15+r15*4]
.text:00000001C002DD66 shl rax, 7 ; rax = r15 * 0x5 * 0x80 == r15 * 0x280
.text:00000001C002DD6A sub rcx, rax ; if rcx == rax, it's ok
.text:00000001C002DD6D jnz short loc_1C002DD3F
Затем адрес SURFACE необходимо отобразить в битовый индекс, который представляет его в RTL_BITMAP. Чтобы получить соответствующий битовый индекс, он получает view_index (то есть, в котором 0x1000-байтовом View находится этот объект SURFACE), а затем просто выполняет этот расчет:
view_index * 6 + index_within_view.
Код:
.text:00000001C002DD72 mov eax, ebp ; eax = lo_dword(SURFACE)
.text:00000001C002DD74 xor ecx, [r14+8] ; ecx = lo_dword(xor_key) ^ lo_dword(xored_view)
.text:00000001C002DD78 sub eax, ecx ; eax = lo_dword(SURFACE) - lo_dword(view)
.text:00000001C002DD7A mov rcx, [r14+18h] ; rcx = CSectionBitmapAllocator->xored_rtl_bitmap
.text:00000001C002DD7E shr eax, 0Ch ; eax /= 0x1000 == view_index
.text:00000001C002DD81 xor rcx, r8 ; BitMapHeader = xored_rtl_bitmap ^ xor_key
.text:00000001C002DD84 lea eax, [rax+rax*2]
.text:00000001C002DD87 lea edx, [r15+rax*2] ; BitNumber = view_index * 6 + index_within_view
.text:00000001C002DD8B call cs:__imp_RtlTestBit
.text:00000001C002DD91 test al, al
.text:00000001C002DD93 jz short loc_1C002DD3F ; bit is turned off?
Значение рассчитанного битового индекса проверяется с помощью
nt!RtlTestBit(); если он установлен в 1, как и ожидалось, то выполнение продолжается во фрагменте кода ниже. Как показано здесь, он вызывает CSectionBitmapAllocator::ContainsAllocation() (однако логическое значение, возвращаемое этой функцией, вообще не проверяется), а затем очищает соответствующий бит в RTL_BITMAP, вызывая nt!RtlClearBit(), отмечая слот как cвободный. Наконец, он очищает память от освобожденного заголовка SURFACE, вызывая memset(), и битовый индекс свободного слота сохраняется как bitmap_hint_index, чтобы ускорить будущие операции.
Код:
.text:00000001C002DDA9 mov rdx, rbp ; rdx = SURFACE header
.text:00000001C002DDAC mov rcx, r14 ; rcx = bitmap_allocator
.text:00000001C002DDAF call NSInstrumentation::CSectionBitmapAllocator<163840,640>::ContainsAllocation(void const *)
.text:00000001C002DDB4 mov ecx, [r14+8] ; ecx = CSectionBitmapAllocator->xored_view
.text:00000001C002DDB8 mov eax, ebp ; [!] return value from ContainsAllocation() is not checked
.text:00000001C002DDBA xor ecx, [r14+10h] ; CSectionBitmapAllocator->xored_view ^ CSectionBitmapAllocator->xor_key
.text:00000001C002DDBE sub eax, ecx ; eax = lo_dword(SURFACE) - lo_dword(view)
.text:00000001C002DDC0 mov rcx, [r14+18h] ; rcx = CSectionBitmapAllocator->xored_rtl_bitmap
.text:00000001C002DDC4 xor rcx, [r14+10h] ; BitMapHeader = xored_rtl_bitmap ^ xor_key
.text:00000001C002DDC8 shr eax, 0Ch ; eax /= 0x1000 == view_index
.text:00000001C002DDCB lea eax, [rax+rax*2]
.text:00000001C002DDCE lea esi, [r15+rax*2]
.text:00000001C002DDD2 mov edx, esi ; BitNumber = view_index * 6 + index_within_view
.text:00000001C002DDD4 call cs:__imp_RtlClearBit ; mark the slot as available
.text:00000001C002DDDA xor edx, edx ; Val
.text:00000001C002DDDC mov r8d, 280h ; Size
.text:00000001C002DDE2 mov rcx, rbp ; Dst
.text:00000001C002DDE5 call memset ; null-out the freed SURFACE header in the view
.text:00000001C002DDEA xor edx, edx
.text:00000001C002DDEC mov [r14+20h], esi ; bitmap_allocator->bitmap_hint_index = index of freed slot
Расширение Windbg
Во время реверс-инжиниринга Win32k Type Isolation я разработал небольшое расширение WinDbg, которое помогает мне определять состояние структур Type Isolation.
Расширение доступно на: https://github.com/fdfalcon/TypeIsolationDbg
Расширение WinDbg предоставляет следующие команды:
- !gptypeisolation [адрес]: выводит первую структуру CTypeIsolation (адрес по умолчанию:
win32kbase!gpTypeIsolation) - !typeisolation [адрес]: выводит структуру NSInstrumentation::CTypeIsolation
- !sectionentry [адрес]: выводит структуру NSInstrumentation::CSectionEntry
- !sectionbitmapallocator [адрес]: выводит структуру NSInstrumentation::CSectionBitmapAllocator
- !rtlbitmap [адрес]: выводит структуру RTL_BITMAP
Код:
kd> !gptypeisolation
win32kbase!gpTypeIsolation is at address 0xffffe6cf95138a98.
Pointer [1] stored at win32kbase!gpTypeIsolation: 0xffffe6a4400006b0.
Pointer [2]: 0xffffe6a440000680.
NSInstrumentation::CTypeIsolation
+0x000 next : 0xffffe6a440000620
+0x008 previous : 0xffffe6a441d8ca20
+0x010 pushlock : 0xffffe6a440000660
+0x018 size : 0xF00 [number of section entries: 0x10]
kd> !sectionentry ffffe6a440000620
NSInstrumentation::CSectionEntry
+0x000 next : 0xffffe6a441ca2470
+0x008 previous : 0xffffe6a440000680
+0x010 section : 0xffff86855f09f260
+0x018 view : 0xffffe6a4403a0000
+0x020 bitmap_allocator : 0xffffe6a4400005e0
kd> !sectionbitmapallocator ffffe6a4400005e0
NSInstrumentation::CSectionBitmapAllocator
+0x000 pushlock : 0xffffe6a4400005c0
+0x008 xored_view : 0xa410b31c3f332f4c [decoded: 0xffffe6a4403a0000]
+0x010 xor_key : 0x5bef55b87f092f4c
+0x018 xored_rtl_bitmap : 0xa410b31c3f092acc [decoded: 0xffffe6a440000580]
+0x020 bitmap_hint_index : 0xC0
+0x024 num_commited_views : 0x27
kd> !rtlbitmap ffffe6a440000580
RTL_BITMAP
+0x000 size : 0xF0
+0x008 bitmap_buffer : 0xffffe6a440000590
kd> dyb ffffe6a440000590 L20
76543210 76543210 76543210 76543210
-------- -------- -------- --------
ffffe6a4`40000590 00000101 00000000 00000110 10110000 05 00 06 b0
ffffe6a4`40000594 00011100 10000000 11011011 11110110 1c 80 db f6
ffffe6a4`40000598 01111101 11111111 11111111 11111111 7d ff ff ff
ffffe6a4`4000059c 11111111 11011111 11110111 01111111 ff df f7 7f
ffffe6a4`400005a0 11111111 11111111 11111111 01111111 ff ff ff 7f
ffffe6a4`400005a4 11111101 11111001 11111111 01101111 fd f9 ff 6f
ffffe6a4`400005a8 11111110 11111111 11111111 11111111 fe ff ff ff
ffffe6a4`400005ac 11111111 00000011 00000000 00000000 ff 03 00 00
Заключение
Изоляции типов, реализованная в компоненте Win32k Windows 10 1709, изменяет способ выделения объектов Bitmap GDI в пространстве ядра: заголовок SURFACE выделяется в представлении раздела, а буфер данных пикселей выделяется в пуле PagedPoolSession. Это определенно исключает обычную технику эксплуатации, заключающуюся в использовании Bitmap в качестве целей для ограниченных уязвимостей, связанных с повреждением памяти, поскольку больше невозможно сделать выровненный спрей из соседних Bitmap, где за концом буфера данных пикселей сразу же следует заголовок следующего объекта SURFACE
Между тем, разработчики эксплойтов уже перешли на другие полезные объекты ядра, такие как Palettes [5] [6] [7].
Любопытно, что объект CSectionBitmapAllocator сохраняет как указатель на представления разделов, так и указатель на RTL_BITMAP, запутанный с помощью операции XOR, однако родительская структура CSectionEntry сохраняет тот же указатель на представления в обычном виде.
Ссылки
[1] - https://msdn.microsoft.com/en-us/library/dd183377(v=vs.85).aspx
[2] - https://www.coresecurity.com/system...6/10/Abusing-GDI-Reloaded-ekoparty-2016_0.pdf
[3] - https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/section-objects-and-views
[4] - https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/managing-memory-sections
[5] - https://sensepost.com/blog/2017/abusing-gdi-objects-for-ring0-primitives-revolution/
[6] - https://labs.bluefrostsecurity.de/f...ring0_exploit_primitives_Evolution_Slides.pdf
[7] - http://theevilbit.blogspot.com/2017/10/abusing-gdi-objects-for-kernel.html
От ТС
Оригинал доступен тут - https://blog.quarkslab.com/reverse-engineering-the-win32k-type-isolation-mitigation.html
Очень интересная и подробная статья, и не менее интересное расширение для WinDbg
Перевод:
Azrv3l cпециально для xss.pro