Chapter 4 Exploring the Memory Management Mechanism of WINDOWS 2000
Translation: kendiv (fcczj@263.net)
Update:
Sunday, February 17, 2005
Disclaimer: Please indicate the source and guarantee the integrity of the article, and all rights to the translation.
Memory SPY Device Example
Microsoft said the most of Windows NT and 2000 is that they are safe operating systems. They not only joined the user verification system in the network environment, but also strengthen the robustness of the system (Robustness) to further reduce the probability of endangering system integrity, these errors may use illegal pointers or Writing operations are performed outside the memory data structure. These are very distressed on Windows 3.x, because Windows 3.x systems and all applications share a single memory space. Windows NT provides completely independent memory space for system and application memory and concurrent processes. Each process has its own independent 4GB address space, as shown in Figure 4-2. Whenever task switching occurs, the current address space will be replaced (switch out), and the other is mapped in, each of which uses different segment registers, page tables, and other memory management structures. This design avoids the application where the application is inadvertently modify the memory used by another program. Since each process will inevitably request access to system resources, some system data and code are always included in 4GB space, and a different technique is used to protect these memory locations not overwriting overwritten.
WINDOWS 2000 memory segmentation
Windows 2000 inherits the basic memory segmentation model of Windows NT 4.0. By default, the model divides 4GB address space into two blocks. Low half of the address range is: 0x00000000 ---- 0x7fffffff, which contains data and code that runs in user mode (used in Intel, is a privileged 3 or Ring 3) application. Higher half of the address range is: 0x80000000 --- 0xffffffFfffff, the default is all reserved to the system, the code in this range is running in the kernel mode (ie, the privilege level is 0 or RING 0). The privilege level determines what the code can do and can access that block memory. This means that some CPU instructions are prohibited from executing certain CPU instructions or access certain memory areas. For example, if a program in a user mode hits any address of any 0x80000000 (ie, half of the 4GB address space) or more, the system will throw an exception and then terminate the operation of the program, and will not give any opportunities.
Figure 4-5. User mode cannot access the address of 0x80000000 or more
Figure 4-5 shows the case when the program tries to read the 0x80000000 address. This strict access restriction is a good thing for the integrity of the system, but it is not good news for debugging tools, because debugging tools need to access all available memory. Fortunately, there is a simple method: use the kernel driver, similar to the system itself, it also runs in high privilege level (ie, Ring 3), so they can perform all CPU instructions to access all memory areas. The trick is to inject a SPY driver into the system, use it to access the required memory and send the read content to its partner, which will wait in user mode. Of course, the kernel driver cannot read the virtual memory address, and the support of the paging mechanism is not available. Therefore, such drivers must be careful before accessing an address to avoid blue screen cranes (Blue Screen of Death, BSOD). The exceptions caused by the driver will stop the entire system with respect to the exception caused by the application (programs that only have problems). Device I / O Control Dispatcher (Device I / O Control Dispatcher)
There is a source code for the Spy Device on this book CD, which is implemented as a kernel driver. You can find its source code in / src / w2k_spy directory. This device is based on the driver skeleton generated by the driving wizard of the third chapter. The interface in the user mode is w2k_spy.sys, w2k_spy.sys uses Win32's device I / O control (IOCTL), and it is briefly talked about IOCTL in Chapter 3. Spy Device Defines a device named / device / w2k_spy and a symbolic link / dosdevices / w2k_spy, defining a symbolic link is to access the device in user mode. Very ridiculous is the name space of the symbolic link is actually / dosdevice, where we are using, is not a DOS device driver. This is like a famous root in history, which is originally called J. After installing the symbolic link, the driver can be opened by any module in user mode, and the method is: Use the Win32 API function createfile (), the path is //w2k_spy. String //.a is a universal escape character, indicating a local device. For example, //. / C: Points C: partitions on the local hard drive. More details of CREATEFILE () can be learned from the SDK documentation.
A portion of the header file of the driver has been given by a list 4-2 to the list 4-5. This file is a header file like a DLL: it is included in the compilation process, the definition required for the module, and also provides sufficient interface information for the client program. DLLs and drivers and client programs include the same header file, but each module takes out the definition required to complete the correct operation. However, the troubles from the header files give the kernel driver far from the DLL, which is due to Microsoft's special development environment provided by Microsoft. Unfortunately, the header in the DDK cannot be compatible with Win32 files in the SDK. At least C engineering, the header file of the two cannot be mixed. Such a result is to fall into a deadlock. In this case, the kernel driver can be accessed, the macro and data type are unused for client programs. Therefore, w2k_spy.c defines a logo constant called _w2k_spy_sys, w2k_spy.h through # iFDef ... .. # else ... .. # ENDIF to check if the constant is present to determine which missing definitions need to be added. This means that all definitions that appear after #ifdef _w2k_spy_sys_ can only be seen by the drive code, and after the #else is dedicated to the client program. All parts other than the conditional statements in W2K_SPY.H are used simultaneously by these two modules. In Chapter 3, when I discuss my driver wizard, I give the guide skeleton generated by the guide, as shown in the list 3-3. The new drive engineering generated by the drive guide begins with the DeviceDispatcher () function. This function accepts a device context pointer and a pointer to the IRP (I / O request package), which will then be dispatched. The wizard's sample code has processed basic I / O requests: IRP_MJ_CREATE, IRP_MJ_MJANUP, and IRP_MJ_CLSE, send these I / O requests to the device when the customer wants to turn off a device. DeviceDispatcher () For these requests, simply returns Status_Success, so the device can be properly opened and closed. For some devices, this action is sufficient, but some devices also need to initialize and clean up code, which are more complicated. For other requests, the driver skeleton in Chapter 3 always returns Status_not_Implement. The first step in extending the skeleton code is to modify the default action to handle more I / O requests. Just like W2K_SPY.sys, one of the main tasks of W2k_SPy.sys: The data that will be sent to the Win32 application through data that cannot be accessed in user mode, so you need to add a function of processing IRP_MJ_DEvice_Control in DeviceDispatcher (). Listing 4-6 gives updated code.
NTSTATUS DeviceDispatcher (PDevice_Context PDeviceContext,
PIRP PIRP)
{
PIO_STACK_LOCATION PISL;
DWORD DINFO = 0;
NTSTATUS NS = status_not_implement;
PISL = IOGETCURRENTIRPSTACKLOCATION (PIRP);
Switch (PISL-> Majorfunction)
{
Case IRP_MJ_CREATE:
Case IRP_MJ_CLEANUP:
Case IRP_MJ_CLOSE:
{
NS = status_success;
Break;
}
Case IRP_MJ_DEVICE_CONTROL:
{
ns = spydispatcher (PDEviceContext,
Pisl-> parameters.deviceioControl.iocontrolcode, Pirp-> Associatedirp.systembuffer,
Pisl-> parameters.deviceioControl.inputBufferLength,
Pirp-> Associatedirp.systembuffer,
PISL-> Parameters.DeviceioControl.OrputbufferLength,
& DINFO);
Break;
}
}
PIRP-> iostatus.status = ns;
PIRP-> iostatus.information = dinfo;
IOCOMPLETEREQUEST (PIRP, IO_NO_INCREMENT);
Return ns;
}
Listing 4-6. Add processing for Dispatcher to add IRP_MJ_DEVICE_CONTROL functions
The IOCTL processing code in Listing 4-6 is very simple, which only calls spydispatcher () and passes an extended IRP structure and the current I / O stack position to SpyDispatcher (). SpyDispatcher () is given in Listing 4-7, which requires the following parameters:
l PDeviceContext A driver's device context pointer. The driver wizard provides the basic Device_Context structure, which contains drivers and device object pointers (see List 3-4). However, the SPY driver adds a pair of private members in this structure.
l DCode specifies the IOCTL encoding to determine the command that the SPY device needs to be executed. An IOCTL encoding is a 32-bit integer that contains 4 positions, as shown in Figure 4-6.
l PINPUT points to an input buffer to provide IOCTL to provide input data.
l DINPUT Enter the size of the buffer.
l Poutput points to the output buffer used to receive IOCTL output data.
l Doutput output buffer size
l PDINFO points to a DWORD variable that saves the number of bytes written in the output buffer.
Figure 4-6. Device I / O Control Coded Structure
Depending on the transmission mode used by the IOCTL used, the input / output buffer passes from the system to the driver in a different manner. The SPY device uses a cached I / O (Buffered I / O), and the system copies the input data to a secure buffer (this buffer is automatically assigned by the system), when returned, the specified number of data from the same The system buffer is copied into the output buffer provided by the caller. Be sure to keep in mind: In this case, the input and output buffer is overlapping, so the processing code of the IOCTL must save all the input data that may be used later before writing any data in the output buffer. The pointer of the system I / O buffer is stored in the SystemBuffer member in the IRP structure (see NTDDK.H). The size of the input / output buffer is saved in a different place, which is part of the IRP parameter member DeviceIoControl, which is INPUTBUFFERLENGTH and OUTPUTBUFFERLENGTH. The DeviceioControl sub-structure also provides IOCTL encoding through its IOCONTROLCODE members. For information about the transmission mode of the IOCTL of Windows NT / 2000, how do they incorporate / out data, refer to I published in Windows Developer's Journal (Schreiber 1997). "A spy filter driver for Windows NT".
NTSTATUS SpyDispatcher (PDevice_Context PDeviceContext,
DWORD DCODE,
PVOID PINPUT,
DWORD DINPUT,
Pvoid Poutput,
DWORD DOUTPUT,
PDWORD PDINFO)
{
SPY_MEMORY_BLOCK SMB;
SPY_PAGE_ENTRY SPE;
SPY_CALL_INPUT SCI;
Physical_address Pa;
DWORD DVALUE, DCOUNT;
Bool Fresh, FPAUSE, FFILTER, FLINE
PVOID PADDRESS;
PBYTE PBNAME;
Handle hobject;
NTSTATUS NS = Status_INVALID_PARAMETER;
Mutex_wait (PDeviceContext-> kmdispatch);
* pdinfo = 0;
Switch (dcode)
{
Case SPY_IO_VERSION_INFO:
{
NS = SPYOUTPUTVERSIONFO (Poutput, Doutput, PDINFO);
Break;
}
Case SPY_IO_OS_INFO:
{
NS = SPYOUTPUTOSINFO (Poutput, Doutput, PDINFO);
Break;
}
Case spy_io_segment:
{
IF ((ns = spyinputdword (& DValue,
PINPUT, DINPUT))
== STATUS_SUCCESS)
{
NS = SPYOUTPUTSEGMENT (DVALUE,
Poutput, doutput, pdinfo;
}
Break;
}
Case SPY_IO_ITERRUPT:
{
IF ((ns = spyinputdword (& DValue,
PINPUT, DINPUT))
== STATUS_SUCCESS)
{
NS = SPYOUTPUTINTERRUPT (DVALUE,
Poutput, doutput, pdinfo;
}
Break;
}
Case SPY_IO_PHYSICAL:
{
IF ((ns = spyinputpointpointer (& paddress,
PINPUT, DINPUT))
== STATUS_SUCCESS)
{
PA = MmgetPhysicalAddRess (Paddress);
NS = SPYOUTPUTBINARY (& pa, physical_address_,
Poutput, doutput, pdinfo;
}
Break;
}
Case SPY_IO_CPU_INFO:
{
NS = SPYOUTPUTCPUINFO (Poutput, Doutput, PDINFO);
Break;
}
Case spy_io_pde_Array:
{
ns = spyoutputbinary (x86_pde_array, spy_pde_array_,
Poutput, doutput, pdinfo;
Break;
}
CASE SPY_IO_PAGE_ENTRY:
{
IF ((ns = spyinputpointpointer (& paddress,
PINPUT, DINPUT) == STATUS_SUCCESS
{
SpyMemoryPagentry (Paddress, & Spe);
NS = SPYOUTPUTBINARY (& spe, spy_page_entry_,
Poutput, doutput, pdinfo;
}
Break;
}
Case SPY_IO_MEMORY_DATA:
{
IF ((ns = spyinputmemory (& sm,
PINPUT, DINPUT))
== STATUS_SUCCESS)
{
NS = SPYOUTPUTMEMORY (& SMB,
Poutput, doutput, pdinfo;
}
Break;
}
Case SPY_IO_MEMORY_BLOCK:
{
IF ((ns = spyinputmemory (& sm,
PINPUT, DINPUT))
== STATUS_SUCCESS)
{
NS = SPYOUTPUTBLOCK (& SMB,
Poutput, doutput, pdinfo;
}
Break;
}
Case SPY_IO_HANDLE_INFO:
{
IF ((ns = spyinputhandle (& hobject,
PINPUT, DINPUT))
== STATUS_SUCCESS)
{
ns = spyoutputhandleinfo (HOBJECT,
Poutput, doutput, pdinfo;
}
Break;
}
Case SPY_IO_HOOK_INFO:
{
NS = SPYOUTPUTHOKINFO (Poutput, Doutput, PDINFO);
Break;
}
Case SPY_IO_HOK_INSTALL:
{
IF (((ns = spyinputbool (& FRESET,
PINPUT, DINPUT))
== STATUS_SUCCESS)
&&&&
((ns = spyhookinstall (fresh, & dcount))
== STATUS_SUCCESS))))))
{
ns = SPYOUTPUTDWORD (DCOUNT,
Poutput, doutput, pdinfo;
}
Break;
}
Case SPY_IO_HOOK_REMOVE:
{
IF (((ns = spyinputbool (& FRESET,
PINPUT, DINPUT))
== STATUS_SUCCESS)
&&&&
((ns = spyhookremove (FRESET, & DCOUNT))
== STATUS_SUCCESS))))))
{
ns = SPYOUTPUTDWORD (DCOUNT,
Poutput, doutput, pdinfo;
}
Break;
}
Case SPY_IO_HOOK_PAUSE:
{
IF ((ns = spyinputbool (& fpause,
PINPUT, DINPUT))
== STATUS_SUCCESS)
{
FPAUSE = SPYHOOKPAUSE (FPAUSE);
NS = SPYOUTPUTBOOL (FPAUSE,
Poutput, doutput, pdinfo;
}
Break;
}
Case SPY_IO_HOOK_FILTER:
{
IF ((NS = SpyinputBool (& Ffilter, Pinput, Dinput))
== STATUS_SUCCESS)
{
Ffilter = spyhookfilter (ffilter);
NS = SPYOUTPUTBOOL (Ffilter,
Poutput, doutput, pdinfo;
}
Break;
}
Case SPY_IO_HOOK_RESET:
{
Spyhookreset ();
NS = status_success;
Break;
}
Case SPY_IO_HOOK_READ:
{
IF ((ns = spyinputbool (& fline,
PINPUT, DINPUT))
== STATUS_SUCCESS)
{
NS = SPYOUTPUTHOKREAD (FLINE,
Poutput, doutput, pdinfo;
}
Break;
}
Case SPY_IO_HOOK_WRITE:
{
SpyhookWrite (pinput, dinput);
NS = status_success;
Break;
}
Case SPY_IO_MODULE_INFO:
{
IF ((ns = spyinputpocointer (& pbname)
PINPUT, DINPUT))
== STATUS_SUCCESS)
{
NS = SPYOUTPUTMODULEINFO (PBNAME,
Poutput, doutput, pdinfo;
}
Break;
}
Case spy_io_pe_header:
{
IF ((ns = spyinputpointpointer (& paddress,
PINPUT, DINPUT))
== STATUS_SUCCESS)
{
NS = SPYOUTPUTPEHEADER (Paddress,
Poutput, doutput, pdinfo;
}
Break;
}
Case SPY_IO_PE_EXPORT:
{
IF ((ns = spyinputpointpointer (& paddress,
PINPUT, DINPUT))
== STATUS_SUCCESS)
{
NS = SPYOUTPUTPEEXPORT (Paddress,
Poutput, doutput, pdinfo;
}
Break;
}
Case SPY_IO_PE_SYMBOL:
{
IF ((ns = spyinputpocointer (& pbname)
PINPUT, DINPUT))
== STATUS_SUCCESS)
{
NS = SPYOUTPUTPESYMBOL (PBNAME,
Poutput, doutput, pdinfo;
}
Break;
}
Case SPY_IO_CALL:
{
IF ((ns = spyinputbinary (& sci, spy_call_input_,
PINPUT, DINPUT))
== STATUS_SUCCESS)
{
NS = SPYOUTPUTCALL (& sci,
Poutput, doutput, pdinfo;
}
Break;
}
}
Mutex_release (PDeviceContext-> kmdispatch);
Return ns;
}
Listing 4-7. SPY Driver Internal Command Dispatcher
#define CTL_CODE (DeviceType, Function, Method, Access) / ((DeviceType) << 16) | (Access << 14) | (Function << 2) (Method))
Listing 4-8. CTL_CODE () macro used to build I / O control encoding
DDK's main header file NTDDK.H and Win32 files in the SDK define a simple but very useful macro ---- CTL_Close (), as shown in Listing 4-8. This macro can be convenient to establish the IOCTL encoding shown in Figure 4-6. The four parts in the encoding are served on the following four purposes:
1. DeviceType This is a 16-bit device type ID. NTDDK.H lists a pair of predefined types, indicated by symbol constant file_device_ *. 0x0000 to 0x7FFF is reserved to Microsoft, and developers can use 0x8000 to 0xFFF. The SPY driver defines its own device ID: file_device_spy, its value is 0x8000.
2. The 2-bit access check value is used to determine the access rights required for IOCTL operations. Possible values are: file_Access (0), file_ve_access (1), file_write_access (2) and the last two combinations: file_read_access | file_write_access (3). See NTDDK.H for details.
3. The ID of 12 bits indicates the selected operation function, and the selected operation will be executed by the device. 0x0000 to 0x7FFF is reserved to Microsoft, and developers can use 0x8000 to 0xFFF. The IOCTL function ID of the SPY device is located at 0x8000 to 0xFFFF.
4. Transmission mode occupies 2 bits, and select one in four available I / O transmission modes, these four modes are: method_buffered (0), method_in_direct (1), method_out_direct (2) and Method_Netther (3), Find these definitions in NTDDK.H. SPY device uses Method_Buffered to all requests, which is a very secure but some slow mode, because data needs to be copied between the client and system buffers. Because Memory SPY is not sensitive to I / O, select security is a good notice. If you want to know the details of other modes, please refer to the article "A Spy Filter Driver for Windows NT" in Windows Developer's Journal (Schreiber 1997).
Table 4-2 lists all IOCTL functions supported by W2k_spy.sys. 0 to 10 Function IDs are the most basic memory detection function, most of the tasks will be used; this chapter will discuss them later. The remaining function IDs are different from 11 to 23, which are different IOCTL groups. In the next chapter, we will discuss them. In the next chapter, we will discuss Native API Hook and invoke the kernel in user mode. Note that some IOCTL encodings require write permissions, represented by Section 15 (see Figure 4-6). Specifically, all IOCTL commands such as 0x80006nnn only read permissions, while the shape of the 0x8000ennn requires read / write permissions. A typical example requiring read privileges is CREATEFILE (), which opens the device by specifying a combination of DWDesiredAccess parameters for generic_read and generic_write.
Table 4-2 The leftmost function name also appears in SpyDispatcher () (see List 4-7) in the huge Switch / CASE statement. These functions first get the Dispatcher Mutex of the device, which ensures that if more than one client or a multi-thread program and device communication, only one request is executed at the same time. Mutex_wait () is the outer macpo (Wrapper Marco) of KewaitFormutexObject (), and KewaitFormuteXObject () requires at least 5 parameters. KewaitFormuteXObject () itself is also a macro, which passes the incoming parameters to KeewaitForsingleObject (). Listing 4-9 gives Mutex_Wait () and its partner mutex_release () and mutex_initialize (). After the MUTEX object becomes a signal (Signaled) state, spydispatcher () turns to different branches based on the received IOCTL, and each branch contains a variety of simple code sequences. Table 4-2. W2k_spy.sys Support IOCTL functions
Function name
Id
IOCTL encoding
Describe
SPY_IO_VERSION_INFO 0 0x80006000 returned Spy version information SPY_IO_OS_INFO 1 0x80006004 return to the operating system version SPY_IO_SEGMENT 2 0x80006008 returns attribute SPY_IO_INTERRUPT a segment 3 0x8000600C returns an interrupt gate attribute SPY_IO_PHYSICAL 4 0x80006010 linear address into a physical address SPY_IO_CPU_INFO 5 0x80006014 returns the special CPU registers value SPY_IO_PDE_ARRAY 6 0x80006018 returns the 0xC0300000 of PDE array SPY_IO_PAGE_ENTRY 7 0x8000601C return the PDE or PTE 10 0x80006028 Find in the handle of a linear address SPY_IO_MEMORY_DATA 8 0x80006020 returned content SPY_IO_MEMORY_BLOCK memory block 9 0x80006024 returns contents of memory block SPY_IO_HANDLE_INFO object properties SPY_IO_HOOK_INFO 11 0x8000602C returns information about the Native API Hook mounting SPY_IO_HOOK_INSTALL 12 0x8000E030 Native API Hook SPY_IO_HOOK_REMOVE 13 0x8000E034 remove a Native API Hook SPY_IO_HOOK_PAUSE 14 0x8000E038 suspend / resume protocol SPY_IO_HOOK_FILTER 15 0x8000E03C Hook enable / disable Hook protocol filters SPY_IO_HOOK_RESET 16 0x8000E040 clear agreement Hook Hook SPY_IO_HOOK_READ 17 0x8000E044 protocol data read from SPY_IO_HOOK_WRITE 18 0x8000E048 SPY_IO_MODULE_INFO 19 0x8000E04C return a loaded module information SPY_IO_PE_HEADER 20 0x8000E050 return IMAGE_NT_HEADERS SPY_IO_PE_EXPORT 21 0x8 data is written to the input protocol Hook 000E054 Returns image_export_directory data SPY_IO_PE_SYMBOL 22 0x8000E058 Return to the address of the export system symbol SPY_IO_CALL 23 0x8000E05C Call a function in the loaded module
#define mutex_initialize (_Mutex) /
KeinitializationMutex / (& (_ mutex), 0)
#define mutex_wait (_Mutex) /
KewaitFormutexObject /
(& (_ Mutex), Executive, KernelMode, False, NULL
#define mutex_release (_mutex) /
KeeleleaseMutex /
(& (_ MUTEX), FALSE
Listing 4-9. Manage Kernel-Mutex macro
SpyDispatcher () uses a pair of help functions to read input parameters to obtain the requested data and write the generated data to the output buffer provided by the caller. Just as mentioned earlier, the driver of the kernel mode is always excessively discerning the parameters from the user mode it accepts. From the point of view of the driver, the code in all user mode is harmful, and they don't know anything in addition to making the system crashes. How many doubts of doubts are not ridiculous ---- only small ratios will cause the entire system to terminate immediately, and there is a blue screen. So if a client program says: "This is my buffer ----- it can accommodate 4,096 bytes," driver does not accept this buffer - even if the buffer points to valid Memory, and its size is also correct. Under the buffered I / O mode of IOCTL (Buffered I / O) (if the IOCTL encoded mode portion is Method_Buffered), the system is carefully checked and assigned a buffer enough to accommodate all input / output data. However, other I / O transmission patterns, especially Method_Netther, and drivers accept the buffer pointer of the original user mode.
……………..to be continued……………