Showing posts with label ntkernel. Show all posts
Showing posts with label ntkernel. Show all posts

Tuesday, June 11, 2024

A Workaround for "Levels not implemented for this platform"

The Windows kernel people may be familiar with a cool built-in "pte" extension that can help you troubleshoot ugly BSODs by checking if some kernel memory address is inaccessible.


0: kd> !pte ffff8d89`cce3efe8 
                                           VA ffff8d89cce3efe8
PXE at FFFF91C8E47238D8    PPE at FFFF91C8E471B138    PDE at FFFF91C8E3627338    PTE at FFFF91C6C4E671F0
contains 0A0000021E002863  contains 0A00000002903863  contains 0A0000012845B863  contains 8A0000015AD4F963
pfn 21e002    ---DA--KWEV  pfn 2903      ---DA--KWEV  pfn 12845b    ---DA--KWEV  pfn 15ad4f    -G-DA--KW-V

0: kd> dt nt!_HARDWARE_PTE FFFF91C6C4E671F0
   +0x000 Valid            : 0y1
   +0x000 Write            : 0y1
   +0x000 Owner            : 0y0
   +0x000 WriteThrough     : 0y0
   +0x000 CacheDisable     : 0y0
   +0x000 Accessed         : 0y1
   +0x000 Dirty            : 0y1
   +0x000 LargePage        : 0y0
   +0x000 Global           : 0y1
   +0x000 CopyOnWrite      : 0y0
   +0x000 Prototype        : 0y0
   +0x000 reserved0        : 0y1
   +0x000 PageFrameNumber  : 0y000000000000000101011010110101001111 (0x15ad4f)
   +0x000 reserved1        : 0y0000
   +0x000 SoftwareWsIndex  : 0y00010100000 (0xa0)
   +0x000 NoExecute        : 0y1


Normally it shows PTE address among with other information, but unfortunately it has been broken for a quite some time (and his colleague vtop - too):


0: kd> !pte fffff807`1f8a7c58
Levels not implemented for this platform


And it doesn't look like MS is going to fix it anytime soon, see:

https://learn.microsoft.com/en-us/answers/questions/1010386/windbg-pte-command-levels-not-implemented-for-this?page=1#answer-1315958

https://github.com/microsoftfeedback/WinDbg-Feedback/issues/8


It's no problem if you have a single occasional BSOD, but if you really need to use this extension for a number of dumps in a row it's getting annoying.

Sooo, I decided to use my small and nice (and a little bit outdated) code emulator and emulate nt!MiGetPteAddress instead:


0: kd> .load c:\orthia\orthia.dll;!orthia.profile /f %temp%\test.db; !orthia.vm_vm_def
0: kd> !orthia.vm_vm_call 0 nt!MiGetPteAddress --print rcx=fffff807`1f8a7c58

Diana Error Code: DI_END
rax=fffff6fc038fc538 rbx=fffff8071b881180 rcx=0000007c038fc538
rdx=0000025800000000 rsi=0000000000000001 rdi=000000000000000d
rip=0000000000000000 rsp=fffff8071f8a7c58 rbp=ffff940947d3e040
r8=0000000000000018  r9=ffff9409476e0000 r10=000000000000000b
r11=0000000000009ee6 r12=0000000000000000 r13=fffff8071b881180
r14=0000000000000001 r15=ffffffffffffff00
cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b  efl=00000282
Commands count: 6
Modified pages:
Done

0: kd> dt ntkrnlmp!_HARDWARE_PTE fffff6fc038fc538
 +0x000 Valid            : 0y1
 +0x000 Write            : 0y1
 +0x000 Owner            : 0y0
 +0x000 WriteThrough     : 0y0
   +0x000 CacheDisable     : 0y0
   +0x000 Accessed         : 0y1
 +0x000 Dirty            : 0y1
   +0x000 LargePage        : 0y0
 +0x000 Global           : 0y0
 +0x000 CopyOnWrite      : 0y0
 +0x000 Prototype        : 0y0
 +0x000 reserved0        : 0y1
 +0x000 PageFrameNumber  : 0y000000000000000000000110110010100111 (0x6ca7)
 +0x000 reserved1        : 0y0000
 +0x000 SoftwareWsIndex  : 0y00010010000 (0x90)
 +0x000 NoExecute        : 0y1

As you can see there were just 6 assembler instruction to emulate, so it handled them successfully.

The extension is here: https://github.com/ligen-ua/diana-dasm/releases/tag/v1.1-tools

The sources and this case study is there:

https://github.com/ligen-ua/diana-dasm?tab=readme-ov-file#pte-case-study


Tuesday, June 7, 2016

The terrible story about .NET Invoke

1.  There is something interesting about PostMessage
There is a limit of 10,000 posted messages per message queue. This limit should be sufficiently large. If your application exceeds the limit, it should be redesigned to avoid consuming so many system resources. To adjust this limit, modify the following registry key.
HKEY_LOCAL_MACHINE
   SOFTWARE
      Microsoft
         Windows NT
            CurrentVersion
               Windows
                  USERPostMessageLimit
The minimum acceptable value is 4000. 
https://msdn.microsoft.com/ru-ru/library/windows/desktop/ms644944%28v=vs.85%29.aspx

2.  And this explains why Invoke hangs when reaches the limit:

private object MarshaledInvoke(Control caller, 
                               Delegate method, 
                               object[] args, 
                               bool synchronous)
{
    int lpdwProcessId;
/// ....
/// skipped
/// ....
    if (flag)
        this.InvokeMarshaledCallbacks();
    else
    {
        UnsafeNativeMethods.PostMessage(new HandleRef(this, this.Handle), 
                                        threadCallbackMessage, 
                                        IntPtr.Zero, 
                                        IntPtr.Zero);
    }
    if (!synchronous)
        return entry;
    if (!entry.IsCompleted)
        this.WaitForWaitHandle(entry.AsyncWaitHandle); // <<<< OOPS
    if (entry.exception != null)
        throw entry.exception;
    return entry.retVal;
}

http://workblog.pilin.name/2007/04/control.html 

It's a scary, scary world

Sunday, April 3, 2011

Способ передачи була по ntkernl-овски

В семерке оригинально реализован сабж. Вот так выглядит экспортируемая функция RtlPrefetchMemoryNonTemporal в памяти:
kd> u 0x82603000+FB9A*4
nt!RtlPrefetchMemoryNonTemporal:
82641e68 90 nop
82641e69 a1b4aa7282 mov eax,[nt!KePrefetchNTAGranularity (8272aab4)]
82641e6e 0f184100 prefetchnta byte ptr [ecx]
82641e72 03c8 add ecx,eax
82641e74 2bd0 sub edx,eax
82641e76 77f6 ja nt!RtlPrefetchMemoryNonTemporal+0x6 (82641e6e)
82641e78 c3 ret
82641e79 90 nop
А вот так она же выглядит на диске:
kd> u 0x00050800+FB9A*4
0008f668 c3 ret
0008f669 a1b4aa7282 mov eax,[nt!KePrefetchNTAGranularity (8272aab4)]
0008f66e 0f184100 prefetchnta byte ptr [ecx]
0008f672 03c8 add ecx,eax
0008f674 2bd0 sub edx,eax
0008f676 77f6 ja 0008f66e
0008f678 c3 ret
0008f679 90 nop
_Winnie C++ Colorizer

WRK объясняет, откуда взялось отличие - ntldr таким чудесным образом передает знание о свойствах процессора непосредственно ядру. Т.е. он делает что-то в духе:
*(char*)GetProcAddr(ntosBase, "RtlPrefetchMemoryNonTemporal") = 0xC3
Как по мне, через именованную секцию данных было бы красивее, но и так тоже ничего

Thursday, February 10, 2011

дизассемблер в x64

Рекомендую позитив: http://govnokod.ru/2502.
Нужно сказать, что файл amd64\decode.c из WRK прекрасен от начала и до конца.

Wednesday, February 9, 2011

что странное о InterlockedCompareExchange

Прекрасное рядом:
Interlocked operations cannot be used on non-cached memory.
в DDK.

Как бы так интерпретировать эту дивную фразу?
Ересь какая-то.

З.Ы. Кстати, видели в семерке? сплошное локфри кругом - subj классно ложится на ps-овские ЗарегатьМегаКеллбэк, УдалитьМегаКеллбэк, ВызватьВсеКеллбеки. Массив + CAS. Красота.

Tuesday, February 16, 2010

About IO_OPEN_TARGET_DIRECTORY

Прекрасный способ открытия папки по имени файла:
NTSTATUS OpenParentFolderByFileName(OUT PHANDLE pFileHandle,
IN ACCESS_MASK desiredAccess,
IN POBJECT_ATTRIBUTES pObjectAttributes,
OUT PIO_STATUS_BLOCK pIoStatusBlock,
IN ULONG options)
{
return IoCreateFile( pFileHandle,
desiredAccess,
pObjectAttributes,
pIoStatusBlock,
(PLARGE_INTEGER) NULL,
0,
FILE_SHARE_READ | FILE_SHARE_WRITE,
FILE_OPEN,
FILE_OPEN_FOR_BACKUP_INTENT,
(PVOID) NULL,
0L,
CreateFileTypeNone,
(PVOID) NULL,
options|IO_OPEN_TARGET_DIRECTORY);
}

void Test()
{
UNICODE_STRING fileName;
OBJECT_ATTRIBUTES objectAttributes;
IO_STATUS_BLOCK iosb;

RtlInitUnicodeString(&fileName, L"\\??\\c:\\hello.txt");

InitializeObjectAttributes(
&objectAttributes,
&fileName,
OBJ_CASE_INSENSITIVE|OBJ_KERNEL_HANDLE,
NULL,
NULL
);

HANDLE hFile = 0;
if (NT_SUCCESS(OpenParentFolderByFileName(&hFile, 0, &objectAttributes, &iosb, IO_NO_PARAMETER_CHECKING)))
{
// handle should point to --> \\??\\c:\\ :)
// ..
ZwClose(hFile);
}
}
_Winnie C++ Colorizer
Как видно из листинга, использование флага IO_OPEN_TARGET_DIRECTORY освобождает от унылого и бажного ручного парсинга имени файла и перекладывает эту задачу на файловую систему. Это решение от microsoft кажется мне логичным и прекрасным - кому, как не файловой системе знать о связи между файлом и папкой.
Чисто "black magic for kernel mode", жаль для user-mode это сделать невозможно.

Tuesday, December 23, 2008

открываем файлы

Почему-то IFS DDK не достаточно освещает один забавный момент, связанный с восприятием реальности NT в области открытия файлов. Вообще, его стоило бы добавить к разделу "ZwCreateFile" или к разделу Using Files In A Driver".

Пусть у нас в системе есть некий виртуальный девайс-диск, например "\device\my_super_disk", и на него существует символическая ссылка "\DosDevices\X:".
Допустим он даже примаунчен, что значит, что к нему "снизу" присоединилось соответствующее устройство файловой системы (не то чтобы у них там какой-то "стек", на самом деле диск может, вобщем-то, и не знать, в каком он состоянии находится, ему это без разницы).

Так вот, как мне кажется, у пользователя существует целых ТРИ принципиально разных способа открытия файлов используя "\DosDevices\X". Пользователь может:
1) просто открыть файл, используя в качестве имени что-то вроде "\DosDevices\X:\MyDir\MyFile.txt". Все просто и хорошо изучено.
Что изучено плохо:
- если устройство не примаунчено, NT попытается его примаунтить прямо в месте вызова;
- если вызывать IoDeleteDevice на my_super_disk в процессе маунта, возможны bsod'ы из-за специфики реализации некоторых микрософтовских фильтров(sr.sys);
2) открыть устройство для "прямого" доступа. Делаем тот же ZwCreateFile, но используем в качестве имени "\DosDevices\X:", и, обязательно указываем FILE_READ_DATA в DesiredOptions(или любой другой "неизбранный" флаг - см ниже).
Тоже простой и хорошо изученный способ. Интересности:
- если устройство не примаунчено, NT тоже попытается его примаунтить прямо в месте вызова;
- несмотря на то что my_super_disk открыт вроде бы как "напрямую" (c) MSDN, запросы будут ходить через устройство файловой системы, со всеми вытекающими. Сможете послать ей чего-нибудь;
3) открыть устройство для "по-настоящему-прямого" доступа. Открываем файл примерно как в пункте 2, за исключением того, что в DesiredOptions не ставим никаких других флагов, кроме избранных: ACCESS_SYSTEM_SECURITY, FILE_READ_ATTRIBUTES, SYNCHRONIZE, READ_CONTROL, WRITE_OWNER, WRITE_DAC. Например, указываем только SYNCHRONIZE.
Интересности:
- если устройство не примаунчено, NT не будет пытается его примаунтить;
- если устройство и примаунчено, запросы все равно не будут заходить в файловую систему;
- у вашего файла будет pFileObject->FsContext будет равен 0;
- идеально подходит для того, чтобы пообщаться с my_super_disk напрямую.

Интересно, МСДН как бы намекает обо всем этом в описании IoGetDeviceObjectPointer:
"To get a pointer to the highest-level driver in the file system driver stack, a driver must ensure that the file system is mounted; if it is not, this routine traverses the storage device stack. To ensure that the file system is mounted on the storage device, the driver must specify an appropriate access mask, such as FILE_READ_DATA or FILE_WRITE_ATTRIBUTES, in the DesiredAccess parameter. Specifying FILE_READ_ATTRIBUTES does not cause the file system to be mounted. "
Но - уныло и поверхностно.

Monday, November 10, 2008

PsSetCreateProcessNotifyRoutine - тоже ресурс

"Вот и верь после этого людям." (ц)

Как выяснилось, Symantec Антивирус Великий и Ужасный™ в состоянии в одиночку "сожрать" все доступные ресурсы PsSetCreateProcessNotifyRoutine. Так что не стоит полагаться на этот механизм в боевых условиях. Хорошо, что мы и не полагались. Интересно, что nt сообщает о нехватке ресурсов в виде STATUS_INVALID_PARAMETER, что определенно говорит о сложной душевной организации программистов, писавших сие.

Wednesday, June 25, 2008

Kernel C++

До тех пор, пока вы видите в своем свежескомпилированном драйвере:

mov eax, _pCurrentException
mov [ebp-40h], eax
mov eax, _pCurrentExContext
mov [ebp-44h], eax
вместо

call PsGetCurrentThread
lea ecx, [eax + CurrentExceptionOffset]
mov [ebp-40h], ecx
lea ecx, [eax + CurrentExContextOffset]
mov [ebp-44h], ecx
_Winnie C++ Colorizer
С++ в kernel'е использовать рановато.
Использование "С++ без исключений" порождает интересные вопросы:
  1. как быть с STL?
  2. как быть с уменьшением ширины кода без goto?
  3. как дружить RAII с goto?
  4. как добавлять поддержку non-valid состояния во все объекты, которые в других ситуациях могли бы просто выкинуть исключение в конструкторе?
    Итого, максимум что можно получить, это С со строгой типизацией и возможностью объявлять переменные где угодно.
    Можно, конечно, посмотреть в сторону ParseProcedure/DeleteProcedure, это, наверное, единственный гарантированно 100% работающий вариант - сделать свой микро-TLS в кернеле. Интересно, будут ли хаки этих процедур работать на Vista 64?
    И, самое главное - вы на самом деле готовы отдать (2*sizeof(void*) + sizeof(TreeHeader))*SystemThreadCount байт NonPaged Pool'а на поддержку C++ nested exceptions?

Sunday, April 6, 2008

sessions in kernel mode

Вообще, тема сессий в kernel mode действительно не раскрыта, точнее раскрыта весьма слабо и недокументированно, постоянно появляются разные вопросы...
К примеру, периодически, комрады на РСДН бьются над вопросом получения Id сессии в драйвере, а ведь чудо уже давно произошло; давно уже существует прекрасная функция
ULONG PsGetProcessSessionId (PEPROCESS Process)
экспортируемая начиная аж с XP, но - недокументированная MS до сих пор.
На мой же взгляд, один из самых красивых и переносимых способов получения Id сессии выглядит так:

NTSTATUS GetSessionIdOfThread(PULONG pSessionId, PETHREAD pThread)
{
IRP irp;
NTSTATUS status;
irp.Tail.Overlay.Thread = pThread;

status = IoGetRequestorSessionId(&irp, pSessionId);
return status;
}
_Winnie C++ Colorizer
(c) мой :)
Прекрасно работает на 2k-vista, готовить совместно с PsGetCurrentThread, либо с NtQuerySystemInformation + SYSTEM_THREAD_INFORMATION.

Thursday, April 3, 2008

Товарищ, при использовании memory mapped files - будь бдителен!

В терминологии Джоела Спольски, SEH исключение со статусом STATUS_IN_PAGE_ERROR представляет собой самую настоящую дыру в прекрасной абстракции файлов, проецируемых в память.

Знакомьтесь:
0: kd> !error c0000006
Error code: (NTSTATUS) 0xc0000006 (3221225478) - The instruction at "0x%08lx" referenced memory at "0x%08lx". The required data was not placed into memory because of an I/O error status of "0x%08lx".


Вот и комрад Larry Osterman пишет в своей статье следующие вещи:
For Memory Mapped files (and RPC), the system has to have a way of communicating error status to the caller. If you attempt to read from a memory mapped file and an error occurs when reading the file, there's no way of "failing" a read - it's just a MOV CPU instruction, and it has no failure semantics. As a result, the only way that the system can "fail" the operation is to abort the instruction with some form of access violation.

З.Ы. Еще один довод в пользу /EHa, ибо, в общем случае, высокоуровневый код может и не знать, откуда взялась память, с которой он работает.

Wednesday, November 21, 2007

В очередной раз о Previous Mode

Как и в любой современной операционной системе, в NT есть механизмы, позволяющие ядру защититься от разбушевавшегося приложения, не заблокировав при этом деятельность более дружественных компонентов - драйверов, уровень доверия к которым у системы неизмеримо выше.
Если задаться целью, то можно сформулировать основные постулаты защиты ядра от юзермодного кода (UC) примерно так:
- UC не должен иметь доступа к адресному пространству режима ядра;
- UC не должен иметь доступа к дескрипторам режима ядра(kernel mode handles);
- UC обязан проходить проверку доступа (access check) при работе с объектами системы;

К драйверам режима ядра такие ограничения, естественно, не применимы. Исходя из этих постулатов, возникает вполне закономерный вопрос - каким же образом NT разграничивает доступ к системным сервисам для user mode и kernel mode вызовов?
Дело в том, что в NT с каждым потоком ассоциировано некоторое поле, указывающее системному сервису, в каком режиме находился вызывающий тред до вызова функции ядра - поле это называется Previous Mode, и находится в кернельном TEB'е (Thread Environment Block). Тип этого поля представляет собой enum, описанный в DDK следующим образом:
typedef enum _MODE {
KernelMode,
UserMode,
MaximumMode
} MODE;
т.е. в качестве значений PreviousMode можно использовать зарезервированые костанты KernelMode=0 или UserMode=1, получить же к нему доступ можно (схематично) вот таким образом:
PsGetCurrentThread()->KernelTEB->PreviousMode;
Именно благодаря этому полю любая функция из ntoskrnl знает, откуда пришел выполняющий ее тред и нужно ли проверять параметры ее вызова на предмет юзермодных ограничений.

Для того, чтобы лучше понять детали реализации этого решения Microsoft, имеет смысл воспользоваться следующей схемой:

На схеме представлены два типичных прецедента:
  1. вызов CreateFile из пользовательского приложения (MyApplication.EXE).
    Который раскрывается в цепочку вызовов:
    ntdll.NtCreateFile -> ntoskrnl.KiFastCallEntry -> ntoskrnl.NtCreateFile
  2. вызов ZwCreateFile из драйвера режима ядра (MyDriver.SYS), проходящий другой сложный и интересный путь:
    ntoskrnl.ZwCreateFile -> ntoskrnl.KiSystemService -> ntoskrnl.KiFastCallEntry -> ntoskrnl.NtCreateFile
Интересна функция ntoskrnl.KiFastCallEntry - именно она копирует аргументы и диспетчеризирует все вызовы по их индексу, используя SDT/SST таблицы.

А функция KiSystemService и есть та самая дивная функция, которая устанавливает PreviousMode равным KernelMode, указывая на то, что вызывающий компонент имеет право использовать все свои драйверные полномочия.

Таким образом, алгоритм ZwCreateFile можно схематично представить так:
1. SetPreviousMode(KernelMode);
2. GetCallAddressValueFromSST (that corresponding NtCreateFile in normal way);
3. Call it;
4. SetPreviousMode(OldMode);
Такая себе абстракция "четыре-в-одном".
А что, если нам бы захотелось вызвать NtCreateFile напрямую, без диспетчеризации по SST? Такое желание может возникнуть, к примеру, если мы вспомним о существовании целого класса руткитов, основанных на подмене записей SST таблицы. Чтобы противостоять этому классу руткитов, достаточно написать свою функцию следующего вида:
1. SetPreviousMode(KernelMode);
2. Call NtCreateFile;
3. SetPreviousMode(OldMode);
Однако проблема состоит в том, что функции SetPreviousMode попросту не существует.
Тем более приятно написать свою!
Поскольку вбивать смещения для всех сервиспаков по меньшей мере скучно, попытаемся программно проанализировать код. К примеру, вот так выглядят популярные реализации GetPreviousMode:
2K
nt!KeGetPreviousMode:
80465320 a1 24f1dfff mov eax,[ffdff124]
80465325 0f b6 80 34010000 movzx eax,byte ptr [eax+0x134]
8046532c c3 ret

XP
nt!KeGetPreviousMode:
804daae3 a1 24f1dfff mov eax,[ffdff124]
804daae8 0f b6 80 40010000 movzx eax,byte ptr [eax+0x140]
804daaef c3 ret

2003
nt!KeGetPreviousMode:
8083a3a7 64 a1 24010000 mov eax,fs:[00000124]
8083a3ad 0f b6 80 d7000000 movzx eax,byte ptr [eax+0xd7]
8083a3b4 c3 ret
И этой информации достаточно, чтобы написать свою функцию CreateSetPreviousModeStub, задачей которой будет анализ имеющейся функции KeGetPreviousMode и создание новой SetPreviousMode:

//Lets write our own SetPreviousMode!
// TARGET FUNCTION
// head
static const
unsigned char stub1[]={
0x55, // __asm push ebp
0x8B, 0xEC, // __asm mov ebp, esp
0x8B, 0x4D, 0x08 // __asm mov ecx, [ebp + 8]
};

// autogenerated - for example for 2003
// movzx eax,byte ptr [eax+0xd7]

// tail
static const
unsigned char stub2[]={
0x86, 0x88, 0x00, 0x00, 0x00, 0x00, // __asm xchg byte ptr [eax+...], cl
0x0F, 0xB6, 0xC1, // __asm movzx eax, cl
0x5D, // __asm pop ebp
0xC2, 0x04, 0x00 // __asm ret 4
};

typedef unsigned long (__stdcall *KeSetPreviousModePtr)(unsigned long lMode);

PVOID GetKernelApiAddr(PCHAR ApiName)
{
PVOID Addr = NULL;
ANSI_STRING ApiNameAnsi = {0};
UNICODE_STRING ApiNameUnicode={0};
ApiNameAnsi.Length = ApiNameAnsi.MaximumLength=strlen(ApiName);
ApiNameAnsi.Buffer = ApiName;
RtlAnsiStringToUnicodeString(&ApiNameUnicode,&ApiNameAnsi,TRUE);
Addr = MmGetSystemRoutineAddress(&ApiNameUnicode);
RtlFreeUnicodeString(&ApiNameUnicode);
return Addr;
}

#define FS_prefix 0x64
#define MOV_first_byte 0xA1

// our goal !!!
NTSTATUS CreateSetPreviousModeStub(KeSetPreviousModePtr* ppStub)
{
unsigned char * pData = GetKernelApiAddr("KeGetPreviousMode");
unsigned char * pFirstMov = pData;
int iGetThreadCmdSize = 5;
unsigned long lSecondCmd = 0;

if (!pData)
return STATUS_UNSUCCESSFUL;

if (*pData == FS_prefix)
{
++iGetThreadCmdSize;
++pFirstMov;
}
if (*pFirstMov != MOV_first_byte)
return STATUS_UNSUCCESSFUL;

lSecondCmd = *(unsigned long*)(pFirstMov+5);
lSecondCmd &= 0x00FFFFFF;

if (lSecondCmd!=0x80b60f)
return STATUS_UNSUCCESSFUL;

{
unsigned long MagicOffset = *(unsigned long*)(pFirstMov+8);
unsigned long lStubSize = sizeof(stub1)+ sizeof(stub2) + iGetThreadCmdSize;
unsigned char * pStub = ExAllocatePool(NonPagedPool, lStubSize);
if (!pStub)
return STATUS_NO_MEMORY;

memcpy(pStub,stub1, sizeof(stub1));
memcpy(pStub+sizeof(stub1),pData, iGetThreadCmdSize);
memcpy(pStub+sizeof(stub1)+iGetThreadCmdSize,stub2, sizeof(stub2));

// set magic offset
*(unsigned long *)(pStub+sizeof(stub1)+iGetThreadCmdSize+2) = MagicOffset;

*ppStub = pStub;
}
return STATUS_SUCCESS;
}
void FreeSetPreviousModeStub(KeSetPreviousModePtr pStub)
{
ExFreePool(pStub);
}
_Winnie C++ Colorizer

Итого - вызываем CreateSetPreviousModeStub и наслаждаемся.
Именно в такой последовательности.
Поддержка Vista

Vista:
81891203 64a124010000 mov eax,fs:[nt!KiInitialPCR+0x124 (00000124)]
81891209 8a80e7000000 mov al,[eax+0xe7]
8189120f c3 ret

остается вам в качестве домашнего задания.