Process Hacker
vm.c
Go to the documentation of this file.
1 /*
2  * KProcessHacker
3  *
4  * Copyright (C) 2010-2011 wj32
5  *
6  * This file is part of Process Hacker.
7  *
8  * Process Hacker is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * Process Hacker is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with Process Hacker. If not, see <http://www.gnu.org/licenses/>.
20  */
21 
22 #include <kph.h>
23 
25  __in PEXCEPTION_POINTERS ExceptionInfo,
26  __out PBOOLEAN HaveBadAddress,
27  __out PULONG_PTR BadAddress
28  );
29 
30 #ifdef ALLOC_PRAGMA
31 #pragma alloc_text(PAGE, KphCopyVirtualMemory)
32 #pragma alloc_text(PAGE, KpiReadVirtualMemory)
33 #pragma alloc_text(PAGE, KpiWriteVirtualMemory)
34 #pragma alloc_text(PAGE, KpiReadVirtualMemoryUnsafe)
35 #endif
36 
37 #define KPH_STACK_COPY_BYTES 0x200
38 #define KPH_POOL_COPY_BYTES 0x10000
39 #define KPH_MAPPED_COPY_PAGES 14
40 #define KPH_POOL_COPY_THRESHOLD 0x3ff
41 
43  __in PEXCEPTION_POINTERS ExceptionInfo,
44  __out PBOOLEAN HaveBadAddress,
45  __out PULONG_PTR BadAddress
46  )
47 {
48  PEXCEPTION_RECORD exceptionRecord;
49 
50  *HaveBadAddress = FALSE;
51  exceptionRecord = ExceptionInfo->ExceptionRecord;
52 
53  if ((exceptionRecord->ExceptionCode == STATUS_ACCESS_VIOLATION) ||
54  (exceptionRecord->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION) ||
55  (exceptionRecord->ExceptionCode == STATUS_IN_PAGE_ERROR))
56  {
57  if (exceptionRecord->NumberParameters > 1)
58  {
59  /* We have the address. */
60  *HaveBadAddress = TRUE;
61  *BadAddress = exceptionRecord->ExceptionInformation[1];
62  }
63  }
64 
65  return EXCEPTION_EXECUTE_HANDLER;
66 }
67 
81  __in PEPROCESS FromProcess,
82  __in PVOID FromAddress,
83  __in PEPROCESS ToProcess,
84  __in PVOID ToAddress,
85  __in SIZE_T BufferLength,
86  __in KPROCESSOR_MODE AccessMode,
87  __out PSIZE_T ReturnLength
88  )
89 {
90  UCHAR stackBuffer[KPH_STACK_COPY_BYTES];
91  PVOID buffer;
92  PFN_NUMBER mdlBuffer[(sizeof(MDL) / sizeof(PFN_NUMBER)) + KPH_MAPPED_COPY_PAGES + 1];
93  PMDL mdl = (PMDL)mdlBuffer;
94  PVOID mappedAddress;
95  SIZE_T mappedTotalSize;
96  SIZE_T blockSize;
97  SIZE_T stillToCopy;
98  KAPC_STATE apcState;
99  PVOID sourceAddress;
100  PVOID targetAddress;
101  BOOLEAN doMappedCopy;
102  BOOLEAN pagesLocked;
103  BOOLEAN copyingToTarget = FALSE;
104  BOOLEAN probing = FALSE;
105  BOOLEAN mapping = FALSE;
106  BOOLEAN haveBadAddress;
107  ULONG_PTR badAddress;
108 
109  PAGED_CODE();
110 
111  sourceAddress = FromAddress;
112  targetAddress = ToAddress;
113 
114  // We don't check if buffer == NULL when freeing. If buffer doesn't need
115  // to be freed, set to stackBuffer, not NULL.
116  buffer = stackBuffer;
117 
118  mappedTotalSize = (KPH_MAPPED_COPY_PAGES - 2) * PAGE_SIZE;
119 
120  if (mappedTotalSize > BufferLength)
121  mappedTotalSize = BufferLength;
122 
123  stillToCopy = BufferLength;
124  blockSize = mappedTotalSize;
125 
126  while (stillToCopy)
127  {
128  // If we're at the last copy block, copy the remaining bytes instead
129  // of the whole block size.
130  if (blockSize > stillToCopy)
131  blockSize = stillToCopy;
132 
133  // Choose the best method based on the number of bytes left to copy.
134  if (blockSize > KPH_POOL_COPY_THRESHOLD)
135  {
136  doMappedCopy = TRUE;
137  }
138  else
139  {
140  doMappedCopy = FALSE;
141 
142  if (blockSize <= KPH_STACK_COPY_BYTES)
143  {
144  if (buffer != stackBuffer)
145  ExFreePoolWithTag(buffer, 'ChpK');
146 
147  buffer = stackBuffer;
148  }
149  else
150  {
151  // Don't allocate the buffer if we've done so already.
152  // Note that the block size never increases, so this allocation
153  // will always be OK.
154  if (buffer == stackBuffer)
155  {
156  // Keep trying to allocate a buffer.
157 
158  while (TRUE)
159  {
160  buffer = ExAllocatePoolWithTag(NonPagedPool, blockSize, 'ChpK');
161 
162  // Stop trying if we got a buffer.
163  if (buffer)
164  break;
165 
166  blockSize /= 2;
167 
168  // Use the stack buffer if we can.
169  if (blockSize <= KPH_STACK_COPY_BYTES)
170  {
171  buffer = stackBuffer;
172  break;
173  }
174  }
175  }
176  }
177  }
178 
179  // Reset state.
180  mappedAddress = NULL;
181  pagesLocked = FALSE;
182  copyingToTarget = FALSE;
183 
184  KeStackAttachProcess(FromProcess, &apcState);
185 
186  __try
187  {
188  // Probe only if this is the first time.
189  if (sourceAddress == FromAddress && AccessMode != KernelMode)
190  {
191  probing = TRUE;
192  ProbeForRead(sourceAddress, BufferLength, sizeof(UCHAR));
193  probing = FALSE;
194  }
195 
196  if (doMappedCopy)
197  {
198  // Initialize the MDL.
199  MmInitializeMdl(mdl, sourceAddress, blockSize);
200  MmProbeAndLockPages(mdl, AccessMode, IoReadAccess);
201  pagesLocked = TRUE;
202 
203  // Map the pages.
204  mappedAddress = MmMapLockedPagesSpecifyCache(
205  mdl,
206  KernelMode,
207  MmCached,
208  NULL,
209  FALSE,
210  HighPagePriority
211  );
212 
213  if (!mappedAddress)
214  {
215  // Insufficient resources; exit.
216  mapping = TRUE;
217  ExRaiseStatus(STATUS_INSUFFICIENT_RESOURCES);
218  }
219  }
220  else
221  {
222  memcpy(buffer, sourceAddress, blockSize);
223  }
224 
225  KeUnstackDetachProcess(&apcState);
226 
227  // Attach to the target process and copy the contents out.
228  KeStackAttachProcess(ToProcess, &apcState);
229 
230  // Probe only if this is the first time.
231  if (targetAddress == ToAddress && AccessMode != KernelMode)
232  {
233  probing = TRUE;
234  ProbeForWrite(targetAddress, BufferLength, sizeof(UCHAR));
235  probing = FALSE;
236  }
237 
238  // Copy the data.
239  copyingToTarget = TRUE;
240 
241  if (doMappedCopy)
242  memcpy(targetAddress, mappedAddress, blockSize);
243  else
244  memcpy(targetAddress, buffer, blockSize);
245  }
246  __except (KphpGetCopyExceptionInfo(
247  GetExceptionInformation(),
248  &haveBadAddress,
249  &badAddress
250  ))
251  {
252  KeUnstackDetachProcess(&apcState);
253 
254  // If we mapped the pages, unmap them.
255  if (mappedAddress)
256  MmUnmapLockedPages(mappedAddress, mdl);
257 
258  // If we locked the pages, unlock them.
259  if (pagesLocked)
260  MmUnlockPages(mdl);
261 
262  // If we allocated pool storage, free it.
263  if (buffer != stackBuffer)
264  ExFreePoolWithTag(buffer, 'ChpK');
265 
266  // If we failed when probing or mapping, return the error status.
267  if (probing || mapping)
268  return GetExceptionCode();
269 
270  // Determine which copy failed.
271  if (copyingToTarget && haveBadAddress)
272  {
273  *ReturnLength = (ULONG)(badAddress - (ULONG_PTR)sourceAddress);
274  }
275  else
276  {
277  *ReturnLength = BufferLength - stillToCopy;
278  }
279 
280  return STATUS_PARTIAL_COPY;
281  }
282 
283  KeUnstackDetachProcess(&apcState);
284 
285  if (doMappedCopy)
286  {
287  MmUnmapLockedPages(mappedAddress, mdl);
288  MmUnlockPages(mdl);
289  }
290 
291  stillToCopy -= blockSize;
292  sourceAddress = (PVOID)((ULONG_PTR)sourceAddress + blockSize);
293  targetAddress = (PVOID)((ULONG_PTR)targetAddress + blockSize);
294  }
295 
296  if (buffer != stackBuffer)
297  ExFreePoolWithTag(buffer, 'ChpK');
298 
299  *ReturnLength = BufferLength;
300 
301  return STATUS_SUCCESS;
302 }
303 
317  __in HANDLE ProcessHandle,
318  __in PVOID BaseAddress,
319  __out_bcount(BufferSize) PVOID Buffer,
320  __in SIZE_T BufferSize,
321  __out_opt PSIZE_T NumberOfBytesRead,
322  __in KPROCESSOR_MODE AccessMode
323  )
324 {
325  NTSTATUS status;
326  PEPROCESS process;
327  SIZE_T numberOfBytesRead;
328 
329  PAGED_CODE();
330 
331  if (AccessMode != KernelMode)
332  {
333  if (
334  (ULONG_PTR)BaseAddress + BufferSize < (ULONG_PTR)BaseAddress ||
335  (ULONG_PTR)Buffer + BufferSize < (ULONG_PTR)Buffer ||
336  (ULONG_PTR)BaseAddress + BufferSize > (ULONG_PTR)MmHighestUserAddress ||
337  (ULONG_PTR)Buffer + BufferSize > (ULONG_PTR)MmHighestUserAddress
338  )
339  {
340  return STATUS_ACCESS_VIOLATION;
341  }
342 
343  if (NumberOfBytesRead)
344  {
345  __try
346  {
347  ProbeForWrite(NumberOfBytesRead, sizeof(SIZE_T), sizeof(SIZE_T));
348  }
349  __except (EXCEPTION_EXECUTE_HANDLER)
350  {
351  return GetExceptionCode();
352  }
353  }
354  }
355 
356  if (BufferSize != 0)
357  {
358  status = ObReferenceObjectByHandle(
359  ProcessHandle,
360  0,
361  *PsProcessType,
362  AccessMode,
363  &process,
364  NULL
365  );
366 
367  if (NT_SUCCESS(status))
368  {
369  status = KphCopyVirtualMemory(
370  process,
371  BaseAddress,
372  PsGetCurrentProcess(),
373  Buffer,
374  BufferSize,
375  AccessMode,
376  &numberOfBytesRead
377  );
378  ObDereferenceObject(process);
379  }
380  }
381  else
382  {
383  numberOfBytesRead = 0;
384  status = STATUS_SUCCESS;
385  }
386 
387  if (NumberOfBytesRead)
388  {
389  if (AccessMode != KernelMode)
390  {
391  __try
392  {
393  *NumberOfBytesRead = numberOfBytesRead;
394  }
395  __except (EXCEPTION_EXECUTE_HANDLER)
396  {
397  // Don't mess with the status.
398  NOTHING;
399  }
400  }
401  else
402  {
403  *NumberOfBytesRead = numberOfBytesRead;
404  }
405  }
406 
407  return status;
408 }
409 
423  __in HANDLE ProcessHandle,
424  __in_opt PVOID BaseAddress,
425  __in_bcount(BufferSize) PVOID Buffer,
426  __in SIZE_T BufferSize,
427  __out_opt PSIZE_T NumberOfBytesWritten,
428  __in KPROCESSOR_MODE AccessMode
429  )
430 {
431  NTSTATUS status;
432  PEPROCESS process;
433  SIZE_T numberOfBytesWritten;
434 
435  PAGED_CODE();
436 
437  if (AccessMode != KernelMode)
438  {
439  if (
440  (ULONG_PTR)BaseAddress + BufferSize < (ULONG_PTR)BaseAddress ||
441  (ULONG_PTR)Buffer + BufferSize < (ULONG_PTR)Buffer ||
442  (ULONG_PTR)BaseAddress + BufferSize > (ULONG_PTR)MmHighestUserAddress ||
443  (ULONG_PTR)Buffer + BufferSize > (ULONG_PTR)MmHighestUserAddress
444  )
445  {
446  return STATUS_ACCESS_VIOLATION;
447  }
448 
449  if (NumberOfBytesWritten)
450  {
451  __try
452  {
453  ProbeForWrite(NumberOfBytesWritten, sizeof(SIZE_T), sizeof(SIZE_T));
454  }
455  __except (EXCEPTION_EXECUTE_HANDLER)
456  {
457  return GetExceptionCode();
458  }
459  }
460  }
461 
462  if (BufferSize != 0)
463  {
464  status = ObReferenceObjectByHandle(
465  ProcessHandle,
466  0,
467  *PsProcessType,
468  AccessMode,
469  &process,
470  NULL
471  );
472 
473  if (NT_SUCCESS(status))
474  {
475  status = KphCopyVirtualMemory(
476  PsGetCurrentProcess(),
477  Buffer,
478  process,
479  BaseAddress,
480  BufferSize,
481  AccessMode,
482  &numberOfBytesWritten
483  );
484  ObDereferenceObject(process);
485  }
486  }
487  else
488  {
489  numberOfBytesWritten = 0;
490  status = STATUS_SUCCESS;
491  }
492 
493  if (NumberOfBytesWritten)
494  {
495  if (AccessMode != KernelMode)
496  {
497  __try
498  {
499  *NumberOfBytesWritten = numberOfBytesWritten;
500  }
501  __except (EXCEPTION_EXECUTE_HANDLER)
502  {
503  // Don't mess with the status.
504  NOTHING;
505  }
506  }
507  else
508  {
509  *NumberOfBytesWritten = numberOfBytesWritten;
510  }
511  }
512 
513  return status;
514 }
515 
530  __in_opt HANDLE ProcessHandle,
531  __in PVOID BaseAddress,
532  __out_bcount(BufferSize) PVOID Buffer,
533  __in SIZE_T BufferSize,
534  __out_opt PSIZE_T NumberOfBytesRead,
535  __in KPROCESSOR_MODE AccessMode
536  )
537 {
538  NTSTATUS status;
539  PEPROCESS process;
540  SIZE_T numberOfBytesRead;
541 
542  PAGED_CODE();
543 
544  if (AccessMode != KernelMode)
545  {
546  if (
547  (ULONG_PTR)BaseAddress + BufferSize < (ULONG_PTR)BaseAddress ||
548  (ULONG_PTR)Buffer + BufferSize < (ULONG_PTR)Buffer ||
549  (ULONG_PTR)Buffer + BufferSize > (ULONG_PTR)MmHighestUserAddress
550  )
551  {
552  return STATUS_ACCESS_VIOLATION;
553  }
554 
555  if (NumberOfBytesRead)
556  {
557  __try
558  {
559  ProbeForWrite(NumberOfBytesRead, sizeof(SIZE_T), sizeof(SIZE_T));
560  }
561  __except (EXCEPTION_EXECUTE_HANDLER)
562  {
563  return GetExceptionCode();
564  }
565  }
566  }
567 
568  if (BufferSize != 0)
569  {
570  // Select the appropriate copy method.
571  if ((ULONG_PTR)BaseAddress + BufferSize > (ULONG_PTR)MmHighestUserAddress)
572  {
573  ULONG_PTR page;
574  ULONG_PTR pageEnd;
575 
576  // Kernel memory copy (unsafe)
577 
578  page = (ULONG_PTR)BaseAddress & ~(PAGE_SIZE - 1);
579  pageEnd = ((ULONG_PTR)BaseAddress + BufferSize - 1) & ~(PAGE_SIZE - 1);
580 
581  __try
582  {
583  // This will obviously fail if any of the pages aren't resident.
584  for (; page <= pageEnd; page += PAGE_SIZE)
585  {
586  if (!MmIsAddressValid((PVOID)page))
587  ExRaiseStatus(STATUS_ACCESS_VIOLATION);
588  }
589 
590  memcpy(Buffer, BaseAddress, BufferSize);
591  }
592  __except (EXCEPTION_EXECUTE_HANDLER)
593  {
594  return GetExceptionCode();
595  }
596 
597  numberOfBytesRead = BufferSize;
598  status = STATUS_SUCCESS;
599  }
600  else
601  {
602  // User memory copy (safe)
603 
604  status = ObReferenceObjectByHandle(
605  ProcessHandle,
606  0,
607  *PsProcessType,
608  AccessMode,
609  &process,
610  NULL
611  );
612 
613  if (NT_SUCCESS(status))
614  {
615  status = KphCopyVirtualMemory(
616  process,
617  BaseAddress,
618  PsGetCurrentProcess(),
619  Buffer,
620  BufferSize,
621  AccessMode,
622  &numberOfBytesRead
623  );
624  ObDereferenceObject(process);
625  }
626  }
627  }
628  else
629  {
630  numberOfBytesRead = 0;
631  status = STATUS_SUCCESS;
632  }
633 
634  if (NumberOfBytesRead)
635  {
636  if (AccessMode != KernelMode)
637  {
638  __try
639  {
640  *NumberOfBytesRead = numberOfBytesRead;
641  }
642  __except (EXCEPTION_EXECUTE_HANDLER)
643  {
644  // Don't mess with the status.
645  NOTHING;
646  }
647  }
648  else
649  {
650  *NumberOfBytesRead = numberOfBytesRead;
651  }
652  }
653 
654  return status;
655 }