VitoPlantamura.com logo - Click here to go to the site home page... Click here to go to the site home page... Click here to go to the Blog page... Click here to go to the archives page... Click here to go to the about page...
"extremely impressive work"
- Mark Russinovich, www.sysinternals.com
(referring to my BugChecker)
my face...
Homepage of Vito Plantamura (@), Windows Researcher, VPC Technologies SRL CEO. [user=Guest] - updated: August 08, 2007
 CUSTOM READ/WRITE-PROCESSMEMORY IMPLEMENTATION
CUSTOM READ/WRITE-PROCESSMEMORY IMPLEMENTATION

When dealing with NT loader-syncronized code, it may be necessary to call a custom implementation of the ReadProcessMemory and WriteProcessMemory functions in order to circumvent an insidious deadlock problem.

INTRODUCTION

Several years ago, I developed a Windows 2000 kernel driver whose primary goal was to notify a user-mode counterpart when a particular executable image was started. For achieving this without non-consistent polling solutions, I was forced to use the well-documented "Process Structure Routines" described in the standard DDK documentation. I needed to be notified when a new executable module was being started because I needed to patch its entry point and TLS callback routines: an exact and precise notification mechanism was required in order to apply the code patches strictly before the system was able to execute that code (the TLS callbacks/DllMain/etc. execution by the loader is an operation that occurs just after the image mapping in memory has taken place and -specifically, in the case of that software- as you may imagine, the system had to execute my just modified code and not the "old" version...). So, I installed in the driver both a process and an image creation callbacks (PsSetCreateProcessNotifyRoutine and PsSetLoadImageNotifyRoutine routines). When a new PE module was just mapped in memory (and before any execution had taken place) the image callback was called: my implementation took the image name passed as parameter to the callback routine, transformed it in an user-mode understandable file path (among other things, by sending an IRP to the Mount Manager system component - i.e. IOCTL_MOUNTMGR_QUERY_POINTS - asking to resolve the volume device name present in the original kernel path and eventually by referencing an undocumented slot in the kernel TIB) and then -after having signaled an user mode event- the new module name was handled to the user mode NT service for processing (along with the new module starting address in memory and with the target process ID).

The specific problem occurs now: the user mode component - upon receiving the kernel notification - was required to do its job of opening the just created target process and doing some sort of remote process code injection and modification (as explained above). Meanwhile the image callback routine (that is called as integral part of the process creation) was made wait until the entire user mode operation had completed: this forced synchronization mechanism with the system loader was required in order to prevent the system from starting to execute the code mapped in the new modules before all the injected code and jumps were in place. This was achieved through a simple KeWaitForSingleObject call in the image callback routine specifying a security timeout value in order to unblock the system loader (running and stuck in the context of the new process) in the case the user mode service crashed or something went horribly wrong in the whole procedure. Well, the problem is that, when you stop the loader in this way, calling ReadProcessMemory or WriteProcessMemory on that blocked process from a remote process will cause a system-wide deadlock that will prevent the system loader from being invoked by other system components and services (due to one or more locks - whose names I just forgot - that are held by the loader in the blocked thread).

Resorting to a custom implementation of the ReadProcessMemory and WriteProcessMemory functions can resolve this problem. Well, this is the minimal implementation of a function almost analogous to ReadProcessMemory (it has to be inserted in a simple driver skeleton code in order to be used):

case IOCTL_AVMKRNL_READ_PROCESS_MEM:
      {
            // do the read operation
            if ( irpStack->Parameters.DeviceIoControl.InputBufferLength == sizeof( READPROCESSMEMORY_PARAMS ) &&
                  irpStack->Parameters.DeviceIoControl.OutputBufferLength != 0 )
            {
                  PEPROCESS                           pepProcess;
                  NTSTATUS                            ntMyStatus;
                  PREADPROCESSMEMORY_PARAMS      prpmpParams = (PREADPROCESSMEMORY_PARAMS) Irp->AssociatedIrp.SystemBuffer;
 
                  // try to obtain a PEB pointer to the process
 
                  ntMyStatus = PsLookupProcessByProcessId(
                        prpmpParams->ulProcess_ID,
                        & pepProcess );
 
                  if ( NT_SUCCESS( ntMyStatus ) )
                  {
                        // attach to the process
                        KeAttachProcess( pepProcess );
 
                        // try to copy the memory
                        __try
                        {
                              RtlCopyMemory(
                                    Irp->AssociatedIrp.SystemBuffer,
                                    prpmpParams->pvSourceAddress,
                                    irpStack->Parameters.DeviceIoControl.OutputBufferLength );
 
                              ntStatus = STATUS_SUCCESS;
                        }
                        __except (EXCEPTION_EXECUTE_HANDLER)
                        {
                              ntStatus = STATUS_UNSUCCESSFUL;
                        }
 
                        // detach from the process
                        KeDetachProcess();
                  }
                  else
                  {
                        // unable to get a pointer to the PEB of the process...
                        ntStatus = STATUS_UNSUCCESSFUL;
                  }
            }
            else
            {
                  // we need the basic informations for doing the copy and above all something to copy !
                  ntStatus = STATUS_UNSUCCESSFUL;
            }
      }
      break;

