Chapter 1 Windows 2000 support for debugging technology
Translation: kendiv (fcczj@263.net)
Update:
Monday, January 17, 2005
Disclaimer: Please indicate the source and guarantee the integrity of the article, and all rights to the translation.
Enumerate system modules and drivers (drivers)
PSAPI.dll can return the kernel module in the current memory. This is a very simple job. PSAPI.dll's EnumDevicedrivers () function accepts an array of PVOID types that populate this array with Image Base Address's image base (Image base address) of the current active kernel drive module, including basic kernel. Module NTDLL.DLL, NTOSKRNL.EXE, WIN32K.SYS, HAL.DLL and BOOTVID.DLL. The return value is the virtual memory address (translation, also known as a linear address). If you use the kernel debugger or other debug tool to check these addresses, you will clearly recognize the famous DOS Stub program, which starts with the famous Mark Zbikowski's first letters "MZ", which contains A text message - "this Program Cannot Be Run in dos Mode" or something similar. Listing 1-3 shows a simple function using EnumDevicedRivers (), as well as the prototype of the EnumDevicedRivers function.
Bool WinApi Enumdevicedrivers (PVOID * LPIMAGEBASE,
DWORD CB,
PDWORD LPCBNEEDED);
PPVOID WINAPI DBGDRIVERADDRESSES (PDWORD PDCOUNT)
{
DWORD DSIZE;
DWORD DCOUNT = 0;
PPVOID PPLIST = NULL;
DSIZE = SIZE_MINIMUM * SIZEOF (PVOID);
While (PPList = DBGMEMORYCREATE (DSize))! = NULL)
{
IF (ENUMDEVICEDRIVERS (PPLIST, DSIZE, & DCOUNT) && (DCOUNT { DCOUNT / = SizeOf (PVOID); Break; } DCOUNT = 0; PPList = DBGMEMORYDESTROY (PPLIST); IF ((DSIZE << = 1)> (SIZE_MAXIMUM * SIZEOF (PVOID))))) { Break; } } IF (pdcount! = NULL) { * pdcount = dcount; } Return PPLIST; } Listing 1-3 Enumerate System Module Address Enumdevicedrivers () expect three parameters: A array pointer, a value indicating the input size and a variable used to output the type DWORD. The second parameter specifies the number of bytes of incoming arrays, and the third parameter represents the number of bytes copied into the array. Therefore, you must divide the return value in sizeof (pvoid) to determine how many address data is copied to the array. Unfortunately, this function cannot help you determine how much array is available, although it actually knows how many Driver is running. But it just tells you how many bytes have returned, and if the array is too small, it hides the number of bytes. Therefore, you have to use boring Trial-And-Error loops to determine the appropriate array size, as shown in the list 1-3, as long as the return value is the same as the array size, it is assumed that the data is not copied to an array. At the beginning, a reasonable minimum value - 256 (represented by size_minimum) is used, which is usually large enough, but if not enough, when the new loop is starting, the array size will increase to the original 2 Better until all the pointers or array sizes exceed 65, 536. The memory buffer used by the array is provided by two helper functions dbgmemorycreate () and dbgmemoryDestroy (), which is just the Win32 function Localalloc and LocalFree outsourcing, which is not listed here. Bool WinApi Enumdevicedrivers (PVOID * LPIMAGEBASE, DWORD CB, DWORD * LPCBNEEDED) { System_Module_information SMI; Psystem_module_information psmi; DWORD DSIZE, I; NTSTATUS NS; BOOL fok = false; NS = NTQuerySystemInformation (SystemModuleInformation, & SMI, SIZEOF (SMI), NULL; IF (status_success == ns) | (STATUS_INFO_LENGTH_MISMATCH == NS)) { DSIZE = SIZEOF (System_Module_Information) (Smi.dcount * sizeof (system_module)); IF ((pSMI = LOCALALLOC (LEME_FIXED, DSIZE))! = null) { NS = NTQuerySystemInformation (SystemModuleInformation, PSMI, DSize, NULL); IF (ns == status_success) { For (i = 0; (i LPIMAGEBASE [I] = psmi-> amodules [i] .pimagebase; * lpcbneeded = i * sizeof (dword); Fok = true; } LocalFree (PSMI); IF (! fok) SetLastError (RTLNTSTATUSERROR (NS)); } } Else SetLastError (RTLNTSTATUSERROR (NS)); Return fok; } Listing 1-4 List 1-4 of the EnumDevicedrivers function lists enumDevicedRivers () a possible implementation method. Note that this is not the original code from PSAPI.dll. However, it can become equivalent binary code through the C compiler. In order to maintain simple and clean, I omitted the details of the dispersive attention in the source code, such as structured abnormalities. In the middle of the list 1-4, you will see a lot of work in the ntquerysysteminformation () function. This is one of the Windows 2000 functions I like, because the function can access a variety of important data structures such as drivers, processes, threads, handle (Handle), and LPC port lists, and more. My article "Inside Windows NT Sytem Data" DR.Dobb's Journal in November 1999 provides documentation information about the internal information of this function and its partner function ntsetsysteminformation (). In addition, the documentation of these two functions can be found in the "Indispendsable Windows NT / 2000 Native API Reference" of Gary Nebbett. Don't worry too much about the implementation details of the enumdevicedrivers () function listed in List 1-4. I added these code snippets just to explain the fun of this function, this is like a red line runs through PSAPI.dll. After using the SystemModuleInformation flag second call NtQuerySystemInformation () After obtaining a complete driver list, the code traverses the drive module array and copy its PIMAGEBASE member to the array of pointer provided by the caller (named LPImageBase []). This seems very correct, but unless you don't know other information contained in the module array provided by NTQuerySystemInformation. These data structures are no documentation, but I can now tell you that this information is also the size of the module in memory, their path and name, reference count, and other flag information. Even the offset of the file name is also easy to get! ENUMDEVICEDRIVERS () cruelly throws all of these useful information, only the image base address is retained (Image Base Address). So if you try to get more information about the module by returning pointer, you will definitely fail. When you call getDevicedriverfilename () to get the file path corresponding to the specified image base address, how will psapi.dll do? It runs a code similar to the list 1-4 to obtain a full driver list and traversed the list to find the specified image base. If it finds a match, copy its path to the caller's buffer. Is this very efficient? Why is ENUMDEVICEDRIVERS to copy the path when it first traverses the drive list? Implement this function in this way is not much. Remove performance problems, this design has another potential question: What is the uninstalled problem if the module specified before getDevicedriverfilename () is executed? The address of the module will not appear in the second acquisition drive list, getDevicedriverfilename () will fail. I really don't understand why Microsoft will release such a DLL. Enumeration activity process Another typical job of PSAPI.dll is the process that enumerates running in the current system. For this purpose, the DLL provides the EnumProcesses () function. The function of this function is similar to enumdevicedrivers (), but the return is the process ID instead of the virtual address. Tip again, the function does not prompt the buffer size, so we need to use the trial-and-error loop again, as shown in the list 1-5, these codes and lists 1-3 are similar, except for some different symbols And type names. A process ID is a global digital tab that can only identify a process throughout the system. The processes and threads are taken from the same digital pool, from the idle process starting with 0, at the same time, all running processes and threads will have the same ID. However, when a process is over, another process may use this end process or thread again. Therefore, one process ID acquired at x Time may represent another completely different process. It is also possible that it is not defined or specified to give a thread at that moment. So, EnumProcesses () returns a simple process ID list that cannot be reliably representing the snapshot of the current system activity process. This design defect is really unforgettable if it considers the implementation of the function. Listing 1-6 is the clone of the other function of PSAPI.dll, which is generally outlined. Similar to EnumDevicedrivers (), it also rely on the ntquerysysteminformation () function, but when calling, use SystemProcessInformation instead of SystemModuleInformation. Note that the loop in the list 1-6, where the LPIDPROCESS [] array is populated by data from the system_process_information structure. Nothing is so surprised, this structure has no documentation. Bool WinApi Enumprocesses (DWORD * LPIDPROCESS, DWORD CB, DWORD * LPCBNEDED); PDWORD WINAPI DBGPROCESSIDS (PDWORD PDCOUNT) { DWORD DSIZE; DWORD DCOUNT = 0; PDWORD PDLIST = NULL; DSIZE = Size_MINIMUM * SIZEOF (DWORD); While (PDList = DBGMEMORYCREATE (DSize))! = null) { IF (pdlist, dsize, & dcount) && (dcount { DCOUNT / = SizeOf (DWORD); Break; } DCOUNT = 0; PDList = DBGMEMORYDESTROY (PDLIST); IF ((DSIZE << = 1)> (SIZE_MXAIMUM * SIZEOF (DWORD))) BREAK; } IF (pdcount! = null) * pdcount = dcount; Return PDLIST; } Listing 1-5 Enumeration Process ID After reading EnumDevicedrivers (), how to waste data returned from ntQuerySysteminformation (), unfortunately, EnumProcesses is also a similar function, but in fact, this function is worse! Because the available process information is much more than the information of the drive module, the process data also contains many details on each thread in the system. When I wrote this text, my system is running 37 processes, call NTQuerySystemInformation () produced a 24,488 byte of data block! And when enumprocesses () is processed, only 148 bytes remain, these are just enough to store 37 process IDs. Although enumdevicedirvers () made me sad, Enumprocesses () really hurt my heart. If you need the use of an unnamed API function, the two functions are the best evidence. If the actual work is only one step, it can be done, and why do you want to use such inefficient functions? Why not call the NTQuerySystemInformation () function free to get the system information of interested information? Many system management tools provided by Microsoft rely on NTQuerySystemInformation () instead of psapi.dll, so why settle for less? Bool WinApi Enumprocesses (PDWord LPIDPROCESS, DWORD CB, PDWORD LPCBNEEDED) { Psystem_process_information PSPI, PSPINEXT; DWORD DSIZE, I; NTSTATUS NS; BOOL fok = false; // 0x8000 = 32KB For (DSize = 0x8000; ((pspi = localloc (LMEM_FIXED, DSIZE))! = null); Dsize = 0x8000) { NS = NTQuerySystemInformation (SystemProcessInformation, PSPI, DSize, NULL); IF (status_success == ns) { PSPINEXT = PSPI; For (i = 0; i { LPIDPROCESS [I] = PSPINEXT-> DuniqueProcessID; PSPINEXT = (psystem_process_information) (BYTE) PSPinext PSPINEXT-> DNEXT); } * lpcbneeded = i * sizeof (dword); Fok = true; } LocalFree (PSPI); IF (fok || (ns! = status_info_length_mismatch)) { IF (! fok) SetLastError (RTLNTSTATUSERROR (NS)); Break; } Return fok; } Listing 1-6 Example of Enumprocesses () Functions Implementation Enumeration process module One, you find the process ID you are interested in the process list returned from Enumprocess (), you may want to know which modules have been loaded in the virtual address space of this process. PSAPI.dll provides another API function to complete this feature called EnumProcessModules (). Unlike EnumDeviceDrivers () and Enumprocesses (), this function requires four parameters (see List 1-7). Unlike the first two returns a function of the global list of global lists, EnumprocessModules () only retrieves the list of specified processes, so the increasing parameter is uniquely representing a process. However, this function requires a process handle (HANDLE) instead of the process ID. In order to obtain its handle (Handle) through the process ID, the openprocess () function must be called. Bool WinApi EnumprocessModule (HNADLE HPROCESS, HModule * lphmodule, DWORD CB, DWORD * LPCBNEDED); PHModule WinAPI DBGPROCESSMODULES (Handle Hprocess) (PDWORD PDCOUNT) { DWORD DSIZE; DWORD DCOUNT = 0; Phmodule phlist = null; IF (hprocess! = null) { DSIZE = Size_MINIMUM * SIZEOF (HMODULE); While ((pHlist = dbgmemorycreate (dsize))! = null) { EnumProcessModules (HProcessmodules (HProcess, PHLIST, DSIZE, & DCOUNT) { IF (DCount <= dsize) { DCOUNT / = SizeOf (HModule); Break; } } Else { DCOUNT = 0; } PHLIST = DBGMEMORYDESTROY (PHLIST); IF (! (dsize = dcount)) BREAK; } } IF (pdcount! = null) * pdcount = dcount; Return phlist; } Listing 1-7 Enumeration Process Module EnumProcessModules () Returns a reference to the handle of all modules of the process. In Windows 2000, an HModule is just a simple module image base address. In the SDK header file WINDEF.H, HModule is defined as an alias of Hinstance, both of which are Handle Types. Strictly speaking hmodule is not a handle. Typically, the handle is an index of a table of system management, which can look for object properties through this table. All handles returned by the system have a counter associated with a particular object, which does not be removed from the memory when all handle of an object is not returned. The Win32 API provides a CloseHandle () function to shut down the handle. This function is equivalent to Native API NTClose (). The most important thing about hmodules is that these "handles" do not need to be closed. Another thing that is confusing is that in fact, the module handle is usually not guaranteed to be effective. SDK's getModuleHandle () function document prompts, you must pay more attention to the module handle in multi-threaded programs, because a thread can be invalid by the HModule owned by another thread by uninstalling the module referenced by the HMODule. In a multi-task environment, a program (such as a debugger) may pay attention to this with the module handle of another program. This seems to make HModules not much, but in both cases, the effectiveness of HMODULE will remain long enough: 1. The HModule returned by LoadLibrary () or LoadLibraryEx () will have been valid before the process call free, since these functions contain the module reference count, even in the multi-threaded program, this will also block the module from being accidentally uninstalled. 2. If the module points to HModule will be permanently existed, it will always be effective. For example, all Windows 2000 kernel components (excluding drivers including kernel mode) are always mapped to the same fixed address of each process and have been there in the process life. Unfortunately, these situations do not apply to the module handle returned by the EnumprocessModules () function, at least usually. Copy the HMODule in the buffer provided by the caller, which is valid when the image snapshot is taken. Later, the process may call freeElibrary () to release one or more modules and remove it from memory. At this point, their handle will be invalid, then the process is very likely to call LoadLibrary () to load another DLL, And this new module is mapping to the address released in front. Is this very familiar? Yes, the same problem also exists in an ID array of the enumdevicedrivers () pointer array and the enumprocesses () function. However, these problems can be avoided. PSAPID.DLL After the data collection work is completed, consider the integrity of these data by calling the unnovivated API function, and can return a snapshot of a complete request object, which should include all of the attribute information of interest. This is not necessary to call another function later to get additional information. My point is that PSAPI.dll is designed too simple because it ignores the integrity of the data, which is why I don't use this DLL as a foundation of a professional debugging tool. Compared with the EnumDeviceDrivers () and Enunprocesses () functions are a good citizen because if the buffer provided by the caller cannot put down all of the output data, it will accurately prompt how many bytes are not copied. Note that lists 1-7 do not include a loop, where the buffer will continue to increase until enough. However, the Trial-and-Error loop is still required, because when the next call, the required size of the EnumprocessModules report may have been invalid (if the specified process is loaded between two calls, new modules is loaded). Thus, the code in Listing 1-7 will constantly enumerate the module until the EnumpRocessModules () reports required to be needed or less than the actual available size, or an error occurs. I don't want to describe the ENUMPROCESSMODULES () because the function is slightly more complicated than EnumDevicedrivers and Enumprocesses, which involves several unknown data structures. Basically, it still acquires the address of the Target Process Environment Block (PEB) by calling the ntquerysysteminformation () function (of course, the function has no documentation). Because whether it is a PEB or this linked list, both the address space of the calling process cannot be used directly. EnumprocessModules calls Win32 API ReadProcessMemory () (this function has a document record) to traverse the address space of the target process. By the way, the layout of the PEB structure will be discussed in Chapter 7. In Appendix C, the definition of this structure can be found. Adjusting process privilege Recalling the process handle required for EnumProcessModules earlier. Usually, you first get the process ID --- may be one of the process IDs returned by Enumprocesses. Win32 API OpenProcess () can get its handle through a process ID. This function expects an access sign as its first parameter. Assume that the process ID is stored in a DWORD type variable DID, you call the openprocess with maximum access, as follows: OpenProcess (Process_All_Access, False, DID) To get the handle of the process, you will receive an error code for several low ID processes. This is not bug - this is a security feature! These processes are system services that maintain system activity. A normal user process does not allow all operations for system services. For example, it is not a good idea to allow all processes to kill the system. If a program unexpectedly terminated a system service, the entire system will crash. Therefore, a process only has the appropriate privilege if there is exact access. For a variety of reasons, the debugger must have a lot of permissions to complete his work. Change the privilege of the process can pass the following three simple basic steps: 1. First, you must open the access token of the process (Access token), using the function in Advapi32.dll OpenProcesstoken (). 2. If the previous step is completed correctly, the next step is to prepare the Token_Privileges structure, which contains information about the privileges to be requested. This work requires another function of the ADVAPI32.DLL to lookprivilerage () help. Privileges are specified by name. The SDK document Winnt.h defines the 27 privilege name and its corresponding symbol name. For example, the symbol name of debug privilege is: SE_DEBUG_NAME, the name and string "SedebugPrivilege" is equivalent. 3. If the previous step is completed correctly, you can use the token handle of the process to invoke the AdjustTokenPrivileges () function to initialize the Token_Privileges structure. This function is also exported by Advapi32.dll. If the OpenProcessToken () call is successful, remember to turn off the token handle (Token Handle). W2k_dbg.dll contains a DBGPRIVILEGESET () function that merges these steps, the following list 1-8 lists another function in this function and W2k_dbg.dll: dBgPrivileGedebug (). This function is an outsourcing function of dbgprivilegeset (), in order to facilitate setting debug privileges. By the way, Kill.exe in the Windows NT Server Resource Tool Concentration also uses the same skill. Kill.exe requires debug privileges to eliminate the Starved Services in memory. This is an indispensable tool for NT Server administrators, which is useful for restarting a hanging system service, and can avoid unnecessary system restarts. For those who use IIS (Internet Information Server), this tool may have this tool in their emergency kits to restart inerinfo.exe that occasionally hanged. Bool WinAPI DBGPrivilegeset (PWORD PWNAME) { Handle htokeen; Token_Privileges TP; BOOL fok = false; IF ((PWNAME! = NULL) && OpenProcessToken (GetCurrentProcess (), Token_Adjust_Privileges, & htokeen)) { IF (Lookuppprivilerage (NULL, PWNAME, & TP, Privileges-> LUID)) { TP.Privileges-> attributes = se_privilege_enabled; Tp.privilegectount = 1; Fok = AdjustTokenPrivileges (HToken, False, & TP, 0, NULL, NULL) && () == Error_Success; } CloseHandle (HTOKEN); } Return fok; } / / -------------------------------------------------------------------------------------------- ------------------------------------ Bool WinApi DBGPrivileDebug (Void) { Return DBGPRIVILEGESET (SE_DEBGU_NAME); } Listing 1-8 Requesting a privilege for a process .................to be continued.......................