0% found this document useful (0 votes)
20 views78 pages

Windows Kernel Programming Pavel Yosifovich Instant Download

The document is a promotional listing for the book 'Windows Kernel Programming' by Pavel Yosifovich, available for download along with other related ebooks. It includes links to various resources on Windows kernel programming, debugging, and penetration testing. The book covers topics such as Windows internals, kernel development, and driver programming.

Uploaded by

rutfhwch268
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
20 views78 pages

Windows Kernel Programming Pavel Yosifovich Instant Download

The document is a promotional listing for the book 'Windows Kernel Programming' by Pavel Yosifovich, available for download along with other related ebooks. It includes links to various resources on Windows kernel programming, debugging, and penetration testing. The book covers topics such as Windows internals, kernel development, and driver programming.

Uploaded by

rutfhwch268
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 78

Windows Kernel Programming Pavel Yosifovich

download

https://ebookbell.com/product/windows-kernel-programming-pavel-
yosifovich-10533000

Explore and download more ebooks at ebookbell.com


Here are some recommended products that we believe you will be
interested in. You can click the link to download.

Windows Kernel Programming Pavel Yosifovich

https://ebookbell.com/product/windows-kernel-programming-pavel-
yosifovich-50391194

Windows Kernel Programming Second Edition 2nd Edition Pavel Yosifovich

https://ebookbell.com/product/windows-kernel-programming-second-
edition-2nd-edition-pavel-yosifovich-38249168

Rootkits Subverting The Windows Kernel Greg Hoglund Jamie Butler

https://ebookbell.com/product/rootkits-subverting-the-windows-kernel-
greg-hoglund-jamie-butler-1218384

Practical Reverse Engineering X86 X64 Arm Windows Kernel Reversing


Tools And Obfuscation 1st Edition Bruce Dang

https://ebookbell.com/product/practical-reverse-
engineering-x86-x64-arm-windows-kernel-reversing-tools-and-
obfuscation-1st-edition-bruce-dang-4671304
Practical Reverse Engineering X86 X64 Arm Windows Kernel Reversing
Tools And Obfuscation Bruce Dang

https://ebookbell.com/product/practical-reverse-
engineering-x86-x64-arm-windows-kernel-reversing-tools-and-
obfuscation-bruce-dang-5411236

Windows 2000 Kernel Debugging Steven Mcdowell

https://ebookbell.com/product/windows-2000-kernel-debugging-steven-
mcdowell-34521880

Windows And Linux Penetration Testing From Scratch Harness The Power
Of Pen Testing With Kali Linux For Unbeatable Hardhitting Results 2nd
Edition Phil Bramwell

https://ebookbell.com/product/windows-and-linux-penetration-testing-
from-scratch-harness-the-power-of-pen-testing-with-kali-linux-for-
unbeatable-hardhitting-results-2nd-edition-phil-bramwell-46431436

Windows Server 2022 Administration Fundamentals Third Edition 3rd


Bekim Dauti

https://ebookbell.com/product/windows-server-2022-administration-
fundamentals-third-edition-3rd-bekim-dauti-46473022

Windows Server 2016 Inside Out Includes Current Book Service Orin
Thomas

https://ebookbell.com/product/windows-server-2016-inside-out-includes-
current-book-service-orin-thomas-46508896
Windows Kernel Programming
Pavel Yosifovich
This book is for sale at http://leanpub.com/windowskernelprogramming

This version was published on 2019-10-10

This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishing
process. Lean Publishing is the act of publishing an in-progress ebook using lightweight tools and
many iterations to get reader feedback, pivot until you have the right book and build traction once
you do.

© 2019 Pavel Yosifovich


CONTENTS

Contents

Chapter 1: Windows Internals Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1


Processes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Virtual Memory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Page States . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
System Memory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Thread Stacks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
System Services (a.k.a. System Calls) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
General System Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Handles and Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
Object Names . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
Accessing Existing Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

Chapter 2: Getting Started with Kernel Development . . . . . . . . . . . . . . . . . . . . . . . . 20


Installing the Tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
Creating a Driver Project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
The DriverEntry and Unload Routines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
Deploying the Driver . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Simple Tracing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31

Chapter 3: Kernel Programming Basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32


General Kernel Programming Guidelines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
Unhandled Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Termination . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Function Return Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
IRQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
C++ Usage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
Testing and Debugging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
Debug vs. Release Builds . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
The Kernel API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
Functions and Error Codes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38

(C)2019 Pavel Yosifovich


CONTENTS

Dynamic Memory Allocation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40


Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
The Driver Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
Device Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48

Chapter 4: Driver from Start to Finish . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49


Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
Driver Initialization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
Passing Information to the Driver . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
Client / Driver Communication Protocol . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
Creating the Device Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
Client Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
The Create and Close Dispatch Routines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
The DeviceIoControl Dispatch Routine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
Installing and Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67

Chapter 5: Debugging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
Debugging Tools for Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
Introduction to WinDbg . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
Tutorial: User mode debugging basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
Kernel Debugging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
Local Kernel Debugging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
Local kernel Debugging Tutorial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
Full Kernel Debugging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
Configuring the Target . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
Configuring the Host . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
Kernel Driver Debugging Tutorial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104

Chapter 6: Kernel Mechanisms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105


Interrupt Request Level . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
Raising and Lowering IRQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
Thread Priorities vs. IRQLs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
Deferred Procedure Calls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
Using DPC with a Timer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
Asynchronous Procedure Calls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
Critical Regions and Guarded Regions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
Structured Exception Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
Using __try/__except . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
Using __try/__finally . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
Using C++ RAII Instead of __try / __finally . . . . . . . . . . . . . . . . . . . . . . . 119
System Crash . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122

(C)2019 Pavel Yosifovich


CONTENTS

Crash Dump Information . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124


Analyzing a Dump File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
System Hang . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
Thread Synchronization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
Interlocked Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
Dispatcher Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
Mutex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
Fast Mutex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
Semaphore . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
Event . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
Executive Resource . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
High IRQL Synchronization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
The Spin Lock . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
Work Items . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151

Chapter 7: The I/O Request Packet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152


Introduction to IRPs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
Device Nodes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
IRP Flow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
IRP and I/O Stack Location . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
Viewing IRP Information . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162
Dispatch Routines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
Completing a Request . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
Accessing User Buffers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
Buffered I/O . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167
Direct I/O . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
User Buffers for IRP_MJ_DEVICE_CONTROL . . . . . . . . . . . . . . . . . . . . . . . . . . 176
Putting it All Together: The Zero Driver . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
Using a Precompiled Header . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178
The DriverEntry Routine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180
The Read Dispatch Routine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182
The Write Dispatch Routine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183
Test Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185

Chapter 8: Process and Thread Notifications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186


Process Notifications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186
Implementing Process Notifications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189
The DriverEntry Routine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192
Handling Process Exit Notifications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194
Handling Process Create Notifications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197
Providing Data to User Mode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199

(C)2019 Pavel Yosifovich


CONTENTS

The User Mode Client . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201


Thread Notifications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204
Image Load Notifications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207
Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209

Chapter 9: Object and Registry Notifications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210


Object Notifications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210
Pre-Operation Callback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212
Post-Operation Callback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215
The Process Protector Driver . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216
Object Notification Registration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217
Managing Protected Processes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218
The Pre-Callback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222
The Client Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223
Registry Notifications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 226
Handling Pre-Notifications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 228
Handling Post-Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 228
Performance Considerations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229
Implementing Registry Notifications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229
Handling Registry Callback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 231
Modified Client Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233
Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235

Chapter 10: Introduction to File System Mini-Filters . . . . . . . . . . . . . . . . . . . . . . . . 236


Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237
Loading and Unloading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238
Initialization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 240
Operations Callback Registration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243
The Altitude . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247
Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250
INF Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250
Installing the Driver . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258
Processing I/O Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258
Pre Operation Callbacks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258
Post Operation Callbacks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261
The Delete Protector Driver . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263
Handling Pre-Create . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264
Handling Pre-Set Information . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269
Some Refactoring . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272
Generalizing the Driver . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 275
Testing the Modified Driver . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 281

(C)2019 Pavel Yosifovich


CONTENTS

File Names . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 282


File Name Parts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284
RAII FLT_FILE_NAME_INFORMATION wrapper . . . . . . . . . . . . . . . . . . . . . . . . 287
The Alternate Delete Protector Driver . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289
Handling Pre-Create and Pre-Set Information . . . . . . . . . . . . . . . . . . . . . . . . . 296
Testing the Driver . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 299
Contexts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 299
Managing Contexts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 301
Initiating I/O Requests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303
The File Backup Driver . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 304
The Post Create Callback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 307
The Pre-Write Callback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 312
The Post-Cleanup Callback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319
Testing the Driver . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 320
Restoring Backups . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 321
User Mode Communication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323
Creating the Communication Port . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323
User Mode Connection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 325
Sending and Receiving Messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 326
Enhanced File Backup Driver . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 327
The User Mode Client . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 330
Debugging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 332
Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 335
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 336

Chapter 11: Miscellaneous Topics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 337


Driver Signing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 337
Driver Verifier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 342
Example Driver Verifier Sessions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 346
Using the Native API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 352
Filter Drivers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353
Filter Driver Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 355
Attaching Filters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 356
Attaching Filters at Arbitrary Time . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 358
Filter Cleanup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 360
More on Hardware-Based Filter Drivers . . . . . . . . . . . . . . . . . . . . . . . . . . . . 361
Device Monitor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 362
Adding a Device to Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 364
Removing a Filter Device . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 367
Initialization and Unload . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 369
Handling Requests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 371
Testing the Driver . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 374
Results of Requests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 378

(C)2019 Pavel Yosifovich


CONTENTS

Driver Hooking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 380


Kernel Libraries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 383
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 384

(C)2019 Pavel Yosifovich


Chapter 1: Windows Internals
Overview
This chapters describes the most important concepts in the internal workings of Windows. Some of
the topics will be described in greater detail later in the book, where it’s closely related to the topic
at hand. Make sure you understand the concepts in this chapter, as these make the foundations upon
any driver and even user mode low-level code, is built.

In this chapter:

• Processes
• Virtual Memory
• Threads
• System Services
• System Architecture
• Handles and Objects

Processes
A process is a containment and management object that represents a running instance of a program.
The term “process runs” which is used fairly often, is inaccurate. Processes don’t run – processes
manage. Threads are the ones that execute code and technically run. From a high-level perspective,
a process owns the following:

• An executable program, which contains the initial code and data used to execute code within
the process.
• A private virtual address space, used for allocating memory for whatever purposes the code
within the process needs it.
• A primary token, which is an object that stores the default security context of the process,
used by threads executing code within the process (unless a thread assumes a different token
by using impersonation).
• A private handle table to executive objects, such as events, semaphores and files.
Chapter 1: Windows Internals Overview 2

• One or more threads of execution. A normal user mode process is created with one thread
(executing the classic main/WinMain function). A user mode process without threads is mostly
useless and under normal circumstances will be destroyed by the kernel.

These elements of a process are depicted in figure 1-1.

Figure 1-1: Important ingredients of a process

A process is uniquely identified by its Process ID, which remains unique as long as the kernel process
object exists. Once it’s destroyed, the same ID may be reused for new processes. It’s important to
realize that the executable file itself is not a unique identifier of a process. For example, there may be
five instances of notepad.exe running at the same time. Each process has its own address space, its
own threads, its own handle table, its own unique process ID, etc. All those five processes are using
the same image file (notepad.exe) as their initial code and data. Figure 1-2 shows a screen shot of
Task Manager’s Details tab showing five instances of Notepad.exe, each with its own attributes.

(C)2019 Pavel Yosifovich


Chapter 1: Windows Internals Overview 3

Figure 1-2: Five instances of notepad

Virtual Memory
Every process has its own virtual, private, linear address space. This address space starts out empty
(or close to empty, since the executable image and NtDll.Dll are the first to be mapped, followed
by more subsystem DLLs). Once execution of the main (first) thread begins, memory is likely to
be allocated, more DLLs loaded, etc. This address space is private, which means other processes
cannot access it directly. The address space range starts at zero (although technically the first 64KB
of address cannot be allocated or used in any way), and goes all the way to a maximum which
depends on the process “bitness” (32 or 64 bit) and the operating system “bitness” as follows:

