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.
Although the SPY device uses buffered I / O, it will still check the validity of the input / output buffer. Because the data incorporated by the client program may not contain output data than the less or provided buffer than the required buffer. The system cannot capture these language errors because it does not know the type of data transmitted in the ioctL transmission. Therefore, SpyDispatcher () calls help functions SpyInput * () and spyoutput * () to copy or write data from the I / O buffer. These functions are only performed when the buffer size is matched to the needs of the operation. Listing 4-10 gives basic input functions, and list 4-11 gives basic output functions. Spyinputbinary () and spyoutputbinary () are widely used, and they test the size of the buffer, if OK, use the Windows 2000 runtime library function RTLCopyMemory () to copy the requested data. The remaining functions are just a simple outsourcing of the two basic functions, used to operate common data types DWORD, BOOL, PVOID, and HANDLE, etc. SpyoutPutBlock () replicates the data block specified by the caller in the SPY_MEMORY_BLOCK structure, of course, this requires first, first verification request ranges are readable. If the size of the incoming input buffer is incorrect, the spyinput * () function will return Status_INVALID_Buffer_size, and if the output buffer is smaller than the required, the spyoutput * () function will return Status_buffer_too_small.
NTSTATUS SPYINPUTBINARY (PVOID PDATA,
DWORD DDATA,
PVOID PINPUT,
DWORD DINPUT)
{
NTSTATUS NS = Status_INVALID_BUFFER_SIZE;
IF (DDATA <= DINPUT)
{
RTLCopyMemory (PDATA, PINPUT, DDATA);
NS = status_success;
}
Return ns;
}
/ / -------------------------------------------------------------------------------------------- -----------------
NTSTATUS SPYINPUTDWORD (PDWORD PDVALUE,
PVOID PINPUT,
DWORD DINPUT)
{
Return SpyinputBinary (PdValue, DWORD_, PINPUT, DINPUT);
}
/ / -------------------------------------------------------------------------------------------- -----------------
NTSTATUS SpyinputBool (Pbool PfValue,
PVOID PINPUT,
DWORD DINPUT)
{
Return SpyinputBinary (PfValue, Bool_, Pinput, DINPUT);
}
/ / -------------------------------------------------------------------------------------------- -----------------
NTSTATUS SPYINPUTPOINTER (PPVoid PPAddress,
PVOID PINPUT,
DWORD DINPUT)
{
Return SpyinputBinary (PPADDRESS, PVOID_, PINPUT, DINPUT);
}
/ / -------------------------------------------------------------------------------------------- ---------------- NTSTATUS SPYINPUTHANDLE (Phandle PhoBject,
PVOID PINPUT,
DWORD DINPUT)
{
Return Spyinputbinary (PhoBject, Handle_, Pinput, DINPUT);
}
Listing 4-10. Read input data from the IOCTL buffer
NTSTATUS SPYOUTPUTBINARY (PVOID PDATA,
DWORD DDATA,
Pvoid Poutput,
DWORD DOUTPUT,
PDWORD PDINFO)
{
NTSTATUS NS = status_buffer_too_small;
* pdinfo = 0;
IF (DDATA <= DOUTPUT)
{
RTLCopyMemory (Poutput, PDATA, * PDINFO = DDATA);
NS = status_success;
}
Return ns;
}
/ / -------------------------------------------------------------------------------------------- -----------------
NTSTATUS SPYOUTPUTBLOCK (PSPY_MEMORY_BLOCK PSMB,
Pvoid Poutput,
DWORD DOUTPUT,
PDWORD PDINFO)
{
NTSTATUS NS = Status_INVALID_PARAMETER;
IF (SpyMemorytestBlock (psmb-> paddress, psmb-> dbytes))
{
NS = SPYOUTPUTBINARY (PSMB-> Paddress, PSMB-> DBYTES,
Poutput, doutput, pdinfo;
}
Return ns;
}
/ / -------------------------------------------------------------------------------------------- -----------------
NTSTATUS SPYOUTPUTDWORD (DWORD DVALUE,
Pvoid Poutput,
DWORD DOUTPUT,
PDWORD PDINFO)
{
Return SPYOUTPUTBINARY (& DVALUE, DWORD_,
Poutput, doutput, pdinfo;
}
/ / -------------------------------------------------------------------------------------------- -----------------
NTSTATUS SPYOUTPUTBOOL (BOOL FVALUE,
Pvoid Poutput,
DWORD DOUTPUT,
PDWORD PDINFO)
{
Return Spyoutputbinary (& Fvalue, Bool_,
Poutput, doutput, pdinfo;
}
/ / -------------------------------------------------------------------------------------------- -----------------
NTSTATUS SPYOUTPUTPOINTER (PVOID PVALUE,
Pvoid Poutput,
DWORD DOUTPUT,
PDWORD PDINFO)
{
Return SPYOUTPUTBINARY (& PVALUE, PVOID_,
Poutput, doutput, pdinfo;
}
Listing 4-11. Write data to IOCTL's buffer
You may notice that SpyDispatcher () in Listing 4-7 also references other SpyInput * () and spyoutput * () functions. Although these functions are ultimately call spyinputbinary () and spyoutputbinary (), they are still more complicated than the basic functions in the list 4-10 and 4-11, so we are discussing them later. Now let's start from spydispatcher (), step by step to analyze its Switch / Case statement. IOCTL function spy_io_version_info
The spy_io_version_info function of IOCTL uses the spy_version_info structure provided by SPY driver itself. This feature does not require input parameters, you need to use the spyoutputVersionInfo () help function. Listing 4-12 gives this function and spy_version_info structure, which is simple, which sets the DVersion member to the SPY_VERSION constant (currently 100, represents V1.00), which is defined in W2k_SPy.h. Then copy the symbolized name of the driver, that is, the string constant DRV_NAME ("SBS Windows 2000 Spy Device" to the AWNAME member. The main version number is obtained by the entire DVersion, and the remaining is the second version number.
Typedef struct _sspy_version_info
{
DWORD DVERSION;
Word frs [spy_name];
}
SPY_VERSION_INFO, * pspy_version_info, ** ppspy_version_info;
#DEFINE SPY_VERSION_INFO_SEOF (SPY_VERSION_INFO)
NTSTATUS SPYOUTPUTVERSIONFO (PVOID POUTPUT,
DWORD DOUTPUT,
PDWORD PDINFO)
{
SPY_VERSION_INFO SVI;
Svi.dversion = SPY_VERSION;
WCSCPYN (SVI.AWNAME, USTRING (CSTRING (DRV_NAME)), SPY_NAME);
Return SPYOUTPUTBINARY (& SVI, SPY_VERSION_INFO_,
Poutput, doutput, pdinfo;
}
Listing 4-12. Get version information of the SPY driver
IOCTL function spy_io_os_info
This function is more interesting than the previous. It is another function that only outputs, does not require input parameters, and uses the internal parameters of several operating systems to fill the SPY_OS_INFO structure provided by the adjuster. Listing 4-13 lists the definition of this structure, and the spyoutputosinfo () help function called by Dispatcher. Some structural members are simply set to define constants defined in the DDK header file and W2K_SPY.H; others will be set to the current value read from several internal kernel variables and structures. In the second chapter, you have learned the variable NTBuildNumber and NTGLOBALFLAG (exported by ntoskrnl.exe, see Table B-1 in Appendix B). Unlike other NT * symbols, these two symbols do not point to the API function, but point to variables in the .data section located in the kernel. In Win32 world, export variables are very rare. However, several kernel modules of Windows 2000 use this technology. Ntoskrnl.exe exports at least 55 variables, NTDLL.DLL provides 4, and Hal.dll provides one. Spyoutputosinfo () Copys MmHighestUseraddress, MmHiGheSTARTUSERADDRESS, MMUSERPROBEADDRESS, MMSystemRangestart, NTGLOBALFLAG, KEI386MACHINEPE, KEI386MACHINEPE, KENUMBERPROCHINEPE, NTBUILDNUMBER to the output buffer. When a module imports data from another module, it needs to use the extern keyword to notify the compiler and linker. This will cause the linker to generate an entry to the module export section and resolve the symbol name to determine its address. Some Extern declarations are already included in NTDDK.H. Listing 4-13 gives missing extern declarations.
Extern PWord NLSansicodePage;
Extern PWord NLsoemcodePage;
Extern PWord NTBuildNumber;
Extern Pdword NTGLOBALFLAG;
Extern PDWORD KEI386MACHINEPE;
Typedef struct _sspy_OS_INFO
{
DWORD DPAGESIZE;
DWORD DPAGESHIFT;
DWORD DPTISHIFT;
DWORD DPDISHIFT;
DWORD DPAGEMASK;
DWORD DPTIMASK;
DWORD DPDIMASK;
PX86_PE PTEARRAY;
PX86_PE PDEARRAY;
PVOID PLOWESTUSERADDRESS;
Pvoid pthreadenvironmentblock;
Pvoid PHIGHESTUSERADDRESS;
PVOID PUSERPROBEADDRESS;
Pvoid psystemrangestart;
Pvoid PlowestSystemAddress;
Pvoid pshaeduserdata;
PVOID PPROCESSORCONTROLREGION;
Pvoid PPRocessorControlblock;
DWORD DGLOBALFLAG;
DWORD DI386MACHINETYPE;
DWORD DNUMBERPROCESSORS;
DWORD DPRODTTYPE;
DWORD DBUILDNUMBER;
DWORD DNTMAJORVERSON;
DWORD DNTMINORVERSION;
Word AwntsystemRoot [max_path];
SPY_OS_INFO, * PSPY_OS_INFO, ** PPSPY_OS_INFO;
#define spy_OS_INFO_ SIZEOF (SPY_OS_INFO)
NTSTATUS SPYOUTPUTOSINFO (PVOID POUTPUT,
DWORD DOUTPUT,
PDWORD PDINFO)
{
Spy_segment ss;
SPY_OS_INFO SOI;
NT_PRODUCT_TYPE NTPRODUCTTYPE;
PKPCR PKPCR;
NTProductType = (SharedUserData-> ProductTypeisvalid
? SharedUserData-> NTPRoductType
: 0);
Spysegment (x86_segment_fs, 0, & ss);
PKPCR = SS.PBASE;
SOI.DPAGESIZE = Page_Size;
SOI.DPAGESHIFT = Page_shift;
SOI.dptishift = pti_shift;
SOI.dpdishift = PDI_SHIFT;
SOI.DPAGEMASK = X86_PAGE_MASK;
SOI.DPTIMASK = x86_pti_mask;
SOI.DPDIMASK = x86_pdi_mask;
SOI.PTEARRAY = x86_pte_Array;
SOI.PDEARRAY = x86_pde_array;
SOI.PlowESTUSERADDRESS = mm_lowest_user_address;
SOI.PTHREADENVIRONMENTBLOCK = PKPCR-> NTTIB.SELF;
SOI.PHIGHESTUSERADDRESS = * mmhighestuseraddress;
SOI.PUSERPROBEADDRESS = (pvoid) * mmuserProbeAddress;
SOI.PSystemRangestart = * mmsystemRangestart;
SOI.PlowestSystemAddress = mm_lowest_system_address;
SOI.PSharedUserdata = shareduserdata;
SOI.pprocessorControlRegion = PKPCR;
SOI.pprocessorControlblock = PKPCR-> PRCB;
SOI.DGLOBALFLAG = * NTGLOBALFLAG;
SOI.DI386MACHINETYPE = * Kei386MachineType;
SOI.DNUMBERPROCESSORS = * KenumberProcessors;
Soi.dproductType = NTPRODUCTTYPE;
SOI.DBUILDNUMBER = * NTBUILDNUMBER;
SOI.dntmajorversion = shareduserdata-> ntmajorversion; soi.dntminorversion = shareduserdata-> ntminorversion;
WCSCPYN (Soi.awntsystemRoot, SharedUserData-> NTSystemRoot,
MAX_PATH);
Return SPYOUTPUTBINARY (& SOI, SPY_OS_INFO_,
Poutput, doutput, pdinfo;
}
Listing 4-13. Get information about the operating system
The remaining members of the SPY_OS_INFO structure are populated by the system data structure located in the memory. For example, SPYOUTPUTOSINFO () assigns the base address of the kernel's Processor Control Region, KPCR to the PPRocessorControlRegion member. KPCR is a very important data structure that includes many thread-related data items, so it is located in your own memory segment, which is given by the CPU's FS register. Both Windows NT 4.0 and Windows 2000 directs FS to linear address 0xffdff000 in kernel mode. SpyoutputosInfo () calls the spysegment () function (discussed later) to query the base address in the linear address space. This segment also includes the kernel's processor control block, kprcb, the KPCR structure PRCB member points to the first address of the KPRCB structure, followed by a Context structure, which contains the underlying CPU information of the current thread . KPCR, KPRCB, and Context structures are defined in NTDDK.h header files.
The other internal data structure referenced in Listing 4-13 is SharedUserData. This structure is actually a structural pointer obtained by a "well-known address" through type conversion. Listing 4-14 gives it definitions in NTDDK.H. The "well-known address" is located in the linear address space, which is set when compiled, so there is no need to spend additional time or configure it. Obviously, SharedUserData is a pointer to the kuser_shared_data structure, the base address of the structure at 0xFFDF0000 (this is a linear address). This memory area is shared by the application of the system and user mode, which contains data like the operating system version number, spyoutputosinfo () copies the version data to the DNTMAJORVERSION and DNTMINORVERSION members provided by the caller). Just like I have to show later, the kuser_shared_data structure will be mapped to 0x7ffe0000, so that the user mode's code can access it.
An example program will also be provided after the explanation of the IOCTL function of the SPY device, which will display the returned data on the screen.
#define ki_user_shared_data 0xffdf0000
#define shareduserdata ((kuser_shared_data * const) ki_user_shared_data)
Listing 4-14. SharedUserData structure definition
IOCTL function spy_io_segment
Go now to become more interesting. The SPY_IO_SEGMENT function queries the properties of the specified segment by some more underlying operations, and the caller needs to give a selector. SpyDispatcher () first calls SpyInputDWord () to get the value of the selector incorporated by the call program. You may still remember the selector (selector) is a 16-bit number. However, as long as it may, I will try to avoid the use of 16-bit data types because the native Word is 32-bit DWORD types in the 32-bit mode of the I386 CPU. Therefore, I extend the selector parameters to DWORD, but the height of 16 is always 0. If the spyinputdword () report is successful, the spyoutputsegemnt () function is called next (this function is given). No matter how the spysegment () help function, spyoutputsegemnt () always returns to the caller. Basically, spysegment () will populate the Spy_SEGMENT structure, which is defined at the top of the list 4-15. It gives the value of the selector in the form of x86_selector structure (see List 4-2), followed by 64-bit x86_descriptor, and corresponding segment base, segment size limit and a sign named fok, This flag is used to point out if the spy_segment structure is valid. In some of the later functions, you need to return multiple segments attributes once, using the FOK member, the caller can filter out the invalid segment information from the output data. Typedef struct _sspy_segment
{
X86_Selector Selector;
X86_Descriptor descriptor;
PVOID PBASE;
DWORD DLIMIT;
BOOL FOK;
}
Spy_segment, * pspy_segment, ** ppspy_segment;
#define spy_segment_ sizeof (spy_segment)
NTSTATUS SPYOUTSEGMENT (DWORD DSELector,
Pvoid Poutput,
DWORD DOUTPUT,
PDWORD PDINFO)
{
Spy_segment ss;
Spysegment (x86_segment_other, dselector, & ss);
Return SPYOUTPUTBINARY (& SS, SPY_SEGMENT_,
Poutput, doutput, pdinfo;
}
Bool spysegment (DWORD DSEGMENT,
DWORD DSELECTOR,
PSPY_SEGMENT PSEGMENT)
{
BOOL fok = false;
IF (psegment! = null)
{
Fok = true;
IF (! spyselector (dsegment, dselector,
& pSegment-> selector))
{
Fok = false;
}
IF (! Spydescriptor (& PSEGMENT-> Selector,
& pSEGMENT-> Descriptor)))
{
Fok = false;
}
PSEGMENT-> PBASE =
SpyDescriptorBase (& PSEGMENT-> Descriptor);
PSEGMENT-> DLIMIT =
SpydescriptorLimit (& PSEGMENT-> Descriptor); psegment-> fok = fok;
}
Return fok;
}
Listing 4-15. Properties of the query segment
The spysegment () function rely on several other help functions to build some parts of the spy_segment structure. First, spyselector () copies the value of a selector to the incoming x86_selector structure. If the first parameter DSEGMENT of the spyselector () function is set to x86_segment_other (ie 0), the DSELECTOR parameter will assume a valid selector value, so the value will be simply attached to the Wvalue member of the output structure x86_selector. Otherwise, DSELECTOR will be ignored, and DSEGMENT will be used in a Switch / Case structure to select a segment register or task register TR. Note that this request requires a small amount of embedded assembly, and the C language does not provide a standard method access to the processor related characteristics, such as segment registers.
#define x86_segment_other 0
#define x86_segment_cs 1
#define x86_segment_ds 2
#define x86_segment_es 3
#define x86_segment_fs 4
#define x86_segment_gs 5
#define x86_segment_ss 6
#define x86_segment_tss 7
/ / -------------------------------------------------------------------------------------------- ---------------
Bool spyselector (DWORD DSEGMENT,
DWORD DSELECTOR,
PX86_SELECTOR PSELECTOR)
{
X86_Selector Selector = {0, 0};
BOOL fok = false;
IF (pselector! = null)
{
Fok = true;
Switch (DSEGMENT)
{
Case x86_segment_other:
{
IF (fok = (DSELECTOR >> X86_Selector_shift)
<= X86_selector_limit))
{
Selector.wvalue = (word) DSELector;
}
Break;
}
Case x86_segment_cs:
{
__ASM MOV Selector.wvalue, CS
Break;
}
Case x86_segment_ds:
{
__ASM MOV Selector.wvalue, DS
Break;
}
Case x86_segment_es:
{
__ASM MOV Selector.wvalue, ES
Break;
}
Case x86_segment_fs:
{
__ASM MOV Selector.wvalue, FS
Break;
}
Case x86_segment_gs:
{
__ASM MOV Selector.wvalue, GS
Break;
}
Case x86_segment_ss:
{
__ASM MOV Selector.wvalue, SS
Break;
}
Case x86_segment_tss:
{
__ASM Str Selector.wValuebreak;
}
DEFAULT:
{
Fok = false;
Break;
}
}
RTLCopyMemory (pselector, & selector, x86_selector_);
}
Return fok;
}
Listing 4-16. Get the value of the selector (Selector)
SpyDispatcher () reads data from a 64-bit descriptor, and the segment selector points to this descriptor (see List 4-17). As you remember, all selectors contain a table indicator (Ti) bit to determine the descriptor referenced by the selector is located in GDT (Ti = 0) or LDT (Ti = 1). The upper part of the list 4-17 is processed by the LDT. First, use assembly instructions SLDT and SGDT to read the value of the LDT selector and the size limit of the segment and the base address of the GDT. I still remember that the linear foundation site of GDT is specified, while LDT is indirectly referenced by selector in GDT? So, SpyDispatcher () will first verify the value of the LDT selector. If the segment selector is not empty and does not exceed the GDT limit, spydescriptorType (), SpyDescriptorLimit (), and SpyDescriptorBase () (list 4-17 give these functions) to get the basic properties of the LDT:
l SpyDescriptORTYPE () Returns the type data of the descriptor and its S bit field (see List 4-2). The LDT selector must point to a system descriptor that type x86_descriptor_sys_ldt.
l SpyDescriptorLimit () The size limit of the sum segment from the Limit1, LIMIT2 of the descriptor. The method of processing is different depending on the memory allocation granularity specified by the M flag of the descriptor.
l SpyDescriptorBase () is just a simple base1, base2, and base3 bits through the appropriate organizational descriptor to get a 32-bit linear address.
Bool SpyDescriptor (PX86_Selector Pselector,
PX86_DEScriptor PDEScriptor)
{
X86_Selector LDT;
X86_Table GDT;
DWORD DTYPE, DLIMIT;
Bool fsystem;
PX86_DESCRIPTOR PDEScriptors = NULL;
BOOL fok = false;
IF (pdescriptor! = null)
{
IF (pselector! = null)
{
IF (pselector-> ti) // ldt descriptor
{
__ASM
{
SLDT LDT.WVALUE
SGDT GDT.WLIMIT
}
IF ((! ldt.ti) && ldt.index &&
((ldt.wvalue & x86_selector_index)
<= gdt.wlimit)))
{
DTYPE = SpyDescriptype (gdt.pdescriptors
LDT.Index,
& fsystem);
DLIMIT = SpyDescriptorLimit (gdt.pdescriptors
ldt.index);
IF (fsystem && (dtype == x86_descriptor_sys_ldt) &&
((DWORD) (pselector-> wvalue
& X86_selector_index)
<= DLIMIT))
{
PDEScriptors =
SpyDescriptorBase (GDT.PDescriptors
ldt.index);
}
}
}
Else // GDT Descriptor
{
IF (pselector-> index)
{
__ASM
{
SGDT GDT.WLIMIT
}
IF ((pselector-> wvalue & x86_selector_index)
<= gdt.wlimit)
{
PDEScriptors = gdt.pdescriptors;
}
}
}
}
IF (pdescriptors! = null)
{
RTLCopyMemory (pdescriptor,
PDEScriptors Pselector-> Index,
X86_Descriptor_);
Fok = true;
}
Else
{
RTLZEROMEMORY (PDEScriptor,
X86_Descriptor_);
}
}
Return fok;
}
/ / -------------------------------------------------------------------------------------------- -----------------
Pvoid SpyDescriptorBase (PX86_DEScriptor PDEScriptor)
{
Return (PVOID) (PDEScriptor-> Base1) |
(pdescriptor-> base2 << 16) |
(pdescriptor-> base3 << 24));
}
/ / -------------------------------------------------------------------------------------------- -----------------
DWORD SpyDescriptorLimit (PX86_DEScriptor PDEScriptor)
{
Return (pdescriptor-> g? (pdescriptor-> limited1 << 12) |
(pdescriptor-> limited2 << 28) | 0xFFF
: (PDEScriptor-> limit1) |
(pdescriptor-> limited2 << 16));
}
/ / -------------------------------------------------------------------------------------------- -----------------
DWORD SpyDescriptORTYPE (PX86_Descriptor PDEScriptor,
Pbool pfsystem)
{
IF (pfsystem! = null) * pfsystem =! pdescriptor-> s;
Return PDEScriptor-> type;
}
Listing 4-17. Get the value of the descriptor
If the TI bit of the selector specifies a GDT descriptor, things are simple. Use the SGDT instruction again to remove the position and size of GDT in linear memory, and if the descriptor specified by the selector is being directed to the appropriate range, the PDEScriptors variable will be set to the base address of the GDT. For LDT and GDT, PDScriptors variables will not be empty. If the selector incorporated by the caller is valid, the 64-bit descriptor value will be copied to the X86_Descriptor structure provided by the caller. Otherwise, all members of this structure will be set to 0 by RTLzeromeMory (). We still discuss the spysegment () function in the list 4-15. Spyselector () and spydescriptor () calls have been explained. There is only the last SpyDescriptorBase () and spydescriptorlimit () call, but you should already know what these functions have been made (see List 4-17). If spyselector () and spydescriptor () success, the returned Spy_SEGMENT structure will be valid. SpyDescriptorBase () and SpyDescriptorLimit () will not return an error flag. Because they cannot fail, if the descriptor provided is invalid, it will only return the wrong data.
……………..to be continued……………