Статья ASM. В ядре Windows(3) – объекты USER/GDI и обратные вызовы KernelCallback.

Marylin

Mod.Assembler
Red Team
05.06.2019
319
1 425
BIT
560
В предыдущей статье рассматривались объекты KERNEL, теперь-же ознакомимся с организацией пользовательских объектов USER и GDI, которые полностью удовлетворяют потребностям двумерной графики. Например один только объект типа «Window» охватывает буквально все элементы окна (кроме строки меню), а это: кнопки, поля ввода, всевозможные списки, чекбоксы, и многое другое. В свою очередь объекты GDI обрамляют окна рамками, рисуют простые фигуры, заливают элементы цветом, меняют шрифты, и прочее в том-же духе. В общем здесь есть о чём поговорить.


1. Пулы памяти Windows

Прежде, чем начать разговор о пользовательских объектах, определимся с расположением их в памяти.
Известно, что всей физической ОЗУ управляет системный диспетчер. Его аллокатор может распределять память как фиксированными страницами по 4КБ, так и произвольными блоками внутри этих страниц – это позволяет более экономно расходовать ресурсы. Для выделения больших объёмов юзеру доступна VirtualAlloc(), а запросы для мелких нужд обеспечивает HeapAlloc(). В конечном счёте, обе эти функции обращаются к ядерным NtAllocateVirtualMemory() и RtlAllocateHeap() соответственно. Таким образом Pool – это несколько областей распределённой на данный момент памяти.

В зависимости от назначения и свойств, пулы делятся на Paged (выгружаемый из физ.памяти на диск в pagefile.sys), и закреплённый в ОЗУ невыгружаемый NonPagedPool, к которому может приняться атрибут запрета на исполнение NХ (No eXecute). При старте системы диспетчер создаёт 5 базовых выгружаемых пула 0-4, которые позже могут расширяться динамически. Команда отладчика WinDbg !vm собирает информацию о текущем положении дел с вирт.памятью, и на своей машине я виже такой лог. Здесь «Usage» подразумевает используется, а «Сommit» – изначально выделено (значения за скобками представляют счётчик в 4К байтных пейджах):

Код:
0: kd> !vm 5
*** Virtual Memory Usage ***
        Physical Memory:       521790 (   2087160 Kb)
        Page File: \??\H:\pagefile.sys
          Current:   2087160 Kb  Free Space:   1364748 Kb
          Minimum:   2087160 Kb  Maximum:      6261480 Kb

        NonPagedPool Usage:  97295160 ( 389180640 Kb)
        NonPagedPoolNx Usage:    6716 (     26864 Kb)
        NonPagedPool Maximum:  380442 (   1521768 Kb)

        ********** Excessive NonPaged Pool Usage *****
        PagedPool 0 Usage:      40011 (    160044 Kb)
        PagedPool 1 Usage:       2859 (     11436 Kb)
        PagedPool 2 Usage:        862 (      3448 Kb)
        PagedPool 3 Usage:        829 (      3316 Kb)
        PagedPool 4 Usage:        875 (      3500 Kb)
        PagedPool Usage:        45436 (    181744 Kb)
        PagedPool Commit:       45647 (    182588 Kb)
        PagedPool Maximum:   33554432 ( 134217728 Kb)

        Terminal Server Memory Usage By Session:

        Session ID 0 @ fffff88002e34000:
        Paged Pool Usage:    3784K
        Commit Usage:        7496K

        Session ID 1 @ fffff88002ef3000:
        Paged Pool Usage:   24464K
        Commit Usage:       28440K

Каждого пользователя система запирает в свою Session, внутри каждой сессии находится одна или несколько рабочих станций WorkStation, причём интерактивной (с клавиатурой и мышью) может быть только одна «WinSta0». И наконец в станцию уже помещается один или несколько рабочих столов Desktop. Этой матрёшке была когда-то посвящена статья «Сессии, станции, рабочие столы», поэтому повторяться не будем. Сейчас важно понять, что у каждой сессии свой личный пул памяти, о чём свидетельствуют 2 записи в хвосте лога выше. Начиная с Win7, ОС создаёт закрытую от всех сессию(0) для своих служб «Services», чтобы оградить смертных юзеров от доступа к ним. В данном случае на своём узле зареган только я, а если добавить ещё и юзера с гостем, то сессий в логе было-бы уже 4. Такой расклад приводит к тому, что имеем три основных пула памяти, каждый из которых числится в системе под своим ID – при выделении памяти, драйвер передаёт тип пула в функцию ExAllocatePoolWithTag():

Код:
PagedPool              = 1
NonPagedPool           = 0
NonPagedPoolNx         = 200h

PagedPoolSession       = 21h
NonPagedPoolSession    = 20h
NonPagedPoolSessionNx  = 220h

Обычно пул содержит в себе сотни мелких блоков памяти, общий объём которых может переполнить не одну 4КБ страницу. Поэтому диспетчер ведёт учёт не только каждого из пулов, но и каждой из его страниц, сохраняя инфу в своей структуре POOL_HEADER. Здесь видим индекс блока внутри страницы, его размер, перечисленный выше тип пула, 4-байтный тег для быстрого поиска, а так-же указатель на структуру EPROCESS хозяина:

Код:
0: kd> dt _pool_header
nt!_POOL_HEADER
   +0x000 PreviousSize     : Pos 0,  8 Bits
   +0x000 PoolIndex        : Pos 8,  8 Bits
   +0x000 BlockSize        : Pos 16, 8 Bits
   +0x000 PoolType         : Pos 24, 8 Bits
   +0x004 PoolTag          : Uint4B
   +0x008 ProcessBilled    : Ptr64 _EPROCESS
   +0x00a PoolTagHash      : Uint2B

Собрать информацию о пуле любой сессии можно командой !sprocess отладчика, чтобы получить от него линк на структуру MM_SESSION_SPACE. В ней указывается размер, а в дескрипторе более детальные сведения:

Код:
0: kd> !sprocess
Dumping Session 1

_MM_SESSION_SPACE fffff88002ef3000   <----------//
_MMSESSION        fffff88002ef3b40
......

0: kd> dt _MM_SESSION_SPACE fffff88002ef3000 Paged* Session* Non*
nt!_MM_SESSION_SPACE
   +0x008 SessionId           : 1
   +0x028 SessionPageDirIndex : 0x16f7f
   +0x030 NonPagablePages     : 0x79
   +0x040 PagedPoolStart      : 0xfffff900`c0000000  Void
   +0x048 PagedPoolEnd        : 0xfffff920`bfffffff  Void
   +0x050 SessionObject       : 0xfffffa80`0189d590  Void
   +0x058 SessionObjectHandle : 0xffffffff`80000298  Void
   +0xb40 Session             : _MMSESSION
   +0xb98 PagedPoolInfo       : _MM_PAGED_POOL_INFO
   +0xcc0 PagedPool           : _POOL_DESCRIPTOR   <------------//

0: kd> dt _MM_SESSION_SPACE fffff88002ef3000 PagedPool.
nt!_MM_SESSION_SPACE
   +0xcc0 PagedPool      :
      +0x000 PoolType         : 21 ( PagedPoolSession )
      +0x008 PagedLock        : _KGUARDED_MUTEX
      +0x008 NonPagedLock     : 1
      +0x040 RunningAllocs    : 0n3327649
      +0x044 RunningDeAllocs  : 0n3313992
      +0x048 TotalBigPages    : 0n4508
      +0x04c ThreadsDeferrals : 0n0
      +0x050 TotalBytes       : 0x1ff2180
      +0x080 PoolIndex        : 0
      +0x0c0 TotalPages       : 0n1608
      +0x100 PendingFrees     : 0xfffff900`c2531680  -> 0xfffff900`c273d980 Void
      +0x108 PendingFreeDepth : 0n9
      +0x140 ListHeads        : [256] _LIST_ENTRY [ 0xfffff880`02ef3e00 - 0xfffff880`02ef3e00 ]

