Try   HackMD

интёрналсы окон ебучих

Здесь будут собраны мои мучения по WinDBG и всему такому, удачных бессонных ночей в ядре : )

Как там эти дебаг символы то загружать

Есть несколько вариантов:

  1. переменная среды
  2. ручной релоад символов
  3. мой странный, но в целом убодный способ с папкой юзера
  1. Сетаем переменную среды _NT_SYMBOL_PATH в путь с отладочными символами для windbg
    Примеры что-то вроде такого:
    SRV*C:\ms_symbols*https://msdl.microsoft.com/download/symbols;c:\symbols

*** кэшироваться будут в C:\ProgramData\dbg\sym\
*** если что-то не работает - !sym noisy будет выводиться больше инфы

    1. папка ms_symbols - выкачиваем сюда все pdb microsoft'a
      .symfix C:\ms_symbols
      .reload /f
    2. папка symbols - наши кастомные pdb'шки
      .sympath+ C:\symbols
      .reload
  1. Мой странный, но рабочий (остальные постоянно ломаются) способ
    Windbg на хосте ищет папку с дебаг символами, но обычно компилим мы на самой вартуалке, поэтому можем просто создать такую же папку на хосте, типо C:\Users\user\Source\Repos\driver1\x64\Release
    И спокойно положить в неё наш driver1.pdb
    При обновлении и перекомпиляции всё, что нам необходимо, так это просто закинуть новый файл с символами
    профит

Ищем EPROCESS и ETHREAD, отнимаем байтики

Каждый процесс представлен структурой EPROCESS (executive process block) в ядре

EPROCESS указывает на число связанных структур, например: у каждого процесса есть 1 или более потоков, которые представляются структурой ETHREAD

EPROCESS указывает на PEB (process environment block) в адресном пространстве процесса

ETHREAD указывает на TEB (thread environment block) в адресном пространстве процесса

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Что есть в PEB и TEB

PEB - Process Environment Block

  • базовая информация об образе (базовый адрес, значение версии, список модулей)
  • информации о куче в процессе
  • переменные среды
  • параметры командной строки
  • путь для поиска DLL

Чтобы отобразить:

  • !peb
  • dt nt!_PEB

Чтобы посмотреть для чужого процесса:

kd> .process /p ffffe20fb340e080; !peb 10f90d5000
предварительно получив список процессов с адресами: !process 0 0

TEB - Thread Environment Block

  • информация о стеке (база стека, лимит стека)
  • TLS (thread local storage) массив

Чтобы отобразить:

  • !teb
  • dt nt!_TEB

Посмотреть потоки другого процесса:

kd> !process 0 4 processname.exe
kd> dt nt!_KTHREAD ffffe20faeb39080

Получаем руками все процессы:

Общая идея примерно такая:

  1. Из PsActiveProcessHead получаем адрес, где лежит head списка процессов
  2. Само значение, которое мы получили, уже является ссылкой на следующий объект списка (то есть FLINK - forward link)
  3. Адрес, который мы получили - это структура LIST_ENTRY, в которой два поля: на след объект и на предыдущий, сам по себе этот полученный адрес лежит в середине структуры процесса в ActiveProcessLinks
  4. Получаем начало структуры процесса отнимая от полученного адреса сдвиг до начала
kd> x nt!PsActiveProcessHead

fffff803`5f01df60 nt!PsActiveProcessHead = <no type information>
kd> dq fffff803`5f01df60

fffff803`5f01df60  ffffca86`79a5b488 ffffca86`806b5788
fffff803`5f01df70  00000000`000001f4 00000000`00000000
fffff803`5f01df80  00000000`00000000 00000000`00000000
fffff803`5f01df90  fffff803`5ed07ea0 00000000`00000000
kd> dt nt!_EPROCESS
...
+0x448 ActiveProcessLinks : _LIST_ENTRY
...
kd> ?ffffca86`79a5b488 - 0x448

Evaluate expression: -58796061380544 = ffffca86`79a5b040
kd> dt nt!_EPROCESS ffffca86`79a5b040
(как прувнуть, что всё ок)
...
+0x5a8 ImageFileName    : [15]  "System"
...

Как получить следующие в списке процессы?

  • Берём значение из ActiveProcessLinks
  • первый адрес - flink, второй - blink
  • отнимаем оффсет
  • дампим структуру

Автоматизация:

dt nt!_EPROCESS -l ActiveProcessLinks.Flink ffffca86`79a5b040

Получаем руками все потоки процесса:

kd> !process 0 0
...
PROCESS ffffca8680630080
    SessionId: 1  Cid: 19cc    Peb: 002ff000  ParentCid: 0abc
    DirBase: 135b3000  ObjectTable: ffffb60d17072980  HandleCount:  57.
    Image: threads.exe
...
kd> dt nt!_EPROCESS ffffca8680630080
...
+0x5e0 ThreadListHead   : _LIST_ENTRY [ 0xffffca86`818bc9e8 - 0xffffca86`7bf5e9e8 ]
...
kd> dt nt!_ETHREAD
...
+0x4e8 ThreadListEntry  : _LIST_ENTRY
...
kd> ?0xffffca86`818bc9e8 - 0x4e8

Evaluate expression: -58795928861440 = ffffca86`818bc500
kd> dt nt!_ETHREAD -l ThreadListEntry.Flink -y Thread ffffca86`818bc500
...
ThreadListEntry.Flink at 0xffffca86`818bc500
+0x4e8 ThreadListEntry : _LIST_ENTRY [ 0xffffca86`851e59e8 - 0xffffca86`80630660 ]
...
ThreadListEntry.Flink at 0xffffca86`851e5500
+0x4e8 ThreadListEntry : _LIST_ENTRY [ 0xffffca86`7e37c9e8 - 0xffffca86`818bc9e8 ]
...
ThreadListEntry.Flink at 0xffffca86`7e37c500
+0x4e8 ThreadListEntry : _LIST_ENTRY [ 0xffffca86`7bf5e9e8 - 0xffffca86`851e59e8 ]
...
ThreadListEntry.Flink at 0xffffca86`7bf5e500
+0x4e8 ThreadListEntry : _LIST_ENTRY [ 0xffffca86`80630660 - 0xffffca86`7e37c9e8 ]
...

напочитать:

Английская статья, откуда брал почти весь материал и пара ссылок на структуры

Полазаем по стеку и куче в ядре

Структура стека:

Что мы можем прочитать на msdn:

  • Each new thread receives its own stack space, consisting of both committed and reserved memory
  • By default, each thread uses 1 Mb of reserved memory, and one page of committed memory
  • The system will commit one page block from the reserved stack memory as needed. (see MSDN CreateThread > dwStackSize > "Thread Stack Size")

Посчитаем руками, сколько у нас commited и reserved memory

kd> !teb
TEB at 000000000033f000
...
StackBase:            0000000000700000
StackLimit:           00000000006fc000
...
kd> dt nt!_TEB DeallocationStack 33f000

+0x1478 DeallocationStack : 0x00000000`00500000 Void
StackBase - StackLimit = commited memory
kd> ?700000 - 6fc000
Evaluate expression: 16384 = 00000000`00004000
0x1000 - одна страница памяти
0x1000 = 4096 = 4Кб -> 0x4000 - 4 страницы памяти или 16Кб

StackBase - DeallocationStack = reserved memory
kd> ?700000 - 500000
Evaluate expression: 2097152 = 00000000`00200000
0x1000 - одна страница памяти
0x1000 = 4096 = 4Кб -> 0x200000 / 0x1000 = 512 страниц памяти или 2Мб

Как растёт стек

  • The ESP register points to the current stack location of a thread
  • If a program attempts to access an address within a guard page, the system raises a STATUS_GUARD_PAGE_VIOLATION (0x80000001) exception. A guard page provides a one-shot alarm for memory page access
  • If a stack grows until the end of reserved memory, a STATUS_STACK_OVERFLOW is raised

Заполним стек

kd> !teb
...
StackBase:            0000000000700000
StackLimit:           00000000006fc000
...

kd> dt nt!_TEB -y DeallocationStack 000000000033f000
+0x1478 DeallocationStack : 0x00000000`00500000 Void

kd> ?0000000000700000 - 00000000006fc000
Evaluate expression: 16384 = 00000000`00004000

//TODO прописать заполнение адреса 

А что там с кучей то?

If page heap is disabled (а он по дефолту выключен), apply the following structs:

  • _HEAP struct
    • defined в ntdll.dll: dt nt!_HEAP
    • for every HeapCreate there is a unique _HEAP
    • !heap -p -all to get addresses for all _HEAP structs in process
  • _HEAP_ENTRY struct
    • defined in ntdll: dt nt!_HEAP_ENTRY
    • for every HeapAlloc there is a unique _HEAP_ENTRY
    • !heap -p -all to get addresses for all heap entries in process

If page heap is enabled, apply the following structs:

  • _DPH_HEAP_ROOT struct
    • defined в ntdll.dll: dt nt!_DPH_HEAP_ROOT
    • for every HeapCreate there is a unique _DPH_HEAP_ROOT
    • !heap -p -all to get addresses for all heap roots in process
      • Usually address of a _DPH_HEAP_ROOT = value of HeapHandle + 0x1000
  • _DPH_HEAP_BLOCK struct
    • defined in ntdll: dt nt!_DPH_HEAP_BLOCK
    • for every HeapAlloc there is a unique _DPH_HEAP_BLOCK
    • !heap -p -all to get addresses for all heap blocks in process

Кто вызвал HeapAlloc?

Включаем stack traces и page heap для процесса:

  • Либо в gui gflags.exe тыкаем Create user mode stack trace database и Enable page heap
  • gflags.exe /i <IMAGE.EXE> +ust +hpa

Меняем контекст процесса:

kd> .process /i ffffe78c24835240
You need to continue execution (press 'g' <enter>) for the context
to be switched. When the debugger breaks in again, you will be in
the new process context.

kd> g
Break instruction exception - code 80000003 (first chance)
nt!DbgBreakPointWithStatus:
fffff803`7d9ff050 cc              int     3

Идем до места, где вызываем HeapAlloc и получаем адрес возврата, который является DPH_HEAP_BLOCK

kd> !heap -p -a 282bff41000
    address 00000282bff41000 found in
    _DPH_HEAP_ROOT @ 282bfd11000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                             282bfd17478:      282bff41000             2000 -      282bff40000             4000
ReadMemory error for address 00000282bff41000
    00007ff97ca6867b ntdll!RtlDebugAllocateHeap+0x000000000000003b
    00007ff97c99d255 ntdll!RtlpAllocateHeap+0x00000000000000f5
    00007ff97c99b44d ntdll!RtlpAllocateHeapInternal+0x0000000000000a2d
    00007ff72cfa107a +0x00007ff72cfa107a
    00007ff72cfa12e0 +0x00007ff72cfa12e0
    00007ff97bcd7034 +0x00007ff97bcd7034
    00007ff97c9c2651 ntdll!RtlUserThreadStart+0x0000000000000021
kd> dt ntdll!_DPH_HEAP_BLOCK StackTrace 282bfd17478
   +0x060 StackTrace : 0x00000282`be539400 _RTL_TRACE_BLOCK
посмотрим на stack trace

kd> dq /c1 0x00000282`be539400 L10
00000282`be539400  00000000`00000000
00000282`be539408  00070000`00003801        0033:00007ff9`7ca68675  call    qword ptr [ntdll!_guard_dispatch_icall_fptr (00007ff9`7caf3000)]
00000282`be539410  00007ff9`7ca6867b ---->  0033:00007ff9`7ca6867b  mov     rbx,qword ptr [rsp+70h]

                                            0033:00007ff9`7c99d250  call    ntdll!RtlDebugAllocateHeap (00007ff9`7ca68640)
00000282`be539418  00007ff9`7c99d255 ---->  0033:00007ff9`7c99d255  jmp     ntdll!RtlpAllocateHeap+0xc6 (00007ff9`7c99d226)

                                            0033:00007ff9`7c99b448  call    ntdll!RtlpAllocateHeap (00007ff9`7c99d160)
