Chapter III Writing Kernel Mode Drivers
Translation: kendiv (fcczj@263.net)
Update:
Thursday, February 10, 2005
Disclaimer: Please indicate the source and guarantee the integrity of the article, and all rights to the translation.
Device I / O Control
As mentioned in the introduction of this chapter, in this book, we will not build a specific hardware driver. Alternatively, we will use powerful kernel drivers to study the secrets of Windows 2000. From the actual results, the power of the driver is that they can run on the highest privilege level of the CPU. This means that kernel drivers can access all system resources, read all memory spaces, and also allowed to perform the privilege instructions of the CPU, such as read the current value of the CPU control register. The program in user mode If you try to read a byte from the kernel space or attempt to perform the assembly instructions such as MOV EAX, CR3 is immediately terminated. However, this powerful bottom line is a small error of the driver, which will make the entire system crash. Even very small errors occur, the system is blue screen, so the developer of the developer program must be more careful than Win32 applications or DLL developers. Remember the Windows 2000 Killer Device Driver that we used in the first chapter in the first chapter? Everything it makes just touches the virtual memory address 0x00000000, then --- Boom! ! ! You should be aware that you will restart your machine more frequently in the past when developing kernel drivers.
In the subsequent chapter, the driver code I give will use technology called device I / O control (IOCTL) to allow the code in user mode to implement "remote control" of a certain program. If the application needs to access the system resources that cannot be touched in user mode, the kernel driver will be able to complete this work, and IOCTL is a bridge that is contacted. In fact, IOCTL is not new technologies adopted by Windows 2000. Even if the old operating system --- DOS 2.11 also has this capability, the 0x44 function and its subunies constitute DOS IOCTL. Basically, IOCTL is a way to control the communication and device communication, and the control path is logically independent of the data path. Imagine a hard disk device transmitting the contents in the magnetic disk sector through its primary data path. If the customer wants to get the media information used by the current device, it must use another different path. For example, the DOS function is 0x44, its subfunction 0x0d, 0x66 constitutes DOS IOCTL, calling these functions to read 32-bit continuous data of the disk (refer to Brown and Kyle 1991, 1993).
Device I / O Control can have a variety of implementations according to the device to be controlled. For its general form, IOCTL has the following categories:
l The client controls the device through a special entry point. In DOS, this entry point is int 21h, the function number 0x44. In Windows 2000, the Win32 function deviceiocontrol () exported by kernel32.dll.
l Client By providing the unique identifier of the device, control code, and a buffer that stores input data, a buffer that stores the output data to invoke the entry point of IOCTL. For Windows 2000, the device identifier is a handle of the device that is successfully opened.
l Control code is used to tell the target device's IOCTL Dispatcher, which control function request is of the client.
l Input buffers can include arbitrary additional data, and the device may require this data to complete the operations requested by the client.
l Any data generated by the client requested, will save in the output buffer provided on the client. l IOCTL operation is expressed by returning status code to the client.
Obviously this is a powerful general mechanism that can be applied to a wide range of control requests. For example, the application will be disabled when the memory space occupied by the system kernel, because the program will immediately throw an exception when the program touches the memory space, but the program can complete this work by loading a kernel driver. This avoids abnormalities. Two modules interacting must follow the IOCTL protocol to manage data transmission. For example, the program may read memory or send 0x80002001 to the memory by sending control code 0x80002000 to the driver. For read requests, the IOCTL input buffer may provide the base address and the number of bytes to read. The kernel driver can obtain these requests and determine whether the read operation or write operations are used by control code. For read requests, the kernel driver copies data within the requesting memory to the output buffer provided by the caller, and returns success code if the output buffer is sufficient enough to accommodate this data. For write requests, the driver copies the data in the input buffer to the specified memory (the starting position of the memory is also specified by the input buffer). In the fourth chapter, I will provide a Memory SPY's quote code.
Now, it can be seen that IOCTL is a back door of Win32 applications. Through IOCTL, almost all operations can be performed, but these operations only allow privileged modules. Of course, this needs to first write a privileged module, however, once you have a spy module running in the system, everything is very simple. The two objectives of this book are: explain in detail how to write a driver of kernel mode and a sample code that can complete a lot of active drivers.
Windows 2000 Killer Device
Let's take a very simple driver before starting the higher driver project. In the first chapter, I introduced the Killer Device of Windows 2000 - W2K_KILL.SYS, which was designed to trigger a benign system crash. This driver does not need most of the code in Example 3-3, as it will crash before it has the opportunity to receive the first I / O request package. Example 3-7 gives its implementation code. There is no W2k_kill.h file here because it does not contain any code we are interested in.
The code in the schematic 3-7 does not perform an initialization operation in DriveREntry () because the system will crash before driverenTry (), so it is not necessary to make these additional work.
#define _w2k_kill_sys_
#include
#include "w2k_kill.h"
/ / =========================================================================================================================================================================================== ==================
// discardable functions
/ / =========================================================================================================================================================================================== ================== NTSTATUS DRIVERENTRY (PDRIVER_OBJECT PDRIVEROBJECT,
Punicode_String PusregistryPath);
#ifdef alloc_pragma
#pragma alloc_text (init, driverentry)
#ENDIF
/ / =========================================================================================================================================================================================== ==================
// Driver Initialization
/ / =========================================================================================================================================================================================== ==================
NTSTATUS DRIVERENTRY (PDRIVER_OBJECT PDRIVEROBJECT,
Punicode_String PusregistryPath)
{
Return * (NTSTATUS *) 0);
}
/ / =========================================================================================================================================================================================== ==================
// end of program
/ / =========================================================================================================================================================================================== ================== List 3-7. A small system crash
Load / unload driver
After completing a kernel driver, you may want to execute it immediately. How to do it? Typical approach is to load the driver and execute when the system is started. But this does this mean that every time we update the driver, you must restart the system? Fortunately, this is not necessary. One feature of Windows 2000 is to provide a Win32 interface to allow the driver to be loaded or unloaded at runtime. This is done by the Service Control Manager, SCM. The following will be described in detail.
Service Control Manager
The name of "Service Control Manager" is easy to misunderstand because it suggests that the component is only used for service management. Services is a very powerful module for Windows 2000, which runs a set of programs in the background and does not require user interaction (which means there is no common user interface or console). In other words, a service is a Win32 process that is always running in the system, even if there is no user login coming in. Although development services are an exciting topic, it does not belong to the category of this book. To learn more about the development of services, please read the very nice tutorials provided by Paula Tomlinson in Windows Developer's Journal (WDJ) (Tomlinson 1996a), and subsequent papers published in her WDJ column --- Understanding NT.
The SC Manager (ie, the service control manager) controls the service and drivers. For the sake of simplicity, I use the word "service" to represent all objects controlled by the SC Manager, which includes strict service and kernel drivers. The SC interface is available for Win32 programs, which is provided by Win32 subsystem components - Advapi32.dll, which provides a lot of interesting API functions. Table 3-3 shows the name of the API function for loading, controlling, and uninstalling services, and also gives a brief description. Before you can load or access any service, you must get the SC manager's handle (by calling OpenScManager ()), in subsequent discussion, the handle will be referred to as: a manager handle. CreateService () and OpenService () require this handle, and the handle returned by these functions will be referred to as: a service handle. This type of handle can be passed to a function that needs to reference a service, such as ControlService (), deleteService (), and StartService (). These two types of SC handles are released through the ClosESERVICEHANDLE () function.
Name
Describe
CloseServiceHandle Close Handle from OpenScManager (), CreateService () or OpenService () Stop, Pause, Continue, Query, or notify the loaded service / driver CreateService Load a Service / Driver DeleteService Uninstall a service / driver OpenScManager Get SC The handle of the manager OpenService Gets the handle QueryServiceStatus that has been loaded / driver QueryServiceStatus queries the properties of the service / driver and the current status StartService launched a loaded service / driver table 3-3. Basic service control function
Loading and running a typical steps to perform execution:
1. Call OpenScManager () to get a manager handle
2. Call CREATSERVICE () to add a service to your system
3. Call StartService () to run a service
4. Call the ClosServiceHandle () to release the manager or service handle
To make sure that when an error occurs, roll back to the last successful call, then start again. For example, when you call StartService (), the SC Manager reports an error, you need to call deleteService (). Otherwise, the service will remain in a non-expected state. Another error that uses the SC Manager API is that the full path name must be provided for the CreateService () function, otherwise, if the function does not find the executable file in the current directory, it will fail. Therefore, you should use the Win32 function --- getFullPathName () to specify all file names pass to createService () unless they are already a full path.
High-level driver management function
In order to interact with the SC manager, the CD supplied with this book provides a number of more advanced outsourcing functions, which block some of the original inconvenient special requirements. These functions are part of this book's huge Windows 2000 tool library (located / SRC / W2K_LIB in the book CD). All functions exported by W2K_LIB.dll have a global name prefix W2K, service, and driver management functions that use W2KService prefixes. Listing 3-8 gives details of the functions implemented in the tool library provided in this book.
/ / =========================================================================================================================================================================================== ==================
// service / driver management
/ / =========================================================================================================================================================================================== ================== SC_HANDLE WINAPI W2KServiceConnect (VOID)
{
Return OpenScManager (NULL, NULL, SC_MANAGER_ALL_ACCESS);
}
/ / -------------------------------------------------------------------------------------------- -----------------
SC_HANDLE WINAPI W2KSERVICEDISCONNECT (SC_HANDLE HMANAGER)
{
IF (HManager! = null) ClosESERVICEHANDLE (HMANAGER);
Return NULL;
}
/ / -------------------------------------------------------------------------------------------- -----------------
SC_Handle WinAPI W2KServiceManager (SC_Handle HManager,
PSC_Handle Phmanager,
BOOL FOPEN
{
SC_HANDLE HMANAGER1 = NULL;
IF (phmanager! = NULL)
{
IF (FOPEN)
{
IF (hmanager == null)
{
* phmanager = w2kserviceConnect ();
}
Else
{
* phmanager = hmanager;
}
}
Else
{
IF (hmanager == null)
{
* phmanager = w2kservicedisconnect (* phManager);
}
}
HManager1 = * phmanager;
}
Return HManager1;
}
/ / -------------------------------------------------------------------------------------------- -----------------
SC_Handle WinAPI W2KServiceOpen (SC_Handle HManager,
PWORD PWNAME)
{
SC_HANDLE HMANAGER1;
SC_HANDLE HSERVICE = NULL;
W2KServiceManager (HManager, & HManager1, true);
IF ((HManager1! = null) && (pwname! = null))
{
Hservice = OpenService (HManager1, Pwname,
Service_all_access);
}
W2KServiceManager (HManager, & HManager1, False);
Return Hservi;
}
/ / -------------------------------------------------------------------------------------------- -----------------
Bool WinApi W2KServiceClose (sc_handle hservice) {
Return (Hservice! = null) && closeServiceHandle (HService);
}
/ / -------------------------------------------------------------------------------------------- -----------------
Bool WinAPI W2KServiceAdd (sc_handle hmanager,
PWORD PWNAME,
PWORD PWINFO,
PWORD PWPATH)
{
SC_HANDLE HMANAGER1, HSERVICE;
PWORD PWFILE;
Word awpath [max_path];
DWORD N;
BOOL fok = false;
W2KServiceManager (HManager, & HManager1, true);
IF ((HManager1! = null) && (Pwname! = NULL) &&
(PWINFO! = NULL) && (pwpath! = null) &&
(n = getfullpathname (PWPATH, MAX_PATH, AWPATH, & PWFILE) &&
(n { IF ((HService = CreateService) (HManager1, Pwname, PWINFO, SERVICE_ALL_ACCESS, Service_kernel_driver, Service_demand_start, Service_ERROR_NORMAL, AWPATH, NULL, NULL, NULL, NULL, NULL) ! = NULL) { W2KServiceClose (Hservice); Fok = true; } Else { Fok = (getLastError () == Error_Service_exists); } } W2KServiceManager (HManager, & HManager1, False); Return fok; } / / -------------------------------------------------------------------------------------------- ----------------- Bool WinApi W2KServiceRemove (SC_Handle HManager, PWORD PWNAME) { SC_HANDLE HSERVICE; BOOL fok = false; IF ((HService = W2KServiceOpen (HManager, PWNAME))! = NULL) { IF (deleteService (Hservice)) { Fok = true; } Else { Fok = (getLastError () == ERROR_SERVICE_MARKED_FOR_DELETE); } W2KServiceClose (Hservice); } Return fok; } / / -------------------------------------------------------------------------------------------- ----------------- Bool WinApi W2KServiceStart (SC_Handle HManager, PWORD PWNAME) { SC_HANDLE HSERVICE; Bool fok = false; if ((HService = w2kserviceopen (hmanager, pwname))! = NULL) { IF (StartService (Hservice, 1, & PWName) { Fok = true; } Else { Fok = (getLastError () == ERROR_SERVICE_ALREADY_RUNNING); } W2KServiceClose (Hservice); } Return fok; } / / -------------------------------------------------------------------------------------------- ----------------- Bool WinAPI W2KServiceControl (SC_Handle HManager, PWORD PWNAME, DWORD DCONTROL) { SC_HANDLE HSERVICE; Service_status serviceArstatus; BOOL fok = false; IF ((HService = W2KServiceOpen (HManager, PWNAME))! = NULL) { QueryServiceStatus (Hservice, & ServiceStatus)) { Switch (ServiceStatus.dwcurrentState) { Case service_stop_pending: Case service_stopped: { Fok = (DControl == Service_Control_stop); Break; } Case service_pause_pending: Case service_paused: { Fok = (DControl == Service_Control_pause); Break; } Case service_start_pending: Case service_continue_pending: Case service_running: { Fok = (dcontrol == service_control_continue); Break; } } } Fok = fok || ControlService (Hservice, DControl, & Service); W2KServiceClose (Hservice); } Return fok; } / / -------------------------------------------------------------------------------------------- ----------------- Bool WinAPI W2KServiceStop (SC_Handle HManager, PWORD PWNAME) { Return W2KServiceControl (HManager, PWNAME, Service_control_stop); } / / -------------------------------------------------------------------------------------------- ----------------- Bool WinAPI W2KServicePause (sc_handle hmanager, PWORD PWNAME) { Return W2KServiceControl (HManager, PWNAME, Service_control_pause); } / / -------------------------------------------------------------------------------------------- ---------------- Bool WinApi W2kServiceContinue (SC_HANDLE HMANAGER, PWORD PWNAME) { Return W2KServiceControl (HManager, PWNAME, Service_control_continue); } / / -------------------------------------------------------------------------------------------- ----------------- SC_Handle WinAPI W2KServiceLoad (PWORD PWNAME, PWORD PWINFO, PWORD PWPATH, BOOL FSTART) { BOOL FOK; SC_HANDLE HMANAGER = NULL; IF ((HManager = W2KServiceConnect ())! = null) { Fok = W2KServiceAdd (HManager, PWNAME, PWINFO, PWPATH); IF (fok && fstart) { IF (! (fok = w2kservicestart (hmanager, pwname)))) { W2KServiceRemove (HManager, PWNAME); } } IF (! fok) { HManager = w2kserventicedisconnect (hmanager); } } Return HManager; } / / -------------------------------------------------------------------------------------------- ----------------- SC_Handle WinAPI W2KServiceLoadex (PWORD PWPATH, BOOL FSTART) { PVS_VersionData PVVD; PWORD PWPATH1, PWINFO; Word frs [MAX_PATH]; DWORD DNAME, DEXTENSION; SC_HANDLE HMANAGER = NULL; IF (PWPATH! = NULL) { DNAME = W2kpathname (PWPATH, & DEXTENSITION); LSTRCPYN (Awname, PWPATH DNAME, MIN (MAX_PATH, DEXTENSION - DNAME 1)); PWPATH1 = W2kpathevaluate (pwpath, null); PVVD = W2kversionData (PWPATH1, -1); PWINFO = ((pvvd! = null) && pvvd-> awfiledescription [0] ? pvvd-> awfiledescription : awname; HManager = W2KServiceLoad (Awname, PWINFO, PWPATH1, FSTART); W2kmemoryDestroy (PVVD); W2kmemoryDestroy (pwpath1); } Return HManager; } / / -------------------------------------------------------------------------------------------- ----------------- Bool WinApi W2KServiceUnload (PWORD PWNAME, SC_HANDLE HMANAGER) { SC_HANDLE HMANAGER1 = HManager; BOOL FOK = FALSE; IF (PWNAME! = NULL) { IF (hmanager1 == null) { HManager1 = w2kserviceConnect (); } IF (HManager1! = NULL) { W2KServiceStop (HManager1, Pwname); Fok = W2KServiceRemove (HManager1, Pwname); } } W2KServicedisconnect (HManager1); Return fok; } / / -------------------------------------------------------------------------------------------- ----------------- Bool WinAPI W2KServiceunloadex (PWORD PWPATH, SC_HANDLE HMANAGER) { DWORD DNAME, DEXTENSION; Word frs [MAX_PATH]; PWORD PWNAME = NULL; IF (PWPATH! = NULL) { DNAME = W2kpathname (PWPATH, & DEXTENSITION); LSTRCPYN (PWNAME = AWNAME, PWPATH DNAME, MIN (MAX_PATH, DEXTENSION - DNAME 1)); } Return W2KServiceunload (PWNAME, HMANAGER); } / / -------------------------------------------------------------------------------------------- ----------------- Listing 3-8. Service and Drive Management Library Functions ....................to be continued.........................