Учитывая вышеописанную иерархию Session-->WorkStation-->Desktop можно утверждать, что пользовательские объекты USER находятся в выделенной для рабочего стола кучи Heap, а для объектов GDI резервируется память из невыгружаемого пула NonPagedSession. Передаваемые в функцию ExAllocatePoolWithTag() теги (4 символа) способствуют быстрому поиску блоков внутри огромных пулов, и для объектов GDI они начинаются с буквы(G). Во-второй аргумент команды отладчика !poolused можно передать маску для поиска (опционально), а первый аргумент позволяет сортировать вывод:

Код:
0: kd> !poolused 2 G*  <-----// Сортировка: 2 = NonPaged, 4 = Paged, G* = маска тега.

           NonPaged           Paged
 Tag    Allocs    Used    Allocs    Used
 ----   ------   -----    ------    ----    --------------
 Gsem      331   52832         0       0    Gdi Semaphores
 Gfsm        6     480         0       0    Gdi Fast mutex
 GOPM        4     320         2      96    GDITAG_OPM,                       Binary: win32k.sys
 Gla4        2     288         0       0    GDITAG_HMGR_LOOKASIDE_RGN_TYPE,   Binary: win32k.sys
 Gla;        2     288         0       0    GDITAG_HMGR_LOOKASIDE_RFONT_TYPE, Binary: win32k.sys
 Gla:        2     288         0       0    GDITAG_HMGR_LOOKASIDE_LFONT_TYPE, Binary: win32k.sys
 Gla1        2     288         0       0    GDITAG_HMGR_LOOKASIDE_DC_TYPE,    Binary: win32k.sys
 Gla5        2     288         0       0    GDITAG_HMGR_LOOKASIDE_SURF_TYPE,  Binary: win32k.sys
 Gla8        2     288         0       0    GDITAG_HMGR_LOOKASIDE_PAL_TYPE,   Binary: win32k.sys
 Gla@        2     288         0       0    GDITAG_HMGR_LOOKASIDE_BRUSH_TYPE, Binary: win32k.sys
 Gi2c        0       0         2      96    GDITAG_DDCCI,                     Binary: win32k.sys
 TOTAL     355   55648         4     192

Обратите внимание, что все эти блоки внутри пула сессии создал диспетчер окон win32k.sys, причём отчётливо видны объекты GDI типа: регион, шрифт, контекст устройства вывода DC (монитор или принтер), палитра и кисть. Аналогичную картину можно наблюдать и в окне софта П.Йосифовича «Kernel-Pool-Monitor». Память из невыгружаемого пула обычно выделяется драйверам режима ядра, но для объектов USER и GDI система делает исключение:

GdiTag.png


2. Объекты графики GDI

GDI это интерфейс подсистемы Win32, который позволяет рисовать в приложениях двумерные графические примитивы. К объектам GDI относятся: DC, Pen, Brush, Font, Bitmap, Region, Palette и Metafile, а библиотека Gdi32.dll предоставляет массу функций для работы с ними. В большинстве случаев предполагается, что эти объекты не живут долго – их следует создавать, использовать и немедленно освобождать. Причина в строгом ограничении доступных для каждого процесса объектов, о чём пойдёт речь далее.

В Win7+ объекты GDI хранятся в клиентской части модуля Gdi32.dll. Это означает, что любой объект действителен только в контексте приложения, которое его создало, т.к. у каждого процесса свой экземпляр системных библиотек. Например если создать новую кисть Brush и передать его дескриптор другому приложению, то выбор им нашего объекта через SelectObject() может вернуть OK, однако с вероятностью 99% вместо кисти в контекст устройства попадёт например карандаш Pen. Это потому, что дескрипторы GDI внутренне реализуются как смещения в «глобальной таблице дескрипторов», которая проецируется в пространство каждого процесса.

Указатель на таблицу система прописывает в поле «GdiSharedHandleTable» структуры PEB, а драйвер оконного менеджера на стороне ядра win32k.sys потом оперирует записями в её ячейках cell. Следующей последовательность команд отладчика можно вывести дамп этой таблицы, только подопытным обязательно должно быть приложение с графическим интерфейсом, импортируещее в свою тушку либу Gdi32.dll:

Код:
0: kd> !process 0 0 taskmgr.exe
PROCESS fffffa8001beb060
    SessionId: 1  Cid: 0d5c  Peb: 7fffffdf000  ParentCid: 06a4
    Image: taskmgr.exe

0: kd> .process /p fffffa8001beb060     <-------// Подключиться к процессу
Implicit process is now fffffa80`01beb060

0: kd> dt _peb 7fffffdf000 Gdi*         <-------// Запросить из РЕВ поля по маске
ntdll!_PEB
   +0x0f8 GdiSharedHandleTable : 0x00000000`00670000  <----// линк на таблицу GDI
   +0x108 GdiDCAttributeList   : 0x14
   +0x140 GdiHandleBuffer      : [60] 0