• For 32-bit processes on 32-bit Windows systems, the process address space size is 2 GB by
default.
• For 32-bit processes on 32-bit Windows systems that use the increase user address space setting
(LARGEADDRESSAWARE flag in the Portable Executable header), that process address space size
can be as large as 3 GB (depending on the exact setting). To get the extended address space
range, the executable from which the process was created must have been marked with the
LARGEADDRESSAWARE linker flag in its header. If it was not, it would still be limited to 2 GB.
• For 64-bit processes (on a 64-bit Windows system, naturally), the address space size is 8 TB
(Windows 8 and earlier) or 128 TB (Windows 8.1 and later).
• For 32-bit processes on a 64-bit Windows system, the address space size is 4 GB if the executable
image is linked with the LARGEADDRESSAWARE flag. Otherwise, the size remains at 2 GB.

The requirement of the LARGEADDRESSAWARE flag stems from the fact that a 2 GB address
range requires 31 bits only, leaving the most significant bit (MSB) free for application use.
Specifying this flag indicates that the program is not using bit 31 for anything and so setting
that bit to 1 (which would happen for addresses larger than 2 GB) is not an issue.

(C)2019 Pavel Yosifovich


Chapter 1: Windows Internals Overview 4

Each process has its own address space, which makes any process address relative, rather than
absolute. For example, when trying to determine what lies in address 0x20000, the address itself
is not enough; the process to which this address relates to must be specified.
The memory itself is called virtual, which means there is an indirect relationship between an address
range and the exact location where it’s found in physical memory (RAM). A buffer within a process
may be mapped to physical memory, or it may temporarily reside in a file (such as a page file). The
term virtual refers to the fact that from an execution perspective, there is no need to know if the
memory about to be accessed is in RAM or not; if the memory is indeed mapped to RAM, the CPU
will access the data directly. If not, the CPU will raise a page fault exception that will cause the
memory manager’s page fault handler to fetch the data from the appropriate file, copy it to RAM,
make the required changes in the page table entries that map the buffer, and instruct the CPU to try
again. Figure 1-3 shows this mapping from virtual to physical memory for two processes.

Figure 1-3: virtual memory mapping

The unit of memory management is called a page. Every attribute related to memory is always at a
page’s granularity, such as its protection. The size of a page is determined by CPU type (and on some
processors, may be configurable), and in any case the memory manager must follow suit. Normal
(sometimes called small) page size is 4 KB on all Windows supported architectures.
Apart from the normal (small) page size, Windows also supports large pages. The size of a large
page is 2 MB (x86/x64/ARM64) and 4 MB (ARM). This is based using the Page Directory Entry
(PDE) to map the large page without using a page table. This results in quicker translation, but most

(C)2019 Pavel Yosifovich


Chapter 1: Windows Internals Overview 5

importantly better use the Translation Lookaside Buffer (TLB) – a cache of recently translated pages
maintained by the CPU. In the case of a large page, a single TLB entry is able to map significantly
more memory than a small page.

The downside of large pages is the need to have the memory contiguous in RAM, which can
fail if memory is tight or very fragmented. Also, large pages are always non-pageable and
must be protected with read/write access only. Huge pages of 1 GB in size are supported on
Windows 10 and Server 2016 and later. These are used automatically with large pages if an
allocation is at least 1 GB in size and that page can be located as contiguous in RAM.

Page States
Each page in virtual memory can be in one of three states:

• Free – the page is not allocated in any way; there is nothing there. Any attempt to access that
page would cause an access violation exception. Most pages in a newly created process are free.
• Committed – the reverse of free; an allocated page that can be accessed successfully sans
protection attributes (for example, writing to a read only page causes an access violation).
Committed pages are usually mapped to RAM or to a file (such as a page file).
• Reserved – the page is not committed, but the address range is reserved for possible future
commitment. From the CPU’s perspective, it’s the same as Free – any access attempt raises
an access violation exception. However, new allocation attempts using the VirtualAlloc
function (or NtAllocateVirtualMemory, the related native API) that does not specify a
specific address would not allocate in the reserved region. A classic example of using reserved
memory to maintain contiguous virtual address space while conserving memory is described
later in this chapter in the section “Thread Stacks”.

System Memory
The lower part of the address space is for processes’ use. While a certain thread is executing, its
associated process address space is visible from address zero to the upper limit as described in the
previous section. The operating system, however, must also reside somewhere – and that somewhere
is the upper address range that’s supported on the system, as follows:

• On 32-bit systems running without the increase user virtual address space setting, the operating
system resides in the upper 2 GB of virtual address space, from address 0x8000000 to
0xFFFFFFFF.
• On 32-bit systems configured with the increase user virtual address space setting, the operating
system resides in the address space left. For example, if the system is configured with 3 GB
user address space per process (the maximum), the OS takes the upper 1 GB (from address
0xC0000000 to 0xFFFFFFFF). The entity that suffers mostly from this address space reduction
is the file system cache.

(C)2019 Pavel Yosifovich


Chapter 1: Windows Internals Overview 6

• On 64-bit systems on Windows 8, Server 2012 and earlier, the OS takes the upper 8 TB of virtual
address space.
• On 64-bit systems on Windows 8.1, Server 2012 R2 and later, the OS takes the upper 128 TB of
virtual address space.

System space is not process-relative – after all, it’s the same “system”, the same kernel, the same
drivers that service every process on the system (the exception is some system memory that is on
a per-session basis but is not important for this discussion). It follows that any address in system
space is absolute rather than relative, since it “looks” the same from every process context. Of course,
actual access from user mode into system space results in an access violation exception.
System space is where the kernel itself, the Hardware Abstraction Layer (HAL) and kernel drivers
reside once loaded. Thus, kernel drivers are automatically protected from direct user mode access.
It also means they have a potentially system-wide impact. For example, if a kernel driver leaks
memory, that memory will not be freed even after the driver unloads. User mode processes, on the
other hand, can never leak anything beyond their life time. The kernel is responsible for closing and
freeing everything private to a dead process (all handles are closed and all private memory is freed).

Threads
The actual entities that execute code are threads. A Thread is contained within a process, using the
resources exposed by the process to do work (such as virtual memory and handles to kernel objects).
The most important information a thread owns is the following:

• Current access mode, either user or kernel.


• Execution context, including processor registers and execution state.
• One or two stacks, used for local variable allocations and call management.
• Thread Local Storage (TLS) array, which provides a way to store thread-private data with
uniform access semantics.
• Base priority and a current (dynamic) priority.
• Processor affinity, indicating on which processors the thread is allowed to run on.

The most common states a thread can be in are:

• Running – currently executing code on a (logical) processor.


• Ready – waiting to be scheduled for execution because all relevant processors are busy or
unavailable.
• Waiting – waiting for some event to occur before proceeding. Once the event occurs, the thread
goes to the Ready state.

(C)2019 Pavel Yosifovich


Chapter 1: Windows Internals Overview 7

Figure 1-4 shows the state diagram for these states. The numbers in parenthesis indicate the state
numbers, as can be viewed by tools such as Performance Monitor. Note that the Ready state has
a sibling state called Deferred Ready, which is similar, and really exists to minimize some internal
locking.

Figure 1-4: Common thread states

Thread Stacks
Each thread has a stack it uses while executing, used for local variables, parameter passing to
functions (in some cases) and where return addresses are stored prior to function calls. A thread
has at least one stack residing in system (kernel) space, and it’s pretty small (default is 12 KB on
32-bit systems and 24 KB on 64-bit systems). A user mode thread has a second stack in its process
user space address range and is considerably larger (by default can grow to 1 MB). An example with
three user mode threads and their stacks is shown in figure 1-5. In the figure, threads 1 and 2 are in
process A and thread 3 is in process B.
The kernel stack always resides in RAM while the thread is in the Running or Ready states. The
reason for this is subtle and will be discussed later in this chapter. The user mode stack, on the other
hand, may be paged out, just like any user mode memory.
The user mode stack is handled differently than the kernel mode stack, in terms of its size. It starts
out with a small amount of memory committed (could be as small as a single page), with the rest
of the stack address space as reserved memory, meaning it’s not allocated in any way. The idea is
to be able to grow the stack in case the thread’s code needs to use more stack space. To make this
work, the next page (sometimes more than one) right after the committed part is marked with a
special protection called PAGE_GUARD – this is a guard page. If the thread needs more stack space
it would write to the guard page which would throw an exception that is handled by the memory
manager. The memory manager then removes the guard protection and commits the page and marks
the next page as a guard page. This way, the stack grows as needed and the entire stack memory is
not committed upfront. Figure 1-6 shows the way a user mode’s thread stack looks like.

(C)2019 Pavel Yosifovich


Chapter 1: Windows Internals Overview 8

Figure 1-5: User mode threads and their stacks

Figure 1-6: Thread’s stack in user space

The sizes of a thread’s user mode stack are determined as follows:

(C)2019 Pavel Yosifovich


Chapter 1: Windows Internals Overview 9

• The executable image has a stack commit and reserved values in its Portable Executable (PE)
header. These are taken as defaults if a thread does not specify alternative values.
• When a thread is created with CreateThread (and similar functions), the caller can specify
its required stack size, either the upfront committed size or the reserved size (but not both),
depending on a flag provided to the function; specifying zero goes with the default according
the above bullet.

Curiously enough, the functions CreateThread and CreateRemoteThread(Ex) only allow


specifying a single value for the stack size and can be the committed or the reserved size,
but not both. The native (undocumented) function, NtCreateThreadEx allows specifying
both values.

System Services (a.k.a. System Calls)


Applications need to perform various operations that are not purely computational, such as
allocating memory, opening files, creating threads, etc. These operations can only be ultimately
performed by code running in kernel mode. So how would user-mode code be able to perform such
operations? Let’s take a classic example: a user running a Notepad process uses the File menu to
request opening a file. Notepad’s code responds by calling the CreateFile documented Windows
API function. CreateFile is documented as implemented in kernel32.Dll, one of the Windows
subsystem DLLs. This function still runs in user mode, so there is no way it can directly open a
file. After some error checking, it calls NtCreateFile, a function implemented in NTDLL.dll, a
foundational DLL that implements the API known as the “Native API”, and is in fact the lowest
layer of code which is still in user mode. This (officially undocumented) API is the one that
makes the transition to kernel mode. Before the actual transition, it puts a number, called system
service number, into a CPU register (EAX on Intel/AMD architectures). Then it issues a special CPU
instruction (syscall on x64 or sysenter on x86) that makes the actual transition to kernel mode
while jumping to a predefined routine called the system service dispatcher.
The system service dispatcher, in turn, uses the value in that EAX register as an index into a System
Service Dispatch Table (SSDT). Using this table, the code jumps to the system service (system call)
itself. For our Notepad example, the SSDT entry would point to the I/O manager’s NtCreateFile
function. Notice the function has the same name as the one in NTDLL.dll and in fact has the same
arguments as well. Once the system service is complete, the thread returns to user mode to execute
the instruction following sysenter/syscall. This sequence of events is depicted in figure 1-7.

(C)2019 Pavel Yosifovich


Chapter 1: Windows Internals Overview 10

Figure 1-7: System service function call flow

General System Architecture


Figure 1-8 shows the general architecture of Windows, comprising of user mode and kernel mode
components.

(C)2019 Pavel Yosifovich


Chapter 1: Windows Internals Overview 11

Figure 1-8: Windows system architecture

Here’s a quick rundown of the named boxes appearing in figure 1-8:

