Uninformed: Informative Information for the Uninformed

Vol 1» 2005.May

The Problem

During some experimentation with changing the default address space layout of processes on NT-based versions of Windows, it was noticed that machines that were using the ATI Radeon 9000 series drivers would crash if a process attempted to do 3D operations and the location of the process' parameter information was changed from the address at which it is normally mapped at. Before proceeding, it is first necessary for the reader to understand the purpose of the process parameter information structure and how it is that it's mapped into the process' address space.

Most programmers are familiar with the API routine kernel32!CreateProcess[A/W]. This routine serves as the primary means by which user-mode applications spawn new processes. The function itself is robust enough to support a number of ways in which a new process can be initialized and then executed. Behind the scenes, CreateProcess performs all of the necessary operations to prepare the new task for execution. These options include opening the executable image file and creating a section object that is then passed to ntdll!NtCreateProcessEx which returns a unique process handle on success. If a handle is obtained, CreateProcess then proceeds to prepare the process for execution by initializing the process' parameters as well as creating and initializing the first thread in the process. A more complete analysis of the way in which CreateProcess operates can be found in David Probert's excellent analysis of Windows NT's process architecture[1].

For the purpose of this document, however, the part that is of most concern is that step in which CreateProcess initializes the new process' parameters. This is accomplished by making a call into kernel32!BasePushProcessParameters which in turn calls into ntdll!RtlCreateProcessParameters. The parameters are initialized within the process that is calling CreateProcess and are then, in turn, copied into the address space of the new process by first allocating storage with ntdll!NtAllocateVirtualMemory and then by copying the memory from the parent process to the child with ntdll!NtWriteVirtualMemory. Due to the fact that this occurs before the new process actually executes any code, the address that the process parameter structure is allocated at is almost guaranteed to be at the same address. This address happens to be 0x00020000. This fact is most likely why ATI made the assumption that the process parameter information would always be at a static address.

If, however, ntdll!NtAllocateVirtualMemory allocates the process parameter storage at any place other than the static address described above, ATI's driver will attempt to reference a potentially invalid address when it comes time to perform 3D operations. The specific portion of the driver suite that has the error is the ATI3DUAG.DLL kernel-mode graphics driver. Inside this image there is a portion of code that attempts to make reference to the addresses 0x00020038 and 0x0002003C without doing any sort of probing and locking or validation on the region it's requesting. If the region does not exist or contains unexpected data, a blue screen is a sure thing. The actual portion of the driver that makes this assumption can be found below:

mov     [ebp+var_4], eax
mov     edx, 20000h                    <--
mov     [ebp+var_24], edx
movzx   ecx, word ptr ds:dword_20035+3 <--
shr     ecx, 1
mov     [ebp+var_28], ecx
lea     eax, [ecx-1]
mov     [ebp+var_1C], eax
test    eax, eax
jbe     short loc_227CC
mov     ebx, [edx+3Ch]                 <--
cmp     word ptr [ebx+eax*2], '\'

The lines of intereste are marked by ``<-'' indicators pointing to the exact instructions that result in a reference being made to an address that is expected to be within a process' parameter information structure. For the sake of investigation, one might wonder what it is that the driver could be attempting to reference. To determine that, it is first necessary to dump the format of the process parameter structure which, as stated previously, is RTL_USER_PROCESS_PARAMETERS:

   +0x000 MaximumLength    : Uint4B
   +0x004 Length           : Uint4B
   +0x008 Flags            : Uint4B
   +0x00c DebugFlags       : Uint4B
   +0x010 ConsoleHandle    : Ptr32 Void
   +0x014 ConsoleFlags     : Uint4B
   +0x018 StandardInput    : Ptr32 Void
   +0x01c StandardOutput   : Ptr32 Void
   +0x020 StandardError    : Ptr32 Void
   +0x024 CurrentDirectory : _CURDIR
   +0x030 DllPath          : _UNICODE_STRING
   +0x038 ImagePathName    : _UNICODE_STRING
   +0x040 CommandLine      : _UNICODE_STRING
   +0x048 Environment      : Ptr32 Void
   +0x04c StartingX        : Uint4B
   +0x050 StartingY        : Uint4B
   +0x054 CountX           : Uint4B
   +0x058 CountY           : Uint4B
   +0x05c CountCharsX      : Uint4B
   +0x060 CountCharsY      : Uint4B
   +0x064 FillAttribute    : Uint4B
   +0x068 WindowFlags      : Uint4B
   +0x06c ShowWindowFlags  : Uint4B
   +0x070 WindowTitle      : _UNICODE_STRING
   +0x078 DesktopInfo      : _UNICODE_STRING
   +0x080 ShellInfo        : _UNICODE_STRING
   +0x088 RuntimeData      : _UNICODE_STRING
   +0x090 CurrentDirectores : [32] _RTL_DRIVE_LETTER_CURDIR

To determine the attribute that the driver is attempting to reference, one must take the addresses and subtract them from the base address 0x00020000. This produces two offsets: 0x38 and 0x3c. Both of these offsets are within the ImagePathName attribute which is a UNICODE_STRING. The UNICODE_STRING structure is defined as:

   +0x000 Length           : Uint2B
   +0x002 MaximumLength    : Uint2B
   +0x004 Buffer           : Ptr32 Uint2B

This would mean that the driver is attempting to reference the path name of the process' executable image. The 0x38 offset is the length of the image path name and the 0x3c is the pointer to the image path name buffer that actually contains the path. The reason that the driver would need to get access to the executable path is outside of the scope of this discussion, but suffice to say that the method on which it is based is an assumption that may not always be safe to make, especially under conditions where the process' parameter information is not mapped at 0x00020000.