0: kd> db 0x00000000`006700f0 L60
00000000`006700f0  e0 0e 00 c0 00 f9 ff ff-00 00 00 00 04 01 04 40  ...............@
00000000`00670100  00 00 00 00 00 00 00 00-e0 0d 00 c0 00 f9 ff ff  ................
00000000`00670110  00 00 00 00 88 01 08 40-00 00 00 00 00 00 00 00  .......@........
00000000`00670120  30 09 00 c0 00 f9 ff ff-00 00 00 00 08 01 08 40  0..............@
00000000`00670130  00 00 00 00 00 00 00 00-40 08 00 c0 00 f9 ff ff  ........@.......
00000000`00670140  00 00 00 00 08 01 08 40-00 00 00 00 00 00 00 00  .......@........
......

Перед нами фрагмент таблицы дескрипторов GDI текущего процесса. Каждый дескриптор описывает в нём оформленная в структуру запись GDI_TABLE_ENTRY размером 18h байт (полтора строки в дампе), а сама таблица состоит из массива таких записей. Важно отметить, что данная таблица имеет строго фиксированный размер, а не расширяется динамически как большинство системных таблиц. Именно поэтому использовав объект нужно сразу освобождать его. В глобальной таблице сессии зарезервировано место всего для 65.536 дескрипторов, хотя лимит для одного процесса итого меньше 16.384. В ветке реестра HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows имеется ключ «GDIProcessHandleQuota», где по умолчанию выставлена квота в 10.000 хэндлов на процесс, хотя при желании можно подкрутить её в диапазоне 256-16384. Возьмём из дампа выше первые 18h байт, и заполним ими одну запись таблицы:

Код:
struct GDI_TABLE_ENTRY
  pKernelAddress   dq  fffff900`c0000ee0
  wProcessId       dw  0000
  wCount           dw  0000
  wUniq            dw  0104
  wType            dw  4004
  pUserAddress     dq  00000000`00000000
ends

Значит каждая такая структура описывает один объект, тип которого указывается в поле «Type». Информативны в этом поле только 5 мл.бит, поэтому нужно сбрасывать всё остальное маской 1Fh=00011111. В данном случае получаем Type=4, что соответствует объекту типа «Region». В поле «Uniq» лежит счётчик уникальности – диспетчер объектов меняет его, когда данная запись используется повторно после освобождения уже отработанного объекта. Примечательно, что младший байт поля «Uniq» всегда будет совпадать со-значением поля «Type».

32-битное значение Handle, которое возвращает например функция CreateBitmap() состоит из двух 16-битных частей: старшее слово это как раз значение поля «Uniq», а младшее – это индекс (порядковый номер) записи в таблице. Таким образом, данная запись с индексом(1) принадлежит дескриптору Handle со значением 0x01040001, и описывает она графический объект типа «Region». Кстати лимит в 65536 дескрипторов для одной сессии связан именно с 16-битным значением индекса в дескрипторе – этот хвост тянется ещё с 16-битных версий Windows 3.1, обрезать который у инженеров никак не хватает духа.

В поле «Count» лежит счётчик-ссылок на объект – при выборе объекта он взводится в единицу, а при освобождении сбрасывается в нуль, что означает свободную запись Entry. Поле «ProcessId» хранит идентификатор процесса-создателя. Использование чужих объектов GDI как-правило приводит к сбою, за исключением стандартных объектов. Такие объекты как: чёрное перо, белая кисть и системный шрифт являются (в терминологии gdi) стоковыми объектами, и выбираются специальной функцией gdi32!GetStockObject(). Они создаются один раз с предопределёнными свойствами, и могут использоваться во всех процессах – их Pid будет равен нуль.

И наконец в поле «KernelAddress» диспетчер прописывает указатель на объект, структура которого напрямую зависит от его типа. Драйвер win32k.sys держит все объекты у себя в ядерном пространстве, а в юзер-спэйс таблица лишь проецируется (мапится) с атрибутом «Только для чтения». После того-как объект создан в ядре, благодаря отображённой таблице код пользователя получает к нему мгновенный доступ, без дорогостоящих переходов в кернел. Раньше и сами структуры объектов мапились в память юзера, и поле «UserAddress» хранило как-раз линк на них. Но сейчас лавочку прикрыли, а потому адрес юзера будет всегда сброшен в нуль.

Программно обойти таблицу дескрипторов GDI можно следующим образом..
Значит берём из РЕВ поле «GdiSharedHandleTable», и с шагом 18h парсим структуры таблицы.
Если в первом аргументе функции user32!GetGuiResources() передать константу GR_GLOBAL = -2, то получим общее кол-во записей в глобальной таблице на текущий момент. Если-же хотим отфильтровать вывод только для своего процесса, то передаём его Pid предварительным вызовом kernel32!GetCurrentProcessId(). Функция может считать как объекты GDI так и USER, для чего предусмотрен второй аргумент. А в остальном всё элементарно:

C-подобный:
format   pe64 console 5.0
include 'win64ax.inc'
entry    start
;//-----------
section '.data' data readable writable

struct GDI_TABLE_ENTRY   ;//<---- массив структур
  pKernelAddress   dq  0 ;
  wProcessId       dw  0 ;
  wCount           dw  0 ;
  wUniq            dw  0 ;
  wType            dw  0 ;
  pUserAddress     dq  0 ;
ends

typeTable  dd  0x01,@00,0x04,@01,0x05,@02,0x06,@03
           dd  0x08,@04,0x0A,@05,0x10,@06
        
@00   db  '0x01 = DC',0         ;// список типов объектов GDI
@01   db  '0x04 = Region',0
@02   db  '0x05 = Bitmap',0
@03   db  '0x06 = Metafile',0
@04   db  '0x08 = Pallete',0
@05   db  '0x0A = Font',0
@06   db  '0x10 = Brush\Pen',0
@unk  db  'Unknown',0

align 16
GR_GDIOBJ   = 0   ;// запрос объектов GDI
GR_USEROBJ  = 1   ;// запрос объектов USER
GR_GLOBAL   = -2  ;// по всей системе, иначе PID

gdiTable  dq  0
pid       dq  0
index     dq  0
totalObj  dq  0

;//-----------
section '.text' code readable executable
start:   sub     rsp,8

         invoke  GetCurrentProcessId
         mov     [pid],rax
         invoke  GetGuiResources, GR_GLOBAL, GR_GDIOBJ
         mov     [totalObj],rax
         push    rax

         mov     rsi,[gs:0x60]    ;// PEB address
         mov     rax,[rsi+0xF8]   ;// GdiSharedHandleTable
         mov     [gdiTable],rax
         pop     rbx
        cinvoke  printf,<10,' Current process ID....:  0x%04x',\
                         10,' GdiSharedHandleTable..:  0x%016llx',\
                         10,' Total GDI objects.....:  %d',\
                         10,\
                         10,' Index  Pid    Kernel address    Handle   Type',\
                         10,' -----  ----  ----------------  --------  --------------',0>,\
                             [pid],rax,rbx

         mov     rdi,[gdiTable]
@@:      push    rdi
         movzx   eax,word[rdi+GDI_TABLE_ENTRY.wProcessId]
;//         cmp     rax,[pid]    ;// для фильтра своего PID
;//         jnz      @next
         cmp     eax,4           ;// валидный PID начинается с 4
         jb      @next           ;// пропустить, если меньше

         movzx   ebx,word[rdi+GDI_TABLE_ENTRY.wUniq]
         shl     ebx,16          ;// собрать Handle из [Uniq << 16 + Index]
         add     rbx,[index]

         movzx   ebp,word[rdi+GDI_TABLE_ENTRY.wType]
         and     ebp,0x1F

         push    rax             ;// поиск строки по типу
         mov     esi,typeTable
         mov     ecx,7
@type:   lodsd
         cmp     eax,ebp
         je      @found
         add     esi,4
         loop    @type
         mov     ebp,@unk        ;// ошибка!
         jmp     @fuck
@found:  mov     ebp,[esi]
@fuck:   pop     rax

        cinvoke  printf,<10,' %04x   %04x  %016llx  %08x  %s',0>,\
                        [index],rax,qword[rdi],rbx,rbp
@next:   pop     rdi
         add     rdi,18h         ;// sizeof GDI_TABLE_ENTRY
         inc     [index]
         dec     [totalObj]      ;// всю таблицу проверили?
         jnz     @b

@exit:   invoke  GetDC,0         ;// подгрузить либу User32.dll
         invoke  ReleaseDC,0,eax

        cinvoke  _getch
        cinvoke  exit, 0
;//-----------
section '.idata' import data readable
library  kernel32,'kernel32.dll', user32,'user32.dll', msvcrt,'msvcrt.dll'
include 'api\kernel32.inc'
include 'api\user32.inc'
include 'api\msvcrt.inc'

GdiSharedTable.png



3. Пользовательские объекты USER

C объектами USER дела обстоят аналогично, только есть несколько принципиальных отличий.
Все пользовательские объекты индексируются в таблице дескрипторов для каждого сеанса. Сама таблица хранится в ядре, и проецируется в каждый процесс с графическим интерфейсом GUI. Как и в случае с объектами GDI, такая схема позволяет процессам получать доступ на чтение ядерных структур прямо из пользовательского режима, без необходимости прибегать к системному вызову syscall. Указатель на таблицу хранится в структуре общей информации ядра win32k!tagSHAREDINFO, а указатель на саму SHAREDINFO возвращает как переменная ядра win32k!gSharedInfo, так и пользовательская функция user32!gSharedInfo().

sPool.png

Структура ядра SHAREDINFO довольно творческая единица, и в ней зарыт огромный объём информации. Отложим пока на полку таблицу дескрипторов USER, и посмотрим на содержимое шары. Для этого в отладчике нужно присоединиться к любому процессу с оконным интерфейсом GUI, например к тому-же диспетчеру задач Win taskmgr.exe. Далее запрашиваем значение переменной win32k!gSharedInfo, и дампим структуру ядра:

Код:
0: kd> !process 0 0 taskmgr.exe
PROCESS fffffa800210b1f0
    SessionId: 1   Cid: 0df8  Peb: 7fffffde000  ParentCid: 069c
    Image: taskmgr.exe

0: kd> .process /p fffffa800210b1f0    <-----// подключились к процессу
Implicit process is now fffffa80`0210b1f0