The only difference is that you need to provide to the function a process ID instead of a process handle (the latter is tipically obtained by calling the OpenProcess API, the former comes for free). Obviously handling a remote process read and write operations in this way circumvents completely the NT security checkings of the caller related permissions, so this implementation must be secured by making it reachable only from trusted code. This is the implementation of the WriteProcessMemory counterpart:

case IOCTL_AVMKRNL_WRITE_PROCESS_MEM:
      {
            // do the write operation
            if ( irpStack->Parameters.DeviceIoControl.InputBufferLength > sizeof( WRITEPROCESSMEMORY_PARAMS ) )
            {
                  PEPROCESS                           pepProcess;
                  NTSTATUS                            ntMyStatus;
                  PWRITEPROCESSMEMORY_PARAMS      pwpmpParams = (PWRITEPROCESSMEMORY_PARAMS) Irp->AssociatedIrp.SystemBuffer;
                  PVOID                               pvSourceMem = (BYTE*) Irp->AssociatedIrp.SystemBuffer + sizeof( WRITEPROCESSMEMORY_PARAMS );
                  ULONG                               ulSourceMemLen = irpStack->Parameters.DeviceIoControl.InputBufferLength - sizeof( WRITEPROCESSMEMORY_PARAMS );
 
                  // try to obtain a PEB pointer to the process
 
                  ntMyStatus = PsLookupProcessByProcessId(
                        pwpmpParams->ulProcess_ID,
                        & pepProcess );
 
                  if ( NT_SUCCESS( ntMyStatus ) )
                  {
                        // attach to the process
                        KeAttachProcess( pepProcess );
 
                        // try to copy the memory
                        __try
                        {
                              RtlCopyMemory(
                                    pwpmpParams->pvTargetAddress,
                                    pvSourceMem,
                                    ulSourceMemLen );
 
                              ntStatus = STATUS_SUCCESS;
                        }
                        __except (EXCEPTION_EXECUTE_HANDLER)
                        {
                              ntStatus = STATUS_UNSUCCESSFUL;
                        }
 
                        // detach from the process
                        KeDetachProcess();
                  }
                  else
                  {
                        // unable to get a pointer to the PEB of the process...
                        ntStatus = STATUS_UNSUCCESSFUL;
                  }
            }
            else
            {
                  // we need the basic informations for doing the copy and above all something to copy !
                  ntStatus = STATUS_UNSUCCESSFUL;
            }
      }
      break;

As a final note, consider that KeAttachProcess, KeDetachProcess and PsLookupProcessByProcessId are undocumented functions whose prototypes are not listed in NTDKK.H (but are exported from ntoskrnl.lib, so specifying their signatures by hand in your code files is enough for using them). The prototypes are as follows:

VOID KeAttachProcess(
  IN PEPROCESS Process
);
 
VOID KeDetachProcess(
  VOID
);
 
NTSTATUS PsLookupProcessByProcessId(
  IN ULONG Process_ID,
  OUT PVOID *EProcess
);

 Quotes
"Among the Windows experts I know personally, no one can beat Vito Plantamura."
- Francesco Balena, Code Architects SRL

"Your NDIS Monitor application, is amongst the most impressive networking code I have seen on the .Net framework."
- Ben Hakim.
 Photos
Various images from italian conferences and events (keep the mouse on a thumbnail for a short description):
Me at the Microsoft/HP/Intel organized Route64 event in Milan in May 2005, explaining how COM+ behaves on 64-bit Microsoft operating systems. I was there with the friends of Code Architects.
Me at the Microsoft Security Roadshow event in Bari in April 2006, explaining how the logon process works in Windows NT. There were 250 attendees.
Microsoft Security Roadshow 2006 in Treviso. This is an image of the huge 700-seats conference room.
Me at the Microsoft Security Roadshow 2006 in Treviso. This is a moment of the 3-hours session.
 Site login
NOTE: Actually the login feature is used only for administrative and content management purposes.
Username

Password

Everything here (code, binaries, text, graphics, design, html) is © 2010 Vito Plantamura and VPC Technologies SRL (VATID: IT06203700965).
If you download something (compilable or not) from the site, you should read the license policy file.
If you want to contact me via email, write at this address.