Process Hacker
memedit.c
Go to the documentation of this file.
1 /*
2  * Process Hacker -
3  * memory editor window
4  *
5  * Copyright (C) 2010-2015 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 <settings.h>
25 #include <hexedit.h>
26 #include <windowsx.h>
27 
28 #define WM_PH_SELECT_OFFSET (WM_APP + 300)
29 
30 typedef struct _MEMORY_EDITOR_CONTEXT
31 {
32  PH_AVL_LINKS Links;
33  union
34  {
35  struct
36  {
37  HANDLE ProcessId;
38  PVOID BaseAddress;
39  SIZE_T RegionSize;
40  };
41  ULONG_PTR Key[3];
42  };
43  HANDLE ProcessHandle;
44  HWND WindowHandle;
45  PH_LAYOUT_MANAGER LayoutManager;
46  HWND HexEditHandle;
47  PUCHAR Buffer;
48  ULONG SelectOffset;
49  PPH_STRING Title;
50  ULONG Flags;
51 
52  BOOLEAN LoadCompleted;
54 
56  _In_ PPH_AVL_LINKS Links1,
58  );
59 
60 INT_PTR CALLBACK PhpMemoryEditorDlgProc(
61  _In_ HWND hwndDlg,
62  _In_ UINT uMsg,
63  _In_ WPARAM wParam,
64  _In_ LPARAM lParam
65  );
66 
68 static RECT MinimumSize = { -1, -1, -1, -1 };
69 
71  _In_ HANDLE ProcessId,
72  _In_ PVOID BaseAddress,
73  _In_ SIZE_T RegionSize,
74  _In_ ULONG SelectOffset,
75  _In_ ULONG SelectLength,
76  _In_opt_ PPH_STRING Title,
77  _In_ ULONG Flags
78  )
79 {
80  PMEMORY_EDITOR_CONTEXT context;
81  MEMORY_EDITOR_CONTEXT lookupContext;
82  PPH_AVL_LINKS links;
83 
84  lookupContext.ProcessId = ProcessId;
85  lookupContext.BaseAddress = BaseAddress;
86  lookupContext.RegionSize = RegionSize;
87 
88  links = PhFindElementAvlTree(&PhMemoryEditorSet, &lookupContext.Links);
89 
90  if (!links)
91  {
92  context = PhAllocate(sizeof(MEMORY_EDITOR_CONTEXT));
93  memset(context, 0, sizeof(MEMORY_EDITOR_CONTEXT));
94 
95  context->ProcessId = ProcessId;
96  context->BaseAddress = BaseAddress;
97  context->RegionSize = RegionSize;
98  context->SelectOffset = SelectOffset;
99  PhSwapReference(&context->Title, Title);
100  context->Flags = Flags;
101 
102  context->WindowHandle = CreateDialogParam(
104  MAKEINTRESOURCE(IDD_MEMEDIT),
105  NULL,
107  (LPARAM)context
108  );
109 
110  if (!context->LoadCompleted)
111  {
112  DestroyWindow(context->WindowHandle);
113  return;
114  }
115 
116  if (SelectOffset != -1)
117  PostMessage(context->WindowHandle, WM_PH_SELECT_OFFSET, SelectOffset, SelectLength);
118 
119  PhRegisterDialog(context->WindowHandle);
120  PhAddElementAvlTree(&PhMemoryEditorSet, &context->Links);
121 
122  ShowWindow(context->WindowHandle, SW_SHOW);
123  }
124  else
125  {
126  context = CONTAINING_RECORD(links, MEMORY_EDITOR_CONTEXT, Links);
127 
128  if (IsIconic(context->WindowHandle))
129  ShowWindow(context->WindowHandle, SW_RESTORE);
130  else
131  SetForegroundWindow(context->WindowHandle);
132 
133  if (SelectOffset != -1)
134  PostMessage(context->WindowHandle, WM_PH_SELECT_OFFSET, SelectOffset, SelectLength);
135 
136  // Just in case.
137  if ((Flags & PH_MEMORY_EDITOR_UNMAP_VIEW_OF_SECTION) && ProcessId == NtCurrentProcessId())
138  NtUnmapViewOfSection(NtCurrentProcess(), BaseAddress);
139  }
140 }
141 
143  _In_ PPH_AVL_LINKS Links1,
144  _In_ PPH_AVL_LINKS Links2
145  )
146 {
147  PMEMORY_EDITOR_CONTEXT context1 = CONTAINING_RECORD(Links1, MEMORY_EDITOR_CONTEXT, Links);
148  PMEMORY_EDITOR_CONTEXT context2 = CONTAINING_RECORD(Links2, MEMORY_EDITOR_CONTEXT, Links);
149 
150  return memcmp(context1->Key, context2->Key, sizeof(context1->Key));
151 }
152 
153 INT_PTR CALLBACK PhpMemoryEditorDlgProc(
154  _In_ HWND hwndDlg,
155  _In_ UINT uMsg,
156  _In_ WPARAM wParam,
157  _In_ LPARAM lParam
158  )
159 {
160  PMEMORY_EDITOR_CONTEXT context;
161 
162  if (uMsg != WM_INITDIALOG)
163  {
164  context = GetProp(hwndDlg, PhMakeContextAtom());
165  }
166  else
167  {
168  context = (PMEMORY_EDITOR_CONTEXT)lParam;
169  SetProp(hwndDlg, PhMakeContextAtom(), (HANDLE)context);
170  }
171 
172  if (!context)
173  return FALSE;
174 
175  switch (uMsg)
176  {
177  case WM_INITDIALOG:
178  {
179  NTSTATUS status;
180 
181  if (context->Title)
182  {
183  SetWindowText(hwndDlg, context->Title->Buffer);
184  }
185  else
186  {
187  PPH_PROCESS_ITEM processItem;
188 
189  if (processItem = PhReferenceProcessItem(context->ProcessId))
190  {
191  SetWindowText(hwndDlg, PhaFormatString(L"%s (%u) (0x%Ix - 0x%Ix)",
192  processItem->ProcessName->Buffer, (ULONG)context->ProcessId,
193  context->BaseAddress, (ULONG_PTR)context->BaseAddress + context->RegionSize)->Buffer);
194  PhDereferenceObject(processItem);
195  }
196  }
197 
198  PhInitializeLayoutManager(&context->LayoutManager, hwndDlg);
199 
200  if (context->RegionSize > 1024 * 1024 * 1024) // 1 GB
201  {
202  PhShowError(NULL, L"Unable to edit the memory region because it is too large.");
203  return TRUE;
204  }
205 
206  if (!NT_SUCCESS(status = PhOpenProcess(
207  &context->ProcessHandle,
209  context->ProcessId
210  )))
211  {
212  if (!NT_SUCCESS(status = PhOpenProcess(
213  &context->ProcessHandle,
215  context->ProcessId
216  )))
217  {
218  PhShowStatus(NULL, L"Unable to open the process", status, 0);
219  return TRUE;
220  }
221  }
222 
223  context->Buffer = PhAllocatePage(context->RegionSize, NULL);
224 
225  if (!context->Buffer)
226  {
227  PhShowError(NULL, L"Unable to allocate memory for the buffer.");
228  return TRUE;
229  }
230 
231  if (!NT_SUCCESS(status = PhReadVirtualMemory(
232  context->ProcessHandle,
233  context->BaseAddress,
234  context->Buffer,
235  context->RegionSize,
236  NULL
237  )))
238  {
239  PhShowStatus(PhMainWndHandle, L"Unable to read memory", status, 0);
240  return TRUE;
241  }
242 
243  PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDOK), NULL,
245  PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_SAVE), NULL,
247  PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_BYTESPERROW), NULL,
249  PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_GOTO), NULL,
251  PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_WRITE), NULL,
253  PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_REREAD), NULL,
255 
256  if (MinimumSize.left == -1)
257  {
258  RECT rect;
259 
260  rect.left = 0;
261  rect.top = 0;
262  rect.right = 290;
263  rect.bottom = 140;
264  MapDialogRect(hwndDlg, &rect);
265  MinimumSize = rect;
266  MinimumSize.left = 0;
267  }
268 
269  context->HexEditHandle = GetDlgItem(hwndDlg, IDC_MEMORY);
270  PhAddLayoutItem(&context->LayoutManager, context->HexEditHandle, NULL, PH_ANCHOR_ALL);
271  HexEdit_SetBuffer(context->HexEditHandle, context->Buffer, (ULONG)context->RegionSize);
272 
273  {
274  PH_RECTANGLE windowRectangle;
275 
276  windowRectangle.Position = PhGetIntegerPairSetting(L"MemEditPosition");
277  windowRectangle.Size = PhGetIntegerPairSetting(L"MemEditSize");
278  PhAdjustRectangleToWorkingArea(hwndDlg, &windowRectangle);
279 
280  MoveWindow(hwndDlg, windowRectangle.Left, windowRectangle.Top,
281  windowRectangle.Width, windowRectangle.Height, FALSE);
282 
283  // Implement cascading by saving an offsetted rectangle.
284  windowRectangle.Left += 20;
285  windowRectangle.Top += 20;
286 
287  PhSetIntegerPairSetting(L"MemEditPosition", windowRectangle.Position);
288  PhSetIntegerPairSetting(L"MemEditSize", windowRectangle.Size);
289  }
290 
291  {
292  PWSTR bytesPerRowStrings[7];
293  ULONG i;
294  ULONG bytesPerRow;
295 
296  for (i = 0; i < sizeof(bytesPerRowStrings) / sizeof(PWSTR); i++)
297  bytesPerRowStrings[i] = PhaFormatString(L"%u bytes per row", 1 << (2 + i))->Buffer;
298 
299  PhAddComboBoxStrings(GetDlgItem(hwndDlg, IDC_BYTESPERROW),
300  bytesPerRowStrings, sizeof(bytesPerRowStrings) / sizeof(PWSTR));
301 
302  bytesPerRow = PhGetIntegerSetting(L"MemEditBytesPerRow");
303 
304  if (bytesPerRow >= 4)
305  {
306  HexEdit_SetBytesPerRow(context->HexEditHandle, bytesPerRow);
307  PhSelectComboBoxString(GetDlgItem(hwndDlg, IDC_BYTESPERROW),
308  PhaFormatString(L"%u bytes per row", bytesPerRow)->Buffer, FALSE);
309  }
310  }
311 
312  context->LoadCompleted = TRUE;
313  }
314  break;
315  case WM_DESTROY:
316  {
317  if (context->LoadCompleted)
318  {
319  PhSaveWindowPlacementToSetting(L"MemEditPosition", L"MemEditSize", hwndDlg);
320  PhRemoveElementAvlTree(&PhMemoryEditorSet, &context->Links);
321  PhUnregisterDialog(hwndDlg);
322  }
323 
324  RemoveProp(hwndDlg, PhMakeContextAtom());
325 
326  PhDeleteLayoutManager(&context->LayoutManager);
327 
328  if (context->Buffer) PhFreePage(context->Buffer);
329  if (context->ProcessHandle) NtClose(context->ProcessHandle);
330  PhClearReference(&context->Title);
331 
332  if ((context->Flags & PH_MEMORY_EDITOR_UNMAP_VIEW_OF_SECTION) && context->ProcessId == NtCurrentProcessId())
333  NtUnmapViewOfSection(NtCurrentProcess(), context->BaseAddress);
334 
335  PhFree(context);
336  }
337  break;
338  case WM_SHOWWINDOW:
339  {
340  SendMessage(hwndDlg, WM_NEXTDLGCTL, (WPARAM)context->HexEditHandle, TRUE);
341  }
342  break;
343  case WM_COMMAND:
344  {
345  switch (LOWORD(wParam))
346  {
347  case IDCANCEL:
348  case IDOK:
349  DestroyWindow(hwndDlg);
350  break;
351  case IDC_SAVE:
352  {
353  static PH_FILETYPE_FILTER filters[] =
354  {
355  { L"Binary files (*.bin)", L"*.bin" },
356  { L"All files (*.*)", L"*.*" }
357  };
358  PVOID fileDialog;
359  PPH_PROCESS_ITEM processItem;
360 
361  fileDialog = PhCreateSaveFileDialog();
362 
363  PhSetFileDialogFilter(fileDialog, filters, sizeof(filters) / sizeof(PH_FILETYPE_FILTER));
364 
365  if (!context->Title && (processItem = PhReferenceProcessItem(context->ProcessId)))
366  {
367  PhSetFileDialogFileName(fileDialog,
368  PhaFormatString(L"%s_0x%Ix-0x%Ix.bin", processItem->ProcessName->Buffer,
369  context->BaseAddress, context->RegionSize)->Buffer);
370  PhDereferenceObject(processItem);
371  }
372  else
373  {
374  PhSetFileDialogFileName(fileDialog, L"Memory.bin");
375  }
376 
377  if (PhShowFileDialog(hwndDlg, fileDialog))
378  {
379  NTSTATUS status;
380  PPH_STRING fileName;
381  PPH_FILE_STREAM fileStream;
382 
383  fileName = PhGetFileDialogFileName(fileDialog);
384  PhAutoDereferenceObject(fileName);
385 
386  if (NT_SUCCESS(status = PhCreateFileStream(
387  &fileStream,
388  fileName->Buffer,
389  FILE_GENERIC_WRITE,
390  FILE_SHARE_READ,
392  0
393  )))
394  {
395  status = PhWriteFileStream(fileStream, context->Buffer, (ULONG)context->RegionSize);
396  PhDereferenceObject(fileStream);
397  }
398 
399  if (!NT_SUCCESS(status))
400  PhShowStatus(hwndDlg, L"Unable to create the file", status, 0);
401  }
402 
403  PhFreeFileDialog(fileDialog);
404  }
405  break;
406  case IDC_GOTO:
407  {
408  PPH_STRING selectedChoice = NULL;
409 
410  while (PhaChoiceDialog(
411  hwndDlg,
412  L"Go to Offset",
413  L"Enter an offset:",
414  NULL,
415  0,
416  NULL,
418  &selectedChoice,
419  NULL,
420  L"MemEditGotoChoices"
421  ))
422  {
423  ULONG64 offset;
424 
425  if (selectedChoice->Length == 0)
426  continue;
427 
428  if (PhStringToInteger64(&selectedChoice->sr, 0, &offset))
429  {
430  if (offset >= context->RegionSize)
431  {
432  PhShowError(hwndDlg, L"The offset is too large.");
433  continue;
434  }
435 
436  SendMessage(hwndDlg, WM_NEXTDLGCTL, (WPARAM)context->HexEditHandle, TRUE);
437  HexEdit_SetSel(context->HexEditHandle, (LONG)offset, (LONG)offset);
438  break;
439  }
440  }
441  }
442  break;
443  case IDC_WRITE:
444  {
445  NTSTATUS status;
446 
447  if (!NT_SUCCESS(status = PhWriteVirtualMemory(
448  context->ProcessHandle,
449  context->BaseAddress,
450  context->Buffer,
451  context->RegionSize,
452  NULL
453  )))
454  {
455  PhShowStatus(hwndDlg, L"Unable to write memory", status, 0);
456  }
457  }
458  break;
459  case IDC_REREAD:
460  {
461  NTSTATUS status;
462 
463  if (!NT_SUCCESS(status = PhReadVirtualMemory(
464  context->ProcessHandle,
465  context->BaseAddress,
466  context->Buffer,
467  context->RegionSize,
468  NULL
469  )))
470  {
471  PhShowStatus(hwndDlg, L"Unable to read memory", status, 0);
472  }
473 
474  InvalidateRect(context->HexEditHandle, NULL, TRUE);
475  }
476  break;
477  case IDC_BYTESPERROW:
478  if (HIWORD(wParam) == CBN_SELCHANGE)
479  {
480  PPH_STRING bytesPerRowString = PhaGetDlgItemText(hwndDlg, IDC_BYTESPERROW);
481  PH_STRINGREF firstPart;
482  PH_STRINGREF secondPart;
483  ULONG64 bytesPerRow64;
484 
485  if (PhSplitStringRefAtChar(&bytesPerRowString->sr, ' ', &firstPart, &secondPart))
486  {
487  if (PhStringToInteger64(&firstPart, 10, &bytesPerRow64))
488  {
489  PhSetIntegerSetting(L"MemEditBytesPerRow", (ULONG)bytesPerRow64);
490  HexEdit_SetBytesPerRow(context->HexEditHandle, (ULONG)bytesPerRow64);
491  SendMessage(hwndDlg, WM_NEXTDLGCTL, (WPARAM)context->HexEditHandle, TRUE);
492  }
493  }
494  }
495  break;
496  }
497  }
498  break;
499  case WM_SIZE:
500  {
501  PhLayoutManagerLayout(&context->LayoutManager);
502  }
503  break;
504  case WM_SIZING:
505  {
506  PhResizingMinimumSize((PRECT)lParam, wParam, MinimumSize.right, MinimumSize.bottom);
507  }
508  break;
509  case WM_PH_SELECT_OFFSET:
510  {
511  HexEdit_SetEditMode(context->HexEditHandle, EDIT_ASCII);
512  HexEdit_SetSel(context->HexEditHandle, (ULONG)wParam, (ULONG)wParam + (ULONG)lParam);
513  }
514  break;
515  }
516 
517  return FALSE;
518 }