0: kd> x win32k!gSharedInfo
fffff960`003ce520  win32k!gSharedInfo  <-----// переменная ядра

0: kd> dt win32k!tagSHAREDINFO fffff960`003ce520
   +0x000 psi               : 0xfffff900`c0580a70  tagSERVERINFO
   +0x008 aheList           : 0xfffff900`c0400000 _HANDLEENTRY
   +0x010 HeEntrySize       : 0x18
   +0x018 pDispInfo         : 0xfffff900`c0581e50  tagDISPLAYINFO
   +0x020 ulSharedDelta     : 0
   +0x028 awmControl        : [31] _WNDMSG
   +0x218 DefWindowMsgs     : _WNDMSG
   +0x228 DefWindowSpecMsgs : _WNDMSG
0: kd>

Значит в поле «aheList» лежит указатель на таблицу дескрипторов, которая содержит в себе массив структур HANDLEENTRY с описанием каждого из объектов USER, а в сл.поле «HeEntrySize» размер одной структуры 18h байт. Мы вернёмся к этому массиву чуть позже, а сейчас запросим инфу о сервере, в качестве которого выступает ядро. Поскольку структура большая, я оставил в ней только наиболее интересные поля:

Код:
0: kd> dt win32k!tagSERVERINFO 0xfffff900`c0580a70
   +0x008  cHandleEntries        : 0x2c00       <-------// Всего дескрипторов в таблице USER
   +0x188  apfnClientA           : _PFNCLIENT
   +0x38c  dwDefaultHeapSize     : 0x01400000   <-------// Размер хипа в дефолте
   +0x754  dwInstalledEventHooks : 0x8001       <-------// Кол-во установленных в системе хуков
   +0xad0  hbrGray               : 0x00ff9933`00f2e4d7  HBRUSH__
   +0xad8  ptCursor              : tagPOINT
   +0xb14  tmSysFont             : tagTEXTMETRICW
   +0xb80  hIcoWindows           : 0x00000000`0110007a  HICON__

Обратите внимание, что объект «BRUSH» (кисть) принадлежит к GDI и находится в ядре, а объект иконки окна «ICON» уже в пространстве юзера. Это говорит о том, что не все объекты создаются в ядре – некоторые из них порождает сама либа User32.dll. В поле «apfnClientA» хранится массив указателей на внутренние функции оконного менеджера, которые использует данный процесс. Поскольку я под отладчиком режима ядра, он не может распознать имена пользовательских функций, и предоставляет только их адреса:

Код:
0: kd> dt win32k!tagSERVERINFO 0xfffff900`c0580a70 apfnClientA.
   +0x188 apfnClientA  :
      +0x000 pfnScrollBarWndProc      : 0x00000000`77572ff4
      +0x008 pfnTitleWndProc          : 0x00000000`7751e5a4
      +0x010 pfnMenuWndProc           : 0x00000000`77573014
      +0x018 pfnDesktopWndProc        : 0x00000000`77573034
      +0x020 pfnDefWindowProc         : 0x00000000`7751e5a4
      +0x028 pfnMessageWindowProc     : 0x00000000`7751e5a4
      +0x030 pfnSwitchWindowProc      : 0x00000000`77573054
      +0x038 pfnButtonWndProc         : 0x00000000`77573074
      +0x040 pfnComboBoxWndProc       : 0x00000000`77573094
      +0x048 pfnComboListBoxProc      : 0x00000000`775730b4
      +0x050 pfnDialogWndProc         : 0x00000000`775730d4
      +0x058 pfnEditWndProc           : 0x00000000`775730e4
      +0x060 pfnListBoxWndProc        : 0x00000000`77573104
      +0x068 pfnMDIClientWndProc      : 0x00000000`77573124
      +0x070 pfnStaticWndProc         : 0x00000000`77573144
      +0x078 pfnImeWndProc            : 0x00000000`77573164
      +0x080 pfnGhostWndProc          : 0x00000000`77573174
      +0x088 pfnHkINLPCWPSTRUCT       : 0x00000000`77573194
      +0x090 pfnHkINLPCWPRETSTRUCT    : 0x00000000`775731a4
      +0x098 pfnDispatchHook          : 0x00000000`775731c4
      +0x0a0 pfnDispatchDefWindowProc : 0x00000000`775731e4
      +0x0a8 pfnDispatchMessage       : 0x00000000`77573204
      +0x0b0 pfnMDIActivateDlgProc    : 0x00000000`77573214

Ну и теперь поговорим о таблице дескрипторов, на которую указывает поле aheList = 0xfffff900`c0400000.
Общее кол-во записей в таблице лежит в поле «HandleEntries» структуры SERVERINFO, и в данном случае видим в нём значение 2c00h = 11.264, некоторые из которых могут быть свободные. Поскольку таблица объектов USER поддерживается на стороне сервера win32k.sys, ограничение на кол-во объектов в таблице является глобальным – начиная с Win7 оно составляет 64К записей Entry. Как и в случае с GDI, лимит в диапазоне 256...65536 можно выставить в указанной выше ветке реестра.

Учитывая размер одной HANDLEENTRY = 18h байт, добавляя его к базе можно сдампить например первые три записи из таблицы USER. Как видим первая пустая и содержит нули, а вот следущие уже хотят о чём-то нам поведать:

Код:
0: kd> dt win32k!_HANDLEENTRY 0xfffff900`c0400000
   +0x000 phead            : (null) <---- указатель на заголовок объекта в ядре
   +0x008 pOwner           : (null) <---- линк на структуру W32THREAD владельца
   +0x010 bType            : 0      <---- тип объекта (см.табл.ниже)
   +0x011 bFlags           : 0      <---- флаги
   +0x012 wUniq            : 1      <---- счётчик уникальности объекта

0: kd> dt win32k!_HANDLEENTRY 0xfffff900`c0400000 + 0x18
   +0x000 phead            : 0xfffff900`c05824b0 _HEAD
   +0x008 pOwner           : (null)
   +0x010 bType            : 0xc    <---- тип «Montor»
   +0x011 bFlags           : 0
   +0x012 wUniq            : 1

0: kd> dt win32k!_HANDLEENTRY 0xfffff900`c0400000 + 0x18 + 0x18
   +0x000 phead            : 0xfffff900`c08ddd20 _HEAD
   +0x008 pOwner           : 0xfffff900`c0849870 Void
   +0x010 bType            : 0x1    <---- тип «Window»
   +0x011 bFlags           : 0x40
   +0x012 wUniq            : 1

0: kd> dt win32k!_W32THREAD 0xfffff900`c0849870
   +0x000 pEThread         : 0xfffffa80`03306060 _ETHREAD
   +0x008 RefCount         : 1
   +0x010 ptlW32           : (null)
   +0x018 pgdiDcattr       : 0x00000000`00110680 Void
........

В записях объектов GDI имелось поле Pid, по значению которого можно было искать в таблице объекты только своего процесса. В дескрипторах HANDLEENTRY уже такого флага нет – здесь свои объекты мы можем обнаружить только прочитав указатель в поле «Owner» (владелец). Не беда, что из пользовательского процесса у нас нет доступа к ядерной win32k!_W32THREAD – значение «Owner» система специально для юзера дублирует в поле «Win32ThreadInfo» структуры TEB потока. Если команде !process отладчика передать аргумент(4), она выводит инфу не только о процессах, но и всех его потоках (см.первую строку в блоке):

