• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Реверсинг изоляции типов в Win32k

Azrv3l

win32kfull
Эксперт
Регистрация
30.03.2019
Сообщения
215
Реакции
539
Учитывая популярность объектов GDI Bitmap для эксплуатации уязвимостей ядра - из-за того, что практически любой вид уязвимости, связанной с повреждением памяти (за исключением записи NULL), может быть использован для надежного получения произвольных примитивов чтения/записи в памяти ядра путем злоупотребления Bitmap - Microsoft решила убить технику эксплуатации, основанную на bitmap. Для этого в Windows 10 Fall Creators Update (также известном как Windows 10 1709) была введена функция изоляции типов, предотвращение эксплуатации в подсистеме Win32k, которая разделяет структуру памяти объектов SURFACE, внутреннее представление bitmap в ядре. В этом посте подробно рассказывается о том, как реализована изоляция типов.

Примечание

Первоначально этот анализ проводился с использованием 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 в целях эксплуатации, повредив элементы, упомянутые ранее:
  1. И GetBitmapBits, и SetBitmapBits GDI API работают с буфером данных пикселей, на который указывает член pvScan0 структуры SURFOBJ. Перезапись этого указателя обеспечивает произвольную перезапись памяти ядра из пользовательского режима.
  2. Поле sizlBitmap структуры SURFOBJ содержит свойства ширины и высоты bitmap. Перезаписывая sizlBitmap.cx или sizlBitmap.cy, можно «увеличить» буфер данных пикселей. Это обеспечивает доступ для чтения / записи к памяти ядра за пределами буфера данных пикселей.
Оба способа заканчиваются настройкой схемы Manager/Worker, в которой задействованы 2 объекта Bitmap; Если вы хотите глубже изучить эту тему, я рекомендую прочитать слайды из презентации конференции Ekoparty 2016 года под названием «Abusing GDI for ring0 exploit primitives: Reloaded» [2], написанные Диего Хуаресом и Николасом Эконому.

Первый метод обеспечивает полные возможности чтения/записи. Его недостаток в том, что для него требуется «хорошая» уязвимость (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;

Следующая диаграмма пытается прояснить отношения между всеми задействованными структурами данных.

1.png


На этом рисунке представлено гипотетическое состояние структур 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
Выходные данные расширения включают несколько интерактивных ссылок, которые помогут вам следить за структурами данных Type Isolation. Он также декодирует указатели XORed, чтобы сэкономить вам время. В следующем фрагменте кода показаны выходные данные TypeIsolationDbg при выгрузке глобального объекта CTypeIsolation и следовании структурам данных для одного CSectionEntry, вплоть до карты битов, представляющих занятое/свободное состояние слотов CSectionEntry:
Код:
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
 
А ты сам шаришь в этом всём или чисто переводишь только?
Не считаю себя спецом в эксплуатации ядра win, но если бы ничего не понимал - сам бы не взялся переводить.
У меня и авторская статья есть, на тему эксплуатации GDI объектов.
 
Последнее редактирование:


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх