Process Hacker
memsrch.c
Go to the documentation of this file.
1 /*
2  * Process Hacker -
3  * memory searchers
4  *
5  * Copyright (C) 2010 wj32
6  *
7  * This file is part of Process Hacker.
8  *
9  * Process Hacker is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * Process Hacker is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with Process Hacker. If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 #include <phapp.h>
24 #include <memsrch.h>
25 #include <windowsx.h>
26 
27 #define WM_PH_MEMORY_STATUS_UPDATE (WM_APP + 301)
28 
29 #define PH_SEARCH_UPDATE 1
30 #define PH_SEARCH_COMPLETED 2
31 
32 typedef struct _MEMORY_STRING_CONTEXT
33 {
34  HANDLE ProcessId;
35  HANDLE ProcessHandle;
36  ULONG MinimumLength;
37  BOOLEAN DetectUnicode;
38  BOOLEAN Private;
39  BOOLEAN Image;
40  BOOLEAN Mapped;
41 
42  HWND WindowHandle;
43  HANDLE ThreadHandle;
45  PPH_LIST Results;
47 
48 INT_PTR CALLBACK PhpMemoryStringDlgProc(
49  _In_ HWND hwndDlg,
50  _In_ UINT uMsg,
51  _In_ WPARAM wParam,
52  _In_ LPARAM lParam
53  );
54 
55 INT_PTR CALLBACK PhpMemoryStringProgressDlgProc(
56  _In_ HWND hwndDlg,
57  _In_ UINT uMsg,
58  _In_ WPARAM wParam,
59  _In_ LPARAM lParam
60  );
61 
62 PVOID PhMemorySearchHeap = NULL;
65 
67  _In_ SIZE_T Size
68  )
69 {
70  PVOID memory;
71 
72  PhAcquireQueuedLockExclusive(&PhMemorySearchHeapLock);
73 
74  if (!PhMemorySearchHeap)
75  {
76  assert(PhMemorySearchHeapRefCount == 0);
78  HEAP_GROWABLE | HEAP_CLASS_1,
79  NULL,
80  8192 * 1024, // 8 MB
81  2048 * 1024, // 2 MB
82  NULL,
83  NULL
84  );
85  }
86 
88  {
89  // Don't use HEAP_NO_SERIALIZE - it's very slow on Vista and above.
90  memory = RtlAllocateHeap(PhMemorySearchHeap, 0, Size);
91 
92  if (memory)
94  }
95  else
96  {
97  memory = NULL;
98  }
99 
100  PhReleaseQueuedLockExclusive(&PhMemorySearchHeapLock);
101 
102  return memory;
103 }
104 
106  _In_ _Post_invalid_ PVOID Memory
107  )
108 {
109  PhAcquireQueuedLockExclusive(&PhMemorySearchHeapLock);
110 
111  RtlFreeHeap(PhMemorySearchHeap, 0, Memory);
112 
113  if (--PhMemorySearchHeapRefCount == 0)
114  {
116  PhMemorySearchHeap = NULL;
117  }
118 
119  PhReleaseQueuedLockExclusive(&PhMemorySearchHeapLock);
120 }
121 
123  _In_ PVOID Address,
124  _In_ SIZE_T Length
125  )
126 {
127  PPH_MEMORY_RESULT result;
128 
130 
131  if (!result)
132  return NULL;
133 
134  result->RefCount = 1;
135  result->Address = Address;
136  result->Length = Length;
137  result->Display.Length = 0;
138  result->Display.Buffer = NULL;
139 
140  return result;
141 }
142 
144  _In_ PPH_MEMORY_RESULT Result
145  )
146 {
147  _InterlockedIncrement(&Result->RefCount);
148 }
149 
151  _In_ PPH_MEMORY_RESULT Result
152  )
153 {
154  if (_InterlockedDecrement(&Result->RefCount) == 0)
155  {
156  if (Result->Display.Buffer)
157  PhFreeForMemorySearch(Result->Display.Buffer);
158 
159  PhFreeForMemorySearch(Result);
160  }
161 }
162 
164  _In_reads_(NumberOfResults) PPH_MEMORY_RESULT *Results,
165  _In_ ULONG NumberOfResults
166  )
167 {
168  ULONG i;
169 
170  for (i = 0; i < NumberOfResults; i++)
171  PhDereferenceMemoryResult(Results[i]);
172 }
173 
175  _In_ HANDLE ProcessHandle,
176  _In_ PPH_MEMORY_STRING_OPTIONS Options
177  )
178 {
179  ULONG minimumLength;
180  BOOLEAN detectUnicode;
181  ULONG memoryTypeMask;
182  PVOID baseAddress;
183  MEMORY_BASIC_INFORMATION basicInfo;
184  PUCHAR buffer;
185  SIZE_T bufferSize;
186  PWSTR displayBuffer;
187  SIZE_T displayBufferCount;
188 
189  minimumLength = Options->MinimumLength;
190  detectUnicode = Options->DetectUnicode;
191  memoryTypeMask = Options->MemoryTypeMask;
192 
193  if (minimumLength < 4)
194  return;
195 
196  baseAddress = (PVOID)0;
197 
198  bufferSize = PAGE_SIZE * 64;
199  buffer = PhAllocatePage(bufferSize, NULL);
200 
201  if (!buffer)
202  return;
203 
204  displayBufferCount = PH_DISPLAY_BUFFER_COUNT;
205  displayBuffer = PhAllocatePage((displayBufferCount + 1) * sizeof(WCHAR), NULL);
206 
207  if (!displayBuffer)
208  {
209  PhFreePage(buffer);
210  return;
211  }
212 
214  ProcessHandle,
215  baseAddress,
217  &basicInfo,
218  sizeof(MEMORY_BASIC_INFORMATION),
219  NULL
220  )))
221  {
222  ULONG_PTR offset;
223  SIZE_T readSize;
224 
225  if (Options->Header.Cancel)
226  break;
227  if (basicInfo.State != MEM_COMMIT)
228  goto ContinueLoop;
229  if ((basicInfo.Type & memoryTypeMask) == 0)
230  goto ContinueLoop;
231  if (basicInfo.Protect == PAGE_NOACCESS)
232  goto ContinueLoop;
233  if (basicInfo.Protect & PAGE_GUARD)
234  goto ContinueLoop;
235 
236  readSize = basicInfo.RegionSize;
237 
238  if (basicInfo.RegionSize > bufferSize)
239  {
240  // Don't allocate a huge buffer though.
241  if (basicInfo.RegionSize <= 16 * 1024 * 1024) // 16 MB
242  {
243  PhFreePage(buffer);
244  bufferSize = basicInfo.RegionSize;
245  buffer = PhAllocatePage(bufferSize, NULL);
246 
247  if (!buffer)
248  break;
249  }
250  else
251  {
252  readSize = bufferSize;
253  }
254  }
255 
256  for (offset = 0; offset < basicInfo.RegionSize; offset += readSize)
257  {
258  ULONG_PTR i;
259  UCHAR byte; // current byte
260  UCHAR byte1; // previous byte
261  UCHAR byte2; // byte before previous byte
262  BOOLEAN printable;
263  BOOLEAN printable1;
264  BOOLEAN printable2;
265  ULONG length;
266 
268  ProcessHandle,
269  PTR_ADD_OFFSET(baseAddress, offset),
270  buffer,
271  readSize,
272  NULL
273  )))
274  continue;
275 
276  byte1 = 0;
277  byte2 = 0;
278  printable1 = FALSE;
279  printable2 = FALSE;
280  length = 0;
281 
282  for (i = 0; i < readSize; i++)
283  {
284  byte = buffer[i];
285  printable = PhCharIsPrintable[byte];
286 
287  // To find strings Process Hacker uses a state table.
288  // * byte2 - byte before previous byte
289  // * byte1 - previous byte
290  // * byte - current byte
291  // * length - length of current string run
292  //
293  // The states are described below.
294  //
295  // [byte2] [byte1] [byte] ...
296  // [char] means printable, [oth] means non-printable.
297  //
298  // 1. [char] [char] [char] ...
299  // (we're in a non-wide sequence)
300  // -> append char.
301  // 2. [char] [char] [oth] ...
302  // (we reached the end of a non-wide sequence, or we need to start a wide sequence)
303  // -> if current string is big enough, create result (non-wide).
304  // otherwise if byte = null, reset to new string with byte1 as first character.
305  // otherwise if byte != null, reset to new string.
306  // 3. [char] [oth] [char] ...
307  // (we're in a wide sequence)
308  // -> (byte1 should = null) append char.
309  // 4. [char] [oth] [oth] ...
310  // (we reached the end of a wide sequence)
311  // -> (byte1 should = null) if the current string is big enough, create result (wide).
312  // otherwise, reset to new string.
313  // 5. [oth] [char] [char] ...
314  // (we reached the end of a wide sequence, or we need to start a non-wide sequence)
315  // -> (excluding byte1) if the current string is big enough, create result (wide).
316  // otherwise, reset to new string with byte1 as first character and byte as
317  // second character.
318  // 6. [oth] [char] [oth] ...
319  // (we're in a wide sequence)
320  // -> (byte2 and byte should = null) do nothing.
321  // 7. [oth] [oth] [char] ...
322  // (we're starting a sequence, but we don't know if it's a wide or non-wide sequence)
323  // -> append char.
324  // 8. [oth] [oth] [oth] ...
325  // (nothing)
326  // -> do nothing.
327 
328  if (printable2 && printable1 && printable)
329  {
330  if (length < displayBufferCount)
331  displayBuffer[length] = byte;
332 
333  length++;
334  }
335  else if (printable2 && printable1 && !printable)
336  {
337  if (length >= minimumLength)
338  {
339  goto CreateResult;
340  }
341  else if (byte == 0)
342  {
343  length = 1;
344  displayBuffer[0] = byte1;
345  }
346  else
347  {
348  length = 0;
349  }
350  }
351  else if (printable2 && !printable1 && printable)
352  {
353  if (byte1 == 0)
354  {
355  if (length < displayBufferCount)
356  displayBuffer[length] = byte;
357 
358  length++;
359  }
360  }
361  else if (printable2 && !printable1 && !printable)
362  {
363  if (length >= minimumLength)
364  {
365  goto CreateResult;
366  }
367  else
368  {
369  length = 0;
370  }
371  }
372  else if (!printable2 && printable1 && printable)
373  {
374  if (length >= minimumLength + 1) // length - 1 >= minimumLength but avoiding underflow
375  {
376  length--; // exclude byte1
377  goto CreateResult;
378  }
379  else
380  {
381  length = 2;
382  displayBuffer[0] = byte1;
383  displayBuffer[1] = byte;
384  }
385  }
386  else if (!printable2 && printable1 && !printable)
387  {
388  // Nothing
389  }
390  else if (!printable2 && !printable1 && printable)
391  {
392  if (length < displayBufferCount)
393  displayBuffer[length] = byte;
394 
395  length++;
396  }
397  else if (!printable2 && !printable1 && !printable)
398  {
399  // Nothing
400  }
401 
402  goto AfterCreateResult;
403 
404 CreateResult:
405  {
406  PPH_MEMORY_RESULT result;
407  ULONG lengthInBytes;
408  ULONG bias;
409  BOOLEAN isWide;
410  ULONG displayLength;
411 
412  lengthInBytes = length;
413  bias = 0;
414  isWide = FALSE;
415 
416  if (printable1 == printable) // determine if string was wide (refer to state table, 4 and 5)
417  {
418  isWide = TRUE;
419  lengthInBytes *= 2;
420  }
421 
422  if (printable) // byte1 excluded (refer to state table, 5)
423  {
424  bias = 1;
425  }
426 
427  if (!(isWide && !detectUnicode) && (result = PhCreateMemoryResult(
428  PTR_ADD_OFFSET(baseAddress, i - bias - lengthInBytes),
429  lengthInBytes
430  )))
431  {
432  displayLength = (ULONG)(min(length, displayBufferCount) * sizeof(WCHAR));
433 
434  if (result->Display.Buffer = PhAllocateForMemorySearch(displayLength + sizeof(WCHAR)))
435  {
436  memcpy(result->Display.Buffer, displayBuffer, displayLength);
437  result->Display.Buffer[displayLength / sizeof(WCHAR)] = 0;
438  result->Display.Length = displayLength;
439  }
440 
441  Options->Header.Callback(
442  result,
443  Options->Header.Context
444  );
445  }
446 
447  length = 0;
448  }
449 AfterCreateResult:
450 
451  byte2 = byte1;
452  byte1 = byte;
453  printable2 = printable1;
454  printable1 = printable;
455  }
456  }
457 
458 ContinueLoop:
459  baseAddress = PTR_ADD_OFFSET(baseAddress, basicInfo.RegionSize);
460  }
461 
462  if (buffer)
463  PhFreePage(buffer);
464 }
465 
467  _In_ HWND ParentWindowHandle,
468  _In_ PPH_PROCESS_ITEM ProcessItem
469  )
470 {
471  NTSTATUS status;
472  HANDLE processHandle;
473  MEMORY_STRING_CONTEXT context;
474  PPH_SHOWMEMORYRESULTS showMemoryResults;
475 
476  if (!NT_SUCCESS(status = PhOpenProcess(
477  &processHandle,
479  ProcessItem->ProcessId
480  )))
481  {
482  PhShowStatus(ParentWindowHandle, L"Unable to open the process", status, 0);
483  return;
484  }
485 
486  memset(&context, 0, sizeof(MEMORY_STRING_CONTEXT));
487  context.ProcessId = ProcessItem->ProcessId;
488  context.ProcessHandle = processHandle;
489 
490  if (DialogBoxParam(
492  MAKEINTRESOURCE(IDD_MEMSTRING),
493  ParentWindowHandle,
495  (LPARAM)&context
496  ) != IDOK)
497  {
498  NtClose(processHandle);
499  return;
500  }
501 
502  context.Results = PhCreateList(1024);
503 
504  if (DialogBoxParam(
506  MAKEINTRESOURCE(IDD_PROGRESS),
507  ParentWindowHandle,
509  (LPARAM)&context
510  ) == IDOK)
511  {
512  showMemoryResults = PhAllocate(sizeof(PH_SHOWMEMORYRESULTS));
513  showMemoryResults->ProcessId = ProcessItem->ProcessId;
514  showMemoryResults->Results = context.Results;
515 
516  PhReferenceObject(context.Results);
519  showMemoryResults
520  );
521  }
522 
523  PhDereferenceObject(context.Results);
524  NtClose(processHandle);
525 }
526 
527 INT_PTR CALLBACK PhpMemoryStringDlgProc(
528  _In_ HWND hwndDlg,
529  _In_ UINT uMsg,
530  _In_ WPARAM wParam,
531  _In_ LPARAM lParam
532  )
533 {
534  switch (uMsg)
535  {
536  case WM_INITDIALOG:
537  {
538  SetProp(hwndDlg, PhMakeContextAtom(), (HANDLE)lParam);
539  SetDlgItemText(hwndDlg, IDC_MINIMUMLENGTH, L"10");
540  Button_SetCheck(GetDlgItem(hwndDlg, IDC_DETECTUNICODE), BST_CHECKED);
541  Button_SetCheck(GetDlgItem(hwndDlg, IDC_PRIVATE), BST_CHECKED);
542  }
543  break;
544  case WM_DESTROY:
545  {
546  RemoveProp(hwndDlg, PhMakeContextAtom());
547  }
548  break;
549  case WM_COMMAND:
550  {
551  switch (LOWORD(wParam))
552  {
553  case IDCANCEL:
554  EndDialog(hwndDlg, IDCANCEL);
555  break;
556  case IDOK:
557  {
558  PMEMORY_STRING_CONTEXT context = (PMEMORY_STRING_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom());
559  ULONG64 minimumLength = 10;
560 
561  PhStringToInteger64(&PhaGetDlgItemText(hwndDlg, IDC_MINIMUMLENGTH)->sr, 0, &minimumLength);
562 
563  if (minimumLength < 4)
564  {
565  PhShowError(hwndDlg, L"The minimum length must be at least 4.");
566  break;
567  }
568 
569  context->MinimumLength = (ULONG)minimumLength;
570  context->DetectUnicode = Button_GetCheck(GetDlgItem(hwndDlg, IDC_DETECTUNICODE)) == BST_CHECKED;
571  context->Private = Button_GetCheck(GetDlgItem(hwndDlg, IDC_PRIVATE)) == BST_CHECKED;
572  context->Image = Button_GetCheck(GetDlgItem(hwndDlg, IDC_IMAGE)) == BST_CHECKED;
573  context->Mapped = Button_GetCheck(GetDlgItem(hwndDlg, IDC_MAPPED)) == BST_CHECKED;
574 
575  EndDialog(hwndDlg, IDOK);
576  }
577  break;
578  }
579  }
580  break;
581  }
582 
583  return FALSE;
584 }
585 
586 static BOOL NTAPI PhpMemoryStringResultCallback(
587  _In_ _Assume_refs_(1) PPH_MEMORY_RESULT Result,
588  _In_opt_ PVOID Context
589  )
590 {
591  PMEMORY_STRING_CONTEXT context = Context;
592 
593  PhAddItemList(context->Results, Result);
594 
595  return TRUE;
596 }
597 
599  _In_ PVOID Parameter
600  )
601 {
602  PMEMORY_STRING_CONTEXT context = Parameter;
603 
604  context->Options.Header.Callback = PhpMemoryStringResultCallback;
605  context->Options.Header.Context = context;
606  context->Options.MinimumLength = context->MinimumLength;
607  context->Options.DetectUnicode = context->DetectUnicode;
608 
609  if (context->Private)
610  context->Options.MemoryTypeMask |= MEM_PRIVATE;
611  if (context->Image)
612  context->Options.MemoryTypeMask |= MEM_IMAGE;
613  if (context->Mapped)
614  context->Options.MemoryTypeMask |= MEM_MAPPED;
615 
616  PhSearchMemoryString(context->ProcessHandle, &context->Options);
617 
618  SendMessage(
619  context->WindowHandle,
622  0
623  );
624 
625  return STATUS_SUCCESS;
626 }
627 
629  _In_ HWND hwndDlg,
630  _In_ UINT uMsg,
631  _In_ WPARAM wParam,
632  _In_ LPARAM lParam
633  )
634 {
635  switch (uMsg)
636  {
637  case WM_INITDIALOG:
638  {
640 
641  PhCenterWindow(hwndDlg, GetParent(hwndDlg));
642  SetProp(hwndDlg, PhMakeContextAtom(), (HANDLE)context);
643 
644  SetDlgItemText(hwndDlg, IDC_PROGRESSTEXT, L"Searching...");
645 
646  PhSetWindowStyle(GetDlgItem(hwndDlg, IDC_PROGRESS), PBS_MARQUEE, PBS_MARQUEE);
647  SendMessage(GetDlgItem(hwndDlg, IDC_PROGRESS), PBM_SETMARQUEE, TRUE, 75);
648 
649  context->WindowHandle = hwndDlg;
650  context->ThreadHandle = PhCreateThread(0, PhpMemoryStringThreadStart, context);
651 
652  if (!context->ThreadHandle)
653  {
654  PhShowStatus(hwndDlg, L"Unable to create the search thread", 0, GetLastError());
655  EndDialog(hwndDlg, IDCANCEL);
656  return FALSE;
657  }
658 
659  SetTimer(hwndDlg, 1, 500, NULL);
660  }
661  break;
662  case WM_DESTROY:
663  {
664  PMEMORY_STRING_CONTEXT context;
665 
666  context = (PMEMORY_STRING_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom());
667 
668  if (context->ThreadHandle)
669  NtClose(context->ThreadHandle);
670 
671  RemoveProp(hwndDlg, PhMakeContextAtom());
672  }
673  break;
674  case WM_COMMAND:
675  {
676  switch (LOWORD(wParam))
677  {
678  case IDCANCEL:
679  {
680  PMEMORY_STRING_CONTEXT context =
681  (PMEMORY_STRING_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom());
682 
683  EnableWindow(GetDlgItem(hwndDlg, IDCANCEL), FALSE);
684  context->Options.Header.Cancel = TRUE;
685  }
686  break;
687  }
688  }
689  break;
690  case WM_TIMER:
691  {
692  if (wParam == 1)
693  {
694  PMEMORY_STRING_CONTEXT context =
695  (PMEMORY_STRING_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom());
696  PPH_STRING progressText;
697  PPH_STRING numberText;
698 
699  numberText = PhFormatUInt64(context->Results->Count, TRUE);
700  progressText = PhFormatString(L"%s strings found...", numberText->Buffer);
701  PhDereferenceObject(numberText);
702  SetDlgItemText(hwndDlg, IDC_PROGRESSTEXT, progressText->Buffer);
703  PhDereferenceObject(progressText);
704  InvalidateRect(GetDlgItem(hwndDlg, IDC_PROGRESSTEXT), NULL, FALSE);
705  }
706  }
707  break;
709  {
710  PMEMORY_STRING_CONTEXT context;
711 
712  context = (PMEMORY_STRING_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom());
713 
714  switch (wParam)
715  {
716  case PH_SEARCH_COMPLETED:
717  EndDialog(hwndDlg, IDOK);
718  break;
719  }
720  }
721  break;
722  }
723 
724  return FALSE;
725 }