Код:
0: kd> !process 0 4 taskmgr.exe
PROCESS fffffa800215bb10
    SessionId: 1  Cid: 0bac   Peb: 7fffffdf000  ParentCid: 06a8
    Image: taskmgr.exe

      THREAD fffffa8001b98b50  Cid 0bac.0b24  Teb: 000007fffffdd000  Win32Thread: fffff900c1ecb5e0 WAIT <----//
      THREAD fffffa8001ac0b50  Cid 0bac.0794  Teb: 000007fffffdb000  Win32Thread: fffff900c204f010 WAIT
      THREAD fffffa8002097b50  Cid 0bac.060c  Teb: 000007fffffd7000  Win32Thread: fffff900c2e1dc30 WAIT
      THREAD fffffa8001a9e640  Cid 0bac.08e8  Teb: 000007fffffd5000  Win32Thread: fffff900c2b5c9a0 WAIT
      THREAD fffffa800379eaf0  Cid 0bac.0510  Teb: 000007fffffd3000  Win32Thread: 0000000000000000 WAIT
      THREAD fffffa80020f8b50  Cid 0bac.06e0  Teb: 000007fffffae000  Win32Thread: 0000000000000000 WAIT

Теперь прочитав поле «Owner» в записи HANDLEENTRY и сравнив его со-своим, мы сможем отфильтровать принадлежащие нашему процессу дескрипторы:

Код:
0: kd> dt _teb 000007fffffdd000 Win32*
ntdll!_TEB
   +0x078 Win32ThreadInfo : 0xfffff900`c1ecb5e0  <-----//
   +0x800 Win32ClientInfo : [62] 0x188
0: kd>

В данном случае в третьей записи таблицы видим Owner = 0xfffff900`c0849870, а у себя получили Win32ThreadInfo = 0xfffff900`c1ecb5e0. От сюда следует, что дескриптор(3) чужой и не принадлежит USER-объекту нашего процесса. Командой отладчика !pool можно узнать, в каком из пулов система выделила ему память – это PagedSessionPool:

Код:
0: kd> !pool 0xfffff900`c1ecb5e0
Pool page fffff900c1ecb5e0 region is Paged session pool  <--------//

 fffff900c1ecb000 size:  330  previous size:    0  (Allocated)  Uspi  Process: fffffa80032dbb10
 fffff900c1ecb330 size:   30  previous size:  330  (Free)       ....
 fffff900c1ecb360 size:   d0  previous size:   30  (Allocated)  Gla@  win32k.sys
 fffff900c1ecb430 size:   10  previous size:   d0  (Free)       GTmp  win32k.sys
 fffff900c1ecb440 size:   a0  previous size:   10  (Allocated)  Gh1?  win32k.sys
 fffff900c1ecb4e0 size:   f0  previous size:   a0  (Allocated)  Gla8  win32k.sys
*fffff900c1ecb5d0 size:  3e0  previous size:   f0  (Allocated) *Usti  Process: fffffa800215bb10

          Pooltag Usti:  USERTAG_THREADINFO, Binary : win32k!AllocateW32Thread
0: kd>

Вернёмся к структуре HANDLEENTRY и разберём её поля, чтобы позже сдампить их массив программно.
По сути названия всех полей говорят сами за себя, и к этому трудно что-то добавить. Как и в случае с GDI, 32-битный дескриптор Handle собирается из двух 16-битных частей – в старшем слово кладём значение «Uniq», а в младшее порядковый номер (индекс) записи в таблице, т.е Uniq << 16 + Index. Всего в системе числится 21-тип пользовательских объектов, при этом флаги нам не интересны:

C-подобный:
struct HANDLEENTRY  ;//<----- размер 18h байт
  phead   dq  0     ;// линк на заголовок объекта
  pOwner  dq  0     ;// линк на структуру W32THREAD владельца
  bType   db  0     ;// тип объекта из таблицы ниже
  bFlags  db  0     ;// флаги
  wUniq   dw  0     ;// счётчик уникальности объекта
ends


ObjType.png


HndlSchema.png

Значит чтобы подобраться к таблице под юзером, нужно запросить у либы user32.dll адрес её функции gSharedInfo(), через вызов двойки LoadLibrary() + GetProcAddress(). Так мы получим указатель не на ядерный оригинал таблицы дескрипторов, а на доступную только для чтения её копию в пользовательском пространстве (см.рисунок с отображением памяти выше). Поскольку вызов GetProcAddress() всегда возвращает адрес из указанного нами модуля, выходит, что win32k.sys мапит таблицу в юзер-мод именно в пространство библиотеки user32.dll.

Вот собственно и код сбора инфы об объектах USER. На старте он запрашивает тип объекта для вывода в диапазоне 1-21, после чего проходит по всей таблице поиском. Кстати работать будет только на системах Win7+, т.к. либа user32.dll на WinХР и ниже не экспортирует функцию gSharedInfo():

C-подобный:
format   pe64 console 5.0
include 'win64ax.inc'
entry    start
;//-----------
section '.data' data readable writable

struct HANDLEENTRY
  phead      dq  0
  pOwner     dq  0
  bType      db  0
  bFlags     db  0
  wUniq      dw  0
ends
struct SERVERINFO
  dwSRVIFlags     dq  0
  cHandleEntries  dq  0
ends
struct SHAREDINFO
  psi             dq  0  ;// tagSERVERINFO
  aheList         dq  0  ;// HANDLEENTRY
  HeEntrySize     dq  0  ;// sizeof HANDLEENTRY
ends

align 16
typeTable  dd  @00,@01,@02,@03,@04,@05,@06,@07,@08,@09,@10
           dd  @11,@12,@13,@14,@15,@16,@17,@18,@19,@20,@21
        
@00 db '00 = Free',0
@01 db '01 = Window',0
@02 db '02 = Icon\Menu',0
@03 db '03 = Cursor',0
@04 db '04 = SetWindowPos',0
@05 db '05 = Hook',0
@06 db '06 = ClipData',0
@07 db '07 = CallProc',0
@08 db '08 = Acceltable',0
@09 db '09 = DDE.Access',0
@10 db '10 = DDE.Conv',0
@11 db '11 = DDE.Xact',0
@12 db '12 = Monitor',0
@13 db '13 = KBD.Layout',0
@14 db '14 = KBD.File',0
@15 db '15 = EventHook',0
@16 db '16 = Timer',0
@17 db '17 = InputContext',0
@18 db '18 = HID.Data',0
@19 db '19 = DeviceInfo',0
@20 db '20 = TouchInput',0
@21 db '21 = GestureInfo',0

pid       dq  0
index     dq  0
totalObj  dd  0
found     dd  0
bType     dd  0

;//-----------
section '.text' code readable executable
start:   sub     rsp,8
         invoke  SetConsoleTitle,<' *** User object list from SharedInfo ***',0>

;// Принимаем у юзера тип объекта
@@:     cinvoke  printf,<10,' Input object type (1-21): ',0>
        cinvoke  scanf,<'%d',0>,bType
         cmp     [bType],21
         jbe     @f
        cinvoke  printf,<' Error - Invalid type!',10,0>
         jmp     @b

@@:      invoke  GetCurrentProcessId
         mov     [pid],rax

;// Берём указатель на таблицу дескрипторов
         invoke  LoadLibrary,'user32.dll'
         invoke  GetProcAddress,rax,'gSharedInfo'
         push    rax

