├── LICENSE ├── PSMemory.Format.ps1xml ├── PSMemory.Types.ps1xml ├── PSMemory.psd1 ├── PSMemory.psm1 └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, Tobias Heilig 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /PSMemory.Format.ps1xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Default 6 | 7 | PSMemory.Reference 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 15 | 16 | 17 | 18 | 19 | 20 | 21 | 10 22 | 23 | 24 | 25 | 14 26 | 27 | 28 | 29 | 8 30 | right 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | '0x' + $_.Address.ToString('X16') 39 | 40 | 41 | 42 | Value 43 | 44 | 45 | Type 46 | 47 | 48 | Protection 49 | 50 | 51 | Page 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /PSMemory.Types.ps1xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | PSMemory.Reference 5 | 6 | 7 | RegionEnd 8 | 9 | $this.RegionStart + $this.RegionSize 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /PSMemory.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | RootModule = 'PSMemory.psm1' 3 | ModuleVersion = '1.1.0' 4 | GUID = '54d44ebf-3b85-428e-9030-a6b7581a50c2' 5 | Author = 'Tobias Heilig' 6 | Copyright = '3-Clause BSD Copyright 2019 Tobias Heilig' 7 | Description = 'Windows 64 Bit Memory Scanner' 8 | FunctionsToExport = @('Format-Memory','Search-Memory','Compare-Memory','Update-Memory') 9 | FormatsToProcess = @('PSMemory.Format.ps1xml') 10 | TypesToProcess = @('PSMemory.Types.ps1xml') 11 | } 12 | -------------------------------------------------------------------------------- /PSMemory.psm1: -------------------------------------------------------------------------------- 1 | # BSD 3-Clause License 2 | # 3 | # Copyright(c) 2019, Tobias Heilig 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions are met: 8 | # 9 | # 1. Redistributions of source code must retain the above copynotice, this 10 | # list of conditions and the following disclaimer. 11 | # 12 | # 2. Redistributions in binary form must reproduce the above copynotice, 13 | # this list of conditions and the following disclaimer in the documentation 14 | # and/or other materials provided with the distribution. 15 | # 16 | # 3. Neither the name of the copyholder nor the names of its 17 | # contributors may be used to endorse or promote products derived from 18 | # this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYHOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | # DISCLAIMED. IN NO EVENT SHALL THE COPYHOLDER OR CONTRIBUTORS BE LIABLE 24 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | 32 | try { 33 | & { 34 | $ErrorActionPreference = 'Stop' 35 | [void] [PSMemory.Native] 36 | } 37 | } catch { 38 | Add-Type -TypeDefinition @" 39 | using System; 40 | using System.Runtime.InteropServices; 41 | namespace PSMemory { 42 | public class Native { 43 | [StructLayout(LayoutKind.Sequential)] 44 | public struct SYSTEM_INFO { 45 | public ushort wProcessorArchitecture; 46 | public ushort wReserved; 47 | public uint dwPageSize; 48 | public IntPtr lpMinimumApplicationAddress; 49 | public IntPtr lpMaximumApplicationAddress; 50 | public UIntPtr dwActiveProcessorMask; 51 | public uint dwNumberOfProcessors; 52 | public uint dwProcessorType; 53 | public uint dwAllocationGranularity; 54 | public ushort wProcessorLevel; 55 | public ushort wProcessorRevision; 56 | }; 57 | 58 | [StructLayout(LayoutKind.Sequential)] 59 | public struct MEMORY_BASIC_INFORMATION64 { 60 | public ulong BaseAddress; 61 | public ulong AllocationBase; 62 | public int AllocationProtect; 63 | public int __alignment1; 64 | public ulong RegionSize; 65 | public int State; 66 | public int Protect; 67 | public int Type; 68 | public int __alignment2; 69 | } 70 | 71 | [DllImport("kernel32.dll", SetLastError = true)] 72 | public static extern IntPtr OpenProcess( 73 | uint processAccess, 74 | bool bInheritHandle, 75 | int processId); 76 | 77 | [DllImport("kernel32.dll", SetLastError = true)] 78 | public static extern void GetNativeSystemInfo( 79 | ref SYSTEM_INFO lpSystemInfo); 80 | 81 | [DllImport("kernel32.dll", SetLastError = true)] 82 | public static extern int VirtualQueryEx( 83 | IntPtr hProcess, 84 | IntPtr lpAddress, 85 | out MEMORY_BASIC_INFORMATION64 lpBuffer, 86 | uint dwLength); 87 | 88 | [DllImport("kernel32.dll", SetLastError = true)] 89 | public static extern bool ReadProcessMemory( 90 | IntPtr hProcess, 91 | IntPtr lpBaseAddress, 92 | byte[] lpBuffer, 93 | Int32 nSize, 94 | out IntPtr lpNumberOfBytesRead); 95 | 96 | [DllImport("kernel32.dll", SetLastError = true)] 97 | public static extern bool WriteProcessMemory( 98 | IntPtr hProcess, 99 | IntPtr lpBaseAddress, 100 | byte[] lpBuffer, 101 | Int32 nSize, 102 | out IntPtr lpNumberOfBytesWritten); 103 | 104 | [DllImport("kernel32.dll", SetLastError=true)] 105 | public static extern bool CloseHandle( 106 | IntPtr hHandle); 107 | 108 | [DllImport("msvcrt.dll", CallingConvention=CallingConvention.Cdecl)] 109 | public static extern int memcmp( 110 | byte[] b1, 111 | byte[] b2, 112 | long count); 113 | } 114 | } 115 | "@ 116 | } 117 | 118 | 119 | function New-Win32Exception { 120 | param( 121 | $LastWin32Error, 122 | $From 123 | ) 124 | $e = [ComponentModel.Win32Exception]$LastWin32Error 125 | [ComponentModel.Win32Exception]::New( 126 | "$From (0x$($e.HResult.ToString('x8'))): $($e.Message)" 127 | ) 128 | } 129 | 130 | 131 | function Format-Memory { 132 | [Alias('fm')] 133 | [OutputType([PSObject[]])] 134 | [CmdletBinding()] 135 | param( 136 | [Parameter(Mandatory, ValueFromPipeline, Position=0)] 137 | [Microsoft.PowerShell.Commands.GroupInfo] 138 | $Reference 139 | ) 140 | 141 | $Reference | Select-Object -ExpandProperty Group 142 | 143 | <# 144 | .SYNOPSIS 145 | Format memory references 146 | 147 | .DESCRIPTION 148 | Turn a memory reference group object into readable format. 149 | 150 | .PARAMETER Reference 151 | A memory reference Microsoft.PowerShell.Commands.GroupInfo object as returned 152 | by the *-Memory Cmdlets in this module. 153 | 154 | .INPUTS 155 | Microsoft.PowerShell.Commands.GroupInfo 156 | 157 | .OUTPUTS 158 | System.Management.Automation.PSObject 159 | #> 160 | } 161 | 162 | 163 | function Search-Memory { 164 | [Alias('srmem')] 165 | [OutputType([Microsoft.PowerShell.Commands.GroupInfo])] 166 | [CmdletBinding()] 167 | param( 168 | [Parameter(Mandatory, ValueFromPipeline)] 169 | [System.Diagnostics.Process] 170 | $Process, 171 | 172 | [Parameter(Mandatory, Position=0)] 173 | [System.Collections.Hashtable] 174 | $Values 175 | ) 176 | 177 | $searchObjects = foreach ($type in $Values.Keys) { 178 | foreach ($val in $Values[$type]) { 179 | $size, $bytes = switch ($type) { 180 | 'Byte' { 181 | 1, $val 182 | } 183 | 'Short' { 184 | 2, [BitConverter]::GetBytes($val) 185 | } 186 | 'Int' { 187 | 4, [BitConverter]::GetBytes($val) 188 | } 189 | 'Long' { 190 | 8, [BitConverter]::GetBytes($val) 191 | } 192 | 'Bytes' { 193 | $val.Length, $val 194 | } 195 | 'String' { 196 | $val.Length, [Text.Encoding]::ASCII.GetBytes($val) 197 | } 198 | default { 199 | Write-Error -Exception (New-Object System.ArgumentException) ` 200 | -Category InvalidData -TargetObject $Values -ErrorAction Stop ` 201 | -Message "Unsupported value type '$type'. Supported " + ` 202 | "value types are Byte, Short, Int, Long, String and Bytes." 203 | } 204 | } 205 | [PSCustomObject]@{ 206 | value = $val 207 | type = $type 208 | size = $size 209 | bytes = $bytes 210 | } 211 | } 212 | } 213 | 214 | # PROCESS_QUERY_INFORMATION (0x0400) | PROCESS_VM_READ (0x10) 215 | if (($processHandle = [PSMemory.Native]::OpenProcess( 216 | 0x0400 -bor 0x10, 217 | $false, 218 | $Process.Id)) -eq [IntPtr]::Zero 219 | ) { 220 | $e = [Runtime.InteropServices.Marshal]::GetLastWin32Error() 221 | throw New-Win32Exception $e -From OpenProcess 222 | } 223 | 224 | $systemInfo = New-Object PSMemory.Native+SYSTEM_INFO 225 | [PSMemory.Native]::GetNativeSystemInfo([ref]$systemInfo) 226 | $minAddress = [long]$systemInfo.lpMinimumApplicationAddress 227 | $maxAddress = [long]$systemInfo.lpMaximumApplicationAddress 228 | 229 | $memoryInfo = New-Object PSMemory.Native+MEMORY_BASIC_INFORMATION64 230 | $memoryInfoSize = [Runtime.InteropServices.Marshal]::SizeOf($memoryInfo) 231 | 232 | $progressTimer = [System.Diagnostics.Stopwatch]::StartNew() 233 | 234 | $searchResult = ` 235 | for ($baseAddress = $minAddress; $baseAddress -lt $maxAddress; $baseAddress += $memoryRegionSize) { 236 | if ([PSMemory.Native]::VirtualQueryEx( 237 | $processHandle, 238 | $baseAddress, 239 | [ref]$memoryInfo, 240 | $memoryInfoSize) -eq 0 241 | ) { 242 | $e = [Runtime.InteropServices.Marshal]::GetLastWin32Error() 243 | throw New-Win32Exception $e -From VirtualQueryEx 244 | } 245 | 246 | $memoryRegionSize = [long]$memoryInfo.RegionSize 247 | 248 | if ($progressTimer.Elapsed.TotalMilliseconds -ge 500) { 249 | $percentComplete = ($baseAddress-$minAddress)/($maxAddress-$minAddress)*100 250 | Write-Progress -Activity 'Searching Virtual Address Space' ` 251 | -Status "$baseAddress/$maxAddress" -PercentComplete $percentComplete 252 | $progressTimer.Restart() 253 | } 254 | 255 | # PAGE_GUARD (0x100) 256 | if ($memoryInfo.Protect -band 0x100) { 257 | continue 258 | } 259 | 260 | # MEM_COMMIT (0x1000) 261 | # PAGE_READWRITE (0x04) | PAGE_WRITECOPY (0x08) | PAGE_EXECUTE_READWRITE (0x40) | PAGE_EXECUTE_WRITECOPY (0x80) 262 | if ($memoryInfo.State -band 0x1000 -and 263 | $memoryInfo.Protect -band (0x04 -bor 0x08 -bor 0x40 -bor 0x80) 264 | ) { 265 | $buffer = [byte[]]::New($memoryRegionSize) 266 | if ([PSMemory.Native]::ReadProcessMemory( 267 | $processHandle, 268 | $baseAddress, 269 | $buffer, 270 | $memoryRegionSize, 271 | [ref][IntPtr]::Zero) -eq 0 272 | ) { 273 | $e = [Runtime.InteropServices.Marshal]::GetLastWin32Error() 274 | throw New-Win32Exception $e -From ReadProcessMemory 275 | } 276 | 277 | foreach ($obj in $searchObjects) { 278 | $offset = -1 279 | while (($offset = [array]::IndexOf($buffer, $obj.bytes[0], $offset+1)) -ge 0) { 280 | if ([PSMemory.Native]::memcmp( 281 | $buffer[$offset..($offset+$obj.size-1)], 282 | $obj.bytes, 283 | $obj.size) -eq 0 284 | ) { 285 | $match = [PSCustomObject]@{ 286 | ProcessName = $Process.Name 287 | ProcessId = $Process.Id 288 | Address = $baseAddress + $offset 289 | Value = $obj.value 290 | Type = $obj.type 291 | Size = $obj.size 292 | Page = switch ($memoryInfo.Type) { 293 | 0x1000000 { 294 | 'Image' 295 | } 296 | 0x40000 { 297 | 'Mapped' 298 | } 299 | 0x20000 { 300 | 'Private' 301 | } 302 | } 303 | Protection = switch ($memoryInfo.Protect) { 304 | 0x04 { 305 | 'ReadWrite' 306 | } 307 | 0x08 { 308 | 'WriteCopy' 309 | } 310 | 0x40 { 311 | 'ExecuteReadWrite' 312 | } 313 | 0x80 { 314 | 'ExecuteWriteCopy' 315 | } 316 | } 317 | RegionStart = $memoryInfo.BaseAddress 318 | RegionSize = $memoryRegionSize 319 | } 320 | $match.PSObject.TypeNames.Insert(0, 'PSMemory.Reference') 321 | $match 322 | } 323 | } 324 | } 325 | } 326 | } 327 | 328 | $searchResult | Group-Object -Property ProcessId 329 | 330 | [void] [PSMemory.Native]::CloseHandle($processHandle) 331 | 332 | <# 333 | .SYNOPSIS 334 | Search process memory 335 | 336 | .DESCRIPTION 337 | Search any values within the virtual address space of a process. 338 | 339 | .PARAMETER Process 340 | A System.Diagnostics.Process object as returned by the Get-Process Cmdlet 341 | representing the process whose memory to scan. 342 | 343 | .PARAMETER Values 344 | A System.Collections.Hashtable containing typed values to search for. The keys of 345 | the hashtable define the data type while the corresponding values may contain a 346 | comma-separated list of concrete values of that type to search for. Valid data 347 | types respectively hashtable keys are Byte, Short, Int, Long, String and Bytes. 348 | 349 | .INPUTS 350 | System.Diagnostics.Process 351 | System.Collections.Hashtable 352 | 353 | .OUTPUTS 354 | Microsoft.PowerShell.Commands.GroupInfo 355 | 356 | .COMPONENT 357 | Windows API 358 | 359 | .EXAMPLE 360 | Get-Process notepad | Search-Memory -Values @{Int=1234,5678; String='Notepad'} 361 | #> 362 | } 363 | 364 | 365 | function Compare-Memory { 366 | [Alias('crmem')] 367 | [OutputType([Microsoft.PowerShell.Commands.GroupInfo])] 368 | [CmdletBinding()] 369 | param( 370 | [Parameter(Mandatory, ValueFromPipeline, Position=0)] 371 | [Microsoft.PowerShell.Commands.GroupInfo] 372 | $Reference, 373 | 374 | [ScriptBlock] 375 | $Filter, 376 | 377 | [switch] 378 | $Increased, 379 | 380 | [switch] 381 | $Decreased, 382 | 383 | [switch] 384 | $Changed, 385 | 386 | [switch] 387 | $Unchanged 388 | ) 389 | 390 | # PROCESS_QUERY_INFORMATION (0x0400) | PROCESS_VM_READ (0x10) 391 | if (($processHandle = [PSMemory.Native]::OpenProcess( 392 | 0x0400 -bor 0x10, 393 | $false, 394 | $Reference.Name)) -eq [IntPtr]::Zero 395 | ) { 396 | $e = [Runtime.InteropServices.Marshal]::GetLastWin32Error() 397 | throw New-Win32Exception $e -From OpenProcess 398 | } 399 | 400 | $compareResult = ` 401 | foreach ($ref in $Reference.Group) { 402 | $buffer = [byte[]]::New($ref.Size) 403 | if ([PSMemory.Native]::ReadProcessMemory( 404 | $processHandle, 405 | $ref.Address, 406 | $buffer, 407 | $ref.Size, 408 | [ref][IntPtr]::Zero) -eq 0 409 | ) { 410 | $e = [Runtime.InteropServices.Marshal]::GetLastWin32Error() 411 | throw New-Win32Exception $e -From ReadProcessMemory 412 | } 413 | 414 | $value = switch ($ref.Type) { 415 | 'Byte' { 416 | $buffer[0] 417 | } 418 | 'Short' { 419 | [BitConverter]::ToInt16($buffer, 0) 420 | } 421 | 'Int' { 422 | [BitConverter]::ToInt32($buffer, 0) 423 | } 424 | 'Long' { 425 | [BitConverter]::ToInt64($buffer, 0) 426 | } 427 | 'Bytes' { 428 | $buffer 429 | } 430 | 'String' { 431 | [Text.Encoding]::ASCII.GetString($buffer) 432 | } 433 | } 434 | 435 | $match = $ref.PSObject.Copy() 436 | $match.Value = $value 437 | 438 | if ($ref.Type -in 'Byte','Short','Int','Long') { 439 | if ($Increased.IsPresent -and ($value -gt $ref.Value)) { 440 | $match 441 | continue 442 | } 443 | 444 | if ($Decreased.IsPresent -and ($value -lt $ref.Value)) { 445 | $match 446 | continue 447 | } 448 | } 449 | 450 | if ($Changed.IsPresent -and ($value -ne $ref.Value)) { 451 | $match 452 | continue 453 | } 454 | 455 | if ($Unchanged.IsPresent -and ($value -eq $ref.Value)) { 456 | $match 457 | continue 458 | } 459 | 460 | if ($PSBoundParameters.ContainsKey('Filter') -and ($ref | Where-Object $Filter)) { 461 | $match 462 | continue 463 | } 464 | } 465 | 466 | $compareResult | Group-Object -Property ProcessId 467 | 468 | <# 469 | .SYNOPSIS 470 | Compare process memory 471 | 472 | .DESCRIPTION 473 | Compare memory references found by the Search-Memory Cmdlet with their current in-memory 474 | values as present in the virtual address space of the process. 475 | 476 | .PARAMETER Reference 477 | A Microsoft.PowerShell.Commands.GroupInfo object representing memory references as returned by 478 | the Search-Memory or Compare-Memory Cmdlets. 479 | 480 | .PARAMETER Filter 481 | A System.Management.Automation.ScriptBlock representing a filter being applied to the memory 482 | references whether to include them in the result. 483 | 484 | .PARAMETER Increased 485 | Include those memory references in the result whose in-memory value has increased. Only applies 486 | to numerical values. 487 | 488 | .PARAMETER Decreased 489 | Include those memory references in the result whose in-memory value has decreased. Only applies 490 | to numerical values. 491 | 492 | .PARAMETER Changed 493 | Include those memory references in the result whose in-memory value has changed. 494 | 495 | .PARAMETER Unchanged 496 | Include those memory references in the result whose in-memory value has not changed. 497 | 498 | .INPUTS 499 | Microsoft.PowerShell.Commands.GroupInfo 500 | System.Management.Automation.ScriptBlock 501 | 502 | .OUTPUTS 503 | Microsoft.PowerShell.Commands.GroupInfo 504 | 505 | .COMPONENT 506 | Windows API 507 | 508 | .EXAMPLE 509 | Get-Process notepad | Search-Memory -Values @{Int=1234} -OutVariable matches 510 | $matches | Compare-Memory -Increased -Filter {$_.Value -lt 1000} 511 | #> 512 | } 513 | 514 | 515 | function Update-Memory { 516 | [CmdletBinding()] 517 | [Alias('udmem')] 518 | param( 519 | [Parameter(Mandatory, ValueFromPipeline, Position=0)] 520 | [Microsoft.PowerShell.Commands.GroupInfo] 521 | $Reference, 522 | 523 | [Parameter(Mandatory, ParameterSetName='Byte')] 524 | [byte] 525 | $Byte, 526 | 527 | [Parameter(Mandatory, ParameterSetName='Short')] 528 | [int16] 529 | $Short, 530 | 531 | [Parameter(Mandatory, ParameterSetName='Int')] 532 | [int32] 533 | $Int, 534 | 535 | [Parameter(Mandatory, ParameterSetName='Long')] 536 | [long] 537 | $Long, 538 | 539 | [Parameter(Mandatory, ParameterSetName='String')] 540 | [string] 541 | $String, 542 | 543 | [Parameter(Mandatory, ParameterSetName='Bytes')] 544 | [byte[]] 545 | $Bytes 546 | ) 547 | 548 | $valueSize, $value, $valueBytes = switch ($PSCmdlet.ParameterSetName) { 549 | 'Byte' { 550 | 1, $Byte, $Byte 551 | } 552 | 'Short' { 553 | 2, $Short, [BitConverter]::GetBytes($Short) 554 | } 555 | 'Int' { 556 | 4, $Int, [BitConverter]::GetBytes($Int) 557 | } 558 | 'Long' { 559 | 8, $Long, [BitConverter]::GetBytes($Long) 560 | } 561 | 'Bytes' { 562 | $Bytes.Length, $Bytes, $Bytes 563 | } 564 | 'String' { 565 | $String.Length, $String,[Text.Encoding]::ASCII.GetBytes($String) 566 | } 567 | } 568 | 569 | # PROCESS_QUERY_INFORMATION (0x0400) | PROCESS_VM_WRITE (0x20) 570 | if (($processHandle = [PSMemory.Native]::OpenProcess( 571 | 0x0400 -bor 0x20, 572 | $false, 573 | $Reference.Name)) -eq [IntPtr]::Zero 574 | ) { 575 | $e = [Runtime.InteropServices.Marshal]::GetLastWin32Error() 576 | throw New-Win32Exception $e -From OpenProcess 577 | } 578 | 579 | $updateResult = ` 580 | foreach ($ref in $Reference.Group) { 581 | if ([PSMemory.Native]::WriteProcessMemory( 582 | $processHandle, 583 | $ref.Address, 584 | $valueBytes, 585 | $valueSize, 586 | [ref][IntPtr]::Zero) -eq 0 587 | ) { 588 | $e = [Runtime.InteropServices.Marshal]::GetLastWin32Error() 589 | throw New-Win32Exception $e -From WriteProcessMemory 590 | } 591 | 592 | $newRef = $ref.PSObject.Copy() 593 | $newRef.Value = $value 594 | $newRef.Type = $PSCmdlet.ParameterSetName 595 | $newRef.Size = $valueSize 596 | 597 | $newRef 598 | } 599 | 600 | $updateResult | Group-Object -Property ProcessId 601 | 602 | <# 603 | .SYNOPSIS 604 | Update process memory 605 | 606 | .DESCRIPTION 607 | Update in-memory values as present in the virtual address space of a process represented by 608 | memory references as returned from the Search-Memory and Compare-Memory Cmdlets. 609 | 610 | .PARAMETER Reference 611 | A Microsoft.PowerShell.Commands.GroupInfo object representing memory references as returned by 612 | the Search-Memory or Compare-Memory Cmdlet. 613 | 614 | .PARAMETER Byte 615 | An 8-Bit numerical value to update the in-memory value represented by the memory reference with. 616 | 617 | .PARAMETER Short 618 | A 16-Bit numerical value to update the in-memory value represented by the memory reference with. 619 | 620 | .PARAMETER Int 621 | A 32-Bit numerical value to update the in-memory value represented by the memory reference with. 622 | 623 | .PARAMETER Long 624 | A 64-Bit numerical value to update the in-memory value represented by the memory reference with. 625 | 626 | .PARAMETER String 627 | A string value to update the in-memory value represented by the memory reference with. 628 | 629 | .PARAMETER Bytes 630 | A byte array value to update the in-memory value represented by the memory reference with. 631 | 632 | .INPUTS 633 | Microsoft.PowerShell.Commands.GroupInfo 634 | 635 | .OUTPUTS 636 | Microsoft.PowerShell.Commands.GroupInfo 637 | 638 | .COMPONENT 639 | Windows API 640 | 641 | .EXAMPLE 642 | Get-Process notepad | Search-Memory -Values @{Int=1234} | Update-Memory -Int 4321 643 | 644 | .EXAMPLE 645 | Get-Process notepad | Search-Memory -Values @{Long=123456789} -OutVariable matches 646 | $matches | Compare-Memory -Changed 647 | $matches | Update-Memory -Long 123456789 648 | #> 649 | } 650 | 651 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | logo 3 |