• User processes
These are normal processes based on image files, executing on the system, such as instances of
Notepad.exe, cmd.exe, explorer.exe and so on.
• Subsystem DLLs
Subsystem DLLs are dynamic link libraries (DLLs) that implement the API of a subsystem. A
subsystem is a certain view of the capabilities exposed by the kernel. Technically, starting from
Windows 8.1, there is only a single subsystem – the Windows Subsystem. The subsystem DLLs
include well-known files, such as kernel32.dll, user32.dll, gdi32.dll, advapi32.dll, combase.dll
and many others. These include mostly the officially documented API of Windows.
• NTDLL.DLL
A system-wide DLL, implementing the Windows native API. This is the lowest layer of code
which is still in user mode. Its most important role is to make the transition to kernel mode
for system call invocation. NTDLL also implements the Heap Manager, the Image Loader and
some part of the user mode thread pool.
• Service Processes
Service processes are normal Windows processes that communicate with the Service Control
Manager (SCM, implemented in services.exe) and allow some control over their lifetime. The
SCM can start, stop, pause, resume and send other messages to services. Services typically
execute under one of the special Windows accounts – local system, network service or local
service.

(C)2019 Pavel Yosifovich


Chapter 1: Windows Internals Overview 12

• Executive
The Executive is the upper layer of NtOskrnl.exe (the “kernel”). It hosts most of the code that is
in kernel mode. It includes mostly the various “managers”: Object Manager, Memory Manager,
I/O Manager, Plug & Play Manager, Power Manager, Configuration Manager, etc. It’s by far
larger than the lower Kernel layer.
• Kernel
The Kernel layer implements the most fundamental and time sensitive parts of kernel mode OS
code. This includes thread scheduling, interrupt and exception dispatching and implementation
of various kernel primitives such as mutex and semaphore. Some of the kernel code is written
in CPU-specific machine language for efficiency and for getting direct access to CPU-specific
details.
• Device Drivers
Device drivers are loadable kernel modules. Their code executes in kernel mode and so has the
full power of the kernel. This book is dedicated to writing certain types of kernel drivers.
• Win32k.sys
The “kernel mode component of the Windows subsystem”. Essentially this is a kernel module
(driver) that handles the user interface part of Windows and the classic Graphics Device Inter-
face (GDI) APIs. This means that all windowing operations (CreateWindowEx, GetMessage,
PostMessage, etc.) are handled by this component. The rest of the system has little-to-none
knowledge of UI.
• Hardware Abstraction Layer (HAL)
The HAL is an abstraction layer over the hardware closest to the CPU. It allows device drivers to
use APIs that do not require detailed and specific knowledge of things like Interrupt Controller
or DMA controller. Naturally, this layer is mostly useful for device drivers written to handle
hardware devices.
• System Processes
System processes is an umbrella term used to describe processes that are typically “just there”,
doing their thing where normally these processes are not communicated with directly. They are
important nonetheless, and some in fact, critical to the system’s well-being. Terminating some
of them is fatal and causes a system crash. Some of the system processes are native processes,
meaning they use the native API only (the API implemented by NTDLL). Example system
processes include Smss.exe, Lsass.exe, Winlogon.exe, Services.exe and others.
• Subsystem Process
The Windows subsystem process, running the image Csrss.exe, can be viewed as a helper to
the kernel for managing processes running under the Windows system. It is a critical process,
meaning if killed, the system would crash. There is normally one Csrss.exe instance per
session, so on a standard system two instances would exist – one for session 0 and one for the
logged-on user session (typically 1). Although Csrss.exe is the “manager” of the Windows
subsystem (the only one left these days), its importance goes beyond just this role.
• Hyper-V Hypervisor

(C)2019 Pavel Yosifovich


Chapter 1: Windows Internals Overview 13

The Hyper-V hypervisor exists on Windows 10 and server 2016 (and later) systems if they
support Virtualization Based Security (VBS). VBS provides an extra layer of security, where
the actual machine is in fact a virtual machine controlled by Hyper-V. VBS is beyond the scope
of this book. For more information, check out the Windows Internals book.

Windows 10 version 1607 introduced the Windows Subsystem for Linux (WSL). Although
this may look like yet another subsystem, like the old POSIX and OS/2 subsystems supported
by Windows, it is not quite like that at all. The old subsystems were able to execute POSIX
and OS/2 apps if theses were compiled on a Windows compiler. WSL, on the other hand,
has no such requirement. Existing executables from Linux (stored in ELF format) can be run
as-is on Windows, without any recompilation.
To make something like this work, a new process type was created – the Pico process together
with a Pico provider. Briefly, a Pico process is an empty address space (minimal process) that
is used for WSL processes, where every system call (Linux system call) must be intercepted
and translated to the Windows system call(s) equivalent using that Pico provider (a device
driver). There is a true Linux (the user-mode part) installed on the Windows machine.

Handles and Objects


The Windows kernel exposes various types of objects for use by user mode processes, the kernel
itself and kernel mode drivers. Instance of these types are data structures in system space, created
by the Object Manager (part of the executive) when requested to do so by user or kernel mode code.
Objects are reference counted – only when the last reference to the object is released will the object
be destroyed and freed from memory.
Since these object instances reside in system space, they cannot be accessed directly by user mode.
User mode must use an indirect access mechanism, known as handles. A handle is an index to an
entry in a table maintained on a process by process basis that points logically to a kernel object
residing in system space. There are various Create* and Open* functions to create/open objects and
retrieve back handles to these objects. For example, the CreateMutex user mode function allows
creating or opening a mutex (depending whether the object is named and exists). If successful, the
function returns a handle to the object. A return value of zero means an invalid handle (and a
function call failure). The OpenMutex function, on the other hand, tries to open a handle to a named
mutex. If the mutex with that name does not exist, the function fails and returns null (0).
Kernel (and driver) code can use either a handle or a direct pointer to an object. The choice is usually
based on the API the code wants to call. In some cases, a handle given by user mode to the driver
must be turned into a pointer with the ObReferenceObjectByHandle function. We’ll discuss these
details in a later chapter.

Most functions return null (zero) on failure, but some do not. Most notably, the CreateFile
function returns INVALID_HANDLE_VALUE (-1) if it fails.

(C)2019 Pavel Yosifovich


Chapter 1: Windows Internals Overview 14

Handle values are multiples of 4, where the first valid handle is 4; Zero is never a valid handle value.
Kernel mode code can use handles when creating/opening objects, but they can also use direct
pointers to kernel objects. This is typically done when a certain API demands it. Kernel code can
get a pointer to an object given a valid handle using the ObReferenceObjectByHandle function.
If successful, the reference count on the object is incremented, so there is no danger that if the user
mode client holding the handle decided to close it while kernel code holds a pointer to the object
would now hold a dangling pointer. The object is safe to access regardless of the handle-holder until
the kernel code calls ObDerefenceObject, which decrements the reference count; if the kernel code
missed this call, that’s a resource leak which will only be resolved in the next system boot.
All objects are reference counted. The object manager maintains a handle count and total reference
count for objects. Once an object is no longer needed, its client should close the handle (if a handle
was used to access the object) or dereference the object (if kernel client using a pointer). From that
point on, the code should consider its handle/pointer to be invalid. The Object Manager will destroy
the object if its reference count reaches zero.
Each object points to an object type, which holds information on the type itself, meaning there is a
single type object for each type of object. These are also exposed as exported global kernel variables,
some of which are defined in the kernel headers and are actually useful in certain cases, as we’ll see
in later chapters.

Object Names
Some types of objects can have names. These names can be used to open objects by name with
a suitable Open function. Note that not all objects have names; for example, processes and threads
don’t have names – they have IDs. That’s why the OpenProcess and OpenThread functions require
a process/thread identifier (a number) rather than a string-base name. Another somewhat weird case
of an object that does not have a name is a file. The file name is not the object’s name – these are
different concepts.
From user mode code, calling a Create function with a name creates the object with that name if
an object with that name does not exist, but if it exists it just opens the existing object. In the latter
case, calling GetLastError returns ERROR_ALREADY_EXISTS, indicating this is not a new object,
and the returned handle is yet another handle to an existing object.
The name provided to a Create function is not actually the final name of the object. It’s prepended
with \Sessions\x\BaseNamedObjects\ where x is the session ID of the caller. If the session is zero, the
name is prepended with \BaseNamedObjects\. If the caller happens to be running in an AppContainer
(typically a Universal Windows Platform process), then the prepended string is more complex and
consists of the unique AppContainer SID: \Sessions\x\AppContainerNamedObjects\{AppContainerSID}.
All the above means is that object names are session-relative (and in the case of AppContainer
– package relative). If an object must be shared across sessions it can be created in session 0 by
prepending the object name with Global\; for example, creating a mutex with the CreateMutex
function named Global\MyMutex will create it under \BaseNamedObjects. Note that AppContainers

(C)2019 Pavel Yosifovich


Chapter 1: Windows Internals Overview 15

do not have the power to use session 0 object namespace. This hierarchy can be viewed with the
Sysinternals WinObj tool (run elevated) as shown in figure 1-9.

Figure 1-9: Sysinternals WinObj tool

The view shown in figure 1-9 is the object manager namespace, comprising of a hierarchy of named
objects. This entire structure is held in memory and manipulated by the Object Manager (part of
the Executive) as required. Note that unnamed objects are not part of this structure, meaning the
objects seen in WinObj do not comprise all the existing objects, but rather all the objects that were
created with a name.
Every process has a private handle table to kernel objects (whether named or not), which can
be viewed with the Process Explorer and/or Handles Sysinternals tools. A screen shot of Process
Explorer showing handles in some process is shown in figure 1-10. The default columns shown in
the handles view are the object type and name only. However, there are other columns available, as
shown in figure 1-10.

(C)2019 Pavel Yosifovich


Chapter 1: Windows Internals Overview 16

Figure 1-10: Viewing handles in processes with Process Explorer

By default, Process Explorer shows only handles for objects which have names (according to Process
Explorer’s definition of a name, discussed shortly). To view all handles in a process, select Show
Unnamed Handles and Mappings from Process Explorer’s View menu.
The various columns in the handle view provide more information for each handle. The handle value
and the object type are self explanatory. The name column is tricky. It shows true object names for
Mutexes (Mutants), Semaphores, Events, Sections, ALPC Ports, Jobs, Timers, and other, less used
object types. Yet others are shown with a name that has a different meaning than a true named
object:

• Process and Thread objects, the name is shown as their unique ID.
• For File objects it shows the file name (or device name) pointed to by the file object. It’s not
the same as an object’s name, as there is no way to get a handle to a file object given the file
name - only a new file object may be created that accesses the same underlying file or device
(assuming sharing settings for the original file object allow it).
• (Registry) Key objects names are shown with the path to the registry key. This is not a name,
for the same reasoning as for file objects.
• Directory objects show the path, rather than a true object name. A Directory is not a file
system object, but rather it’s an object manager directory - these can be easily viewed with the
Sysinternals WinObj tool.
• Token object names are shown with the user name stored in the token.

(C)2019 Pavel Yosifovich


Chapter 1: Windows Internals Overview 17

Accessing Existing Objects


The Access column in Process Explorer’s handles view shows the access mask which was used to
open or create the handle. This access mask is key to what operations are allowed to be performed
with a specific handle. For example, if client code wants to terminate a process, it must call the
OpenProcess function first, to obtain a handle to the required process with an access mask of (at
least) PROCESS_TERMINATE, otherwise there is no way to terminate the process with that handle.
If the call succeeds, then the call to TerminateProcess is bound to succeed. Here’s a user mode
example for terminating a process given a process ID:

bool KillProcess(DWORD pid) {


// open a powerful-enough handle to the process

HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid);


if (!hProcess)
return false;

// now kill it with some arbitrary exit code


BOOL success = TerminateProcess(hProcess, 1);

// close the handle


CloseHandle(hProcess);

return success != FALSE;


}

The Decoded Access column provides a textual description of the access mask (for some object types),
making it easier to recognize the exact access allowed for a particular handle.
Double clicking a handle entry shows some of the object’s properties. Figure 1-11 shows a screen
shot of an example event object’s properties.

(C)2019 Pavel Yosifovich


Chapter 1: Windows Internals Overview 18