;// Узнаем общее кол-во записей в таблице
         mov     rbx,[rax]
         mov     rbx,[rbx+SERVERINFO.cHandleEntries]
         mov     [totalObj],ebx

        cinvoke  printf,<10,' Current process ID:  0x%04x',\
                         10,' SharedInfo address:  0x%016llx',\
                         10,' Total objects.....:  %d',\
                         10,\
                         10,' Index    Object Head     Owner ThreadInfo    Handle      Type',\
                         10,' -----  ----------------  ----------------  ----------  ----------',0>,\
                         [pid],rax,rbx

;// Парсим таблицу дескрипторов!
         pop     rsi
         mov     rsi,[rsi+SHAREDINFO.aheList]
@@:      push    rsi
         movzx   eax,byte[rsi+HANDLEENTRY.bType]
         cmp     eax,[bType]
         jne     @next

         inc     [found]
         mov     edi,typeTable   ;// переводим тип в строку
         shl     eax,2
         add     edi,eax

         mov     eax,[edi]
         movzx   ebx,word[rsi+HANDLEENTRY.wUniq]
         shl     ebx,16          ;// соберём хэндл из двух составляющих
         add     rbx,[index]

        cinvoke  printf,<10,' %04x   %016llx  %016llx  0x%08x  %s',0>,\
                        [index],[rsi+HANDLEENTRY.phead],[rsi+HANDLEENTRY.pOwner],ebx,eax
@next:   pop     rsi
         add     rsi,18h         ;// sizeof HANDLEENTRY
         inc     [index]
         dec     [totalObj]      ;// конец таблицы?
         jnz     @b              ;// jump if non zero

        cinvoke  printf,<10,10,' -----------------------',\
                            10,' Found: %d entries',0>,[found]

@exit:   invoke  GetDC
        cinvoke  _getch
        cinvoke  exit, 0
;//-----------
section '.idata' import data readable
library  kernel32,'kernel32.dll', user32,'user32.dll',msvcrt,'msvcrt.dll'
include 'api\kernel32.inc'
include 'api\user32.inc'
include 'api\msvcrt.inc'

UserObjeList.png


4. Kernel Callback Table

Под занавес рассмотрим ещё один механизм, который напрямую связан с объектами USER и GDI.
В силу того, что поддержка графических окон размазана чуть-ли не по всей системе Windows, половина служебных структур находится непосредственно в ядерной win32.sys, а половина заброшена на «вражескую» территорию юзера. Как результат, не только код пользовательской подсистемы Win32 должен обращаться к сервисам ядра, но и наоборот кернелу требуется звать на помощь функции из user32.dll. Звучит необычно, но это факт, которым злоупотребляет достаточное кол-во малвари.

Список нужных оконному менеджеру пользовательских функций приличный (порядка 100 штук на Win7), и чтобы постоянно не вычислять их адреса, драйвер win32k.sys создаёт специальный их лист, указатель на который прописывает в поле «KernelCallbackTable» структуры РЕВ. Таблица создаётся динамически при каждой загрузке user32.dll в пространство нашего приложения. Функции из этой таблицы колбэков являются внутренними, и либа user32.dll не экспортирует их из своей тушки напрямую.

В отладчике WinDbg можно собрать всю информацию о данном механизме обратных вызовов, включая полный список самих функций. Для этого нужно переключиться в режим юзера, и загрузить какого-нибудь клиента с графическим интерфейсом GUI. Далее ставим брейк на точку-входа, сразу даём команду go и делаем один шаг p, чтобы все либы импорта проинициализировались. Команды можно передавать прямо в одной строке, разделяя их символом ; . Получив из РЕВ указатель на «KernelCallbackTable», командой !address соберём о нём доп.сведения – угу, область модуля user32.dll с атрибутом ReadOnly. Кстати сам win32k.sys запрашивает адрес таблицы у юзера в виде переменной user32!apfnDispatch, после чего сохраняет этот адрес в РЕВ – команда ln подтверждает сей факт:

Код:
0:000> bp @$exentry; g; p; dt _peb @$peb KernelCallbackTable
Breakpoint 0 hit
ntdll!_PEB
   +0x058 KernelCallbackTable : 0x00000000`775f9500  Void

0:000> ln 0x00000000`775f9500
(00000000`775f9500)  user32!apfnDispatch  | (00000000`775fa730) user32!szIndicDLL

0:000> !address 0x00000000`775f9500
Usage:              Image
Allocation Base:    00000000`77570000
Base Address:       00000000`775f2000
End Address:        00000000`77602000
Region Size:        00000000`00010000
Type:               01000000  MEM_IMAGE
State:              00001000  MEM_COMMIT
Protect:            00000002  PAGE_READONLY
More info:          lmv m user32
More info:          !lmi user32
More info:          ln 0x775f9500

Так мы получили указатель на таблицу обратных вызовов ядра, и теперь командой dqs можем просмотреть её содержимое.

Код:
0:000> dqs 0x00000000`775f9500 L100

00000000`771a9500  00000000`77126f74  user32!_fnCOPYDATA
00000000`771a9508  00000000`7716f760  user32!_fnCOPYGLOBALDATA
00000000`771a9510  00000000`771367fc  user32!_fnDWORD
00000000`771a9518  00000000`7712cb7c  user32!_fnNCDESTROY
00000000`771a9520  00000000`7713f470  user32!_fnDWORDOPTINLPMSG
00000000`771a9528  00000000`7716f878  user32!_fnINOUTDRAG
00000000`771a9530  00000000`771485a0  user32!_fnGETTEXTLENGTHS
00000000`771a9538  00000000`7716fb9c  user32!_fnINCNTOUTSTRING
00000000`771a9540  00000000`7712b790  user32!_fnPOUTLPINT
00000000`771a9548  00000000`7716fa60  user32!_fnINLPCOMPAREITEMSTRUCT
00000000`771a9550  00000000`77130710  user32!_fnINLPCREATESTRUCT
00000000`771a9558  00000000`7713cd30  user32!_fnINLPDELETEITEMSTRUCT
00000000`771a9560  00000000`771480d4  user32!_fnINLPDRAWITEMSTRUCT
00000000`771a9568  00000000`7716fab0  user32!_fnINLPHLPSTRUCT
00000000`771a9570  00000000`7716fab0  user32!_fnINLPHLPSTRUCT
00000000`771a9578  00000000`7716f8cc  user32!_fnINLPMDICREATESTRUCT
00000000`771a9580  00000000`7713edb4  user32!_fnINOUTLPMEASUREITEMSTRUCT
00000000`771a9588  00000000`77132624  user32!_fnINLPWINDOWPOS
00000000`771a9590  00000000`77130a3c  user32!_fnINOUTLPWINDOWPOS
00000000`771a9598  00000000`771455fc  user32!_fnINOUTLPSCROLLINFO
00000000`771a95a0  00000000`7712bbf0  user32!_fnINOUTLPRECT
00000000`771a95a8  00000000`771306ac  user32!_fnINOUTNCCALCSIZE
00000000`771a95b0  00000000`77130a3c  user32!_fnINOUTLPWINDOWPOS
00000000`771a95b8  00000000`7716f930  user32!_fnINPAINTCLIPBRD
00000000`771a95c0  00000000`7716f9c4  user32!_fnINSIZECLIPBRD
00000000`771a95c8  00000000`7713e910  user32!_fnINDESTROYCLIPBRD
00000000`771a95d0  00000000`77129de0  user32!_fnINSTRING
00000000`771a95d8  00000000`77129de0  user32!_fnINSTRING
00000000`771a95e0  00000000`77125a20  user32!_fnINDEVICECHANGE
00000000`771a95e8  00000000`7712ab90  user32!_fnPOWERBROADCAST
00000000`771a95f0  00000000`77145934  user32!_fnINOUTNEXTMENU
00000000`771a95f8  00000000`7713ee08  user32!_fnOPTOUTLPDWORDOPTOUTLPDWORD
00000000`771a9600  00000000`7713ee08  user32!_fnOPTOUTLPDWORDOPTOUTLPDWORD
00000000`771a9608  00000000`7716f700  user32!_fnOUTDWORDINDWORD
00000000`771a9610  00000000`7713f394  user32!_fnOUTLPRECT
00000000`771a9618  00000000`7712b790  user32!_fnPOUTLPINT
00000000`771a9620  00000000`7716fab0  user32!_fnINLPHLPSTRUCT
00000000`771a9628  00000000`7712b790  user32!_fnPOUTLPINT
00000000`771a9630  00000000`7716f7ec  user32!_fnSENTDDEMSG
00000000`771a9638  00000000`77137f50  user32!_fnINOUTSTYLECHANGE
00000000`771a9640  00000000`7712f63c  user32!_fnHkINDWORD
00000000`771a9648  00000000`771401bc  user32!_fnHkINLPCBTACTIVATESTRUCT
00000000`771a9650  00000000`7713f918  user32!_fnHkINLPCBTCREATESTRUCT
00000000`771a9658  00000000`7716fe00  user32!_fnHkINLPDEBUGHOOKSTRUCT
00000000`771a9660  00000000`771485f0  user32!_fnHkINLPMOUSEHOOKSTRUCTEX
00000000`771a9668  00000000`7716fc98  user32!_fnHkINLPKBDLLHOOKSTRUCT
00000000`771a9670  00000000`7716fcf0  user32!_fnHkINLPMSLLHOOKSTRUCT
00000000`771a9678  00000000`7713483c  user32!_fnHkINLPMSG
00000000`771a9680  00000000`7716fc4c  user32!_fnHkINLPRECT
00000000`771a9688  00000000`7716fd3c  user32!_fnHkOPTINLPEVENTMSG
00000000`771a9690  00000000`77143070  user32!_ClientCopyDDEIn1
00000000`771a9698  00000000`77142ea0  user32!_ClientCopyDDEIn2
00000000`771a96a0  00000000`77142f00  user32!_ClientCopyDDEOut1
00000000`771a96a8  00000000`7716ffdc  user32!_ClientCopyDDEOut2
00000000`771a96b0  00000000`77131ee4  user32!_ClientCopyImage
00000000`771a96b8  00000000`7713d4ac  user32!_ClientEventCallback
00000000`771a96c0  00000000`77170244  user32!_ClientFindMnemChar
00000000`771a96c8  00000000`7716fe58  user32!_ClientFreeDDEHandle
00000000`771a96d0  00000000`77148644  user32!_ClientFreeLibrary
00000000`771a96d8  00000000`77121774  user32!_ClientGetCharsetInfo
00000000`771a96e0  00000000`7716ff68  user32!_ClientGetDDEFlags
00000000`771a96e8  00000000`771700c8  user32!_ClientGetDDEHookData
00000000`771a96f0  00000000`7713ea0c  user32!_ClientGetListboxString
00000000`771a96f8  00000000`77139144  user32!_ClientGetMessageMPH
00000000`771a9700  00000000`77125fc0  user32!_ClientLoadImage
00000000`771a9708  00000000`77130bec  user32!_ClientLoadLibrary
00000000`771a9710  00000000`771318c0  user32!_ClientLoadMenu
00000000`771a9718  00000000`77123e50  user32!_ClientLoadLocalT1Fonts
00000000`771a9720  00000000`771702a0  user32!_ClientPSMTextOut
00000000`771a9728  00000000`77170308  user32!_ClientLpkDrawTextEx
00000000`771a9730  00000000`771484f0  user32!_ClientExtTextOutW
00000000`771a9738  00000000`7712b728  user32!_ClientGetTextExtentPointW
00000000`771a9740  00000000`771701c4  user32!_ClientCharToWchar
00000000`771a9748  00000000`77123f14  user32!_ClientAddFontResourceW
00000000`771a9750  00000000`7713a824  user32!_ClientThreadSetup
00000000`771a9758  00000000`77170424  user32!_ClientDeliverUserApc
00000000`771a9760  00000000`7717039c  user32!_ClientNoMemoryPopup
00000000`771a9768  00000000`77135be0  user32!_ClientMonitorEnumProc
00000000`771a9770  00000000`7712ddd4  user32!_ClientCallWinEventProc
00000000`771a9778  00000000`77139344  user32!_ClientWaitMessageExMPH
00000000`771a9780  00000000`7712ba08  user32!_ClientWOWGetProcModule
00000000`771a9788  00000000`77170568  user32!_ClientWOWTask16SchedNotify
00000000`771a9790  00000000`771217c0  user32!_ClientImmLoadLayout
00000000`771a9798  00000000`771489a8  user32!_ClientImmProcessKey
00000000`771a97a0  00000000`77170438  user32!_fnIMECONTROL
00000000`771a97a8  00000000`7713f608  user32!_fnINWPARAMDBCSCHAR
00000000`771a97b0  00000000`771485a0  user32!_fnGETTEXTLENGTHS
00000000`771a97b8  00000000`7716fb14  user32!_fnINLPKDRAWSWITCHWND
00000000`771a97c0  00000000`771229ec  user32!_ClientLoadStringW
00000000`771a97c8  00000000`77190b94  user32!_ClientLoadOLE
00000000`771a97d0  00000000`771909f8  user32!_ClientRegisterDragDrop
00000000`771a97d8  00000000`77190a54  user32!_ClientRevokeDragDrop
00000000`771a97e0  00000000`77170510  user32!_fnINOUTMENUGETOBJECT
00000000`771a97e8  00000000`7713f404  user32!_ClientPrinterThunk
00000000`771a97f0  00000000`77144924  user32!_fnOUTLPCOMBOBOXINFO
00000000`771a97f8  00000000`77145660  user32!_fnOUTLPSCROLLBARINFO
00000000`771a9800  00000000`77145934  user32!_fnINOUTNEXTMENU
00000000`771a9808  00000000`771459e0  user32!_fnINLPUAHDRAWMENUITEM
00000000`771a9810  00000000`77145934  user32!_fnINOUTNEXTMENU
00000000`771a9818  00000000`77145988  user32!_fnINOUTLPUAHMEASUREMENUITEM
00000000`771a9820  00000000`77145934  user32!_fnINOUTNEXTMENU
00000000`771a9828  00000000`77145730  user32!_fnOUTLPTITLEBARINFOEX
00000000`771a9830  00000000`771705b8  user32!_fnTOUCH
00000000`771a9838  00000000`77170624  user32!_fnGESTURE
00000000`771a9840  00000000`7716fab0  user32!_fnINLPHLPSTRUCT
00000000`771a9848  00000000`00000000
00000000`771a9850  00000000`771429f8  user32!ButtonWndProcWorker
00000000`771a9858  00000000`77163c6c  user32!ComboBoxWndProcWorker
00000000`771a9860  00000000`771865b0  user32!ListBoxWndProcWorker
00000000`771a9868  00000000`771476d8  user32!DefDlgProcWorker
00000000`771a9870  00000000`77178ba4  user32!EditWndProcWorker
00000000`771a9878  00000000`771865b0  user32!ListBoxWndProcWorker
00000000`771a9880  00000000`771412a0  user32!MDIClientWndProcWorker
00000000`771a9888  00000000`771974cc  user32!StaticWndProcWorker
00000000`771a9890  00000000`77130ed0  user32!ImeWndProcWorker
00000000`771a9898  00000000`77148ce4  user32!DefWindowProcWorker
00000000`771a98a0  00000000`7712f610  user32!CtfHookProcWorker
00000000`771a98a8  00000000`00000000
00000000`771a98c0  00000000`77162884  user32!ScrollBarWndProcW
00000000`771a98c8  00000000`771374d4  user32!DefWindowProcW
00000000`771a98d0  00000000`771626d8  user32!MenuWndProcW
00000000`771a98d8  00000000`771625ac  user32!DesktopWndProcW
00000000`771a98e0  00000000`771374d4  user32!DefWindowProcW
00000000`771a98e8  00000000`771374d4  user32!DefWindowProcW
00000000`771a98f0  00000000`771374d4  user32!DefWindowProcW
00000000`771a98f8  00000000`77142940  user32!ButtonWndProcW
00000000`771a9900  00000000`77165e80  user32!ComboBoxWndProcW
00000000`771a9908  00000000`77187804  user32!ComboListBoxWndProcW
00000000`771a9910  00000000`7714768c  user32!DefDlgProcW
00000000`771a9918  00000000`77178af8  user32!EditWndProcW
00000000`771a9920  00000000`77187804  user32!ComboListBoxWndProcW
00000000`771a9928  00000000`77141250  user32!MDIClientWndProcW
00000000`771a9930  00000000`77197ca8  user32!StaticWndProcW
00000000`771a9938  00000000`77131160  user32!ImeWndProcW
00000000`771a9940  00000000`771374d4  user32!DefWindowProcW
00000000`771a9948  00000000`77125388  user32!fnHkINLPCWPSTRUCTW
00000000`771a9950  00000000`771621b0  user32!fnHkINLPCWPRETSTRUCTW
00000000`771a9958  00000000`77127950  user32!DispatchHookW
00000000`771a9960  00000000`7712ccac  user32!DispatchDefWindowProcW
00000000`771a9968  00000000`7713726c  user32!DispatchClientMessage
00000000`771a9970  00000000`7718ed04  user32!MDIActivateDlgProcW
00000000`771a9978  00000000`00000000
00000000`771a9980  00000000`77162868  user32!ScrollBarWndProcA
00000000`771a9988  00000000`7712b830  user32!DefWindowProcA
00000000`771a9990  00000000`771626bc  user32!MenuWndProcA
00000000`771a9998  00000000`77162590  user32!DesktopWndProcA
00000000`771a99a0  00000000`7712b830  user32!DefWindowProcA
00000000`771a99a8  00000000`7712b830  user32!DefWindowProcA
00000000`771a99b0  00000000`7712b830  user32!DefWindowProcA
00000000`771a99b8  00000000`7715f1f8  user32!ButtonWndProcA
00000000`771a99c0  00000000`77165db4  user32!ComboBoxWndProcA
00000000`771a99c8  00000000`77187758  user32!ComboListBoxWndProcA
00000000`771a99d0  00000000`77173218  user32!DefDlgProcA
00000000`771a99d8  00000000`77178a4c  user32!EditWndProcA
00000000`771a99e0  00000000`77187758  user32!ComboListBoxWndProcA
00000000`771a99e8  00000000`77190480  user32!MDIClientWndProcA
00000000`771a99f0  00000000`77197c58  user32!StaticWndProcA
00000000`771a99f8  00000000`7719e520  user32!ImeWndProcA
00000000`771a9a00  00000000`7712b830  user32!DefWindowProcA
00000000`771a9a08  00000000`771486f8  user32!fnHkINLPCWPSTRUCTA
00000000`771a9a10  00000000`7719cab0  user32!fnHkINLPCWPRETSTRUCTA
00000000`771a9a18  00000000`7714878c  user32!DispatchHookA
00000000`771a9a20  00000000`7719cbec  user32!DispatchDefWindowProcA
00000000`771a9a28  00000000`7713726c  user32!DispatchClientMessage
00000000`771a9a30  00000000`7718ed04  user32!MDIActivateDlgProcW
00000000`771a9a38  00000074`736f6847
00000000`771a9a40  0073006f`00680047
00000000`771a9a48  00000000`00000074

