Forensics/Export-MemoryDump.ps1
|
function Export-MemoryDump { <# .SYNOPSIS Creates memory dumps for forensic analysis .DESCRIPTION Captures memory dumps of specific processes or full system memory using multiple methods. Supports live system memory acquisition, process dumps, and LSASS dumps for credential analysis. .PARAMETER ProcessName Name of specific process to dump (e.g., 'lsass', 'chrome') .PARAMETER ProcessId Process ID to dump .PARAMETER DumpType Type of dump: Full, Mini, or LSASS .PARAMETER OutputPath Path to save memory dump files (default: C:\MemoryDumps) .PARAMETER UseProcDump Use Sysinternals ProcDump if available (more reliable) .PARAMETER Compress Compress dump file after creation .EXAMPLE Export-MemoryDump -ProcessName lsass -OutputPath "C:\Forensics" Export-MemoryDump -ProcessId 1234 -DumpType Full .OUTPUTS PSCustomObject with dump file information #> [CmdletBinding()] param( [Parameter()] [string]$ProcessName, [Parameter()] [int]$ProcessId, [Parameter()] [ValidateSet('Full', 'Mini', 'LSASS')] [string]$DumpType = 'Mini', [Parameter()] [string]$OutputPath = 'C:\MemoryDumps', [Parameter()] [switch]$UseProcDump, [Parameter()] [switch]$Compress ) begin { Write-Host "=== MEMORY DUMP EXPORT ===" -ForegroundColor Cyan Write-Host "Dump Type: $DumpType" -ForegroundColor Yellow Write-Host "Output Path: $OutputPath" -ForegroundColor Yellow # Check if running as Administrator $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) if (-not $isAdmin) { throw "This function requires Administrator privileges!" } # Create output directory if (-not (Test-Path $OutputPath)) { New-Item -Path $OutputPath -ItemType Directory -Force | Out-Null Write-Host "Created output directory: $OutputPath" -ForegroundColor Green } $result = [PSCustomObject]@{ Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" ComputerName = $env:COMPUTERNAME DumpType = $DumpType ProcessName = $null ProcessId = $null DumpFile = $null FileSize = 0 Success = $false Method = $null ErrorMessage = $null } # Memory dumps (LSASS especially) can contain plaintext credentials, so restrict # the dump file to Administrators and SYSTEM instead of the inherited default # that grants every local user read access (mirrors the generated-script lockdown # in Register-ScheduledSecurityScan) function Protect-DumpFile { param([Parameter(Mandatory = $true)][string]$Path) if ($env:OS -ne 'Windows_NT') { return $true } $requiredSids = @('S-1-5-18', 'S-1-5-32-544') try { $fileAcl = Get-Acl -Path $Path -ErrorAction Stop $fileAcl.SetAccessRuleProtection($true, $false) foreach ($existingRule in @($fileAcl.Access)) { $null = $fileAcl.RemoveAccessRule($existingRule) } foreach ($sidString in $requiredSids) { $accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule( (New-Object System.Security.Principal.SecurityIdentifier($sidString)), [System.Security.AccessControl.FileSystemRights]::FullControl, [System.Security.AccessControl.AccessControlType]::Allow) $fileAcl.AddAccessRule($accessRule) } Set-Acl -Path $Path -AclObject $fileAcl -ErrorAction Stop } catch { Write-Warning "Could not restrict permissions on '$Path'; the dump may contain credentials and may be readable by other local users: $_" return $false } $appliedAcl = Get-Acl -Path $Path -ErrorAction SilentlyContinue if ($null -eq $appliedAcl) { Write-Warning "ACL verification FAILED on '$Path': could not re-read the ACL; the dump may contain credentials and may be readable by other local users." return $false } $missingSids = @($requiredSids | Where-Object { $sid = $_ -not @($appliedAcl.Access | Where-Object { Test-IdentityReferenceIsSid -IdentityReference $_.IdentityReference -Sid $sid }).Count }) if ($missingSids.Count -gt 0) { Write-Warning "ACL verification FAILED on '$Path': missing access rules for SID(s) $($missingSids -join ', '); the dump may contain credentials and may be readable by other local users." return $false } return $true } } process { try { # Determine target process $targetProcess = $null if ($DumpType -eq 'LSASS') { Write-Host "`n[*] Targeting LSASS process for credential dump..." -ForegroundColor Cyan Write-Warning "LSASS dumps may trigger antivirus alerts!" $targetProcess = Get-Process -Name lsass -ErrorAction SilentlyContinue if (-not $targetProcess) { Write-Error "Could not find LSASS process" return } $ProcessName = 'lsass' } elseif ($ProcessId) { Write-Host "`n[*] Targeting Process ID: $ProcessId..." -ForegroundColor Cyan $targetProcess = Get-Process -Id $ProcessId -ErrorAction SilentlyContinue if (-not $targetProcess) { Write-Error "Process with ID $ProcessId not found. Use Get-Process to list running processes." return } $ProcessName = $targetProcess.ProcessName } elseif ($ProcessName) { Write-Host "`n[*] Targeting Process: $ProcessName..." -ForegroundColor Cyan $targetProcess = Get-Process -Name $ProcessName -ErrorAction SilentlyContinue | Select-Object -First 1 if (-not $targetProcess) { Write-Error "Process '$ProcessName' not found. Use Get-Process to list running processes." return } } else { Write-Error "No process specified. Use -ProcessName, -ProcessId, or -DumpType LSASS" return } $result.ProcessName = $targetProcess.ProcessName $result.ProcessId = $targetProcess.Id Write-Host "Process: $($targetProcess.ProcessName) (PID: $($targetProcess.Id))" -ForegroundColor Green # Generate dump filename $timestamp = Get-Date -Format "yyyyMMdd_HHmmss" $dumpFileName = "$($targetProcess.ProcessName)_$($targetProcess.Id)_${timestamp}.dmp" $dumpFilePath = Join-Path $OutputPath $dumpFileName # METHOD 1: Try ProcDump if available and requested if ($UseProcDump) { Write-Host "`n[1/3] Attempting ProcDump method..." -ForegroundColor Cyan # Only search admin-controlled fixed paths. This function runs elevated # (and can target LSASS), so a procdump.exe planted in a user-writable # location (TEMP, current directory) would execute attacker-controlled # code with elevated privileges $procDumpPaths = @( "C:\Tools\procdump.exe", "C:\Tools\procdump64.exe" ) $foundProcDump = $null foreach ($path in $procDumpPaths) { if (Test-Path $path) { $foundProcDump = $path break } } if ($foundProcDump) { Write-Host " Found ProcDump: $foundProcDump" -ForegroundColor Green $procDumpArgs = @( "-accepteula", "-ma", # Full dump $targetProcess.Id, $dumpFilePath ) try { $procDumpResult = & $foundProcDump $procDumpArgs 2>&1 if (Test-Path $dumpFilePath) { $result.Success = $true $result.Method = "ProcDump" $result.DumpFile = $dumpFilePath Write-Host " Successfully created dump with ProcDump" -ForegroundColor Green } } catch { Write-Warning "ProcDump failed: $_" } } else { Write-Host " ProcDump not found, trying alternative methods..." -ForegroundColor Gray } } # METHOD 2: Use MiniDumpWriteDump with proper handle if (-not $result.Success) { Write-Host "`n[2/3] Attempting MiniDumpWriteDump method..." -ForegroundColor Cyan $processHandle = [IntPtr]::Zero $fileStream = $null try { # FIXED: Define complete P/Invoke signatures with proper types Add-Type -TypeDefinition @" using System; using System.Runtime.InteropServices; public class ProcessDump { [Flags] public enum ProcessAccessFlags : uint { PROCESS_ALL_ACCESS = 0x001F0FFF, PROCESS_VM_READ = 0x00000010, PROCESS_QUERY_INFORMATION = 0x00000400 } [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr OpenProcess( ProcessAccessFlags processAccess, bool bInheritHandle, int processId ); [DllImport("kernel32.dll", SetLastError = true)] public static extern bool CloseHandle(IntPtr hObject); [DllImport("dbghelp.dll", SetLastError = true)] public static extern bool MiniDumpWriteDump( IntPtr hProcess, uint ProcessId, IntPtr hFile, uint DumpType, IntPtr ExceptionParam, IntPtr UserStreamParam, IntPtr CallbackParam ); } "@ -ErrorAction SilentlyContinue # FIXED: Open process handle with OpenProcess API $processHandle = [ProcessDump]::OpenProcess( [ProcessDump+ProcessAccessFlags]::PROCESS_ALL_ACCESS, $false, $targetProcess.Id ) if ($processHandle -eq [IntPtr]::Zero) { $lastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error() throw "Failed to open process handle. Error code: $lastError (May need SeDebugPrivilege or SYSTEM access)" } Write-Host " Process handle obtained successfully" -ForegroundColor Green # Create dump file $fileStream = [System.IO.File]::Create($dumpFilePath) # Determine dump type flag $dumpTypeFlag = switch ($DumpType) { 'Full' { 2 } # MiniDumpWithFullMemory 'Mini' { 0 } # MiniDumpNormal 'LSASS' { 2 } # Full dump for LSASS } Write-Host " Creating dump file..." -ForegroundColor Gray # Create the dump $success = [ProcessDump]::MiniDumpWriteDump( $processHandle, [uint32]$targetProcess.Id, $fileStream.SafeFileHandle.DangerousGetHandle(), $dumpTypeFlag, [IntPtr]::Zero, [IntPtr]::Zero, [IntPtr]::Zero ) $fileStream.Close() $fileStream = $null [ProcessDump]::CloseHandle($processHandle) | Out-Null $processHandle = [IntPtr]::Zero if ($success -and (Test-Path $dumpFilePath)) { $result.Success = $true $result.Method = "MiniDumpWriteDump" $result.DumpFile = $dumpFilePath Write-Host " Successfully created dump with MiniDumpWriteDump" -ForegroundColor Green } else { $lastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error() throw "MiniDumpWriteDump failed with error code: $lastError" } } catch { Write-Warning "MiniDumpWriteDump failed: $_" $result.ErrorMessage = $_.Exception.Message # Release resources if the attempt failed after they were acquired if ($null -ne $fileStream) { $fileStream.Dispose() } if ($processHandle -ne [IntPtr]::Zero) { [ProcessDump]::CloseHandle($processHandle) | Out-Null } # Clean up failed dump file if (Test-Path $dumpFilePath) { Remove-Item $dumpFilePath -Force -ErrorAction SilentlyContinue } } } # METHOD 3: Use rundll32.exe comsvcs.dll method (LSASS specific) if (-not $result.Success -and $DumpType -eq 'LSASS') { Write-Host "`n[3/3] Attempting comsvcs.dll method (LSASS specific)..." -ForegroundColor Cyan try { $comsvcsPath = "$env:SystemRoot\System32\comsvcs.dll" if (Test-Path $comsvcsPath) { Write-Host " Using rundll32.exe with comsvcs.dll..." -ForegroundColor Gray # Build command $arguments = @( $comsvcsPath, "MiniDump", $targetProcess.Id, "`"$dumpFilePath`"", "full" ) # Run elevated $psi = New-Object System.Diagnostics.ProcessStartInfo $psi.FileName = "rundll32.exe" $psi.Arguments = $arguments -join ' ' $psi.Verb = "runas" $psi.WindowStyle = "Hidden" $proc = [System.Diagnostics.Process]::Start($psi) $proc.WaitForExit(10000) # Wait 10 seconds # Wait a moment for dump to complete Start-Sleep -Seconds 2 if (Test-Path $dumpFilePath) { $result.Success = $true $result.Method = "comsvcs.dll" $result.DumpFile = $dumpFilePath Write-Host " Successfully created LSASS dump with comsvcs.dll" -ForegroundColor Green } } } catch { Write-Warning "comsvcs.dll method failed: $_" } } # Check final result if ($result.Success -and (Test-Path $dumpFilePath)) { $dumpFile = Get-Item $dumpFilePath $result.FileSize = $dumpFile.Length $null = Protect-DumpFile -Path $dumpFilePath $fileSizeMB = [Math]::Round($dumpFile.Length / 1MB, 2) Write-Host "`n[SUCCESS] Memory dump created!" -ForegroundColor Green Write-Host " File: $dumpFilePath" -ForegroundColor Cyan Write-Host " Size: $fileSizeMB MB" -ForegroundColor Cyan Write-Host " Method: $($result.Method)" -ForegroundColor Cyan # Compress if requested if ($Compress) { Write-Host "`n[*] Compressing dump file..." -ForegroundColor Cyan try { $zipPath = "$dumpFilePath.zip" Compress-Archive -Path $dumpFilePath -DestinationPath $zipPath -CompressionLevel Optimal if (Test-Path $zipPath) { $null = Protect-DumpFile -Path $zipPath $zipFile = Get-Item $zipPath $zipSizeMB = [Math]::Round($zipFile.Length / 1MB, 2) $compressionRatio = [Math]::Round((1 - ($zipFile.Length / $dumpFile.Length)) * 100, 1) Write-Host " Compressed: $zipPath" -ForegroundColor Green Write-Host " Compressed Size: $zipSizeMB MB" -ForegroundColor Cyan Write-Host " Compression Ratio: $compressionRatio%" -ForegroundColor Cyan # Optionally remove uncompressed file Write-Host "`n Remove original dump file? (Y/N)" -ForegroundColor Yellow -NoNewline $removeOriginal = Read-Host if ($removeOriginal -eq 'Y') { Remove-Item $dumpFilePath -Force Write-Host " Removed original dump file" -ForegroundColor Gray $result.DumpFile = $zipPath } } } catch { Write-Warning "Compression failed: $_" } } # Security recommendations Write-Host "`n[!] SECURITY RECOMMENDATIONS:" -ForegroundColor Yellow Write-Host " 1. Store dump files securely (contains sensitive data)" -ForegroundColor Yellow Write-Host " 2. Encrypt dump files before transferring" -ForegroundColor Yellow Write-Host " 3. Delete dump files after analysis" -ForegroundColor Yellow if ($DumpType -eq 'LSASS') { Write-Host " 4. LSASS dumps may contain plaintext credentials!" -ForegroundColor Red Write-Host " 5. Use Mimikatz or pypykatz for credential extraction" -ForegroundColor Yellow } } else { Write-Host "`n[FAILED] Could not create memory dump" -ForegroundColor Red Write-Host "Possible reasons:" -ForegroundColor Yellow Write-Host " - Insufficient permissions (need SeDebugPrivilege or SYSTEM)" -ForegroundColor Yellow Write-Host " - Process is protected (PPL - Protected Process Light)" -ForegroundColor Yellow Write-Host " - Antivirus/EDR blocking the operation" -ForegroundColor Yellow Write-Host " - Insufficient disk space" -ForegroundColor Yellow if ($DumpType -eq 'LSASS') { Write-Host "`nFor LSASS dumps, try:" -ForegroundColor Cyan Write-Host " - Using ProcDump: procdump.exe -accepteula -ma lsass.exe lsass.dmp" -ForegroundColor Gray Write-Host " - Task Manager: Right-click lsass.exe > Create dump file" -ForegroundColor Gray Write-Host " - Run PowerShell as SYSTEM: psexec -s -i powershell.exe" -ForegroundColor Gray } } } catch { Write-Error "Error during memory dump: $_" $result.Success = $false $result.ErrorMessage = $_.Exception.Message } } end { return $result } } |