00000282`be539420  00007ff9`7c99b44d ---->  0033:00007ff9`7c99b44d  mov     rdi,rax


непосредственный вызов функи HeapAlloc из моего кода
                                            0033:00007ff7`2cfa1074  call    qword ptr [00007ff7`2cfa2008] (HeapAlloc)
00000282`be539428  00007ff7`2cfa107a ---->  0033:00007ff7`2cfa107a  call    qword ptr [00007ff7`2cfa2010] ds:002b:00007ff7`2cfa2010=00007ff97bcd5bb0 (GetProcessHeap)

                                            0033:00007ff7`2cfa12db  call    00007ff7`2cfa1000 (main из crt start)
00000282`be539430  00007ff7`2cfa12e0 ---->  0033:00007ff7`2cfa12e0  mov     ebx,eax
00000282`be539438  00007ff9`7bcd7034

                                             ntdll!RtlUserThreadStart:
                                             0033:00007ff9`7c9c2630  sub     rsp,78h
                                             0033:00007ff9`7c9c2634  mov     r9,rcx
                                             0033:00007ff9`7c9c2637  mov     rax,qword ptr [ntdll!Kernel32ThreadInitThunkFunction (00007ff9`7cad9ff0)]
                                             0033:00007ff9`7c9c263e  test    rax,rax
                                             0033:00007ff9`7c9c2641  je      ntdll!RtlUserThreadStart+0x23 (00007ff9`7c9c2653)
                                             0033:00007ff9`7c9c2643  mov     r8,rdx
                                             0033:00007ff9`7c9c2646  mov     rdx,rcx
                                             0033:00007ff9`7c9c2649  xor     ecx,ecx
                                             0033:00007ff9`7c9c264b  call    qword ptr [ntdll!_guard_dispatch_icall_fptr (00007ff9`7caf3000)]
00000282`be539440  00007ff9`7c9c2651 ----->  0033:00007ff9`7c9c2651  jmp     ntdll!RtlUserThreadStart+0x43 (00007ff9`7c9c2673)
00000282`be539448  00000000`00000000
00000282`be539450  00000000`00000000
00000282`be539458  00000000`00000000
00000282`be539460  00000000`00000000
00000282`be539468  00000000`00000000
00000282`be539470  00000000`00000000
00000282`be539478  00000000`00000000

Ищем утечки памяти на хипе

Summary about memory usage for your process.
If RegionUsageHeap or RegionUsagePageHeap is growing constantly, then you might have a memory leak on the heap. Proceed with the following steps.
!address --summary

Сделаем фиктивную утечку памяти и посмотрим на неё, можно что-то в этом духе:

До аллокации

kd> !heap -stat -h 0
Allocations statistics for
heap @ 0000021a6eb40000
group-by: TOTSIZE max-display: 20
    size     #blocks     total     ( %) (percent of total busy bytes)
    1000 3 - 3000  (27.66)
    1200 1 - 1200  (10.37)
    c38 1 - c38  (7.04)
    120 9 - a20  (5.83)
    400 2 - 800  (4.61)
    200 4 - 800  (4.61)
    100 8 - 800  (4.61)
    790 1 - 790  (4.36)
    6de 1 - 6de  (3.96)
    1d8 3 - 588  (3.19)
    470 1 - 470  (2.56)
    228 2 - 450  (2.48)
    390 1 - 390  (2.05)
    50 b - 370  (1.98)
    348 1 - 348  (1.89)
    238 1 - 238  (1.28)
    10 1d - 1d0  (1.04)
    20 c - 180  (0.86)
    168 1 - 168  (0.81)
    158 1 - 158  (0.77)
Allocations statistics for
 heap @ 0000021a6e8e0000
group-by: TOTSIZE max-display: 20
    size     #blocks     total     ( %) (percent of total busy bytes)
После аллокации

kd> !heap -stat -h 0
Allocations statistics for
heap @ 0000021a6eb40000
group-by: TOTSIZE max-display: 20
    size     #blocks     total     ( %) (percent of total busy bytes)
    2625a00 1 - 2625a00  (99.89)
    1000 3 - 3000  (0.03)
    1200 1 - 1200  (0.01)
    c38 1 - c38  (0.01)
    120 9 - a20  (0.01)
    400 2 - 800  (0.01)
    200 4 - 800  (0.01)
    100 8 - 800  (0.01)
    790 1 - 790  (0.00)
    6de 1 - 6de  (0.00)
    1d8 3 - 588  (0.00)
    470 1 - 470  (0.00)
    228 2 - 450  (0.00)
    390 1 - 390  (0.00)
    50 b - 370  (0.00)
    348 1 - 348  (0.00)
    238 1 - 238  (0.00)
    10 20 - 200  (0.00)
    20 c - 180  (0.00)
    168 1 - 168  (0.00)
Allocations statistics for
 heap @ 0000021a6e8e0000
group-by: TOTSIZE max-display: 20
    size     #blocks     total     ( %) (percent of total busy bytes)
Найдём все аллокации с нашим размером 2625a00
kd> !heap -flt s 2625a00 
    _DPH_HEAP_ROOT @ 297e9ed1000
    Freed and decommitted blocks
      DPH_HEAP_BLOCK : VirtAddr VirtSize
    Busy allocations
      DPH_HEAP_BLOCK : UserAddr  UserSize - VirtAddr VirtSize
        00000297e9edb068 : 00000297ec170600 0000000002625a00 - 00000297ec170000 0000000002627000
    _HEAP @ 297eaf60000
    _DPH_HEAP_ROOT @ 297eb061000
    Freed and decommitted blocks
      DPH_HEAP_BLOCK : VirtAddr VirtSize
    Busy allocations
      DPH_HEAP_BLOCK : UserAddr  UserSize - VirtAddr VirtSize
    _HEAP @ 297ec160000
kd> !heap -p -a 00000297ec170600 
    address 00000297ec170600 found in
    _DPH_HEAP_ROOT @ 297e9ed1000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                             297e9edb068:      297ec170600          2625a00 -      297ec170000          2627000
    00007ff97ca6867b ntdll!RtlDebugAllocateHeap+0x000000000000003b
    00007ff97c99d255 ntdll!RtlpAllocateHeap+0x00000000000000f5
    00007ff97c99b44d ntdll!RtlpAllocateHeapInternal+0x0000000000000a2d
    00007ff97a8cfde6 ucrtbase!_malloc_base+0x0000000000000036
    00007ff724531717 threads+0x0000000000001717
    00007ff724531058 threads+0x0000000000001058
    00007ff7245314f8 threads+0x00000000000014f8
    00007ff97bcd7034 KERNEL32!BaseThreadInitThunk+0x0000000000000014
    00007ff97c9c2651 ntdll!RtlUserThreadStart+0x0000000000000021

Critical Sections

kd> !cs
...
-----------------------------------------
DebugInfo          = 0x00000297e9f95fd0
Critical section   = 0x00007ff97a52d000 (KERNELBASE!ConsoleStateLock+0x0)
NOT LOCKED
LockSemaphore      = 0x0
SpinCount          = 0x0000000000000000
-----------------------------------------
...
kd> !cs -s -o 0x00007ff97a52d000
-----------------------------------------
Critical section   = 0x00007ff97a52d000 (KERNELBASE!ConsoleStateLock+0x0)
DebugInfo          = 0x00000297e9f95fd0
NOT LOCKED
LockSemaphore      = 0x0
SpinCount          = 0x0000000000000000


Stack trace for DebugInfo = 0x00000297e9f95fd0:

0x00007ff97a2b0cee: KERNELBASE!_KernelBaseBaseDllInitialize+0x44E
0x00007ff97a2b071d: KERNELBASE!KernelBaseDllInitialize+0xD
0x00007ff97c989a1d: ntdll!LdrpCallInitRoutine+0x61
0x00007ff97c9dc1e7: ntdll!LdrpInitializeNode+0x1D3
0x00007ff97c9dbf7a: ntdll!LdrpInitializeGraphRecurse+0x42
0x00007ff97c9dc000: ntdll!LdrpInitializeGraphRecurse+0xC8
0x00007ff97c9ad937: ntdll!LdrpPrepareModuleForExecution+0xBF
0x00007ff97c98fbae: ntdll!LdrpLoadDllInternal+0x19A
0x00007ff97c9873e4: ntdll!LdrpLoadDll+0xA8
0x00007ff97c986af4: ntdll!LdrLoadDll+0xE4
0x00007ff97ca4372f: ntdll!LdrpInitializeProcess+0x1ACF
0x00007ff97c9e4cdb: ntdll!LdrpInitialize+0x15F
0x00007ff97c9e4b63: ntdll!LdrpInitialize+0x3B
0x00007ff97c9e4b0e: ntdll!LdrInitializeThunk+0xE

напочитать:

Классная преза, с кучей полезного по windbg

Pseudo-Registers and Expressions in WinDbg

  • Virtual registers provided by the debugger
  • Begin with a dollar sign ($)
  1. Automatic pseudo-registers

    • are set by the debugger to certain useful values
    • examples: $ra, $peb, $teb, ..
  2. User-defined pseudo-registers

    • there are twenty user-defined registers: $t0, $t1, $t2, .., $t19
    • integer variables that can be used to store intermediate data
    • can additionally hold type-information
    • r? assigns a typed result to an lvalue
      • r? $t0 = @peb->ProcessParameter
        • Assigns a typed value to $t0
        • $t0’s type is remembered so it can be used in further expressions
      • ?? @$t0->CommandLine

Automatic Pseudo-Registers

Команда Пояснение к ней
$ra Return address currently on the stack.
Useful in execution commands, i.e.: “g $ra”
$ip The instruction pointer
x86 = EIP, Itanium = IIP, x64 = RIP
$exentry Entry point of the first executable of the current process
$retreg Primary return value register
x86 = EAX, Itanium = ret0, x64 = rax
$csp Call stack pointer
X86 = ESP, Itanium = BSP, x64 = RSP
$peb Address of the process environment block (PEB)
$teb Address of the thread environment block (TEB) of current thread
$tpid Process ID (PID)
$tid Thread ID (tID)
$ptrsize Size of a pointer
$pagesize Number of bytes in one page of memory

Expressions

  1. MASM expressions

    • evaluated by the ? command
    • each symbol is treated as an addresses (the numerical value of a symbol is the memory address of that symbol to get its value you must dereference it with poi)
    • source line expressions can be used (myfile.c:43)
    • the at sign for register values is optional (eax or @eax are both fine)
    • used in almost all examples in WinDbg’s help
    • the only expression syntax used prior to WinDbg version 4.0 of Debugging Tools
    • The numerical value of any symbol is its memory address
    • Any operator can be used with any number
    • Numerals: are interpreted according to the current radix: n [8 | 10 | 16]
      Can be overridden by a prefix: 0x (hex), 0n (decimal), 0t (octal), 0y (binary)
  2. C++ expressions

    • evaluated by the ?? command
    • symbols are understood as appropriate data types
    • source line expressions cannot be used
    • the at sign for register values is required (eax will not work)
    • The numerical value of a variable is its actual value
    • Operators can be used only with corresponding data types
    • A symbol that does not correspond to a C++ data type will result in a syntax error
    • Data structures are treated as actual structures and must be used accordingly. They do not have numerical values.
    • The value of a function name or any other entry point is the memory address, treated as a function pointer
    • Numerals: the default is always decimal
      Can be overridden by a prefix: 0x (hex), 0 (=zero- octal)

MASM operations are always byte based. C++ operations follow C++ type rules (including the scaling of
pointer arithmetic). In both cases numerals are treated internally as ULON64 values.

kd> ?? ((ntdll!_TEB*) 0x4c17960000)->ClientId
   +0x000 UniqueProcess    : 0x00000000`000001ac Void
   +0x008 UniqueThread     : 0x00000000`00001758 Void
kd> ?? @$teb->ClientId
struct _CLIENT_ID
   +0x000 UniqueProcess    : 0x00000000`000001ac Void
   +0x008 UniqueThread     : 0x00000000`00001758 Void
kd> .process /p ffff8a06a72452c0
kd> r? $t0 = @$peb->ProcessParameters
kd> ?? @$t0->CommandLine
struct _UNICODE_STRING
 ""C:\Windows\system32\cmd.exe" "
   +0x000 Length           : 0x3c
   +0x002 MaximumLength    : 0x3e
   +0x008 Buffer           : 0x000001ab`8c572280  ""C:\Windows\system32\cmd.exe" "

GFlags

  • GFlags enables and disables features by editing the Windows registry
  • GFlags can set system-wide or image-specific settings
  • Image specific settings are stored in:
    • HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\ImageFileName\GlobalFlag
  • The OS reads these settings and adopts its functionality accordingly
  • GFlags can be run from the command line or by using a dialog box
  • We can also use !gflags in WinDbg to set or display the global flags
  • With GFlags we can enable:
    • heap checking
    • heap tagging
    • Loader snaps
    • Debugger for an Image (automatically attached each time an Image is started)
    • Application verifier:
      • is a runtime verification tool for Windows applications
      • is monitoring an application's interaction with the OS
      • profiles and tracks:
        • Microsoft Win32 APIs (heap, handles, locks, threads, DLL load/unload, and more)
        • Exceptions
        • Kernel objects
        • Registry
        • File system
      • with !avrf we get access to this tracking information

Note: Under the hood Application Verifier injects a number of DLLs (verifier.dll, vrfcore.dll, vfbasics.dll, vfcompat.dll, and more) into the target application. More precisely: It sets a registry key according to the selected tests for the image in question. The windows loader reads this registry key and loads the specified DLLs into the applications address space while starting it.

Application Verifier Variants

GFlags Application Verifier:

  • Only verifier.dll is injected into the target process
  • verifier.dll is installed with Windows XP
  • Offers a very limited subset of Application Verifier options
  • Probably this option in GFlags is obsolete and will eventually be removed (?)

Application Verifier:

  • Can freely be downloaded and installed from the MS website
  • Additionally installs vrfcore.dll, vfbasics.dll, vfcompat.dll, and more into Windows\System32
  • Enables much more test options and full functionality of the !avrf extension

напочитать:

По псевдо регистрам офиц ман

Куча полезностей по командам

немного о прерываниях, их обработке и есесна ядре

Тут будет перевод одной статейки для погружения в теорию

статейка

Interrupt Dispatching Internals

Microsoft изменили способ обработки прерываний в последних версиях Windows. Были опубликованы некоторые публичные ресёрчи по обработке прерываний на старых версиях Windows и на 32-битных системах, однако не так много информации можно найти о том, как это работает в современном мире. В этой статье я попытаюсь привести описание обработки исключений на 64-битной Windows 10, в особенности Windows 10 RS1 Anniversary Update Build 10.0.10586

Прерывания используются операционными системами, чтобы получать сообщения об ивентах, происходящих на оборудовании. Обработка исключений - это механизм, в котором процессор передаёт контроль исполнения программному обеспечению, чтобы обработать событие на оборудовании. Прерывания обрабатываются ядром Windows, которое сначала выполняет некоторые служебные действия перед передачей контроля исполнения драйверам железа, которые в свою очередь регистрируют ISR (функции обработчика прерывания). IDT (Interrupt Descriptor Table) - это основная структура, задействованная в обработке исключений и её формат устанавливает разработчик процессора. IDT должна быть заполнена на этапе загрузки и соответственно должна использоваться процессором для обработки прерываний, приходящих с устройств

IDTR Register

У процессоров есть встроенный регистр, называемый IDTR, который Windows заполняет виртуальным адресом IDT в ядре, который он устанавливает для каждого процессора на этапе загрузки.

Значение регистра IDTR для каждого процессора. На мульти процессорной системе каждый процессор имеет свой IDTR регистр, который указывает на локальную приватную копию IDT

0: kd> ~0
0: kd> r @idtr
idtr=fffff8051ae62000

0: kd> ~1
1: kd> r @idtr
idtr=ffffb70107dad000

1: kd> ~2
2: kd> r @idtr
idtr=ffffb701077ea000

Interrupt Descriptor Table

IDT содержит всего 256 значений, некоторые из которых используются для исключений, некоторые для программных прерываний, а остальные для прерываний железа. Индекс в IDT, по которому выбирают конкретный элемент, называется вектором прерывания. Формат каждого элемента IDT описывается разработчиком процессора.

Ядро Windows определяет структуру KIDTENTRY64, которая представляет собой один элемент IDT на 64-битном процессоре. Используя вывод предыдущей команды "r @idtr", мы можем вывести нулевой элемент IDT, на который указывает IDTR

0: kd> ~0
0: kd> r @idtr
idtr=fffff8051ae62000
0: kd> dt nt!_KIDTENTRY64 fffff8051ae62000
   +0x000 OffsetLow        : 0x1c00
   +0x002 Selector         : 0x10
   +0x004 IstIndex         : 0y000
   +0x004 Reserved0        : 0y00000 (0)
   +0x004 Type             : 0y01110 (0xe)
   +0x004 Dpl              : 0y00
   +0x004 Present          : 0y1
   +0x006 OffsetMiddle     : 0x1800
   +0x008 OffsetHigh       : 0xfffff805
   +0x00c Reserved1        : 0
   +0x000 Alignment        : 0x18008e00`00101c00

Комбинация OffsetHigh, OffsetMiddle и OffsetLow даёт нам виртуальный адрес, куда процессор передаст поток выполнения, когда произойдёт прерывание. В выводе выше виртуальный адрес - 0xfffff80518001c00. Это совпадает с выводом "!idt 0" и указывает на фукнцию KiDivideErrorFault(). Значение поля Type в выводе выше (0xe) показывает, что поле в IDT представляет собой Interrupt Gate

0: kd> !idt 0

Dumping IDT: fffff8051ae62000

00:	fffff80518001c00 nt!KiDivideErrorFault

Первые N элементов в IDT нужны для обработки исключений и определены разработчиком процессора. Остальные элементы или используются для программных прерываний, или для хардварных, или не используются вовсе. В выводе "!idt" хардварные прерывания очень просто определить: у них есть указатель на структуру KINTERRUPT. "!idt -a" показывает значения всей IDT

0: kd> !idt -a

Dumping IDT: fffff8051ae62000

00:	fffff80518001c00 nt!KiDivideErrorFault
01:	fffff80518001f40 nt!KiDebugTrapOrFault	Stack = 0xFFFFF8051AEA0000
02:	fffff80518002440 nt!KiNmiInterrupt	Stack = 0xFFFFF8051AE92000
03:	fffff80518002900 nt!KiBreakpointTrap
04:	fffff80518002c40 nt!KiOverflowTrap
05:	fffff80518002f80 nt!KiBoundFault
06:	fffff805180034c0 nt!KiInvalidOpcodeFault
07:	fffff805180039c0 nt!KiNpxNotAvailableFault
08:	fffff80518003cc0 nt!KiDoubleFaultAbort	Stack = 0xFFFFF8051AE8B000
09:	fffff80518003fc0 nt!KiNpxSegmentOverrunAbort
0a:	fffff805180042c0 nt!KiInvalidTssFault
0b:	fffff805180045c0 nt!KiSegmentNotPresentFault
0c:	fffff80518004980 nt!KiStackFault
0d:	fffff80518004cc0 nt!KiGeneralProtectionFault
0e:	fffff80518005000 nt!KiPageFault
0f:	fffff80517ff99e8 nt!KiIsrThunk+0x78
10:	fffff80518005640 nt!KiFloatingErrorFault
11:	fffff80518005a00 nt!KiAlignmentFault
12:	fffff80518005d40 nt!KiMcheckAbort	Stack = 0xFFFFF8051AE99000
13:	fffff80518006840 nt!KiXmmException
14:	fffff80518006c00 nt!KiVirtualizationException
15:	fffff80518007100 nt!KiControlProtectionFault
16:	fffff80517ff9a20 nt!KiIsrThunk+0xB0
17:	fffff80517ff9a28 nt!KiIsrThunk+0xB8
18:	fffff80517ff9a30 nt!KiIsrThunk+0xC0
19:	fffff80517ff9a38 nt!KiIsrThunk+0xC8
1a:	fffff80517ff9a40 nt!KiIsrThunk+0xD0
1b:	fffff80517ff9a48 nt!KiIsrThunk+0xD8
1c:	fffff80517ff9a50 nt!KiIsrThunk+0xE0
1d:	fffff80517ff9a58 nt!KiIsrThunk+0xE8
1e:	fffff80517ff9a60 nt!KiIsrThunk+0xF0
1f:	fffff80517ffb220 nt!KiApcInterrupt
20:	fffff80517ffce00 nt!KiSwInterrupt

В этой статье мы сфокусируемся на хардварных прерываниях, соответственно последние элементы IDT. hex значение в первой колонке - это вектор или индекс прерывания, по которому и находится конкретное прерывание в IDT. Как было сказано ранее, каждый элемент IDT указывает на набор инструкций, которые будут выполнены, как один из этапов обработки исключения.

Давайте возьмём второй элемент в хардварной части IDR, вектор 0x50

0: kd> !idt 50

Dumping IDT: fffff8051ae62000

50:	fffff80517ff9bf0 dxgkrnl!DpiFdoLineInterruptRoutine (KINTERRUPT ffffb70107b9c500)

Выведем IDT 0x50, используя IDTR

0: kd> dt nt!_KIDTENTRY64 @idtr+0x50*0x10
   +0x000 OffsetLow        : 0x9bf0
   +0x002 Selector         : 0x10
   +0x004 IstIndex         : 0y000
   +0x004 Reserved0        : 0y00000 (0)
   +0x004 Type             : 0y01110 (0xe)
   +0x004 Dpl              : 0y00
   +0x004 Present          : 0y1
   +0x006 OffsetMiddle     : 0x17ff
   +0x008 OffsetHigh       : 0xfffff805
   +0x00c Reserved1        : 0
   +0x000 Alignment        : 0x17ff8e00`00109bf0
   
0: kd> dt @idtr + @@c++(0x50 * sizeof(nt!_KIDTENTRY64)) nt!_KIDTENTRY64 
   +0x000 OffsetLow        : 0x9bf0
   +0x002 Selector         : 0x10
   +0x004 IstIndex         : 0y000
   +0x004 Reserved0        : 0y00000 (0)
   +0x004 Type             : 0y01110 (0xe)
   +0x004 Dpl              : 0y00
   +0x004 Present          : 0y1
   +0x006 OffsetMiddle     : 0x17ff
   +0x008 OffsetHigh       : 0xfffff805
   +0x00c Reserved1        : 0
   +0x000 Alignment        : 0x17ff8e00`00109bf0

Когда появляется прерывание, исполнение кода передаётся в 0xfffff80517ff9bf0
Этот адрес указывает на исполняемую страницу памяти в NTOSKRNL и содержит следующие инструкции:

0: kd> u 0xfffff80517ff9bf0 L3
nt!KiIsrThunk+0x280:
fffff805`17ff9bf0 6a50            push    50h
fffff805`17ff9bf2 55              push    rbp
fffff805`17ff9bf3 e989050000      jmp     nt!KiIsrLinkage (fffff805`17ffa181)

Переменная KiIsrThunk из NTOSKRNL указывает на ядреную страницу кода, которая содержит 256 темплейтов, похожих на инструкции выше. После push interrupt vector (0x50) в этом случае и содержимого RBP регистра на стек, KiIsrThunk заглушка передаёт управление KiIsrLinkage(). Это 2 элемента на стеке используются функцией KiIsrLinkage() через структуру KTRAP_FRAME.

KiIsrLinkage()

KiIsrLinkage() выполняет множество служебных задач:

  • Сохраняет контекст изменяемого регистра в части KTRAP_FRAME, созданном в стеке
  • Проверяет, выполнял ли во время прерывания процессор инструкции внтури конкретного региона функции ExpInterlockedPopEntrySList() и, если выполнял, он сбрасывает регистр RIP на допустимую инструкцию возобновления цикла в функции
  • Проверяет, выключены ли прерывания и, если это так, багчекает систему на с кодом остановки TRAP_CAUSE_UNKNOWN
  • Получает указатель на структуру прерывания, ассоциированную с прерыванием и обрабатывает прерывание
  • Восстанавливает котекст изменяемого регистра из KTRAP_FRAME
  • Возвращается из прерывания

Интересно, что большинство частей функции KiIsrLinkage() созданы из макросов, многие из которых доступны в заголовочном файле WDK kxamd64.inc, например GENERATE_INTERRUPT_FRAME, ENTER_INTERRUPT, EXIT_INTERRUPT и RESTORE_TRAP_STATE

KINTERRUPT

Структура KINTERRUPT - основной ключ к обработке прерываний, она содержит всю информацию, необходимую для вызова ISR(interrupt service routine), зарегистрированной драйвером. KiIsrLinkage() определяет, где находится структура KINTERRUPT, связанная с вектором прерывания, используя его, как индекс в массиве указателей структур KINTERRUPT, находищихся в KPCR.CurrentPrcb.InterruptObject[]. Функция KiGetInterruptObjectAddress() из NTOSKRNL получает указатель на объект KINTERRUPT, показано ниже:

0: kd> uf nt!KiGetInterruptObjectAddress
nt!KiGetInterruptObjectAddress:
fffff805`17f771d0 65488b142520000000 mov   rdx,qword ptr gs:[20h]
fffff805`17f771d9 4881c240310000  add     rdx,3140h
fffff805`17f771e0 8bc1            mov     eax,ecx
fffff805`17f771e2 488d04c2        lea     rax,[rdx+rax*8]
fffff805`17f771e6 c3              ret
0: kd> !idt 50
Dumping IDT: fffff8051ae62000
50:	fffff80517ff9bf0 dxgkrnl!DpiFdoLineInterruptRoutine (KINTERRUPT ffffb70107b9c500)

0: kd> dt @$pcr nt!_KPCR -a Prcb.InterruptObject[50]
   +0x180 Prcb                     : 
      +0x3140 InterruptObject          : [80] 0xffffb701`07b9c500 Void

0: kd> dt nt!_KINTERRUPT 0xffffb70107b9c500
   +0x000 Type             : 0n22
   +0x002 Size             : 0n288
   +0x008 InterruptListEntry : _LIST_ENTRY [ 0x00000000`00000000 - 0x00000000`00000000 ]
   +0x018 ServiceRoutine   : 0xfffff805`1b051e60     unsigned char  dxgkrnl!DpiFdoLineInterruptRoutine+0
   +0x020 MessageServiceRoutine : (null) 
   +0x028 MessageIndex     : 0
   +0x030 ServiceContext   : 0xffff990e`5377a030 Void
   +0x038 SpinLock         : 0
   +0x040 TickCount        : 0
   +0x048 ActualLock       : 0xffff990e`530eea10  -> 0
   +0x050 DispatchAddress  : 0xfffff805`17ff8c70     void  nt!KiInterruptDispatch+0
   +0x058 Vector           : 0x50
   +0x05c Irql             : 0x5 ''
   +0x05d SynchronizeIrql  : 0x5 ''
   +0x05e FloatingSave     : 0 ''
   +0x05f Connected        : 0x1 ''
   +0x060 Number           : 0
   +0x064 ShareVector      : 0x1 ''
   +0x065 EmulateActiveBoth : 0 ''
   +0x066 ActiveCount      : 0
   +0x068 InternalState    : 0n0
   +0x06c Mode             : 0 ( LevelSensitive )
   +0x070 Polarity         : 0 ( InterruptPolarityUnknown )
   +0x074 ServiceCount     : 0
   +0x078 DispatchCount    : 0
   +0x080 PassiveEvent     : (null) 
   +0x088 TrapFrame        : 0xfffff805`1ae6e520 _KTRAP_FRAME
   +0x090 DisconnectData   : (null) 
   +0x098 ServiceThread    : (null) 
   +0x0a0 ConnectionData   : 0xffff990e`53931cc0 _INTERRUPT_CONNECTION_DATA
   +0x0a8 IntTrackEntry    : 0xffff990e`532cc500 Void
   +0x0b0 IsrDpcStats      : _ISRDPCSTATS
   +0x110 RedirectObject   : (null) 
   +0x118 PhysicalDeviceObject : 0xffff990e`50fc2360 Void

Поля структуры KINTERRUPT, которые относятся к обработке прерываний:

Название Описание
DispatchAddress Указатель на начальный программный обработчик прерываний в NTOSKRNL (KiChainedDispatch() ) для общих прерываний и KiInterruptDispatch() для других
ServiceRoutine Указатель на программный обработчик прерываний, зарегистрированный драйвером с помощью API ядра IoConnectInterrupt() или IoConnectInterruptEx()
MessageServiceRoutine Используется только для MSI (message signaled interrupts - прерывания, инициируемые сообщениями), т.е. прерывания, которые доставляются путём записи в зарезервированные участи памяти вместо переключения аппаратных линий. Эти прерывания показываются, как отрицательные числа в device manager'e. Для таких прерываний ServiceRoutine указывает на ядерную функцию KiInterruptMessageDispatch(), которая вызывает ISR, связанную с драйвером в MessageServiceRoutine
MessageIndex Индекс MSI, передаваемый, как параметр в ISR у MessageServiceRoutine

В старых версиях Windows KINTERRUPT аллоцировалась из исполняемого невыгружаемого пула памяти, так как содержала начальный код обработки, который был зарегистрирован прямо в IDT. Из-за перехода к механизму из KiIsrThunk() и KiIsrLinkage(), описанному выше, начальная заглушка для прерывания теперь находится в исполняемой памяти в NTOSKRNL и, соответственно, структуре KINTERRUPT больше не нужно быть аллоцированной из исполняемой памяти. Структуры KINTERRUPT теперь пре-аллоцируются и хранятся в списке в KPCR.Prcb.InterruptObjectPool. Функция KeAllocateInterrupt() забирает пре-аллоцированную структуру KINTERRUPT из списка, когда вызывается для аллокации новой структуры KINTERRUPT. Когда этот список заканчивается, алооцируется ещё одна страница со структурами с помощью MmAllocateIndependentPages(), и добалвяет их в список.

Interrupt Dispatching

Одним из важных шагов, предпринятых KiIsrLinkage, является вызов функции в KINTERRUPT.DispatchAddress, что приводит к вызову либо KiInterruptDispatch(), либо KiChainedDispatch(). Обе эти функции вызываются с указателем на структуру KINTERRUPT, как будто у них есть доступ ко всей информации, относящейся к обработке прерывания.

Новые системы используют APIC (Advanced Programmable Interrupt Controller) для обработки прерываний с устройств. Устройства отправляют свои прерывания на процессор с помощью IRQ линий. Однако, устройств больше, чем IRQ линий. Общие прерывания убирают проблему позволяя использовать одни и те же IRQ линии множеству устройств. Когда IRQ шарится, множество драйверов регистрирует свои ISR'ы для одного и того же IRQ и вектора прерывания. Из этого вытекает множественная структура KINTERRUPT, соответствующая устройствам, которые делят прерывание, и на них ссылаются вместе с помощью их полей KINTERRUPT.InterruptListEntry. Увидеть это можно с помощью "!idt -a", когда одному вектору прерывания соответствует множество структур KINTERRUPT, связанных с ним. KiChainedDispatch() обрабатывает прерывания, которые шарятся с множеством устройств, а KiInterruptDispatch() обрабатывает остальные прерывания.

Функции KiInterruptDispatch() и KiChainedDispatch меняются в зависимости от стека прерывания процессора, указатель на который хранится в KPCR.Prb.IsrStack. Этот стек аллоцируется функцией MmAllocateIsrStack(). Размер ISR стека 0x7000 байт, как определено переменными ISR_STACK_SIZE и PAGE_SIZE в заголовочном файле ksamd64.inc WDK. Непосредственный переход на стек ISR происходит с помощью макроса SWITCH_TO_ISR_STACK и также доступен в ksamd64.inc.

Как только выполнение перешло на стек ISR, функции KiInterruptDispatch() и KiChainedDispatch() передают выполнение следующей стадии, вызывая KiInterruptSubDispatch() или KiScanInterruptObjectList() соответственно.

KiInterruptSubDispatch() вызывает KiCallInterruptServiceRoutine() для одиночной структуры KINTERRUPT.

KiScanInterruptObjectList() итерируется по всем объектам KINTERRUPT, зарегистрированным для одного вектора прерывания, используя список KINTERRUPT.InterruptListEntry и вызывает KiCallInterruptServiceRoutine() для каждого KINTERRUPT в цепочке.

KiCallInterruptServiceRoutine() выполняет следующие задачи:

  • Помечает прерывание, как активное в KINTERRUPT.IsrDpcStats.IsrActive
  • Записывает время начала ISR в KINTERRUPT.IsrDpcStats.IsrTimeStart
  • Получает спин-блокировку прерывания в KINTERRUPT.ActualLock
  • Вызывает драйвер, зарегистрированный ISR в KINTERRUPT.ServiceRoutine
  • Записывает длительность ISR в KINTERRUPT.IsrDpcStats.IsrTime
  • Если ISR была прервана другой ISR с большим уровнем IRQL, он подстраивает IsrTime для точного учёта времени
  • Помечает прерывание как неактивное в KINTERRUPT.IsrDpcStats.IsrActive
  • Инкрементирует счётчик экземпляров прерываний в IsrCount

Драйвер, который регистрировал ISR, может сообщить вызывающей функции KiCallInterruptServiceRoutine(), забрал ли он на обработку прерывание, вернув TRUE. Это становится важным в случае пошареных прерываний, где решение вызвать ISR в следующем KINTERRUPT в цепочке или нет зависит от того, забрал ли текущий ISR прерывание на обработку.
Следующая диаграмма показывает все структуры, описанные выше и отношения между ними.

Как и в предыдущих версиях Windows 64, и IDTR, и содержимое IDT защищено PatchGuard (kernel patch protection). Делая структуру KINTERRUPT неисполняемой и удаляю код обработки из структуры, мы закрываем ещё один вектор subversion. Однако, даже с этими новыми изменениями в обработке исключений всё равно возможно для драйвера ядра хукнуть ISR в системе для реализации своего функционала, например для кейлоггера. ISR драйвера в поле KINTERRUPT.ServiceRoutine может быть заменено указателем на хук-функцию и PatchGuard этого не заметит. Так же не заметит, если KINTERRUPT, хранящийся в KPCR.Prcb.InterruptObject[] будет заменён клонированной структурой KINTERRUPT, который будет вести к выполнению кода.

IDT - Interrupt Descriptor Table (таблица дескрипторов прерываний)
Там хранятся элементы _KIDENTRY или _KIDENTRY64 соответственно
В каждой из них есть ссылка на ISR (Interrupt Service Routine) - непостредственно функция, которая вызывается

Вторая статья и практика-практика-практика

Механизм прерываний - ещё один важный элемент уровня железа

Прерывания можно рассматривать, как события уровня железа, использующиеся для сигнализирования процессору, что что-то требует немедленного внимания

  • Прерывания устройств
    Устройства (сетевая карта, клавиатура и тд) вызовут прерывание, чтобы
    сигнализировать процессору, что у них есть новая информация для обработки
    (входящий сетевой пакет, нажатие на клавишу и тд)

  • Ловушки / исключения
    Эти вещи обычно происходят, когда процессор сталкивается с ошибкой,
    такой как деление на ноль или ошибка страницы

  • Программные прерывания
    Это такие прерывания, которые генерируются программами, например INT 2E (syscall)
    используется для перехода из user mode в kernel mode. INT 3 используется
    для генерации программного брейкпоинта и тд

    Значение, которое идёт за инструкцией INT называется вектором прерывания, это просто индекс в IDT (Interrupt Descriptor Table). IDT ассоциирует вектор прерывания с конкретной функцией, которая будет обрабатывать вызванное прерывание. В WDK (Windows Driver Kit) такая функция называется ISR (Interrupt Service Routine)

С точки зрения железа прерывания обрабатываются конкретным куском железа, называемым PIC (Programmable Interrupt Controller - контроллер прерываний). Сейчас у нас обычно стоит новая версия PIC - APIC (Advanced Programmable Interrupt Controller), встроенная прямо в процессов

Плюсы APIC:

  • Поддержка многопроцессорности
  • Больше линий прерываний (256 vs 15 для PIC)

Для каждого CPU свой APIC, и каждый APIC может коммуницировать с другими APIC'ами через IPI (Inter-processor interrupt message)

Одна большая задача в обработке прерываний, которую выполняет APIC - это управление приоритетами прерываний. Каждой линии прерываний выдан свой приоритет и APIC проверяет, что ни один входящий запрос на прерывание с приоритетом ниже или равным текущему обрабатываемому прерыванию не достигнет процессор, обычно это называют Interrupt Masking

Заметьте, что некоторые особые прерывания не могут был замаскированы и всегда будут достигать процессор, они называются NMI (Non-maskable interrupt). Они обычно предназначены для неустранимого сбоя оборудования, что означает, что у вас серьёзные проблемы с железом.

Прерывания, приходящие от устройств, сначала обрабатываются I/O APIC, специальным чипом, встроенным в чипсет, его роль - распределять прерывания по локальным APIC'ам всех CPU, таким образом включая SMP (Symmetric multiprocessing - Симметричная многопроцессорность)

Когда прерывание достигает CPU, процессор и процедура прерывания ОС сохранят стостояние значения регистров в стеке ядра, чтобы можно было восстановить предыдущий поток выполнения и продолжить исполнение кода. Этот набор сохраняемых регистров и некотороая дополнителья информация (например код ошибки) обычно называются Trap Frame (.trap в windbg)

Углубимся немного в механизм обработки прерываний. Откуда процессор знает, где расположения IDT? Ответ - в регистре IDTR. 48-битный регистр делится на две части: 16-бит - IDT limit и 32-бита - base address

Максимальное количество записей в IDT - 256. Каждая запись - 8 бит, содержит флаги, сегментные селекторы, gate type и оффсет или адрес ISR.

Оффсет тоже разделён на две части: биты 0..15 - для младших битив и 48..63 - для старших битов

В винде IDT entry - это _KIDTENTRY

kd> dt nt!_KIDTENTRY
   +0x000 Offset           : Uint2B
   +0x002 Selector         : Uint2B
   +0x004 Access           : Uint2B
   +0x006 ExtendedOffset   : Uint2B

Чтобы отобразить IDT в windbg есть !idt

kd> !idt

Dumping IDT: 8003f400

30:	806f5d50 hal!HalpClockInterrupt
31:	89ec9044 i8042prt!I8042KeyboardInterruptService (KINTERRUPT 89ec9008)

38:	806efef0 hal!HalpProfileInterrupt
39:	89fed174 ACPI!ACPIInterruptServiceRoutine (KINTERRUPT 89fed138)

	         NDIS!ndisMIsr (KINTERRUPT 89f228d8)

3a:	89f24044 VIDEOPRT!pVideoPortInterrupt (KINTERRUPT 89f24008)

	         USBPORT!USBPORT_InterruptService (KINTERRUPT 89eae008)

3b:	8a01d6c4 VBoxGuest+0x27c0 (KINTERRUPT 8a01d688)

portcls!CKsShellRequestor::`scalar deleting destructor'+0x26 (KINTERRUPT 89f179c0)

3c:	89ead564 i8042prt!I8042MouseInterruptService (KINTERRUPT 89ead528)

3e:	89fea9d4 atapi!IdePortInterrupt (KINTERRUPT 89fea998)

3f:	8a03d044 atapi!IdePortInterrupt (KINTERRUPT 8a03d008)

Например разберём поближе i8042prt!I8042KeyboardInterruptService
Для проверки, что это такое вообще (ну вдруг мы по названию не догадались) поставим бряку на него

kd> u i8042prt!I8042KeyboardInterruptService
i8042prt!I8042KeyboardInterruptService:
f76a7495 6a18            push    18h
f76a7497 68a8a76af7      push    offset i8042prt!`string'+0x154 (f76aa7a8)
f76a749c e8fa000000      call    i8042prt!_SEH_prolog (f76a759b)
f76a74a1 8b7d0c          mov     edi,dword ptr [ebp+0Ch]
f76a74a4 8b7728          mov     esi,dword ptr [edi+28h]
f76a74a7 837e3001        cmp     dword ptr [esi+30h],1
f76a74ab 0f8582130000    jne     i8042prt!I8042KeyboardInterruptService+0xa2 (f76a8833)
f76a74b1 a100a96af7      mov     eax,dword ptr [i8042prt!Globals (f76aa900)]

kd> bu i8042prt!I8042KeyboardInterruptService

kd> bl
     0 e Disable Clear  f76a7495     0001 (0001) i8042prt!I8042KeyboardInterruptService
kd> g

Breakpoint 0 hit
i8042prt!I8042KeyboardInterruptService:
f76a7495 6a18            push    18h


Мы нажали на любую кнопку > наш брейкпоинт сработал

Но давайте доберёмся до кода в статике, ведь то, что написано в выводе команды **!idt **(31: 89ec9044 i8042prt! ) не совпадает с фактическим адресом ISR
Каждый элемент IDT занимает 8 байт, мы решили, что нам нужен индекс 31 (такой индекс у нужной нам функции), что нам нужно сделать?

idtr + 0x31 * 8 >

kd> r idtr
idtr=8003f400

kd> dd @idtr+8*0x31

8003f588 - 00089044 89ec8e00 0008dd14 804d8e00 > 0x89ec9044
8003f598 - 0008dd1e 804d8e00 0008dd28 804d8e00
8003f5a8 - 0008dd32 804d8e00 0008dd3c 804d8e00
8003f5b8 - 0008dd46 804d8e00 0008fef0 806e8e00
8003f5c8 - 0008d174 89fe8e00 00084044 89f28e00
8003f5d8 - 0008d6c4 8a018e00 0008d564 89ea8e00
8003f5e8 - 0008dd82 804d8e00 0008a9d4 89fe8e00
8003f5f8 - 0008d044 8a038e00 0008dda0 804d8e00

И так наш ISR адрес 0x89ec9044, но мы же вроде бы только что дампили I8042KeyboardInterruptService и его адрес был 0xf76a7495, непонятно

Чтож, перед тем, как вызывать ISR'ры драйверов системе нужно выполнить некоторые задачи: маскирование прерываний с более низким приоритетом в APIC, поднятие уровня IRQL и тд

Так что вместо того, чтобы заполнить IDT ISR'ами, система заполняет их glue кодом или же иначе функциями-темплейтами

Каждая темплейт-функция взята (скопирована) из KiInterruptTemplate функции и динамически модифицирована, чтобы подходить соответствующему ISR

Давайте посмотрим на темплейт нашей KeyboardInterruptService:

Мы можем заметить, что почти весь код скопирован с оригинального KiInterruptTemplate. Однако есть одна интересная особенность:
темплейт функции клавиатуры вызывает KiInterruptDispatch и кладёт в EDI адрес 0x89EC9008

Этот адрес указывает на interrupt object с типом _KINTERRUPT:

kd> dt nt!_KINTERRUPT 0x89EC9008
   +0x000 Type             : 0n22
   +0x002 Size             : 0n484
   +0x004 InterruptListEntry : _LIST_ENTRY [ 0x89ec900c - 0x89ec900c ]
   +0x00c ServiceRoutine   : 0xf76a7495     unsigned char  i8042prt!I8042KeyboardInterruptService+0
   +0x010 ServiceContext   : 0x89f259d0 Void
   +0x014 SpinLock         : 0
   +0x018 TickCount        : 0xffffffff
   +0x01c ActualLock       : 0x89f25a90  -> 0
   +0x020 DispatchAddress  : 0x804da8e8     void  nt!KiInterruptDispatch+0
   +0x024 Vector           : 0x31
   +0x028 Irql             : 0x1a ''
   +0x029 SynchronizeIrql  : 0x1a ''
   +0x02a FloatingSave     : 0 ''
   +0x02b Connected        : 0x1 ''
   +0x02c Number           : 0 ''
   +0x02d ShareVector      : 0 ''
   +0x030 Mode             : 1 ( Latched )
   +0x034 ServiceCount     : 0
   +0x038 DispatchCount    : 0xffffffff
   +0x03c DispatchCode     : [106] 0x56535554

Как видно выше, как раз в ServiceRoutine хранится адрес ISR
Если мы теперь посмотрим на KiInterruptDispatch мы увидим, что он вызывает interrupt object ServiceRoutine

kd> u nt!KiInterruptDispatch L30
nt!KiInterruptDispatch:
804da8e8 ff05c4f5dfff    inc     dword ptr ds:[0FFDFF5C4h]
804da8ee 8bec            mov     ebp,esp
804da8f0 8b4724          mov     eax,dword ptr [edi+24h]
804da8f3 8b4f29          mov     ecx,dword ptr [edi+29h]
804da8f6 50              push    eax
804da8f7 83ec04          sub     esp,4
804da8fa 54              push    esp
804da8fb 50              push    eax
804da8fc 51              push    ecx
804da8fd ff1504764d80    call    dword ptr [nt!_imp__HalBeginSystemInterrupt (804d7604)]
804da903 0bc0            or      eax,eax
804da905 7436            je      nt!KiInterruptDispatch+0x55 (804da93d)
804da907 83ec0c          sub     esp,0Ch
804da90a 833d0c23568000  cmp     dword ptr [nt!PPerfGlobalGroupMask (8056230c)],0
804da911 c745f400000000  mov     dword ptr [ebp-0Ch],0
804da918 752b            jne     nt!KiInterruptDispatch+0x5d (804da945)
804da91a 8b771c          mov     esi,dword ptr [edi+1Ch]
804da91d 8b4710          mov     eax,dword ptr [edi+10h]
804da920 50              push    eax
804da921 57              push    edi
804da922 ff570c          call    dword ptr [edi+0Ch]

kd> dt nt!_KINTERRUPT 0x89EC9008
...
   +0x00c ServiceRoutine   : 0xf76a7495     unsigned char  i8042prt!I8042KeyboardInterruptService+0
...

Вся структура вызовов:

И как создаётся interrupt object?
Это роль драйвера заполнить структуру вызвав IoConnectInterrupt

Добавим ещё немного экспериментов:

Давайте посмотрим на прерывание и исключение деления на ноль
Оно у нас самое первое в таблице IDT

kd> !idt -a

Dumping IDT: 8003f400

00:	804df370 nt!KiTrap00
01:	804df4eb nt!KiTrap01
02:	Task Selector = 0x0000
03:	804df8bd nt!KiTrap03
04:	804dfa40 nt!KiTrap04
05:	804dfba1 nt!KiTrap05
06:	804dfd22 nt!KiTrap06
07:	804e038a nt!KiTrap07

Поставим бряку
bu nt!KiTrap00
И на машине скомпилим какой-нибудь такой код:

#include <stdio.h>
#include <Windows.h>

int main() {
	sleep(5);
	printf("Go!\n");
	int x = 10;
	int y;
	scanf("%f", &y);
	printf("%f",  x / y);
	return 0;
}

Вводим ноль и Viola! брякаемся

kd> !process 0 0
Failed to get VadRoot
PROCESS 897cf578  SessionId: 0  Cid: 06ac    Peb: 7ffdc000  ParentCid: 0e48
    DirBase: 825e9000  ObjectTable: e2c24c78  HandleCount:   7.
    Image: Untitled1.exe
    
kd> .process /i 897cf578 
You need to continue execution (press 'g' <enter>) for the context
to be switched. When the debugger breaks in again, you will be in
the new process context.
ReadVirtual: 8a01c688 not properly sign extended

kd> g
Break instruction exception - code 80000003 (first chance)
nt!RtlpBreakWithStatusInstruction:
804e351a cc              int     3
ReadVirtual: 8a01c688 not properly sign extended

00401500 55              push    ebp
00401501 89e5            mov     ebp,esp
00401503 83e4f0          and     esp,0FFFFFFF0h
00401506 83ec20          sub     esp,20h
00401509 e8b2090000      call    00401ec0
0040150e c7042405000000  mov     dword ptr [esp],5
00401515 e8b6100000      call    004025d0
0040151a c7042400404000  mov     dword ptr [esp],404000h
00401521 e832110000      call    00402658                    <-- printf("Go!\n")
00401526 c744241c0a000000 mov     dword ptr [esp+1Ch],0Ah
0040152e 8d442418        lea     eax,[esp+18h]
00401532 89442404        mov     dword ptr [esp+4],eax
00401536 c7042404404000  mov     dword ptr [esp],404004h
0040153d e81e110000      call    00402660                    <-- scanf
00401542 8bd9            mov     ebx,ecx
00401544 2418            and     al,18h
00401546 8b44241c        mov     eax,dword ptr [esp+1Ch]
0040154a 99              cdq
0040154b f7f9            idiv    eax,ecx
0040154d 89442404        mov     dword ptr [esp+4],eax
00401551 c7042404404000  mov     dword ptr [esp],404004h
00401558 e80b110000      call    00402668
0040155d b800000000      mov     eax,0
00401562 c9              leave

Падаем в обработку

...
804df3ea 55              push    ebp
804df3eb e8b6431400      call    nt!Ki386CheckDivideByZeroTrap (806237a6)
...

Посмотрим на 64-битную 10-ку

Тут всё выглядит поинтереснее, мб из-за отсутсвия дебаг символом на XP'хе, а мб и нет

kd> !idt

Dumping IDT: fffff80335462000

00:	fffff80330a01c00 nt!KiDivideErrorFault
01:	fffff80330a01f40 nt!KiDebugTrapOrFault	Stack = 0xFFFFF803354A0000
02:	fffff80330a02440 nt!KiNmiInterrupt	Stack = 0xFFFFF80335492000
03:	fffff80330a02900 nt!KiBreakpointTrap
04:	fffff80330a02c40 nt!KiOverflowTrap
05:	fffff80330a02f80 nt!KiBoundFault
06:	fffff80330a034c0 nt!KiInvalidOpcodeFault
07:	fffff80330a039c0 nt!KiNpxNotAvailableFault
08:	fffff80330a03cc0 nt!KiDoubleFaultAbort	Stack = 0xFFFFF8033548B000
09:	fffff80330a03fc0 nt!KiNpxSegmentOverrunAbort
0a:	fffff80330a042c0 nt!KiInvalidTssFault
0b:	fffff80330a045c0 nt!KiSegmentNotPresentFault
0c:	fffff80330a04980 nt!KiStackFault
0d:	fffff80330a04cc0 nt!KiGeneralProtectionFault
0e:	fffff80330a05000 nt!KiPageFault
10:	fffff80330a05640 nt!KiFloatingErrorFault
11:	fffff80330a05a00 nt!KiAlignmentFault
12:	fffff80330a05d40 nt!KiMcheckAbort	Stack = 0xFFFFF80335499000
13:	fffff80330a06840 nt!KiXmmException
14:	fffff80330a06c00 nt!KiVirtualizationException
15:	fffff80330a07100 nt!KiControlProtectionFault
1f:	fffff803309fb220 nt!KiApcInterrupt
20:	fffff803309fce00 nt!KiSwInterrupt
29:	fffff80330a07600 nt!KiRaiseSecurityCheckFailure
2c:	fffff80330a07940 nt!KiRaiseAssertion
2d:	fffff80330a07c80 nt!KiDebugServiceTrap
2f:	fffff803309fd3c0 nt!KiDpcInterrupt
30:	fffff803309fb7c0 nt!KiHvInterrupt
31:	fffff803309fbaa0 nt!KiVmbusInterrupt0
32:	fffff803309fbd80 nt!KiVmbusInterrupt1
33:	fffff803309fc060 nt!KiVmbusInterrupt2
34:	fffff803309fc340 nt!KiVmbusInterrupt3
35:	fffff803309f9b18 nt!HalpInterruptCmciService (KINTERRUPT fffff803312f2f40)
36:	fffff803309f9b20 nt!HalpInterruptCmciService (KINTERRUPT fffff803312f3180)
50:	fffff803309f9bf0 dxgkrnl!DpiFdoLineInterruptRoutine (KINTERRUPT ffffa600bf1fb500)
60:	fffff803309f9c70 USBPORT!USBPORT_InterruptService (KINTERRUPT ffffa600bf1fb780)
70:	fffff803309f9cf0 VBoxGuest+0x22e0 (KINTERRUPT ffffa600bf1fbb40)
80:	fffff803309f9d70 storport!RaidpAdapterInterruptRoutine (KINTERRUPT ffffa600bf1fbc80)
	                 HDAudBus!HdaController::Isr (KINTERRUPT ffffa600bf1fb640)
90:	fffff803309f9df0 i8042prt!I8042MouseInterruptService (KINTERRUPT ffffa600bf1fb8c0)
a0:	fffff803309f9e70 i8042prt!I8042KeyboardInterruptService (KINTERRUPT ffffa600bf1fba00)
b0:	fffff803309f9ef0 ACPI!ACPIInterruptServiceRoutine (KINTERRUPT ffffa600bf1fbdc0)
ce:	fffff803309f9fe0 nt!HalpIommuInterruptRoutine (KINTERRUPT fffff803312f3ba0)
d1:	fffff803309f9ff8 nt!HalpTimerClockInterrupt (KINTERRUPT fffff803312f3960)
d2:	fffff803309fa000 nt!HalpTimerClockIpiRoutine (KINTERRUPT fffff803312f3840)
d7:	fffff803309fa028 nt!HalpInterruptRebootService (KINTERRUPT fffff803312f3600)
d8:	fffff803309fa030 nt!HalpInterruptStubService (KINTERRUPT fffff803312f33c0)
df:	fffff803309fa068 nt!HalpInterruptSpuriousService (KINTERRUPT fffff803312f32a0)
e1:	fffff803309fd8b0 nt!KiIpiInterrupt
e2:	fffff803309fa080 nt!HalpInterruptLocalErrorService (KINTERRUPT fffff803312f34e0)
e3:	fffff803309fa088 nt!HalpInterruptDeferredRecoveryService (KINTERRUPT fffff803312f3060)
fd:	fffff803309fa158 nt!HalpTimerProfileInterrupt (KINTERRUPT fffff803312f3a80)
fe:	fffff803309fa160 nt!HalpPerfInterrupt (KINTERRUPT fffff803312f3720)

Возьмём снова наш обработчик клавиатуры по оффсету a0

kd> dt _kidtentry64 (idtr + (0xa0*0x10))
ntdll!_KIDTENTRY64
   +0x000 OffsetLow        : 0x9e70
   +0x002 Selector         : 0x10
   +0x004 IstIndex         : 0y000
   +0x004 Reserved0        : 0y00000 (0)
   +0x004 Type             : 0y01110 (0xe)
   +0x004 Dpl              : 0y00
   +0x004 Present          : 0y1
   +0x006 OffsetMiddle     : 0x309f
   +0x008 OffsetHigh       : 0xfffff803
   +0x00c Reserved1        : 0
   +0x000 Alignment        : 0x309f8e00`00109e70

Найдём ISR entry point для него, теперь для 64 бит схема немного другая:
OffsetHigh + OffsetMiddle + OffsetLow
0xfffff803309f9e70

Offset: 0xfffff803309f9e70
fffff803`309f9e70 6aa0            push    0FFFFFFFFFFFFFFA0h
fffff803`309f9e72 55              push    rbp
fffff803`309f9e73 e909030000      jmp     nt!KiIsrLinkage (fffff803`309fa181)

Если в табличке !idt искать KINTERRUPT не хочется, можно сделать так:

kd> dt @$pcr nt!_KPCR -a Prcb.InterruptObject[0xa0]
   +0x180 Prcb                       : 
      +0x3140 InterruptObject            : [160] 0xffffa600`bf1fba00 Void
      
      
kd> dt nt!_KINTERRUPT ffffa600bf1fba00
   +0x000 Type             : 0n22
   +0x002 Size             : 0n288
   +0x008 InterruptListEntry : _LIST_ENTRY [ 0x00000000`00000000 - 0x00000000`00000000 ]
   +0x018 ServiceRoutine   : 0xfffff803`36096790     unsigned char  i8042prt!I8042KeyboardInterruptService+0
   +0x020 MessageServiceRoutine : (null) 
   +0x028 MessageIndex     : 0
   +0x030 ServiceContext   : 0xffffcf8b`4e304040 Void
   +0x038 SpinLock         : 0
   +0x040 TickCount        : 0
   +0x048 ActualLock       : 0xffffcf8b`4e3041a0  -> 0
   +0x050 DispatchAddress  : 0xfffff803`309f8c70     void  nt!KiInterruptDispatch+0
   +0x058 Vector           : 0xa0
   +0x05c Irql             : 0xa ''
   +0x05d SynchronizeIrql  : 0xa ''
   +0x05e FloatingSave     : 0 ''
   +0x05f Connected        : 0x1 ''
   +0x060 Number           : 0
   +0x064 ShareVector      : 0 ''
   +0x065 EmulateActiveBoth : 0 ''
   +0x066 ActiveCount      : 0
   +0x068 InternalState    : 0n0
   +0x06c Mode             : 1 ( Latched )
   +0x070 Polarity         : 0 ( InterruptPolarityUnknown )
   +0x074 ServiceCount     : 0
   +0x078 DispatchCount    : 0
   +0x080 PassiveEvent     : (null) 
   +0x088 TrapFrame        : 0xfffffb82`3ab14a20 _KTRAP_FRAME
   +0x090 DisconnectData   : (null) 
   +0x098 ServiceThread    : (null) 
   +0x0a0 ConnectionData   : 0xffffcf8b`4e467d00 _INTERRUPT_CONNECTION_DATA
   +0x0a8 IntTrackEntry    : 0xffffcf8b`4ccac690 Void
   +0x0b0 IsrDpcStats      : _ISRDPCSTATS
   +0x110 RedirectObject   : (null) 
   +0x118 PhysicalDeviceObject : (null) 

напочитать:

PatchGuard

Скрываемся в списке процессов в ядре

Попробуем скрыть процесс в ядре, чтобы в юзерспейсе пользователь не увидел его. Идём по мануалу Manipulating ActiveProcessLinks to unlink processes in userland

Про _EPROCESS мы уже знаем из лабы выше. Двухсвязный список, тип _LIST_ENTRY и всё такое. Задача у нас довольно простая, но хочется проделать это руками: переписать указатели так, чтобы спрятать наш процесс:

Находим адрес процесса (notepad.exe) и берём его FLINK и BLINK

kd> dt _eprocess fffffa80047ce060
nt!_EPROCESS
...
   +0x188 ActiveProcessLinks : _LIST_ENTRY [ 0xfffffa80`061457d8 - 0xfffffa80`070f9c88 ]
...

kd> dq fffffa80047ce060+0x188 L2
fffffa80`047ce1e8  fffffa80`061457d8 fffffa80`070f9c88

Можем глянуть, куда указывают флинк и блинк:

kd> dt _eprocess fffffa80`061457d8-0x188
nt!_EPROCESS
...
   +0x2e0 ImageFileName    : [15]  "mscorsvw.exe"
...

kd> dt _eprocess fffffa80`070f9c88-0x188
nt!_EPROCESS
...
    +0x2e0 ImageFileName    : [15]  "taskhost.exe"
...

Получим пиды окружающих наш процесс процессов:

kd> dt _eprocess fffffa8006145650
nt!_EPROCESS
...
   +0x180 UniqueProcessId  : 0x00000000`00000a4c Void
   +0x188 ActiveProcessLinks : _LIST_ENTRY [ 0xfffff800`02a38940 - 0xfffffa80`047ce1e8 ]
...

kd> dt nt!_EPROCESS fffffa80070f9b00
...
   +0x180 UniqueProcessId  : 0x00000000`00000994 Void
   +0x188 ActiveProcessLinks : _LIST_ENTRY [ 0xfffffa80`047ce1e8 - 0xfffffa80`070375c8 ]
...


kd> dd fffffa8006145650+188-8 L1
fffffa80`061457d0  00000a4c

kd> dd fffffa80070f9b00+188-8 L1
fffffa80`070f9c80  00000994
Image PID EPROCESS ActiveProcessLinks FLINK BLINK
taskhost.exe 994 fffffa80`070f9b00 fffffa80`070f9c88 fffffa80`047ce1e8 fffffa80`070375c8
notepad.exe 854 fffffa80`047ce060 fffffa80`047ce1e8 fffffa80`061457d8 fffffa80`070f9c88
mscorsvw.exe a4c fffffa80`06145650 fffffa80`061457d8 fffff800`02a38940 fffffa80`047ce1e8

Очевидно, нам нужна немного другая табличка)

Image PID EPROCESS ActiveProcessLinks FLINK BLINK
taskhost.exe 994 fffffa80`070f9b00 fffffa80`070f9c88 fffffa80`047ce1e8
fffffa80`061457d8
fffffa80`070375c8
notepad.exe 854 fffffa80`047ce060 fffffa80`047ce1e8 fffffa80`061457d8 fffffa80`070f9c88
mscorsvw.exe a4c fffffa80`06145650 fffffa80`061457d8 fffff800`02a38940 fffffa80`047ce1e8
fffffa80`070f9c88
kd> eq fffffa80`070f9c88 fffffa80`061457d8
kd> eq fffffa80`061457d8+8 fffffa80`070f9c88

Абуз токенов для повышения привилегий в ядре

Будут рассмотрены две техники:

  1. Кража/подмена токена - низкоуровневый токен подменяется высокоуровневым
  2. Корректирование привилегий токена - добавление привилегий текущему токену

Новая ядерная структура - _TOKEN

Попробуем сначала первый способ: берём токен процесса System, меняем свой, профит

kd> dt nt!_EPROCESS fffffa800372cb00
...
   +0x208 Token            : _EX_FAST_REF
...

kd> dq fffffa800372cb00+0x208 L1
fffffa80`0372cd08  fffff8a0`01b46a5b

kd> !token fffffa800372cb00+0x208
The address 0xfffffa80038fe4a8 does not point to a token object.

kd> dt _EX_FAST_REF
ntdll!_EX_FAST_REF
   +0x000 Object           : Ptr64 Void
   +0x000 RefCnt           : Pos 0, 4 Bits
   +0x000 Value            : Uint8B
   
kd> dt _EX_FAST_REF   
ntdll!_EX_FAST_REF
   +0x000 Object           : 0xfffff8a0`01b46a5b Void
   +0x000 RefCnt           : 0y1011
   +0x000 Value            : 0xfffff8a0`01b46a5b


Выходит, что последние 4 бита - это счётчик референсов, уберём их, получим адрес токена:
0xfffff8a0`01b46a5b & 0xf0 > 0xfffff8a0`01b46a50

kd> !token 0xfffff8a0`01b46a50
_TOKEN 0xfffff8a001b46a50
TS Session ID: 0x1
User: S-1-5-21-2382729903-2716558127-3398458678-1003
User Groups: 
 00 S-1-5-21-2382729903-2716558127-3398458678-513
    Attributes - Mandatory Default Enabled 
 01 S-1-1-0
    Attributes - Mandatory Default Enabled 
 02 S-1-5-21-2382729903-2716558127-3398458678-1000
    Attributes - Mandatory Default Enabled 
 03 S-1-5-32-545
    Attributes - Mandatory Default Enabled 
 04 S-1-5-4
    Attributes - Mandatory Default Enabled 
 05 S-1-2-1
    Attributes - Mandatory Default Enabled 
 06 S-1-5-11
    Attributes - Mandatory Default Enabled 
 07 S-1-5-15
    Attributes - Mandatory Default Enabled 
 08 S-1-5-113
    Attributes - Mandatory Default Enabled 
 09 S-1-5-5-0-112295
    Attributes - Mandatory Default Enabled LogonId 
 10 S-1-2-0
    Attributes - Mandatory Default Enabled 
 11 S-1-5-64-10
    Attributes - Mandatory Default Enabled 
 12 S-1-16-8192
    Attributes - GroupIntegrity GroupIntegrityEnabled 
Primary Group: S-1-5-21-2382729903-2716558127-3398458678-513
Privs: 
 19 0x000000013 SeShutdownPrivilege               Attributes - 
 23 0x000000017 SeChangeNotifyPrivilege           Attributes - Enabled Default 
 25 0x000000019 SeUndockPrivilege                 Attributes - 
 33 0x000000021 SeIncreaseWorkingSetPrivilege     Attributes - 
 34 0x000000022 SeTimeZonePrivilege               Attributes - 
Authentication ID:         (0,1b6e2)
Impersonation Level:       Anonymous
TokenType:                 Primary
Source: User32             TokenFlags: 0x2200 ( Token in use )
Token ID: 4bdf2            ParentToken ID: 0
Modified ID:               (0, 4b6ff)
RestrictedSidCount: 0      RestrictedSids: 0x0000000000000000
OriginatingLogonSession: 3e7

Его же мы получим, если сделаем

kd> !process fffffa800372cb00 1
PROCESS fffffa800372cb00
    SessionId: 1  Cid: 0928    Peb: 7fffffde000  ParentCid: 07f0
    DirBase: 68d15000  ObjectTable: fffff8a000d3a940  HandleCount: 271.
    Image: powershell.exe
    VadRoot fffffa800691fd80 Vads 234 Clone 0 Private 8427. Modified 22. Locked 0.
    DeviceMap fffff8a0013e5b40
    Token                             fffff8a001b46a50
    ElapsedTime                       00:00:06.874
    UserTime                          00:00:00.250
    KernelTime                        00:00:00.015
    QuotaPoolUsage[PagedPool]         304952
    QuotaPoolUsage[NonPagedPool]      28324
    Working Set Sizes (now,min,max)  (15169, 50, 345) (60676KB, 200KB, 1380KB)
    PeakWorkingSetSize                16026
    VirtualSize                       553 Mb
    PeakVirtualSize                   565 Mb
    PageFaultCount                    46113
    MemoryPriority                    FOREGROUND
    BasePriority                      8
    CommitCharge                      13117

kd> eq fffffa800372cb00+0x208 fffff8a000004040

Второй способ

Заберём права токена процесса System себе

kd> !token fffff8a0022b7060
_TOKEN 0xfffff8a0022b7060
TS Session ID: 0x1
User: S-1-5-21-2382729903-2716558127-3398458678-1003
User Groups: 
 00 S-1-5-21-2382729903-2716558127-3398458678-513
    Attributes - Mandatory Default Enabled 
 01 S-1-1-0
    Attributes - Mandatory Default Enabled 
 02 S-1-5-21-2382729903-2716558127-3398458678-1000
    Attributes - Mandatory Default Enabled 
 03 S-1-5-32-545
    Attributes - Mandatory Default Enabled 
 04 S-1-5-4
    Attributes - Mandatory Default Enabled 
 05 S-1-2-1
    Attributes - Mandatory Default Enabled 
 06 S-1-5-11
    Attributes - Mandatory Default Enabled 
 07 S-1-5-15
    Attributes - Mandatory Default Enabled 
 08 S-1-5-113
    Attributes - Mandatory Default Enabled 
 09 S-1-5-5-0-112295
    Attributes - Mandatory Default Enabled LogonId 
 10 S-1-2-0
    Attributes - Mandatory Default Enabled 
 11 S-1-5-64-10
    Attributes - Mandatory Default Enabled 
 12 S-1-16-8192
    Attributes - GroupIntegrity GroupIntegrityEnabled 
Primary Group: S-1-5-21-2382729903-2716558127-3398458678-513
Privs: 
 19 0x000000013 SeShutdownPrivilege               Attributes - 
 23 0x000000017 SeChangeNotifyPrivilege           Attributes - Enabled Default 
 25 0x000000019 SeUndockPrivilege                 Attributes - 
 33 0x000000021 SeIncreaseWorkingSetPrivilege     Attributes - 
 34 0x000000022 SeTimeZonePrivilege               Attributes - 
Authentication ID:         (0,1b6e2)
Impersonation Level:       Anonymous
TokenType:                 Primary
Source: User32             TokenFlags: 0x2200 ( Token in use )
Token ID: 70533            ParentToken ID: 0
Modified ID:               (0, 70234)
RestrictedSidCount: 0      RestrictedSids: 0x0000000000000000
OriginatingLogonSession: 3e7

Видим те же права у токена, что и на картинке
Токен системы:

kd> !token fffff8a000004040
_TOKEN 0xfffff8a000004040
TS Session ID: 0
User: S-1-5-18
User Groups: 
 00 S-1-5-32-544
    Attributes - Default Enabled Owner 
 01 S-1-1-0
    Attributes - Mandatory Default Enabled 
 02 S-1-5-11
    Attributes - Mandatory Default Enabled 
 03 S-1-16-16384
    Attributes - GroupIntegrity GroupIntegrityEnabled 
Primary Group: S-1-5-18
Privs: 
 02 0x000000002 SeCreateTokenPrivilege            Attributes - 
 03 0x000000003 SeAssignPrimaryTokenPrivilege     Attributes - 
 04 0x000000004 SeLockMemoryPrivilege             Attributes - Enabled Default 
 05 0x000000005 SeIncreaseQuotaPrivilege          Attributes - 
 07 0x000000007 SeTcbPrivilege                    Attributes - Enabled Default 
 08 0x000000008 SeSecurityPrivilege               Attributes - 
 09 0x000000009 SeTakeOwnershipPrivilege          Attributes - 
 10 0x00000000a SeLoadDriverPrivilege             Attributes - 
 11 0x00000000b SeSystemProfilePrivilege          Attributes - Enabled Default 
 12 0x00000000c SeSystemtimePrivilege             Attributes - 
 13 0x00000000d SeProfileSingleProcessPrivilege   Attributes - Enabled Default 
 14 0x00000000e SeIncreaseBasePriorityPrivilege   Attributes - Enabled Default 
 15 0x00000000f SeCreatePagefilePrivilege         Attributes - Enabled Default 
 16 0x000000010 SeCreatePermanentPrivilege        Attributes - Enabled Default 
 17 0x000000011 SeBackupPrivilege                 Attributes - 
 18 0x000000012 SeRestorePrivilege                Attributes - 
 19 0x000000013 SeShutdownPrivilege               Attributes - 
 20 0x000000014 SeDebugPrivilege                  Attributes - Enabled Default 
 21 0x000000015 SeAuditPrivilege                  Attributes - Enabled Default 
 22 0x000000016 SeSystemEnvironmentPrivilege      Attributes - 
 23 0x000000017 SeChangeNotifyPrivilege           Attributes - Enabled Default 
 25 0x000000019 SeUndockPrivilege                 Attributes - 
 28 0x00000001c SeManageVolumePrivilege           Attributes - 
 29 0x00000001d SeImpersonatePrivilege            Attributes - Enabled Default 
 30 0x00000001e SeCreateGlobalPrivilege           Attributes - Enabled Default 
 31 0x00000001f SeTrustedCredManAccessPrivilege   Attributes - 
 32 0x000000020 SeRelabelPrivilege                Attributes - 
 33 0x000000021 SeIncreaseWorkingSetPrivilege     Attributes - Enabled Default 
 34 0x000000022 SeTimeZonePrivilege               Attributes - Enabled Default 
 35 0x000000023 SeCreateSymbolicLinkPrivilege     Attributes - Enabled Default 
Authentication ID:         (0,3e7)
Impersonation Level:       Anonymous
TokenType:                 Primary
Source: *SYSTEM*           TokenFlags: 0x2000 ( Token NOT in use ) 
Token ID: 3eb              ParentToken ID: 0
Modified ID:               (0, 3ec)
RestrictedSidCount: 0      RestrictedSids: 0x0000000000000000
OriginatingLogonSession: 0
kd> dt _token
nt!_TOKEN
...
   +0x040 Privileges       : _SEP_TOKEN_PRIVILEGES
...

powershell process
kd> dt _sep_token_privileges fffff8a0022b7060+0x40
nt!_SEP_TOKEN_PRIVILEGES
   +0x000 Present          : 0x00000006`02880000
   +0x008 Enabled          : 0x800000
   +0x010 EnabledByDefault : 0x800000
   
System process
kd> dt _sep_token_privileges fffff8a000004040+0x40
nt!_SEP_TOKEN_PRIVILEGES
   +0x000 Present          : 0x0000000f`f2ffffbc
   +0x008 Enabled          : 0x0000000e`60b1e890
   +0x010 EnabledByDefault : 0x0000000e`60b1e890

eq fffff8a0022b7060+0x40 0x0000000f`f2ffffbc
eq fffff8a0022b7060+0x40+8 0x0000000f`f2ffffbc

kd> dt _sep_token_privileges fffff8a0022b7060+0x40
nt!_SEP_TOKEN_PRIVILEGES
   +0x000 Present          : 0x0000000f`f2ffffbc
   +0x008 Enabled          : 0x0000000f`f2ffffbc
   +0x010 EnabledByDefault : 0x800000
  

SSDT / Hooking / прерывания

Service Descriptor Table

Service Descriptor Table - структура ядра, показанная ниже, содержит 4 System Service Table

typedef struct tagSERVICE_DESCRIPTOR_TABLE {
    SYSTEM_SERVICE_TABLE nt;
    SYSTEM_SERVICE_TABLE win32k;
    SYSTEM_SERVICE_TABLE sst3;
    SYSTEM_SERVICE_TABLE sst4;
} SERVICE_DESCRIPTOR_TABLE;

В системе две Service Descriptor Tables:

  • nt!KeServiceDescriptorTable
  • nt!KeServiceDescriptorTableShadow

System Service Table (SST)

SST структура, показанная ниже, содержит поле ServiceTable, которое является указателем на первый элемент массива указателей на рутины(функции) ядра в случае 32-битной ОС или указателем на массив адресов (и некоторой доп информации о количестве параметров), relative to the base address pointed to by it в случае 64-битной ОС

typedef struct tagSYSTEM_SERVICE_TABLE {
    PULONG ServiceTable;
    PULONG_PTR CounterTable;
    ULONG_PTR ServiceLimit;
    PBYTE ArgumentTable;
} SYSTEM_SERVICE_TABLE;
kd> dps nt!KeServiceDescriptorTable L10
    fffff800`02aba900  fffff800`028b3200 nt!KiServiceTable
    fffff800`02aba908  00000000`00000000
    fffff800`02aba910  00000000`00000191
    fffff800`02aba918  fffff800`028b3e8c nt!KiArgumentTable
    fffff800`02aba920  00000000`00000000
    fffff800`02aba928  00000000`00000000
    fffff800`02aba930  00000000`00000000
    fffff800`02aba938  00000000`00000000
    fffff800`02aba940  00000000`00000001
    fffff800`02aba948  00000000`00000000
    fffff800`02aba950  00000000`00000003
    fffff800`02aba958  00000000`00060107
    fffff800`02aba960  fffff800`02aba960 nt!MmPagedPoolInfo+0x20
    fffff800`02aba968  fffff800`02aba960 nt!MmPagedPoolInfo+0x20
    fffff800`02aba970  00000000`00000000
    fffff800`02aba978  00000000`02000000
kd> dps nt!KeServiceDescriptorTableShadow L10
    fffff800`02aba9c0  fffff800`028b3200 nt!KiServiceTable
    fffff800`02aba9c8  00000000`00000000
    fffff800`02aba9d0  00000000`00000191
    fffff800`02aba9d8  fffff800`028b3e8c nt!KiArgumentTable
    fffff800`02aba9e0  fffff960`00165900 win32k!W32pServiceTable
    fffff800`02aba9e8  00000000`00000000
    fffff800`02aba9f0  00000000`0000033b
    fffff800`02aba9f8  fffff960`0016761c win32k!W32pArgumentTable
    fffff800`02abaa00  c0000044`00000005
    fffff800`02abaa08  c0000044`00000005
    fffff800`02abaa10  c000012c`00000000
    fffff800`02abaa18  c00000a1`00000000
    fffff800`02abaa20  c0000001`00000002
    fffff800`02abaa28  fffff800`00a01310
    fffff800`02abaa30  fffff800`00a012c0
    fffff800`02abaa38  00000000`00000002

SSDTs

System Service Dispatch Table или SSDT - это просто таблица функций ядра.

Как говорилось выше, есть массив указателей в случае 32-битной ОС или массив относительных адресов в случае 64-битной, на который указывает поле ServiceTable SST(System Service Table), этот массив - просто SSDT

Смотря на результаты WinDBG выше, мы видем, что есть только одна явная SST таблица в случае nt!KeServiceDescriptorTable и две в случае nt!KeServiceDescriptorTableShadow. Хотя мы видим больше данных во второй, из доступной информации мы можем заключить лишь то, что из возможных 4-ёх SST элементов nt!KeServiceDescriptorTable использует только первый - он описывает SSDT для Windows Native APIs, экспортируемых ntoskrnl.exe'ом. nt!KeServiceDescriptorTableShadow использует 2 SST элемента: первый - копия nt!KiServiceTable из nt!KeServiceDescriptorTable, второй - win32k!W32pServiceTable, описывающий SSDT для User и GDI функции, экспортируемые win32k.sys.

Как используются SSDT

SSDT предоставляют функциональность и приложениям пространства пользователя (разумеется, неявно), и драйверам ядра.

Посмотрим на случай с user mode'ом.

Когда user mode приложение вызывает, явно или неявно, некоторые функции Windows API, множество функций ядра будут вызваны в процессе. Для перехода в режим ядра из режима пользователя используется Sysenter (или Syscall для 64-бит) ассемблерная инструкция (раньше это было 0x2E прерывание, которое ещё актуально, хотя используется не так часто). Конкретная функция обработки будет вызвана по номеру, Dispatch ID, значение которого было в регистре EAX до вызова инструкции Sysenter / Syscall.

Первые 12 бит Dispatch ID - это индекс в SSDT. Биты 12-ый и 13-ый указывают, который из SSDT. Это означает, что Dispatch ID до 0xFFF будет обработан nt!KiServiceTable SSDT, а Dispatch ID между 0x1000 и 0x1FFF будет обработан win32k!W32pServiceTable SSDT.

Когда вызывается функция в пространстве пользователя, например CreateFile, в итоге управление передаётся в ntdll!NtCreateFile и с помощью сискола в ядро nt!NtCreateFile (ntoskrnl)

По сути, сисколы и SSDT (KiServiceTable) работают вместе, как мост между API функциями пространства пользователя и соответсвующими им функциями в пространестве ядра, позволяя ядру понять, которая из функций должна быть выполнена для конкретного сискола, вызванного ещё в пространстве пользователя.

Мы можем найти структуру Service Descriptor Table в KeServiceDescriptorTable. Первый элемент будет KiServiceTable - указатель на саму SSDT

kd> dps nt!keservicedescriptortable L4
    fffff800`02b04900  fffff800`028fd200 nt!KiServiceTable
    fffff800`02b04908  00000000`00000000
    fffff800`02b04910  00000000`00000191
    fffff800`02b04918  fffff800`028fde8c nt!KiArgumentTable

Значения из SSDT

kd> dd /c1 KiServiceTable L5
    fffff800`028fd200  03c19000
    fffff800`028fd204  02528d00
    fffff800`028fd208  ffffb400
    fffff800`028fd20c  02560005
    fffff800`028fd210  02b4ef06

На 64-битных системах SSDT содержит относительные сдвиги на ядерные функции. Чтобы получить абсолютный адрес для сдивига, нужно использовать следующую формулу:

RoutineAbsoluteAddress = KiServiceTableAddtess + (routineOffset >>> 4)

KiServiceTable (fffff800`028fd200) + (03c19000 >>> 4)
kd> u KiServiceTable + (03c19000 >>> 4)
nt!NtMapUserPhysicalPagesScatter:
fffff800`02cbeb00 48895c2408      mov     qword ptr [rsp+8],rbx
fffff800`02cbeb05 4c89442418      mov     qword ptr [rsp+18h],r8
fffff800`02cbeb0a 55              push    rbp
fffff800`02cbeb0b 56              push    rsi
fffff800`02cbeb0c 57              push    rdi
fffff800`02cbeb0d 4154            push    r12
fffff800`02cbeb0f 4155            push    r13
fffff800`02cbeb11 4156            push    r14

(пример на картинке из статьи)

На 32-битных системах ещё проще

kd> u ntdll!NtCreateFile
ntdll!NtCreateFile:
    77ab3250 b875010000      mov     eax,175h
    77ab3255 e803000000      call    ntdll!NtCreateFile+0xd (77ab325d)
    77ab325a c22c00          ret     2Ch
    77ab325d 8bd4            mov     edx,esp
    77ab325f 0f34            sysenter
    
kd> dps nt!KiServiceTable L176
    8190f20c  818c573a nt!NtAccessCheck
    8190f210  818cbfd8 nt!NtWorkerFactoryWorkerReady
    8190f214  81b033b8 nt!NtAcceptConnectPort
    .....
    190f7d8  81ad7b18 nt!NtCreateTimer2
    8190f7dc  81af323a nt!NtCreateIoCompletion
    8190f7e0  81a66958 nt!NtCreateFile

Попробуем найти ядерную функцию для конкретного сискола

Возьмём для примера функцию Sleep. Её функция в nt - это NtDelayExecution

kd> u NtDelayExecution L3
ntdll!NtDelayExecution:
    00000000`78eb9be0 4c8bd1          mov     r10,rcx
    00000000`78eb9be3 b831000000      mov     eax,31h
    00000000`78eb9be8 0f05            syscall

Оффсеты(сдвиги) в KiServiceTable 4-ёх байтовые, так что нам нужно посмотреть значение на 0x31-ой позиции

kd> dd /c1 kiservicetable+4*0x31 L1
    fffff800`028fd2c4  023d1440

Используем формулу

kiservicetable(fffff800`028fd200) + 4 * 0x31
kd> dd kiservicetable+4*0x31 L1
    fffff800`028fd2c4  023d1440
    
kd> u kiservicetable + (023d1440>>>4) L1
nt!NtDelayExecution:
    fffff800`02b3a344 4883ec28        sub     rsp,28h

Найдём адреса всех функций в SSDT

.foreach /ps 1 /pS 1 ( offset {dd /c 1 nt!KiServiceTable L poi(keservicedescriptortable+0x10) }){ dp kiservicetable + ( offset >>> 4 ) L1 } (ПИЗДЕЦ)

.foreach /ps 1 /pS 1 ( offset {dd /c 1 nt!KiServiceTable L poi(nt!KeServiceDescriptorTable+10)}){ r $t0 = ( offset >>> 4) + nt!KiServiceTable; .printf "%p - %y\n", $t0, $t0 } (ОТВАЛ БАШКИ)

kd> .foreach /ps 1 /pS 1 ( offset {dd /c 1 nt!KiServiceTable L poi(nt!KeServiceDescriptorTable+10)}){ r $t0 = ( offset >>> 4) + nt!KiServiceTable; .printf "%p - %y\n", $t0, $t0 }
    fffff80002cbeb00 - nt!NtMapUserPhysicalPagesScatter (fffff800`02cbeb00)
    fffff80002b4fad0 - nt!NtWaitForSingleObject (fffff800`02b4fad0)
    fffff800128fcd40 - fffff800`128fcd40
    fffff80002b53200 - nt!NtReadFile (fffff800`02b53200)
    fffff80002bb20f0 - nt!NtDeviceIoControlFile (fffff800`02bb20f0)
    fffff80002b53c10 - nt!NtWriteFile (fffff800`02b53c10)
    fffff80002b52c40 - nt!NtRemoveIoCompletion (fffff800`02b52c40)
    fffff80002b6c0a0 - nt!NtReleaseSemaphore (fffff800`02b6c0a0)
    fffff80002b44060 - nt!NtReplyWaitReceivePort (fffff800`02b44060)
    ...

Почему SSDT так важны?

Всё просто, в 32-битную эру руткиты модифицировали nt!KiServiceTable ил win32k!W32pServiceTable, чтобы вызвать свой код. Также, многие антивирусы использовали хуки SSDT, чтобы получать мгновенный алёрт об атаке.

Однако с 64-битных ОС был представлен Kernel Patch Protection (PatchGuard). PatchGuard делает периодические проверки, чтобы убедиться, что критические системные структуры, включая SSDT, не были модифицированы в недавнее время.

32-битный и 64-битный код для поиска SSDT

Цель кода не хукнуть системную функцию в SSDT (это довольно просто без PatchGuard'а), об этом уже много написано
Целью и сложной частью является поиск SSDT. В особенности win32k!W32pServiceTable SSDT, на который указывает вторая запись SST nt!KeServiceDescriptorTab1eShadow. С другой стороны найти nt!KiServiceTable SSDT ждя 32-битной ОС легко, символы экспортируются, просто нужно прочитать значение. Символы для nt!KiServiceTable не экспортируются в 64-битной ОС.

В коде мы найдём nt!KeServiceDescriptorTab1eShadow. И тогда мы будем знать расположения nt!KiServiceTable и win32k!W32pServiceTable.

Билдим драйвер ядра и приложение user mode'а. Visual Studio 2015 solution - one project for the driver (both 32-bit and 64-bit) and another for the driver (both 32-bit and 64-bit)

32-бит

  • Энумерируем все процессы, ища PID, полученный из user mode'а.
  • Нашли - энумерируем потоки. ища поток GUI - у них ненулевое значение Win32Thread в KTHREAD
  • GUI потоки гарантированно имеют элемент ServiceTable в KTHREAD, указывающий на nt!KeServiceDescriptorTab1eShadow

64-бит

  • Читаем регистр MSR CPU, используя значение IA32_LSTAR.
  • Это вернёт адрес 64-bit service call dispatcher (nt!kiSystemCall64). Не дальше, чем 512 байт лежит nt!KiSystemServiceRepeat.
  • Дизассемблируем, находим ссылку на nt!KeServiceDescriptorTableShadow.
kd> u nt!KiSystemServiceRepeat
nt!KiSystemServiceRepeat:
    fffff803`f28060a4 4c8d15d5e72700  lea     r10,[nt!KeServiceDescriptorTable (fffff803`f2a84880)]
    fffff803`f28060ab 4c8d1d4eb42600  lea     r11,[nt!KeServiceDescriptorTableShadow (fffff803`f2a71500)]
    fffff803`f28060b2 f7437840000000  test    dword ptr [rbx+78h],40h

Теперь осталось найти паатерн, вытащить RIP адрес и посчитать адрес nt!KeServiceDescriptorTableShadow

SSDT Hook

x64

SSDT:
kd> dps nt!KeServiceDescriptorTable
    fffff800`02b05900  fffff800`028fe200 nt!KiServiceTable
    fffff800`02b05908  00000000`00000000
    fffff800`02b05910  00000000`00000191
    fffff800`02b05918  fffff800`028fee8c nt!KiArgumentTable
    
kd> dd /c 1 nt!KiServiceTable L10
    fffff800`028fe200  03c19000    # function offset
    fffff800`028fe204  02528d00
    fffff800`028fe208  ffffb400
    fffff800`028fe20c  02560005
    fffff800`028fe210  02b4ef06

SSDT Shadow:
kd> dps nt!KeServiceDescriptorTableShadow
    fffff800`02b059c0  fffff800`028fe200 nt!KiServiceTable    # SSDT base address
    fffff800`02b059c8  00000000`00000000
    fffff800`02b059d0  00000000`00000191
    fffff800`02b059d8  fffff800`028fee8c nt!KiArgumentTable
    fffff800`02b059e0  fffff960`00115900 win32k!W32pServiceTable    # SSDT Shadow base address
    fffff800`02b059e8  00000000`00000000
    fffff800`02b059f0  00000000`0000033b
    fffff800`02b059f8  fffff960`0011761c win32k!W32pArgumentTable
    
Перед дампом SSDT Shadow нужно приаттачиться к любом юзермодному гуи приложению (например winlogon.exe)

kd> !process 0 0 winlogon.exe
    PROCESS fffffa80058c48f0
        SessionId: 1  Cid: 01b0    Peb: 7fffffdf000  ParentCid: 017c
        DirBase: 32bc7000  ObjectTable: fffff8a0081ea6e0  HandleCount: 116.
        Image: winlogon.exe
kd> .process /p fffffa80058c48f0
kd> .reload
kd> dd /c 1 win32k!W32pServiceTable L10
    fffff960`00115900  fff35f00    # GDI Function offset
    fffff960`00115904  fff06a81
    fffff960`00115908  00020700

Индекс функции на адрес реальной фукнции:

readAddress = (ULONG_PTR)(ntTable[FunctionIndex] >> 4) + SSDT(Shadow)BaseAddress;

Найти адрес таблицы в драйвере

Найти nt!KeServiceDescriptorTable и nt!KeServiceDescriptorTableShadow в функции ядра nt!KiSystemServiceStart

kd> u nt!KiSystemServiceStart 
nt!KiSystemServiceStart:
    fffff800`0290885e 4889a3d8010000  mov     qword ptr [rbx+1D8h],rsp
    fffff800`02908865 8bf8            mov     edi,eax
    fffff800`02908867 c1ef07          shr     edi,7
    fffff800`0290886a 83e720          and     edi,20h
    fffff800`0290886d 25ff0f0000      and     eax,0FFFh
    nt!KiSystemServiceRepeat:
    fffff800`02908872 4c8d1587d01f00  lea     r10,[nt!KeServiceDescriptorTable (fffff800`02b05900)]
    fffff800`02908879 4c8d1d40d11f00  lea     r11,[nt!KeServiceDescriptorTableShadow (fffff800`02b059c0)]
    fffff800`02908880 f7830001000080000000 test dword ptr [rbx+100h],80h

Как видно выше, мы можем искать по паттерну KiSystemServiceStart от начала ядра

const unsigned char KiSystemServiceStartPattern[] = { 
    0x8B, 0xF8,                     // mov edi, eax
    0xC1, 0xEF, 0x07,               // shr edi, 7
    0x83, 0xE7, 0x20,               // and edi, 20h
    0x25, 0xFF, 0x0F, 0x00, 0x00    // and eax, 0fffh  
};

bool found = false;
ULONG KiSSSOffset;
for(KiSSSOffset = 0; KiSSSOffset < kernelSize - signatureSize; KiSSSOffset++)
{
    if(RtlCompareMemory(
        ((unsigned char*)kernelBase + KiSSSOffset), 
        KiSystemServiceStartPattern, signatureSize) == signatureSize)
    {
        found = true;
        break;
    }
}
if(!found)
    return NULL;

После того, как нашли нужную функцию, мы можем получить адрес nt!KeServiceDescriptorTableShadow из инструкции:
fffff80002908879 4c8d1d40d11f00 lea r11, [nt!KeServiceDescriptorTableShadow (fffff80002b059c0)]

ULONG_PTR addressAfterPattern = kernelBase + kiSSSOffset + signatureSize
ULONG_PTR address = addressAfterPattern + 7 // Skip lea r10,[nt!KeServiceDescriptorTable]
LONG relativeOffset = 0;
// lea r11, KeServiceDescriptorTableShadow
if((*(unsigned char*)address == 0x4c) &&
   (*(unsigned char*)(address + 1) == 0x8d) &&
   (*(unsigned char*)(address + 2) == 0x1d))
{
    relativeOffset = *(LONG*)(address + 3);
}
if(relativeOffset == 0)
    return NULL;

SSDTStruct* shadow = (SSDTStruct*)( address + relativeOffset + 7 );

Затем можем получить адреса nt!KiServiceTable(SSDT) и win32k!W32pServiceTable(SSDT Shadow)

напочитать:

Команды WinDBG

Тематически сгруппированные команды WinDBG
Common WinDbg Commands (Thematically Grouped)

Команда Пояснение к ней
!peb display formatted PEB
dt nt!_PEB Addr full PEB dump
lm list loaded and unloaded modules
lm vm kernel32 verbose output (incl image, syml information)
!dlls display loaded modules with loader info
!imgreloc display relocation info
!dh kernel32 display headers
!gle Get Last Error
!process 0 4 processname.exe print all threads of process
!teb display formatted teb
dt nt!_TEB Addr full TEB dump
k display call stack for current thread
kP P == full parameters for each function called
kf f == distance between adjacent frames to be displayed (useful to check stack consumption of each frame)
kv v == display FPO information + calling convention
kb b == display the first three parameters passed to each function
d, dd, da, du Display memory
dd == double word values
da == display ASCII characters
du == display Unicode characters
f 0012ff40 L20 'A' 'B' 'C' fill 20 elements with ABC starting at address
!vprot MyAddr Displays virtual memory protection information for MyAddr
!address MyAddr Display information (type, protection, usage, ..) about the memory specified by MyAddr
!heap print all heaps
!locks displays a list of locked critical sections for the process
!locks -v display all critical sections for the process
!cs -l [CsAddr] Displays one or more critical sections, or the entire critical section tree. -l == display only locked sections
!cs -s [CsAddr] -s == causes each CS’s initialization stack to be displayed
!cs -o [CsAddr] -o == causes the owner’s stack to be displayed
!cs -t [CsAddr] -t == display critical section tree -> EnterCntr, WaitCnt, …
!avrf -cs Display a list of deleted critical sections (DeleteCriticalSection API)
!critsec [CsAddr] displays the same collection of information as !ntsdexts.locks
dt Display information about a local variable, function parameter, global variable or data type
dv Display local variables
dv /t /i /V Display local variables
/i == classify them into categories (parameters or locals)
/V == show addresses and offsets for the relevant base frame register (usually EBP)
/t == display type information
dd 0046c6b0 L1 display 1 dword at 0046c6b0
dd 0046c6b0 L3 display 3 dwords at 0046c6b0
du 0046c6b0 display Unicode chars at 0046c6b0
as Name Equivalent
as /ma Name Address
as /mu Name Address
Set alias
Set alias to the NULL-terminated ASCII string at Address
Set alias to the NULL-terminated Unicode string at Address
ad Name
ad *
Delete alias with Name
Delete all aliases
al List user-named aliases
${Alias}

${/f:Alias}

${/n:Alias}

${/d:Alias}
${Alias} is replaced by the alias equivalent, even if it is touching other text. If the alias is not defined, the ${Alias} is not replaced

Same as above except that ${/f:Alias} is replaced with an empty string if the alias is not defined

Evaluates to the alias name

Evaluates: 1 = alias defined; 0 = alias not defined
bp
bu
ba
bc
be, bd
Set Breakpoint
Set Unresolved Breakpoint: defers the actual setting of the breakpoint until the module is loaded
Break on Access
Breakpoint Clear
Breakpoint Enable, Disable
ba r4 0012fe34
ba w2 0012fe38
break on access (read or write); monitor 4 bytes
break on access (write); monitor 2 bytes
bu kernel32!LoadLibraryExW 5 Breakpoint that will starts hitting after 5 passes
~1 bu kernel32!LoadLibraryExW Break only if called from thread ~1
bp mod!myFunc* Break at all symbols with pattern myFunc*
.lastevent first-change or second-chance?
!analyze -v Displays detailed information about the current exception
.exr -1 Display most recent exception
.exr Addr Display exception at Addr
!cppexr Addr Display c++ exception at address Addr
g, gH
gN
Go with Exception Handled
Go with Exception Not Handled
.dump /ma D:\large.dmp all possible data: full memory, code sections, PEB and TEB’s, handle data, thread time information, unloaded module lists, and more
.dump /m d:\small.dmp only basic information: module information (signatures), thread and stack information
r print all registers
d * view memory
e * edit memory
~1 ~2 change context to processor 1/2
ed nt!Kd_Default_Mask 8 Включить DbgPrint прям в консоль windbg