Figure 1-11: Object properties in Process Explorer

The properties in figure 1-11 include the object’s name (if any), its type, a description, its address
in kernel memory, the number of open handles, and some specific object information, such as the
state and type of the event object shown. Note that the References shown do not indicate the actual
number of outstanding references to the object. A proper way to see the actual reference count for
the object is to use the kernel debugger’s !trueref command, as shown here:

(C)2019 Pavel Yosifovich


Chapter 1: Windows Internals Overview 19

lkd> !object 0xFFFFA08F948AC0B0


Object: ffffa08f948ac0b0 Type: (ffffa08f684df140) Event
ObjectHeader: ffffa08f948ac080 (new version)
HandleCount: 2 PointerCount: 65535
Directory Object: ffff90839b63a700 Name: ShellDesktopSwitchEvent
lkd> !trueref ffffa08f948ac0b0
ffffa08f948ac0b0: HandleCount: 2 PointerCount: 65535 RealPointerCount: 3

We’ll look more closely at the attributes of objects and the kernel debugger in later chapters.
Now let’s start writing a very simple driver to show and use many of the tools we’ll need later in
this book.

(C)2019 Pavel Yosifovich


Chapter 2: Getting Started with
Kernel Development
This chapter deals with the fundamentals needed to get up and running with kernel driver
development. During the course of this chapter, you’ll install the necessary tools and write a very
basic driver that can be loaded and unloaded.

In this chapter:

• Installing the Tools


• Creating a Driver Project
• The DriverEntry and Unload routines
• Deploying the Driver
• Simple Tracing

Installing the Tools


In the old days (pre-2012), the process of developing and building drivers included using a dedicated
build tool from the Device Driver Kit (DDK), without having an integrated development experience
developers were used to when developing user mode applications. There were some workarounds,
but none of them was perfect nor officially supported. Fortunately, starting with Visual Studio 2012
and Windows Driver Kit 8, Microsoft started officially supporting building drivers with Visual Studio
(and msbuild), without the need to use a separate compiler and build tools.
To get started with driver development, the following tools must be installed (in this order):

• Visual Studio 2017 or 2019 with latest updates. Make sure the C++ workload is selected during
installation. At the time of this writing Visual Studio 2019 has just been released and can be
used for driver development. Note that any SKU will do, including the free Community edition.
• Windows 10 SDK (generally the latest is best). Make sure at least the Debugging Tools for
Windows item is selected during installation.
• Windows 10 Driver Kit (WDK). The latest should be fine, but make sure you also installs the
project templates for Visual Studio at the end of the standard installation.
Chapter 2: Getting Started with Kernel Development 21

• The Sysinternals tools, which are invaluable in any “internals” work, can be downloaded for
free from http://www.sysinternals.com. Click on Sysinternals Suite on the left of that web page
and download the Sysinternals Suite zip file. Unzip to any folder and the tools are ready to go.

A quick way to make sure the WDK templates are installed correctly is to open Visual Studio and
select New Project and look for driver projects, such as “Empty WDM Driver”.

Creating a Driver Project


With the above installations in place, a new driver project can be created. The template you’ll use
in this section is “WDM Empty Driver”. Figure 2-1 shows what the New Project dialog looks like
for this type of driver in Visual Studio 2017. Figure 2-2 shows the same initial wizard with Visual
Studio 2019. The project in both figures is named “Sample”.

Figure 2-1: New WDM Driver Project in Visual Studio 2017

(C)2019 Pavel Yosifovich


Chapter 2: Getting Started with Kernel Development 22

Figure 2-2: New WDM Driver Project in Visual Studio 2019

Once the project is created, the Solution Explorer shows a single file - Sample.inf. You won’t need
this file in this example, so just delete it.
Now it’s time to add a source file. Right-click the Source Files node in Solution Explorer and select
Add / New Item… from the File menu. Select a C++ source file and name it Sample.cpp. Click OK
to create it.

The DriverEntry and Unload Routines


Every driver has an entry point called DriverEntry by default. This can be considered the “main”
of the driver, comparable to the classic main of a user mode application. This function is called by a
system thread at IRQL PASSIVE_LEVEL (0). (IRQLs will be discussed in detail in chapter 8.)
DriverEntry has a predefined prototype, shown here:

NTSTATUS
DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath);

(C)2019 Pavel Yosifovich


Chapter 2: Getting Started with Kernel Development 23

The _In_ annotations are part of the Source (Code) Annotation Language (SAL). These annotations
are transparent to the compiler, but provide metadata useful for human readers and static analysis
tools. We’ll try to use these as much as possible to improve code clarity.
A minimal DriverEntry routine could just return a successful status, like so:

NTSTATUS
DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {
return STATUS_SUCCESS;
}

This code would not yet compile. First, you’ll need to include a header that has the required
definitions for the types present in DriverEntry. Here’s one possibility:

#include <ntddk.h>

Now the code has better chance of compiling, but would still fail. One reason is that by default,
the compiler is set to treat warnings as errors, and the function does not make use of its given
arguments. Removing treat warnings as errors is not recommended, as some warnings may be errors
in disguise. These warnings can be solved by removing the argument names entirely (or commenting
them out), which is fine for C++ files. There is another, classic way to solve this, which is to use the
UNREFERENCED_PARAMETER macro:

NTSTATUS
DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {
UNREFERENCED_PARAMETER(DriverObject);
UNREFERENCED_PARAMETER(RegistryPath);

return STATUS_SUCCESS;
}

As it turns out, this macro actually references the argument given just by writing its value as is, and
this shuts the compiler up, making the argument “referenced”.
Building the project now compiles fine, but causes a linker error. The DriverEntry function must
have C-linkage, which is not the default in C++ compilation. Here’s the final version of a successful
build of the driver consisting of a DriverEntry function only:

(C)2019 Pavel Yosifovich


Chapter 2: Getting Started with Kernel Development 24

extern "C"
NTSTATUS
DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {
UNREFERENCED_PARAMETER(DriverObject);
UNREFERENCED_PARAMETER(RegistryPath);

return STATUS_SUCCESS;
}

At some point the driver may be unloaded. At that time, anything done in the DriverEntry function
must be undone. Failure to do so creates a leak, which the kernel will not clean until the next reboot.
Drivers can have an Unload routine that is automatically called before the driver is unloaded from
memory. Its pointer must be set using the DriverUnload member of the driver object:

DriverObject->DriverUnload = SampleUnload;

The unload routine accepts the driver object (the same one passed in to DriverEntry) and returns
void. As our sample driver has done nothing in terms of resource allocation in DriverEntry, there
is nothing to do in the Unload routine, so we can leave it empty for now:

void SampleUnload(_In_ PDRIVER_OBJECT DriverObject) {


UNREFERENCED_PARAMETER(DriverObject);
}

Here is the complete driver source at this point:

#include <ntddk.h>

void SampleUnload(_In_ PDRIVER_OBJECT DriverObject) {


UNREFERENCED_PARAMETER(DriverObject);
}

extern "C"
NTSTATUS
DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {
UNREFERENCED_PARAMETER(RegistryPath);

DriverObject->DriverUnload = SampleUnload;

return STATUS_SUCCESS;
}

(C)2019 Pavel Yosifovich


Chapter 2: Getting Started with Kernel Development 25

Deploying the Driver


Now the we have a successfully compiled Sample.sys driver file, let’s install it on a system and then
load it. Normally, you would install and load a driver on a virtual machine, to remove the risk of
crashing your primary machine. Feel free to do so, or take the slight risk with this minimalist driver.
Installing a software driver, just like installing a user-mode service, requires calling the Create-
Service API with proper arguments or using existing tools. One of the well-known tools for this
purpose is Sc.exe, a built-in Windows tool for manipulating services. We’ll use this tool to install and
then load the driver. Note that installation and loading of drivers is a privileged operation, normally
only allowed for administrators.
Open an elevated command window and type the following (the last part should be the path on
your system where the SYS file resides):

sc create sample type= kernel binPath= c:\dev\sample\x64\debug\sample.sys

Note there is no space between type and the equal sign, and there is a space between the equal sign
and kernel; same goes for the second part.
If all goes well, the output should indicate success. To test the installation, you can open the registry
editor (regedit.exe) and look for the driver in HKLM\System\CurrentControlSet\Services\Sample.
Figure 2-3 shows a screen shot of the registry editor after the previous command.

(C)2019 Pavel Yosifovich


Chapter 2: Getting Started with Kernel Development 26

Figure 2-3: Registry for an installed driver

To load the driver, we can use the Sc.exe tool again, this time with the start option, which uses
the StartService API to load the driver (the same API used to load services). However, on 64 bit
systems drivers must be signed, and so normally the following command would fail:

sc start sample

Since it’s inconvenient to sign a driver during development (maybe even not possible if you don’t
have a proper certificate), a better option is to put the system into test signing mode. In this mode,
unsigned drivers can be loaded without a hitch.
With an elevated command window, test signing can be turned on like so:

bcdedit /set testsigning on

Unfortunately, this command requires a reboot to take effect. Once rebooted, the previous start
command should succeed.

If you are testing on Windows 10 with Secure Boot enabled, changing the test signing mode
would fail. This is one of the settings protected by Secure Boot (also protected is local kernel
debugging). If you can’t disable Secure Boot through BIOS setting, because of IT policy or
some other reason, your best option is to test on a virtual machine.

(C)2019 Pavel Yosifovich


Chapter 2: Getting Started with Kernel Development 27

There is yet another setting that you may need to specify if you intend to test the driver on pre-
Windows 10 machine. In this case, you have to set the target OS version in the project properties
dialog, as shown in figure 2-4. Notice that I have selected all configurations and all platforms, so that
when switching configurations (Debug/Release) or platforms (x86/x64/ARM/ARM64), the setting is
maintained.

Figure 2-4: Setting Target OS Platform in the project properties

Once test signing mode is on and the driver is loaded, this is the output you should see:

SERVICE_NAME: sample
TYPE : 1 KERNEL_DRIVER
STATE : 4 RUNNING
(STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
WIN32_EXIT_CODE : 0 (0x0)
SERVICE_EXIT_CODE : 0 (0x0)
CHECKPOINT : 0x0
WAIT_HINT : 0x0
PID : 0
FLAGS :

This means everything is well and the driver is loaded. To confirm, we can open Process Explorer

(C)2019 Pavel Yosifovich


Chapter 2: Getting Started with Kernel Development 28

and find the Sample.Sys driver image file. Figure 2-5 shows the details of the sample driver image
loaded into system space.

Figure 2-5: sample driver image loaded into system space

At this point we can unload the driver using the following command:

sc stop sample

Behind the scenes, sc.exe calls the ControlService API with the SERVICE_CONTROL_STOP value.
Unloading the driver causes the Unload routine to be called, which at this time does nothing. You can
verify the driver is indeed unloaded by looking at Process Explorer again; the driver image should
not be there.

Simple Tracing
How can we know for sure that the DriverEntry and Unload routines actually executed? Let’s add
basic tracing to these functions. Drivers can use the KdPrint macro to output printf-style text that
can be viewed using the kernel debugger and other tools. KdPrint is a macro that is only compiled
in Debug builds and calls the underlying DbgPrint kernel API.
Here is updated versions for DriverEntry and the Unload routine that use KdPrint to trace the
fact their code executed:

(C)2019 Pavel Yosifovich


Chapter 2: Getting Started with Kernel Development 29

void SampleUnload(_In_ PDRIVER_OBJECT DriverObject) {


UNREFERENCED_PARAMETER(DriverObject);

KdPrint(("Sample driver Unload called\n"));


}

extern "C"
NTSTATUS
DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {
UNREFERENCED_PARAMETER(RegistryPath);

DriverObject->DriverUnload = SampleUnload;

KdPrint(("Sample driver initialized successfully\n"));

return STATUS_SUCCESS;
}

Notice the double parenthesis when using KdPrint. This is required because KdPrint is a macro, but
apparently accepts any number of arguments, a-la printf. Since macros cannot receive a variable
number of arguments, a compiler trick is used to call the real DbgPrint function.
With these statements in place, we would like to load the driver again and see these messages.
We’ll use a kernel debugger in chapter 4, but for now we’ll use a useful Sysinternals tool named
DebugView. Before running DebugView, you’ll need to make some preparations. First, starting with
Windows Vista, DbgPrint output is not actually generated unless a certain value is in the registry.
You’ll have to add a key named Debug Print Filter under HKLM\SYSTEM\CurrentControlSet\Control\Session
Manager (the key typically does not exist). Within this new key, add a DWORD value named DEFAULT
(not the default value that exists in any key) and set its value to 8 (technically, any value with bit 3
set will do). Figure 2-6 shows the setting in RegEdit. Unfortunately, you’ll have to restart the system
for this setting to take effect.

(C)2019 Pavel Yosifovich


Chapter 2: Getting Started with Kernel Development 30

Figure 2-6: Debug Print Filter key in the registry

Once this setting has been applied, run DebugView (DbgView.exe) elevated. In the Options menu,
make sure Capture Kernel is selected (or press Ctrl+K). You can safely deselect Capture Win32 and
Capture Global Win32 so output from various processes does not clutter the display.
Build the driver, if you haven’t already. Now you can load the driver again from an elevated
command window (sc start sample). You should see output in DebugView as shown in figure
2-7. If you unload the driver, you’ll see another message appearing because the Unload routine was
called. (The third output line is from another driver and has nothing to do with our sample driver)

(C)2019 Pavel Yosifovich


Chapter 2: Getting Started with Kernel Development 31

Figure 2-7: Sysinternals DebugView Output

Exercises
1. Add code to the sample DriverEntry to output the Windows OS version: major, minor and
build number. Use the RtlGetVersion function to retrieve the information. Check the results
with DebugView.

Summary
We’ve seen the tools you need to have for kernel development and wrote a very minimalistic driver
to prove the basic tools work. In the next chapter, we’ll look at the fundamental building blocks of
kernel APIs, concepts and structures.

(C)2019 Pavel Yosifovich


Chapter 3: Kernel Programming
Basics
In this chapter we’ll dig deeper into kernel APIs, structures and definitions. We’ll also examine
some of the mechanisms that invoke code in a driver. Finally, we’ll put all that knowledge together
to create our first functional driver.

In this chapter:

• General Kernel Programming Guidelines


• Debug vs. Release Builds
• The Kernel API
• Functions and Error Codes
• Strings
• Dynamic Memory Allocation
• Lists
• The Driver Object
• Device Objects

General Kernel Programming Guidelines


Developing kernel drivers requires the Windows Driver Kit (WDK), where the appropriate headers
and libraries needed are located. The kernel APIs consists of C functions, very similar in essence to
user mode development. There are several differences, however. Table 3-1 summarizes the important
differences between user mode programming and kernel mode programming.
Chapter 3: Kernel Programming Basics 33

Table 3-1: Differences between user mode and kernel mode development

User Mode Kernel Mode


Unhandled Exception Unhandled exception crashes the Unhandled exception crashes the
process system
Termination When a process terminates, all private If a driver unloads without freeing
memory and resources are freed everything it was using, there is a leak,
automatically only resolved in the next boot
Return values API errors are sometimes ignored Should (almost) never ignore errors
IRQL Always PASSIVE_LEVEL (0) May be DISPATCH_LEVEL (2) or
higher
Bad coding Typically localized to the process Can have system-wide effect
Testing and Debugging Typical testing and debugging done on Debugging must be done with another
the developer’s machine machine
Libraries Can use almost and C/C++ library (e.g. Most standard libraries cannot be used
STL, boost)
Exception Handling Can use C++ exceptions or Structured Only SEH can be used
Exception Handling (SEH)
C++ Usage Full C++ runtime available No C++ runtime

Unhandled Exceptions
Exceptions occurring in user mode that are not caught by the program cause the process to terminate
prematurely. Kernel mode code, on the other hand, being implicitly trusted, cannot recover from an
unhandled exception. Such an exception causes the system to crash with the infamous Blue screen
of death (BSOD) (newer versions of Windows have more diverse colors for the crash screen). The
BSOD may first appear to be a punishment, but it’s essentially a protective mechanism. The rational
being that allowing the code to continue execution could cause irreversible damage to Windows
(such as deleting important files or corrupting the registry) that may cause the system to fail boot.
It’s better, then, to stop everything immediately to prevent potential damage. We’ll discuss the BSOD
in more detail in chapter 6.
All this leads to at least one simple conclusion: kernel code must be carefully coded, meticulous, and
not skipping any details or error checks.

Termination
When a process terminates, for whatever reason - either normally, because of an unhandled
exception or terminated by external code - it never leaks anything: all private memory is freed,
all handles are closed, etc. Of course premature handle closing may cause some loss of data, such as
a file handle being closed before flushing some data to disk - but there is no resource leaks; this is
guaranteed by the kernel.
Kernel drivers, on the other hand, don’t provide such a guarantee. If a driver unloads while
still holding onto allocated memory or open kernel handles - these resources will not be freed
automatically, only released at the next system boot.

(C)2019 Pavel Yosifovich


Chapter 3: Kernel Programming Basics 34

Why is that? Can’t the kernel keep track of a driver’s allocations and resource usage so these can be
freed automatically when the driver unloads?
Theoretically, this would have been possible to achieve (although currently the kernel does not track
such resource usage). The real issue is that it would be too dangerous for the kernel to attempt such
cleanup. The kernel has now way of knowing whether the driver leaked those resources for a reason;
for example, the driver could allocate some buffer and then pass it to another driver, with which
it cooperates. That second driver may use the memory buffer and free it eventually. if the kernel
attempted to free the buffer when the first driver unloads, the second driver would cause an access
violation when accessing that now-freed buffer, causing a system crash.
This again emphasizes the responsibility of a kernel driver to properly clean up after itself; no one
else will do it.

Function Return Values


In typical user mode code, return values from API functions are sometimes ignored, the developer
being somewhat optimistic that the called function is unlikely to fail. This may or may not be
appropriate for one function or another. In the worst case, an unhandled exception would later
crash the process; the system, however, remains intact.
Ignoring return values from kernel APIs is much more dangerous (see the previous Termination
section), and generally should be avoided. Even seemingly “innocent” looking functions can fail for
unexpected reasons, so the golden rule here is - always check return status values from kernel APIs.

IRQL
Interrupt Request Level (IRQL) is an important kernel concept that will be further discussed in
chapter 6. Suffice it to say at this point that normally a processor’s IRQL is zero, and more specifically,
it’s always zero when user mode code is executing. In kernel mode, it’s still zero most of the time -
but not all the time. The effects of higher than zero IRQL will be discussed in chapter 6.

C++ Usage
In user mode programming, C++ has been used for many years and it works well when combined
with user mode API calls. With kernel code, Microsoft started officially supporting C++ with Visual
Studio 2012 and WDK 8. C++ is not mandatory, of course, but it has some important benefits related
to resource cleanup, using an C++ idiom called Resource Acquisition Is Initialization (RAII). We’ll
use this RAII idiom quite a bit to make sure we don’t leak resources.
C++ as a language is almost fully supported for kernel code. But there is no C++ runtime in the
kernel, and so some C++ features just cannot be used:

(C)2019 Pavel Yosifovich


Chapter 3: Kernel Programming Basics 35

• The new and delete operators are not supported and will fail to compile. This is because their
normal operation is to allocate from a user-mode heap, which of course is irrelevant within
the kernel. The kernel API has “replacement” functions that are more closely modeled after the
C functions malloc and free. We’ll discuss these functions later in this chapter. It is possible,
however, to overload these operators similarly as it’s done in user mode C++ and invoke the
kernel allocation and free functions. We’ll see how to do that later in this chapter as well.
• Global variables that have non-default constructors will not be called - there is no-one to call
these constructors. These situations can be avoided in several ways:
– Avoid any code in the constructor and instead create some Init function to be called
explicitly from driver code (e.g. from DriverEntry).
– Allocate a pointer only as a global variable, and create the actual instance dynamically.
The compiler will generate the correct code to invoke the constructor. This works assuming
the new and delete operators have been overloaded as described later in this chapter.
• The C++ exception handling keywords (try, catch, throw) do not compile. This is because
the C++ exception handling mechanism requires its own runtime, which is not present in the
kernel. Exception handling can only be done using Structured Exception Handling (SEH) - a
kernel mechanism to handle exceptions. We’ll take a detailed look at SEH in chapter 6.
• The standard C++ libraries are not available in the kernel. Although most are template-based,
these do not compile, because they depend on user mode libraries and semantics. That said,
C++ templates as a language feature works just fine and can be used, for example, to create
alternative types for user mode library types such as std::vector<>, std::wstring, etc.

The code examples in this book make some use of C++. The features mostly used in the code examples
are:

• The nullptr keyword, representing a true NULL pointer.


• The auto keyword that allows type inference when declaring and initializing variables. This
is useful to reduce clutter, save some typing, and focus on the important pieces.
• Templates will be used when they make sense.
• Overloading of the new and delete operators.
• Constructors and destructors, especially for building RAII types.

Strictly speaking, drivers can be written in pure C without any issues. If you prefer to go that route,
use files with a C extension rather than CPP. This will automatically invoke the C compiler.

Testing and Debugging


With user mode code, testing is generally done on the developer’s machine (if all required
dependencies can be satisfied). Debugging is typically done by attaching the debugger (Visual Studio
in most cases) to the running process (or processes).
With kernel code, testing is typically done on another machine, usually a virtual machine hosted on
the developer’s machine. This ensures that if a BSOD occurs, the developer’s machine is unaffected.

(C)2019 Pavel Yosifovich


Chapter 3: Kernel Programming Basics 36

Debugging kernel code must be done with another machine, where the actual driver is executing.
This is because in kernel mode, hitting a breakpoint freezes the entire machine, not just a particular
process. This means the developer’s machine hosts the debugger itself, while the second machine
(again, usually a virtual machine) executes the driver code. These two machines must be connected
through some mechanism so data can flow between the host (where the debugger is running) and
the target. We’ll look at kernel debugging in more detail in chapter 5.

Debug vs. Release Builds


Just like with user mode projects, building kernel drivers can be done in Debug or Release mode.
The differences are similar to their user mode counterparts - Debug build uses no optimizations by
default, but easier to debug. Release builds utilize compiler optimizations to produce the fastest code
possible. There are a few differences, however.
The actual terms in kernel terminology are Checked (Debug) and Free (Release). Although Visual
Studio kernel projects continue to use the Debug/Release terms, older documentation uses the
Checked/Free terms. From a compilation perspective, kernel Debug builds define the symbol DBG
and set its value to 1 (compared to the _DEBUG symbol defined in user mode). This means you can
use the DBG symbol to distinguish between Debug and Release builds with conditional compilation.
This is in fact what the KdPrint macro does: in Debug builds it compiles to calling DbgPrint, while
in Release build it compiles to nothing, resulting in KdPrint calls having no effect in Release builds.
This is usually what you want because these calls are relatively expensive. We’ll discuss other ways
of logging information in chapter 10.

The Kernel API


Kernel drivers use exported functions from kernel components. These functions will be referred to
as the Kernel API. Most functions are implemented within the kernel module itself (NtOskrnl.exe),
but some may be implemented by other kernel modules, such the HAL (hal.dll).
The Kernel API is a large set of C functions. Most of these functions start with a prefix suggesting
the component implementing that function. Table 3-2 shows some of the common prefixes and their
meaning:

(C)2019 Pavel Yosifovich


Chapter 3: Kernel Programming Basics 37

Table 3-2: Common kernel API prefixes

Prefix Meaning Example


Ex general executive functions ExAllocatePool
Ke general kernel functions KeAcquireSpinLock
Mm memory manager MmProbeAndLockPages
Rtl general runtime library RtlInitUnicodeString
FsRtl file system runtime library FsRtlGetFileSize
Flt file system mini-filter library FltCreateFile
Ob object manager ObReferenceObject
Io I/O manager IoCompleteRequest
Se security SeAccessCheck
Ps process structure PsLookupProcessByProcessId
Po power manager PoSetSystemState
Wmi Windows management instrumentation WmiTraceMessage
Zw native API wrappers ZwCreateFile
Hal hardware abstraction layer HalExamineMBR
Cm configuration manager (registry) CmRegisterCallbackEx

If you take a look at the exported functions list from NtOsKrnl.exe, you’ll find more functions that
are actually documented in the Windows Driver Kit; this is just a fact of kernel developer’s life - not
everything is documented.
One set of functions bears discussion at this point- the Zw prefixed functions. These functions
mirror native APIs available as gateways from NtDll.Dll with the actual implementation within
the Executive. When an Nt function is called from user mode, such as NtCreateFile, it reaches
the Executive at the actual NtCreateFile implementation. At this point, NtCreateFile might do
various checks based on the fact that the original caller is from user mode. This caller information
is stored on a thread-by-thread basis, in the undocumented PreviousMode member in the KTHREAD
structure for each thread.
On the other hand, if a kernel driver needs to call a system service, it should not be subjected to the
same checks and constraints imposed on user mode callers. This is where the Zw functions come
into play. Calling a Zw function sets the previous caller mode to KernelMode (0) and then invokes
the native function. For example, calling ZwCreateFile sets the previous caller to kernelMode and
then calls NtCreateFile, causing NtCreateFile to bypass some security and buffer checks that
would otherwise be performed. The bottom line is, that kernel drivers should call the Zw functions
unless there is a compelling reason to do otherwise.

Functions and Error Codes


Most kernel API functions return a status indicating success or failure of an operation. This is typed
as NTSTATUS, a signed 32-bit integer. The value STATUS_SUCCESS (0) indicates success. A negative
value indicates some kind of error. You can find all the defined NTSTATUS values in the file ntstatus.h.

(C)2019 Pavel Yosifovich


Chapter 3: Kernel Programming Basics 38

Most code paths don’t care about the exact nature of the error, and so testing the most significant bit
is enough. This can be done with the NT_SUCCESS macro. Here is an example that tests for failure
and logs an error if that is the case:

NTSTATUS DoWork() {
NTSTATUS status = CallSomeKernelFunction();
if(!NT_SUCCESS(Statue)) {
KdPirnt((L"Error occurred: 0x%08X\n", status));
return status;
}

// continue with more operations

return STATUS_SUCCESS;
}

In some cases, NTSTATUS values are returned from functions that eventually bubble up to user mode.
In these cases, the STATUS_xxx value is translated to some ERROR_yyy value that is available to
user mode through the GetLastError function. Note that these are not the same numbers; for one,
the error code in user mode have positive values. Second, the mapping is not one-to-one. In any
case, this is not generally a concern for a kernel driver.
Internal kernel driver functions also typically return NTSTATUS to indicate their success/failure
status. This is usually convenient, as these functions make calls to kernel APIs and so can propagate
any error by simply returning the same status they got back from the particular API. This also
implies that the “real” return values from driver functions is typically returned through pointers
and references provided as arguments to the function.

Strings
The kernel API uses strings in many cases where needed. In some cases these strings are simple
Unicode pointers (wchar_t* or one of their typedefs such as WCHAR), but most functions dealing
with strings expect a structure of type UNICODE_STRING.

The term Unicode as used in this book is roughly equivalent to UTF-16, which means 2 bytes per
character. This is how strings are stored internally within kernel components.

The UNICODE_STRING structure represents a string with its length and maximum length known.
Here is a simplified definition of the structure:

(C)2019 Pavel Yosifovich


Chapter 3: Kernel Programming Basics 39

typedef struct _UNICODE_STRING {


USHORT Length;
USHORT MaximumLength;
PWCH Buffer;
} UNICODE_STRING;
typedef UNICODE_STRING *PUNICODE_STRING;
typedef const UNICODE_STRING *PCUNICODE_STRING;

THe Length member is in bytes (not characters) and does not include a Unicode-NULL terminator,
if one exists (a NULL terminator is not mandatory). The MaximumLength member is the number of
bytes the string can grow to without requiring a memory reallocation.
Manipulating UNICODE_STRING structures is typically done with a set of Rtl functions that deal
specifically with strings. Table 3-3 lists some of the common functions for string manipulation
provided by the Rtl functions.

Table 3-3: Common UNICODE_STRING functions

Function Description
RtlInitUnicodeString Initializes a UNICODE_STRING based on an existing C-string
pointer. It sets Buffer, then calculates the Length and sets
MaximumLength to the same value. Note that this function does
not allocate any memory - it just initializes the internal members.
RtlCopyUnicodeString Copies one UNICODE_STRING to another. The destination string
pointer (Buffer) must be allocated before the copy and
MaximumLength set appropriately.
RtlCompareUnicodeString Compares two UNICODE_STRINGs (equal, less, greater), specifying
whether to do a case sensitive or insensitive comparison.
RtlEqualUnicodeString Compares two UNICODE_STRINGs for equality, with case
sensitivity specification.
RtlAppendUnicodeStringToString Appends one UNICODE_STRING to another.
RtlAppendUnicodeToString Appends UNICODE_STRING to a C-style string.

In addition to the above functions, there are functions that work on C-string pointers. Moreover,
some of the well-known string functions from the C Runtime Library are implemented within the
kernel as well for convenience: wcscpy, wcscat, wcslen, wcscpy_s, wcschr, strcpy, strcpy_s
and others.

The wcs prefix works with C Unicode strings, while the str prefix works with C Ansi strings.
The suffix _s in some functions indicates a safe function, where an additional argument
indicating the maximum length of the string must be provided so the function would not
transfer more data than that size.

(C)2019 Pavel Yosifovich


Chapter 3: Kernel Programming Basics 40

Dynamic Memory Allocation


Drivers often need to allocate memory dynamically. As discussed in chapter 1, kernel stack size is
rather small, so any large chunk of memory should be allocated dynamically.
The kernel provides two general memory pools for drivers to use (the kernel itself uses them as well).

• Paged pool - memory pool that can be paged out if required.


• Non Paged Pool - memory pool that is never paged out and is guaranteed to remain in RAM.

Clearly, the non-paged pool is a “better” memory pool as it can never incur a page fault. We’ll see
later in this book that some cases require allocating from non-paged pool. Drivers should use this
pool sparingly, only when required. In all other cases drivers should use the paged pool. The POOL_-
TYPE enumeration represents the pool types. This enumeration includes many “types” of pools, but
only three should be used by drivers: PagedPool, NonPagedPool, NonPagedPoolNx (non-page pool
without execute permissions).
Table 3-4 summarizes the most useful functions used for working with the kernel memory pools.

Table 3-4: Functions for kernel memory pool allocation

Function Description
ExAllocatePool Allocate memory from one of the pools with a default tag. This
function is considered obsolete. The next function in this table should
be used instead.
ExAllocatePoolWithTag Allocate memory from one of the pools with the specified tag.
ExAllocatePoolWithQuotaTag Allocate memory from one of the pools with the specified tag and
charges the current process’ quota for the allocation.
ExFreePool Free an allocation. The function knows from which pool the allocation
was made.
The tag argument in some of the functions allows tagging an allocation with a 4 byte value. Typically
this value is comprised of up to 4 ASCII characters logically identifying the driver, or some part of the
driver. These tags can be used to indicate memory leaks - if any allocations tagged with the driver’s
tag remain after the driver is unloaded. These pool allocations (with their tags) can be viewed with
the Poolmon WDK tool, or my own PoolMonX tool (downloadable from http://www.github.com/
zodiacon/AllTools). Figure 3-1 shows a screen shot of PoolMonX (v2).

(C)2019 Pavel Yosifovich


Chapter 3: Kernel Programming Basics 41

Figure 3-1: PoolMonX (v2)

The following code example shows memory allocation and string copying to save the registry path
passed to DriverEntry, and freeing that string in the Unload routine:

// define a tag (because of little endianess, viewed in PoolMon as 'abcd')

#define DRIVER_TAG 'dcba'

UNICODE_STRING g_RegistryPath;

extern "C" NTSTATUS


DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {
DriverObject->DriverUnload = SampleUnload;

g_RegistryPath.Buffer = (WCHAR*)ExAllocatePoolWithTag(PagedPool,
RegistryPath->Length, DRIVER_TAG);
if (g_RegistryPath.Buffer == nullptr) {
KdPrint(("Failed to allocate memory\n"));
return STATUS_INSUFFICIENT_RESOURCES;
}

(C)2019 Pavel Yosifovich


Chapter 3: Kernel Programming Basics 42

g_RegistryPath.MaximumLength = RegistryPath->Length;
RtlCopyUnicodeString(&g_RegistryPath, (PCUNICODE_STRING)RegistryPath);

// %wZ is for UNICODE_STRING objects


KdPrint(("Copied registry path: %wZ\n", &g_RegistryPath));
//...
return STATUS_SUCCESS;
}

void SampleUnload(_In_ PDRIVER_OBJECT DriverObject) {


UNREFERENCED_PARAMETER(DriverObject);

ExFreePool(g_RegistryPath.Buffer);
KdPrint(("Sample driver Unload called\n"));
}

Lists
The kernel uses circular doubly linked lists in many of its internal data structures. For example, all
processes on the system are managed by EPROCESS structures, connected in a circular doubly linked
list, where its head is stored the kernel variable PsActiveProcessHead.
All these lists are built in a similar fashion, around the LIST_ENTRY structure defined like so:

typedef struct _LIST_ENTRY {


struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY;

Figure 3-2 depicts an example of such a list containing a head and three instances.

Figure 3-2: Circular linked list

(C)2019 Pavel Yosifovich


Other documents randomly have
different content
Columns, ii. 85; metal C., 88;
"proto-doric" do. 96;
polygonal do. 99;
faggot-shaped do. 99;
at Medinet-Abou, 102;
in the Hall at Karnak, id.;
at Philæ, 104;
comparison between Egyptian and Greek C., 121;
ordonnance of C., 133;
spacing, 137;
no rule governing intercolumniation, 143.
Constantinople, ii. 13.
Construction, architectural, ii. 55;
imitation in stone of wooden C., 59;
huge stones only used where necessary, 65;
want of foresight in Egyptian C., 70;
carelessness, id.;
machines used, 72.
Conventions in Egyptian art, ii. 291.
Copper, ii. 378.
Coptic, study of, i. VII.
Copts, i. 13.
Corinth, i. XV.
Corvée, the, i. 25;
its influence upon Egyptian architecture, 27, 30.
Coulanges, M. Fustel de, La cité antique, i. 130.
Crane, the, in the bas-relief, ii. 219.
Crimæa, i. XV.
Crocodile, the, in the bas-reliefs, ii. 218.
Crocodilopolis, ii. 234.
Crown, the red crown, i. 16;
the white do., 16;
the pschent, 16.
Cunningham;
his descriptions of the remains of Græco Buddhic art, i. LIII.
Curtius, Dr.;
history of Greece, i. III.
Græco Buddhic art, LIII.
Curtius, Quintus, ii. 33.
"Cutting, the," i. 435.
Cyclopean walls, ii. 64.
Cylinders, earthenware and soft stone, ii. 291.
Cyma, ii. 153;
do. reversa, ii. 153.
Cyprus, i. X., XXVI.;
painted vases, 78, 161.
Cyrus, i. 79.

D
Darius, i. IX.
Darmesteter, James, i. 69.
Dashour, i. 165, 206.
Dayr, i. 407.
Dayr-el-Bahari, i. 265, 268;
temple or cenotaph of Hatasu, 421-434.
Dayr-el-Medinet, i. 264.
Delbet, Jules, i. 42.
Delhi, ii. 13.
Denderah, i. 326, 351, 434, ii. 67, 69;
pluteus at, 149.
Derri, i. 408.
Desjardins, M. E., i. 302.
Deus Rediculus, temple of the, i. 104.
Deveria, his belief that he had found a portrait of a shepherd
king, ii. 177.
Diocletian, i. 55.
Diodorus Siculus;
his assertion that the first man was born in Egypt, i. 4;
Pyramids, 191;
height of Great Pyramid, 225;
plateau on its summit, 226;
Pyramid of the Labyrinth, 227;
Tomb of Osymandias (Ramesseum), 266, 375;
tombs in the Bab-el-Molouk, 279;
πυλών, 341;
Mœris (Amenemhat III.), 347;
labyrinth, ii. 25;
population of Egypt, 26;
extent of Thebes, 30;
the epithet ἑκατόμπυλος, 40.
Diorite, statue of Chephren in, ii. 221;
the influence of such a material upon style, 303-305.
Djezzar Pacha, ii. 20.
Dog, the, in the bas-reliefs, ii. 219.
Doors, ii. 156.
Dordogne, i. XLII., ii. 78.
"Double," the, i. 128, 135.
Doum (palm), ii, 50.
Drah-abou-l'Neggah, i. 217, 253, 291, 315.
Dromos, i. 336.
Duck, the, in the bas-reliefs, ii. 219.

E
Ebers, Georg.;
extent of the Memphite necropolis, i. 165;
cenotaph in the temple of Abydos, 264;
his opinion upon that temple, id.;
his discovery of a tomb at Thebes, 279;
his opinion upon the Ramesseum, 381;
the funerary character of the temple at Abydos, 391;
his conjectures upon Dayr-el-Bahari, 426;
pavilion of Rameses III. not a palace, ii. 16;
pyramid of the labyrinth, 25;
origin of the quadrangular pier, 90;
uses of papyrus, 126;
his opinion upon the columns in the Bubastite court, Karnak,
146;
propylons of Karnak and Denderah, 157;
his belief in the persistence of the Hyksos type, 237.
Edfou, i. 351, 353;
peripteral temple, 396;
foundations of temple at, ii. 69.
Egger, ii. 126.
Eilithyia, i. 157; ii. 400;
temple of Amenophis III. at, id.
Elephantiné;
peripteral temple at, i. 396;
quarries at, ii. 75, 149.
Empires, classification of the Egyptian, i. 17.
Enamels, ii. 375.
Encaustic painting known to the Egyptians, ii. 336.
Entef, i. 38, 156, 217.
Epochs of Egyptian history, i. 18.
Era, Egypt without one, i. 20.
Erectheum, i. LVII.
Erment, ii. 66.
Esneh, i. 351.
Ethiopia;
its civilization an offshoot from that of Egypt, i. 20;
its pyramids, 217;
its temples, 404;
Ethiopian supremacy in Egypt, ii. 265;
Ethiopians in pictures, 348.
Etruria, i. XLII., 131, 162.
Euripides quoted, i. 130.
"Evandale, Lord," i. 136.

F
Faience, i. 146, ii. 369.
Fayoum, the pyramids in the, i. 226;
statues discovered in the, ii. 233.
Fellowes, Sir Charles, i. X., XXVII.
Feraïg, speos of, i. 406.
Fergusson, James, ii. 8.
Festus, i. XXII.
Fetishism, i. 47-9, 56-8.
Ficus Sycomorus, ii. 54.
Figure, the, ii. 341;
coloured reliefs in the mastabas, 341;
Beni-Hassan, 341;
Thebes, 344;
mandore player at Abd-el-Gournah, 347;
harpers in Bruce's tomb, 348;
Prisoners, 348;
winged figure, 349;
different races distinguished, 350.
Flamingo, the, in the bas-reliefs, ii. 219.
Flandrin, i. IX.
"Foundations," for the service of tomb, i. 144-6.
Fox, the, in the bas-reliefs, ii. 218.
Friedrichs, Carl, i. IV.
Funeral feasts, i. 143.
Funerary figures, i. 145-147.

G
Gailhabaud, M., ii. 36.
Gartasse, i. 433.
Gau, i. 353, 421.
Gautier, Théophile, i. 136, ii. 174.
Gawasi, ii. 249.
Gazelle, in the bas-reliefs, ii. 218.
Gebel-Ahmar, i. 104.
Gebel-Barkal, i. 218, 407.
Gebel-Silsilis, i. 105, 403;
bas-relief at, ii. 246.
Gerhard, i. XV., XVIII.
Gherf-Hossein, hemispeos, i. 407, ii. 138.
Gircheh, i. 421.
Glass, its manufacture represented at Beni-Hassan, ii. 375;
glass-enamelled statuettes, 376.
Globe, winged, ii. 151, 152.
Goat, in the bas-reliefs, ii. 219.
Gods, age of the Egyptian, i. 321.
Goethe, i. 121, 153.
Goose, in the bas-reliefs, ii. 219.
Gorge, the Egyptian, ii. 149.
Gournah, temple of, i. 267, 268, 391, ii. 140.
Gournet-el-Mourraï, ii. 21.
Græco-Buddhic art, i. LIII.
Græco-Scythians, i. XV.
Granaries, ii. 37.
Granite-chambers, Karnak, ii. 52.
Graphic processes, ii. 1.
Grébaut, M., i. 52.
Group, unknown in its proper sense in Egyptian art, ii. 278.
Guglie, ii. 169.
Guillaume, Edouard, i. 42.

H
Hamilton, W. J., i. X., XXVII.
Hamy, M., ii. 377.
Hapi-Toufi, i. 144.
Haram-el-Kabbab ("the false pyramid"), i. 215.
Hare, the, in the bas-reliefs, ii. 218.
Harmachis, i. 237, 389.
Harm-Habi, i. 178.
Ha-ro-bes, ii. 289.
Hatasu, Queen, i. 105;
her obelisks at Karnak, 122, 265, 268;
height of her obelisk, 343;
Dayr-el-Bahari, the cenotaph of H., 425;
height of her obelisk from more recent measurement, ii. 171;
her favourite architect, 178;
her bas-reliefs at Dayr-el-Bahari, 245.
Hathor, i. 58, 69.
Hecuba (Euripides), quoted, i. 130.
Hegel, i. XXXIII.
Height of principal buildings in the world, i. 225.
Helbíg, M. W., i. XV.
Heliopolis;
its walls, ii. 41;
its obelisk, 171.
Hemispeos, i. 253.
Heracleopolis, i. 17.
Hermopolis, i. 15.
Herodotus;
Egypt a present from the Nile, i. 2;
Amasis, 33;
religious observances, 44;
Isis and Osiris the only gods whom all the Egyptians
worshipped, 68;
temples in Delta, 93;
Scythians, 145;
Pyramids, 191, 202, 219;
P. in Lake Mœris, 226, 229;
do. of the Labyrinth, 227;
construction of the Great Pyramid, 233;
tomb of Apries, 306;
Cambyses' treatment of the body of Amasis, 309;
obelisks of Sesostris, 347;
Rhampsinite and Asychis, id.;
propylons and Apis pavilion of Psemethek I., ib.;
monolithic chapel of Amasis, 428;
αὐλὴ built by Psemethek for Apis, 429;
Labyrinth, ii. 25;
level of towns raised artificially, 27;
flat roofs, 36;
λευκὸν τεῖχος of Thebes, 40;
monolithic chapels in the Delta, 75;
Egyptian beans, 125.
Hesiod, i. 133.
Heuzey, i. XVII., 130.
Hippopotamus, the, in the bas-reliefs, ii. 218.
Hittorf, i. XIV. 121.
Hobs (a god), ii. 281.
Homer;
quoted, i. 129, 130;
"Hundred-gated Thebes," ii. 40.
Horeau, his plan of the hemispeos of Gherf-Hossein, i. 408.
Hor-em-khou, i. 321.
Hor-Khom, inscription, i. 157.
Hor-Schesou, i. 196.
Horse, introduced into Egypt about the time of the shepherd
invasion, ii. 250;
his characteristic features in Egyptian art, id.
Horus, i. 63, 69, ii. 273, 383;
do. a private individual, 270.
Hosi, panels from the tomb of, ii. 189.
Hoskins, his plans of the temple of Soleb, i. 384-5.
House, the Egyptian, ii. 26;
its situation, 27;
foundation, id.;
restoration based upon a plan found by Rosellini, 33;
models of houses, 34;
materials and arrangement, id.
Howara, El, i. 217, ii. 25.
Huber, M., i. LVI.
Hyena, the, in the bas-reliefs, ii. 218.
Hyksos, i. 68, 404, ii. 228-38.
"Hypæthra, the Great," at Philæ, i. 33.
Hypogea, general character of, i. 188.
Hypostyle Hall, i. 357, ii. 145-7;
of Karnak, i. 365-9, ii. 163;
of Luxor, i. 371;
of the Ramesseum, 376-7;
of Medinet-Abou, 382-3;
of Soleb, 385;
at Napata, 385;
at Abydos, 389;
at Gournah, 391;
of temple of Khons, ii. 166.

I
Ibex, in the bas-reliefs, ii. 218.
Ibis, in the bas-reliefs, ii. 219.
Ictinus, i. 444.
Illahoun, pyramid of, i. 204.
Illumination;
methods of lighting the temples, ii. 162-7;
methods of lighting the palaces and private houses, 168.
Incas, the, i. 22.
Indra, i. 50.
Ipsamboul, i. 22;
little temple at, 405;
great temple at, 407-8.
Isæus, i. 130.
Isis, i. 68, 69, 301, 389, 430.
Ismandes, i. 376.
Ivory, ii. 384.

J
Japan, i. IV.
Jewelry, ii. 377;
pectorals, 380;
ægis, 382;
true cloisonné enamels unknown, 384;
necklaces, id.;
materials used, ib.;
amber unknown, 387.
Jollois, i. 123.
Jomard, i. 152;
description of the necropolis of Gizeh, 152, 168, 223;
his analysis of the impression produced by the Pyramids, 237;
his description of the temple of the third pyramid, 330-4, 397,
400;
Egyptian cement, ii. 71.
Josephus, quoted, ii. 26.
Joubert, Leo, i. XXI.
Jour des morts, an Egyptian, i. 239.
Judging the Dead, i. 237.
Justinian, i. 55.

K
Ka, the, i. 128.
Kadesh (or Qadech), goddess, ii. 262.
Kalabcheh, i. 407, ii. 107.
Kalaçoka, i. L.
Karnak, i. 25, 28, 105, 155, 263-70, 362-69;
the granite chambers, ii. 52;
stele piers, 94, 97;
columns, 102;
decoration, 104, 130, 132.
Ker-Porter, Sir R., i. IX.
Kha-em-uas, jewelry of, ii. 380.
Khemi, i. 14.
Khetas, i. 266; ii. 327.
Khnumhotep, i. 160.
Khons, i. 54.
temple of, 123, 268, 348, ii. 136.
Khoo-foo-ankh, i. 182;
sarcophagus of, ii. 59.
Klaft, ii. 222.
Kuyler, i., V.
Kummeh, i. 4, ii. 45.

L
Labyrinth, the, i. 226, ii. 25.
Lakes, sacred, in the temples, i. 344, ii. 6.
Language, the Egyptian, i. 10-11.
Larcher, his notes to Herodotus, i. 307.
Lartet, i. XXXVIII.
Layard, H. A., i. VIII.
Lenormant, Fr., i. 25, 377.
Leopard, the, in the bas-reliefs, ii. 218.
Lepsius;
the Egyptians a proto-semitic race, i. 10;
inferiority of Ethiopian to Egyptian art well shown in his
Denkmæler, 21;
Berlin Museum enriched by him, 89;
tombs to the number of 130 examined by him in Middle and
Lower Egypt, 164;
arrangement of the mastabas, 167;
portraits of defunct in public hall of tomb, 178;
sixty-seven pyramids examined by the Prussian commission,
198;
theory of pyramid construction, 201;
pyramids at Drah-abou'l-neggah, 217;
paintings at Beni-Hassan figured by L., 249;
Ramesseum, 376;
great temple at Medinet Abou, 382;
temple of Soleb, 384;
temple of Thothmes III. at Semneh, 400;
Ethiopian temples in Denkmæler, 401;
speos of Silsilis, and hemispeos of Redesieh, 406;
Gebel-Barkal, 407;
fortress of Semneh, ii. 45;
Egyptian methods of preparing for a siege suggested by a
plate in Denkmæler, 49;
building operations figured in Denkmæler, 53;
supposed discovery of the labyrinth, 66;
origin of quadrangular piers, 90;
campaniform capitals in a hypogeum at Gizeh, 101;
capitals in the ambulatory of Thothmes at Karnak, 115;
old form of winged disc at Beni-Hassan, 152;
monuments in Wadimaghara figured in Denkmæler, 184;
thick-set forms discovered in a tomb dating from the fourth
dynasty, 190;
poverty of invention in Theban art seen by glancing through
Denkmæler, 250;
works in high-relief from the mastabas figured in Denkmæler,
284.
Leroux, Hector;
his sketch of Philæ, i. 433;
his opinions on Egyptian painting, ii. 335.
Letronne;
his researches, i. 224, 232.
Lion, the, in Egyptian art, ii. 281, 323.
Longperier, de, his opinion on the age of Egyptian bronzes, ii.
197.
Loret, M. Victor, ii. 135.
Lotus, the, ii. 125.
Lycian remains, i. XXVII.
Lucian (pseudo), i. 323.
Lutzow, Carl von, i. IV.
Luxor, temple of, i. 270, 370, ii. 132;
obelisk of, 171.

M
Mad, i. 354.
Maghara (Wadi), ii. 95, 184.
Mahsarah, i. 105.
Mammisi, i. 433.
Mandore, ii. 344.
Manetho, i. 18;
his account of the shepherd invasion not to be relied on, ii.
239.
Marchandon-de-la-Faye, M., i. 95.
Mariette, Auguste;
formation of Egypt, i. 2;
accession of Menes, 18;
Egyptian chronology, 20;
bad workmanship of Egyptian temples, foundations of great
temples at Abydos, 28;
house in the desert, 41;
protest against M. Renan's conception of ancient Egypt, 71;
excavations, 86;
ancient art chiefly known through his exertions and his
contributions to the Louvre and the French Exhibition, 89;
M. on the arch, 113;
obelisk of Hatasu gilded, 122;
sepulchral formula, 135;
θυμιατήρια in the tomb of Ti, 143;
objects for the support of the Ka sometimes modelled "in the
round," 145;
position of the stele, 157;
tombs constructed during lifetime, 160;
his "theory of the mastaba," 164;
derivation of the word Sakkarah, 166;
boats found in mummy pits, 184;
pyramids always in a necropolis, 191;
Mastabat-el-Faraoun, 215;
pyramids upon Drah-abou'l-neggah, 217;
opening of three unexplored pyramids at Sakkarah, 234;
tomb of Osiris, supposed site, 243;
tombs at Abydos, 244;
steles from Abydos, 249;
temples of the left bank, Thebes, 264;
method of closing tombs in the Bab-el-Molouk, 278;
mummy of Queen Aah-hotep, 291;
tombs of Apis, 295;
the little Serapeum, 302;
temple of the Sphinx, 326;
Sphinxes at the Serapeum of Memphis, 336;
Sphinx avenues ornamental rather than religious, 337;
walls of Karnak, 338;
extent of the temples at Karnak, 362;
sanctuary in the great temple, 384;
temple of Dayr-el-Bahari, 425;
excavations at Sais, 433;
characteristics of the Egyptian temple, 434;
contrast between it and the Greek temple, the Christian
church, and the Mahommedan mosque, 435;
explanation of its elaborate decoration, id.;
Royal Pavilion of Medinet-Abou not a palace, ii. 16;
building materials, 53;
brick-making, id.;
carelessness of the Egyptian builders, 70;
true vaults in the necropolis of Abydos, 77;
inverted arches, 81;
lotiform capitals in the tomb of Ti, 86;
origin of the faggot-shaped column, 99;
origin of the campaniform capital, 128;
proposal that it should be called papyriform, id.;
discards the notion that the columns in the Babastite court, at
Karnak, bore architraves, 145;
his assumption that they once enclosed a hypæthral temple,
ib.;
first appearance of the winged disc, 152;
obelisks in the Theban necropolis, 170;
obelisks of Hatasu gilded, 174;
statues in the tomb of Ti, 181;
statues of Rahotep and Nefert, from Meidoum, 187;
panels from the tomb of Hosi, 188;
the Scribe of the Louvre, 192;
brought figures from Ancient Empire to Paris in 1878, 211;
Nemhotep, 212;
picture of geese, 220;
statues of Chephren discovered in the temple of the Sphinx,
213;
early Theban works rude and awkward, 226;
Menthouthotep, id.;
groups from Tanis, 228;
figure discovered in the Fayoum, 233;
definition of the type of these Tanite remains, 237;
head of Taia discovered, 242;
Amenophis IV. perhaps a eunuch, 243;
expedition to Punt, illustrated at Dayr-el-Bahari, 245;
belief that Punt was in Africa, 246;
detestable style of the remains from the last years of
Rameses II., 258;
Menephtah, son of Rameses II., statue at Boulak, 260;
head of Tahraka at Boulak, 263;
opinion as to the character of the statues in Egyptian temples,
276;
origin of the Sphinx, 281;
tomb of Sabou, sculptures, 285;
models for sculptors at Boulak, their probable date, 324.
Mariette, Edouard, ii. 28, 55.
Maspero G.;
our guide to the history of Egypt, i. 8-9;
his opinion upon the Egyptian language, 13;
periods of Egyptian history, 17-18;
Ethiopian kingdom, 21;
affiliation of the king to the gods, 22;
mildness of rule in Ancient Egypt, 37;
prince Entef's stele, 38;
Egyptian devotion, 39;
do. 43;
the number of their devotional works of art, id.;
character of sacred animals, such as the Apis, 66;
his theory as to the ka, or double, 126, 137-8, 140-6, 148-
153, 155-7;
translation from Papyrus IV. at Boulak, 161;
tomb of Harmhabi, 178;
pyramid of Ounas, 194;
commentary on the second book of Herodotus, 227;
opening of pyramid of Ounas, 235;
opinion on the tombs at Abydos, 242;
the staircase of Osiris, 243;
discovery of remains belonging to royal tombs of the eleventh
dynasty at Drah abou'l-neggah, 253;
ascription of power of speech and movement to statues, 289;
proof that the gods existed in the time of the Ancient Empire,
318;
translation of the stele of Piankhi from Gebel-Barkal, 353;
Hatasu's expedition to Punt, 426;
translations of Egyptian tales, ii. 30;
symbolism of papyrus and lotus, 126;
translation of stele C. 14, in the Louvre, 176;
cause of the Iconic character of Egyptian statutes, 181;
materials for wooden statues, 197;
his translation of funerary songs, 249;
formula by which the right of erecting a statue in a temple
was granted to a private individual, 278;
on the Palestrina mosaic, 288.
Mastaba, i. 164;
in the Memphite necropolis, 165, 189;
materials of the, 168;
Mastabat-el-Faraoun, 169;
Mastabas of Sabou, 171;
Haar, id.;
Ra-en-mar, id.;
Hapi, 171;
general arrangements, 172.
Mastabat-el-Faraoun, i. 169, 214, 326.
Maury, Alfred, i. 286.
Maut, i. 63, 268.
Medinet-Abou, i. 22, 102;
the great temple, 260, 267-8, 375;
the little temple, 376, ii. 169;
the royal pavilion, i. 375, ii. 16;
the great temple, method of lighting, 384;
brackets in royal pavilion, 23.
Medinet-el-Fayoum, ii. 25.
Medledk, i. 159.
Megasthenes, i. L.
Meh, house of, i. 156.
Meidoum, i. 35, 89, 165;
construction of the pyramid of M., i. 200.
"Memnon," statues of, i. 267, 290, 376.
"Memnonium," i. 267, ii. 30.
Memphis, i. 6;
discovery of the Sheik-el-Beled, 9, 16;
political centre of the Ancient Empire, 17, 27;
our knowledge of the early period all derived from the
necropolis of M., 34;
the early Egyptians not oppressed, 37;
worship of Ptah at M., 55;
significance of apis, 67;
situation of necropolis, 136;
doors of the tombs turned eastward, 157;
mastabas, 165;
statue of Rameses II. on the site of M., ii. 240.
Mendes, i. 22.
Menephtah, head of, at Boulak, ii. 258.
Menes, i. X, XLVIII., 15, 17, 22, 38.
Menkaura (Mycerinus), i. 326.
Menthouthotep, a scribe, ii. 226.
Mentou-Ra, ii. 266.
Menzaleh, Lake, fellahs in the neighborhood of their race, ii 237.
Merenzi, i. 234.
Mérimée, M., materials employed by Egyptian painters, ii. 334.
Meroë, i. 20, 217.
Merval, du Barry de, ii. 11.
Mesem Bryanthemum Copticum, ii. 375.
Metal-work, ii. 377;
blow-pipe known, 378;
iron, 379;
damascening, 384.
Metopes, ii. 155.
Mexico, i. V.
Michaëlis, i. XIX.
Michelet, i. 64.
Midas, i. XXVII.
Minutoli, i. 213.
Mit-fares, ii. 234.
Mitrahineh, bas-relief at, ii. 271.
Mnevis, i. 54.
Models for sculptors, ii. 322.
Modulus, its absence from Egyptian architecture, i. 102.
Mœris (Pharaoh), i. 347;
Lake M, i. 7, 216, 228, ii. 25
Mokattam, i. 105, 201, 204.
Monolithic columns rare in Egypt, ii. 66.
Mosel, i. XVII.
Müller, Ottfried, i. III., V., XXI., XXV., XXXI., LIV.
Mummies, i. 135;
m. pits, 181;
method of closing m. pit, 183;
do. of sarcophagus, 182;
furniture of m. chambers, 183;
decoration of the m. cases, ii. 335.
Mycenæ, i. XLII., 162.
Mycerinus, pyramid of, i. 205, 227, 329;
the sarcophagus of his daughter as described by Herodotus,
307;
his own sarcophagus, ii. 55-59.
Museums—
Berlin, i. 89;
papyrus narrating the dedication of a chapel by an
Ousourtesen, 334;
funerary obelisk, ii. 170;
leg in black granite, 228;
enamelled bricks from stepped pyramid, 372.
Boulak, i. 10, 41;
the art of the pyramid builders only to be fully seen at B.,
86, 89, 90, 139;
papyrus IV., 161;
stele with garden about a tomb, 301;
statues of gods, 319;
sphinxes in courtyard, ii. 337;
statue of the architect Nefer, 177;
statues in tomb of Ti, 181;
Rahotep and Nefert, 183-7;
Sheik-el-beled, 183, 194;
panels from tomb of Hosi, 189;
statue of Ra-nefer, 203;
do. of Ti, 203;
wooden statue of a man with long robe, 204;
kneeling statues, 204;
Nefer-hotep and Tenteta, 207;
domestic and agricultural figures, 209;
Nemhotep, 212;
painting of Nile geese, 219;
great statues of Chephren, 221;
Tanite remains, 230-5;
Thothmes III., 241;
Taia, 242;
dancing girls, 249;
Welcome to our website – the perfect destination for book lovers and
knowledge seekers. We believe that every book holds a new world,
offering opportunities for learning, discovery, and personal growth.
That’s why we are dedicated to bringing you a diverse collection of
books, ranging from classic literature and specialized publications to
self-development guides and children's books.

More than just a book-buying platform, we strive to be a bridge


connecting you with timeless cultural and intellectual values. With an
elegant, user-friendly interface and a smart search system, you can
quickly find the books that best suit your interests. Additionally,
our special promotions and home delivery services help you save time
and fully enjoy the joy of reading.

Join us on a journey of knowledge exploration, passion nurturing, and


personal growth every day!

ebookbell.com

You might also like