Функции из библиотек подсистемы Win32 переходят в ядро инструкцией syscall, но она не предусмотрена для обратных переходов из ядра к юзеру. Поэтому здесь применяется механизм трамплинов на основе стековых кадров, которые описывает ядерная структура KTRAP_FRAME. По сути это дальний переход инструкцией ret far. Операцию начинает nt!KeUserModeCallback() с таким прототипом:

C-подобный:
NTSTATUS
KeUserModeCallback (
    IN ULONG   ApiNumber,      ;// индекс функции в таблице
    IN PVOID   InputBuffer,    ;// данные для передачи (копируются в стек юзера)
    IN ULONG   InputLength,    ;// их размер
    OUT PVOID *OutputBuffer,   ;// адрес приёмного буфа
    IN PULONG  OutputLength    ;// его размер
    )

Далее вызывается nt!ProbeForWrite() для проверки, достаточно-ли места в стеке для передаваемых данных, после чего содержимое InputBuffer копируется в стек юзера. Внутренняя функция nt!KiCallUserMode() подготавливает переход в пользовательский режим, сохраняя важную информацию в стеке ядра. Непосредственно вызов функции из таблицы осуществляет nt!KeUserCallbackDispatcher(), а nt!NtCallbackReturn() возвращается обратно в ядро. Команда uf отладчика умеет дизасмить функции, а если передать ей аргументы /c /i, то получим только цепочку вызовов с общим кол-во инструкций – удобно для анализа взаимосвязей:

Код:
0: kd> uf /c /i nt!KeUserModeCallback
nt!KeUserModeCallback (fffff800`02f64f1c), 126 instructions

    call to nt!ProbeForWrite           (fffff800`02f93f80)
    call to nt!RtlpCopyExtendedContext (fffff800`02c691c4)
    call to nt!memmove                 (fffff800`02c6cb50)
    call to nt!KiCallUserMode          (fffff800`02c6d7d0)
    call to win32k!NtGdiFlushUserBatch (fffff960`0016cb3c)
    call to nt!KeBugCheckEx            (fffff800`02c76180)
0: kd>

Так чем-же примечательны эти обратные вызовы ядра?
Во-первых, поскольку таблица лежит в пространстве нашего приложения, то снять запрет на запись в неё можно элементарно через VirtualProtect(). Теперь если подменить в ней указатели на свои процедуры, то ничего не подозревающее ядро будет передавать на них управление. В списке есть довольно много функций, которые можно перехватывать таким образом, например почти все с префиксом user32!_ClientXXX. В частности user32!_ClientLoadLibrary() позволит прямо из ядра незаметно загружать в процессы юзера сторонние DLL. В общем возможности здесь ограничивает только фантазия.


Заключение

Придумать идеальную архитектуру операционной системы не так просто – если заткнёшь дыру в одном месте, она открывается в другом. Организация поддержки объектов USER и GDI прямое этому доказательство. Жили ведь до Win-NT спокойно, когда диспетчер форточек полностью находился в пространстве юзера. Но потом лёгким движением руки инженеры отправили диспетчера в ядро, оставив всех его подопечных на чужбине пользовательского пространства. Такая организация несёт потенциальные угрозы, исключить которые полностью в принципе не возможно. По ссылкам ниже лежат пдфки, где описываются некоторые уязвимости модуля win32k.sys. Всем удачи, до скорого.



 

Вложения

  • User_Gdi_ObjectList.zip
    2,2 КБ · Просмотры: 15
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!