4 | 5 | [![PowerShell Gallery](https://img.shields.io/powershellgallery/v/PSMemory.svg)](https://www.powershellgallery.com/packages/PSMemory) 6 | ![powershell version](https://img.shields.io/badge/powershell-v5-blue.svg) 7 | ![supported windows versions](https://img.shields.io/badge/supported%20windows%20versions-7%2F8%2F10-yellow.svg) 8 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/7f35ae966821403c9952a277d3e5d19a)](https://www.codacy.com/app/off-world/PSMemory?utm_source=github.com&utm_medium=referral&utm_content=off-world/PSMemory&utm_campaign=Badge_Grade) 9 | 10 | ___ 11 | 12 | **PSMemory** is a 64 bit windows memory scanner written in PowerShell hence fully automation capable. 13 | 14 | ___ 15 | 16 | ## Description 17 | 18 | ### Cmdlets 19 | 20 | #### `Search-Memory` 21 | 22 | searches the virtual address space of a process for specific values returning references to the memory they reside in. 23 | Besides the value itself these references contain other related information such as the concrete memory address or the protection of 24 | the page the value was found in. A search can be specified by the `-Values` parameter in the form of a hashtable where the *keys* define 25 | data types and the corresponding *values* define the values of that data type to be searched for as a comma-separated list. Valid data 26 | types to be specified as *keys* for the search table are 27 | - **Byte** for 8 bit numerical values 28 | - **Short** for 16 bit numerical values 29 | - **Int** for 32 bit numerical values 30 | - **Long** for 64 bit numerical values 31 | - **String** for ASCII text of arbitrary length 32 | - **Bytes** for Unicode byte arrays of arbitrary length 33 | 34 | **Example**: a search for two 32 bit numerical values *1234* and *5678* as well as the text *Notepad* within the memory of the process *notepad* saving the result in a variable *notepad* for further processing may look like 35 | ```Powershell 36 | Get-Process notepad | Search-Memory -Values @{ 37 | Int = 1234, 5678 38 | String = 'Notepad' 39 | } -OutVariable notepad 40 | ``` 41 | 42 | #### `Compare-Memory` 43 | 44 | compares those references' values as present in memory when the reference was created or last updated to the current 45 | in-memory value. With the `-Changed` and `-Unchanged` parameters each reference will be matched whose in-memory value has either 46 | changed in any way or stood the same. For numerical values exclusively there are additionally the `-Increased` and `-Decreased` parameters which track if the in-memory value did either become greater or lower. For everything else there is the `-Filter` parameter where a PowerShell ScriptBlock may be supplied with a custom comparison criteria. 47 | 48 | **Example**: given the above search now keep only those references whose in-memory value is either exactly *42* or has increased and update the reference result variable 49 | ```Powershell 50 | $notepad | Compare-Memory -Increased -Filter {$_.Value -eq 42} -OutVariable notepad 51 | ``` 52 | 53 | #### `Update-Memory` 54 | 55 | updates the current in-memory value referenced by a reference. The new value to be written may be supplied by one of the data type parameters depending on what value of what size to write. 56 | 57 | **Example:** after filtering the memory references above now update each remaining referenced in-memory value with a new 32 bit numerical value of *9876* 58 | ```Powershell 59 | $notepad | Update-Memory -Int 9876 60 | ``` 61 | #### `Format-Memory` 62 | 63 | formats reference objects as returned by **all** the aforementioned Cmdlets into formatted and human readable output. 64 | 65 | **Example:** 66 | ```Powershell 67 | Get-Process notepad | Search-Memory -Values @{Int = 42} -OutVariable notepad | Format-Memory 68 | ``` 69 | or 70 | ```Powershell 71 | $notepad | Compare-Memory -Increased -Filter {$_.Value -eq 42} | Format-Memory 72 | ``` 73 | Alternatively, you can use the alias `fm`. 74 | 75 | ## Installation 76 | 77 | Install from [PowerShell Gallery](https://www.powershellgallery.com/packages/PSMemory) 78 | 79 | ```Powershell 80 | Install-Module -Name PSMemory 81 | ``` 82 | or 83 | ```Shell 84 | git clone https://github.com/tobiohlala/PSMemory 85 | ``` 86 | --------------------------------------------------------------------------------