PowerShell script useful for Incident Response and security/configuration baselines for Windows Vista and later. Self-contained
Latest Change v0.3.1 — February, 2016 acochenour, added Office/IE addins, printers & sidebar gadgetsWindows Metadata Extraction:
+ User Accounts
+ System Configuration Files (.sys and .ini)
+ Environment Variables
+ Group Policy Objects
+ Windows Patches
+ Firewall Configuration
+ Command Line History
+ Scheduled Tasks
+ Sidebar Gadgets
+ Installed Printers & Drivers
+ Shared Printers
+ Internet Explorer Browser History
+ Recent Emails (last 30-days)
+ Downloaded Files & MD5 Hashes
Remote & Log Data Extraction:
+ USB Device History
+ Remote Desktop History
+ Successul & Unsuccessful Logons
+ Registry Persistence Entries
+ Startup Drivers
+ User & Temporary Drivers
+ PowerShell Scripts
+ Microsoft Office Addins (all versions)
+ Internet Explorer Addins
Software:
+ Installed Software
+ AV Software List
+ Services
+ Running Prcess Hashes
Service Details
Prefetch Files
AT Jobs
Network Metadata & Configuration:
+ Hosts
+ Networks
+ Network Shares
+ Open SMB Sessions
+ DNS
+ ARP Table
+ Network Status
+ Listening Processes
+ Network Services
+ LMHosts
+ MAC Addresses
+ Network Configuration
User Documents:
+ Complete List of User Documents
+ MD5 Hash of User Documents
Usage Examples:
Capture all standard metadata, save report locally: .\PSInspect.ps1
Capture all standard metadata, email report: .\PSInspect.ps1 -sendEmail -emailFrom user1@domain.com -emailTo user2@domain.com -smtpServer 172.16.1.1
Capture all standard metadata, save report to a network share: .\PSInspect.ps1 -share \share\path -username myUser -password myPassword
Capture all standard metadata and user’s email metadata, save report locally: .\PSInspect.ps1 -email
Capture all standard metadata and user’s email metadata, email report: .\PSInspect.ps1 -email -sendEmail -emailFrom user1@domain.com -emailTo user2@domain.com -smtpServer 172.16.1.1
PowerShell Permissions — Run PowerShell as Administrator
+ Get-ExecutionPolicy –List # Check your current PowerShell permissions
+ Set-ExecutionPolicy Unrestricted
+ Set-ExecutionPolicy -Scope CurrentUser Unrestricted
+ Set-ExecutionPolicy -Scope Process Unrestricted
+ Set-ExecutionPolicy Restricted # Reset PowerShell script permissions back to Restricted
+ Set-ExecutionPolicy -Scope CurrentUser Restricted
+ Set-ExecutionPolicy -Scope Process Restricted
+ Get-ExecutionPolicy –List #Confirm the updated settings before exiting
TODO
+ Firefox & Chrome browser history support
+ Memory acquisition
+ Disk acquisition
Script:
#requires -version 2.0
#============================================================================================#
# Hoplite Industries, Inc. #
# Incident Response Live Data Acquisition #
# Original work by: greg[.]foss[@]logrhythm[.]com & others references #
# v0.2 -- October, 2015 Original release #
# v0.3 -- January, 2016 Hoplite Industries fork/many updates/release #
# v0.3.1 -- February, 2016 acochenour, added Office/IE addins, printers & sidebar gadgets #
#============================================================================================#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at;
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
#=======================================================================================
# CONFIGURATION
#=======================================================================================
[CmdLetBinding()]
param(
[switch]$remote = $false,
[switch]$email = $false,
[switch]$share = $false,
[switch]$sendEmail = $false,
[switch]$lockdown = $false,
[switch]$adLock = $false,
[string]$target,
[string]$username,
[string]$password,
[string]$netShare,
[string]$smtpServer,
[string]$emailFrom,
[string]$emailTo,
[string]$companyName
)
#=======================================================================================
# PSInspect
#=======================================================================================
function Invoke-Recon {
$banner = @"
___ ___ .__ .__ __ ___________ .__
/ | \ ____ ______ | | |__|/ |_ ____ \__ ___/______|__|____ ____ ____
/ ~ \/ _ \\____ \| | | \ __\/ __ \ | | \_ __ \ \__ \ / ___\_/ __ \
\ Y ( <_> ) |_> > |_| || | \ ___/ | | | | \/ |/ __ \_/ /_/ > ___/
\___|_ / \____/| __/|____/__||__| \___ > |____| |__| |__(____ /\___ / \___ >
\/ |__| \/ \//_____/ \/
"@
<#
.NAME
PSInspect
.SYNOPSIS
PowerShell Incident Response -- Live Data Acquisition Tool
.DESCRIPTION
This tool pulls data from a target Windows Vista or later systems where there is suspicious of misuse and/or infection. This will extract useful forensic data that will assist IR teams in gathering quick live data on a potentially compromised host.
.NOTES
This tool is designed to be executed from a hopliteindustries.com SmartResponse(TM) on remote hosts via the hopliteindustries.com agent, remotely using the hopliteindustries.com SIEM, or locally/remotely as a standalone PowerShell script.
The safest way to run this script is locally, however remote execution is possible. Realize this will open the system up to additional risk...
.EXAMPLE
PS C:\> .\PSInspect.ps1
Simply run PSInspect on the local host.
This gathers default data and stores the results in the directory that the script was executed from.
.EXAMPLE
PS C:\> .\PSInspect.ps1 -remote -target [computer] [arguments - EX: -sendEmail -share -username -password]
Run PSInspect Remotely.
This gathers default data and stores the results in the script directory.
If you do not chose the [sendEmail] and/or [share] options all local evidence will be erased on the target.
Caveats:
You will need to ensure that psremoting and unsigned execution is enabled on the remote host. // dangerous to leave enabled!
Be careful, this may inadvertently expose administrative credentials when authenticating to a remote compromised host.
.EXAMPLE
PS C:\> .\PSInspect.ps1 -sendEmail -smtpServer ["127.0.0.1"] -emailTo ["info[at]hopliteindustries.com.com"] -emailFrom ["psinspect[at]hopliteindustries.com.com"]
[sendEmail] parameter allows the script to send the HTML report over SMTP.
[smtpServer] parameter sets the remote SMTP Server that will be used to forward reports.
[emailTo] parameter deifines the email recipient. Multiple recipients can be separated by commas.
[emailFrom] parameter defines the email sender.
.EXAMPLE
PS C:\> .\PSInspect.ps1 -share -netShare ["\\share\"] -Credential Get-Credential
[share] parameter allows the script to push evidence to a remote share or send the HTML report over SMTP.
[netShare] parameter defines the remote share. This should be manually tested with the credentials you will execute the script with.
Make sure to restrict pemrissions to this location and audit all access related to the folder!
.EXAMPLE
PS C:\> .\PSInspect.ps1 -lockdown -adLock [username]
[lockdown] parameter quarantine's the workstation. This disables the NIC's, locks the host and logs the user out.
[adLock] parameter disables the target username ID within Active Directory. A username must be provided...
.EXAMPLE
PS C:\> .\PSInspect.ps1 -email
[email] parameter extracts client email data (from / to / subject / email links).
.EXAMPLE
PS C:\> .\PSInspect.ps1 -username ["admin user"] -password ["pass"]
[username] parameter can be supplied on the command-line or hard-coded into the script.
[password] parameter can be supplied on the command-line or hard-coded into the script. // Bad idea...
These parameters are used when running PSInspect on remote hosts or interacting with Active Directory; not required for local execution.
If neither parameter is supplied, you will be prompted for credentials // safest option aside from local execution
.EXAMPLE
Remotely enable PSRemoting and Unrestricted PowerShell Execution then, run PSInspect.
First, enable PSRemoting
PS C:\> .\PsExec \\10.10.10.10 -u [admin account name] -p [admin account password] -h -d powershell.exe "Enable-PSRemoting -Force"
PS C:\> Test-WSMan 10.10.10.10
PS C:\> Enter-PSSession 10.10.10.10
[10.10.10.10]: PS C:\> Set-ExecutionPolicy Unrestricted -Force
[10.10.10.10]: PS C:\> Exit
PS C:\> .\PSInspect.ps1 -remote -target "10.10.10.10" -sendEmail -smtpServer "127.0.0.1" -emailTo "info[at]hopliteindustries.com.com" -emailFrom "psinspect[at]hopliteindustries.com.com"
.OUTPUTS
The script currently gathers the following data:
-ARP Table
-AT Jobs
-Anti Virus Engine(s) installed
-Capture Host Screenshot
-Command History
-DNS Cache
-Environment Variables
-Extract Internet Explorer history
-Extract Email History and Links
-Firewall Configuration
-GPSresult
-Hash Collected Evidence Files to Verify Authenticity
-Host File Information
-IP Address
-Netstat Information
-List Open Shares
-Local PowerShell Scripts
-Logon Data
-PowerShell Versioning
-PowerShell Executable Hashes
-Process Information
-Prefetch Files
-Remote Desktop Sessions
-Running Services
-Scheduled Processes
-Scheduled Tasks
-Service Details
-Startup Information
-Startup Drivers
-USB Device History
-User and Admin Information
-Windows Patches
-Windows Version Information
#>
#=======================================================================================
# Prepare to Capture Live Host Data
#=======================================================================================
# Mask errors
$ErrorActionPreference= 'silentlycontinue'
# Check for Admin Rights
if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator')) {
Write-Host 'You must run PSInspect from an elevated PowerShell session...'
Exit 1
}
# Enable Logging
New-EventLog -LogName Application -Source "PSInspect"
Write-EventLog -LogName Application -Source "PSInspect" -EntryType Information -EventId 1337 -Message "Forensic Data Acquisition Initiated"
# Define the Drive
$PSReconDir = $(get-location).path
Set-Location -Path $PSReconDir -PassThru > $null 2>&1
# Create directories
function dirs {
mkdir PSRecon\ > $null 2>&1
mkdir PSRecon\config\ > $null 2>&1
mkdir PSRecon\network\ > $null 2>&1
mkdir PSRecon\process\ > $null 2>&1
mkdir PSRecon\system\ > $null 2>&1
mkdir PSRecon\web\ > $null 2>&1
mkdir PSRecon\documents > $null 2>&1
mkdir PSRecon\registry\ > $null 2>&1
}
$exists = "PSRecon_*\"
If (Test-Path $exists){
Remove-Item PSRecon_*\ -Recurse -Force
dirs
}Else{
dirs
}
#=======================================================================================
# Evidence Collection
#=======================================================================================
# Get user and admin info
whoami > PSRecon\config\whoami.html
$whoami = type PSRecon\config\whoami.html
qwinsta > PSRecon\config\activeUsers.html
$activeUsersA = type PSRecon\config\activeUsers.html
$activeUsers = $activeUsersA | foreach {$_ + "<br />"}
# Set environmental variables
$ip = ((ipconfig | findstr [0-9].\.)[0]).Split()[-1]
$computerName = (gi env:\Computername).Value
$userDirectory = (gi env:\userprofile).value
$user = (gi env:\USERNAME).value
$date = Get-Date -format D
$dateString = Get-Date -format MM-dd-yyyy
$dateTime = Get-Date -Format MM/dd/yyyy-H:mm:ss
if (-Not ($companyName)) {
$companyName = "Proprietary / Confidential / Privileged / Not For Disclosure"
} Else {
$companyCheck = "^[a-zA-Z0-9\s+]+$"
if (-not ($companyName -match $companyCheck)) {
Write-Host 'Hey now...'
Write-EventLog -LogName Application -Source "PSInspect" -EntryType Information -EventId 34405 -Message "Possible Attack Detected via companyName parameter: $companyName"
Exit 1
}
$companyName = "Proprietary / Confidential to $companyName � Not For Disclosure"
}
# Display banner and host data
$banner
Write-Host ""
Write-Host "$dateTime : Capturing Host Data : $computerName - $ip"
# Get IP Address Details
ipconfig -all | ConvertTo-Html -Fragment > PSRecon\config\ipconfig.html
$ipconfig = type PSRecon\config\ipconfig.html
# Gathering Scheduled Processes
at > PSRecon\process\at-jobs.html
$atA = get-content PSRecon\process\at-jobs.html
$at = $atA | foreach {$_ + "<br />"}
# Gathering list of Scheduled Tasks
schtasks > PSRecon\system\schtasks.html
$schtasksA = get-content PSRecon\system\schtasks.html
$schtasks = $schtasksA | foreach {$_ + "<br />"}
# Extract security update data
get-hotfix | Where-Object {$_.Description -ne ''} | select Description,HotFixID,InstalledBy | format-list > PSRecon\system\hotfix-status.html
$hotfixA = get-content PSRecon\system\hotfix-status.html
$hotfix = $hotfixA | foreach {$_ + "<br />"}
# Gathering Process Information
tasklist /V /FO CSV | ConvertFrom-Csv | ConvertTo-Html -Fragment > PSRecon\process\user-tasks.html
$taskDetail = type PSRecon\process\user-tasks.html
# Gather Windows Service Data
Get-WmiObject win32_service | Select-Object Name, DisplayName, PathName, StartName, StartMode, State, TotalSessions, Description > PSRecon\process\service-detail.html
$serviceDetailA = get-content PSRecon\process\service-detail.html
$serviceDetail = $serviceDetailA | foreach {$_ + "<br />"}
# DNS Cache
ipconfig -displaydns > PSRecon\network\dnscache.html 2> PSRecon\network\dnserror.html
$dnsCacheA = get-content PSRecon\network\dnscache.html
$dnsCache = $dnsCacheA | foreach {$_ + "<br />"}
# Netstat information
netstat -ant > PSRecon\network\netstat.html 2> PSRecon\network\netstaterror.html
$netstatA = get-content PSRecon\network\netstat.html
$netstat = $netstatA | foreach {$_ + "<br />"}
# Display Listening Processes
netstat -ano | findstr -i listening | ForEach-Object { $_ -split "\s+|\t+" } | findstr /r "^[1-9+]*$" | sort | unique | ForEach-Object { Get-Process -Id $_ } | Select ProcessName,Path,Company,Description | ConvertTo-Html > PSRecon\network\net-processes.html
$listeningProcesses = Get-Content PSRecon\network\net-processes.html
# ARP table
arp -a > PSRecon\network\arp.html
$arpA = get-content PSRecon\network\arp.html
$arp = $arpA | foreach {$_ + "<br />"}
# Gathering information about running services
net start > PSRecon\network\netservices.html
$netServicesA = get-content PSRecon\network\netservices.html
$netServices = $netServicesA | foreach {$_ + "<br />"}
#Gathering information about open shares
net user > PSRecon\system\netuser.html
net use > PSRecon\network\shares.html
$netUserA = get-content PSRecon\system\netuser.html
$netUser = $netUserA | foreach {$_ + "<br />"}
$sharesA = get-content PSRecon\network\shares.html
$shares = $sharesA | foreach {$_ + "<br />"}
# Gathering host file information
type $env:windir\system32\drivers\etc\hosts > PSRecon\network\etchosts.html
type $env:windir\system32\drivers\etc\networks > PSRecon\network\etcnetworks.html
type $env:windir\system32\drivers\etc\lmhosts.sam > PSRecon\network\lmhosts.html
$hostsA = get-content PSRecon\network\etchosts.html
$hosts = $hostsA | foreach {$_ + "<br />"}
$networksA = get-content PSRecon\network\etcnetworks.html
$networks = $networksA | foreach {$_ + "<br />"}
$lmHostsA = get-content PSRecon\network\lmhosts.html
$lmHosts = $lmHostsA | foreach {$_ + "<br />"}
# Gather Currently Installed Software
Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate | ConvertTo-Html -Fragment > PSRecon\process\software.html
$software = type PSRecon\process\software.html
# List Recently Used USB Devices
Get-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Enum\USBSTOR\*\* | Select FriendlyName | ConvertTo-Html -Fragment > PSRecon\system\usb.html
$usb = type PSRecon\system\usb.html
# Gather command history
Get-History | ConvertTo-Html -Fragment > PSRecon\system\command-line-history.html
$commandHist = type PSRecon\system\command-line-history.html
# Gather configuration of on-board network interfaces
function ipconfig/all { ipconfig /all }
ipconfig/all > PSRecon\network\nics.html
$nicsA = get-content PSRecon\network\nics.html
$nics = $nicsA | foreach {$_ + "<br />"}
# Gather MAC addresses of on-board network interfaces
Get-WmiObject win32_networkadapterconfiguration | select description, macaddress | ConvertTo-Html -Fragment > PSRecon\network\mac-addr.html
Get-CimInstance win32_networkadapterconfiguration | select description, macaddress | ConvertTo-Html -Fragment >> PSRecon\network\mac-addr.html
$macs = type PSRecon\network\mac-addr.html
# Dumping the firewall information
echo "Firewall State" > PSRecon\system\firewall-config.html
netsh firewall show state >> PSRecon\system\firewall-config.html
echo "Firewall Config" >> PSRecon\system\firewall-config.html
netsh firewall show config >> PSRecon\system\firewall-config.html
echo "Firewall Dump" >> PSRecon\system\firewall-config.html
netsh dump >> PSRecon\system\firewall-config.html
$firewallA = get-content PSRecon\system\firewall-config.html
$firewall = $firewallA | foreach {$_ + "<br />"}
$firewall > PSRecon\system\firewall-config.html
# Saving the Environment
Get-ChildItem ENV: | Select Name, Value | ConvertTo-Html -Fragment > PSRecon\system\environment.html
$set = type PSRecon\system\environment.html
# Return GPResult Output
& $env:windir\system32\gpresult.exe /v > PSRecon\system\gpresult.html
$gpresultA = get-content PSRecon\system\gpresult.html
$gpresult = $gpresultA | foreach {$_ + "<br />"}
# Get active SMB sessions
Get-SmbSession > PSRecon\network\smbsessions.html
$smbSessionA = get-content PSRecon\network\smbsessions.html
$smbSession = $smbSessionS | foreach {$_ + "<br />"}
# Get ACL's
Get-Acl | Select AccessToString, Owner, Group, Sddl | ConvertTo-Html -Fragment > PSRecon\system\acl.html
$acl = type PSRecon\system\acl.html
# Gathering Windows version information
[Environment]::OSVersion | ConvertTo-Html -Fragment > PSRecon\system\os-version.html
$version = type PSRecon\system\os-version.html
# Dumping the startup information
type $env:SystemDrive\autoexec.bat > PSRecon\system\autoexecBat.html 2>&1
type $env:SystemDrive\config.sys > PSRecon\system\configSys.html 2>&1
type $env:windir\win.ini > PSRecon\system\winIni.html 2>&1
type $env:windir\system.ini > PSRecon\system\systemIni.html 2>&1
$autoexecA = get-content PSRecon\system\autoexecBat.html
$autoexec = $autoexecA | foreach {$_ + "<br />"}
$configSysA = get-content PSRecon\system\configSys.html
$configSys = $ConfigSysA | foreach {$_ + "<br />"}
$winIniA = get-content PSRecon\system\winIni.html
$winIni = $winIniA | foreach {$_ + "<br />"}
$systemIniA = get-content PSRecon\system\systemIni.html
$systemIni = $systemIniA | foreach {$_ + "<br />"}
$psversiontable > PSRecon\config\powershell-version.html
$powershellVersionA = type PSRecon\config\powershell-version.html
$powershellVersion = $powershellVersionA | foreach {$_ + "<br />"}
# Startup Drivers
# Thanks Mark Vankempen!
$startupDrivers = reg query hklm\system\currentcontrolset\services /s | Select-String -pattern "^\s*?ImagePath.*?\.sys$"
$shadyDrivers = $startupDrivers | Select-String -pattern "^\s*?ImagePath.*?(user|temp).*?\\.*?\.(sys|exe)$"
$startupDrivers = $startupDrivers | ConvertTo-Html
$shadyDrivers = $shadyDrivers | ConvertTo-Html
$startupDrivers > PSRecon\registry\startup-drivers.html
Get-ItemProperty HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run > PSRecon\registry\HKLM-Run.html
Get-ItemProperty HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce >> PSRecon\registry\HKLM-Run.html
$hklmRunA = type PSRecon\registry\HKLM-Run.html
$hklmRun = $hklmRunA | foreach {$_ + "<br />"}
Get-ItemProperty HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run > PSRecon\registry\HKCU-Run.html
Get-ItemProperty HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run >> PSRecon\registry\HKCU-Run.html
$hkcuRunA = type PSRecon\registry\HKCU-Run.html
$hkcuRun = $hkcuRunA | foreach {$_ + "<br />"}
# Get installed AV software
Get-WmiObject -Namespace root\SecurityCenter2 -Class AntiVirusProduct > PSRecon\process\av.html
$antiVirusA = type PSRecon\process\av.html
$antiVirus = $antiVirusA | foreach {$_ + "<br />"}
# Get Internet Explorer Addins
Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects > PSRecon\registry\ie-addins.html
$ieAddinsA = type PSRecon\registry\ie-addins.html
$ieAddins = $ieAddinsA | foreach {$_ + "<br />"}
# Get Microsoft Office Addins
Get-ItemProperty HKLM:\SOFTWARE\Wow6432Node\Microsoft\Office\Outlook\Addins > PSRecon\registry\office-addins.html
$officeAddinsA = type PSRecon\registry\office-addins.html
$officeAddins = $officeAddinsA | foreach {$_ + "<br />"}
# List current printers/drivers
Get-Printer -ComputerName ${env:COMPUTERNAME} | Format-List Name,DriverName > PSRecon\system\printers.html
Get-Printer -ComputerName ${env:COMPUTERNAME} | Format-List Name,DriverName | where Shared -eq $true | fl Name > PSRecon\system\shared-printers.html
$printersA = type PSRecon\system\printers.html
$printers = $printersA | foreach {$_ + "<br />"}
$sharedPrintersB = type PSRecon\system\shared-printers.html
$sharedPrinters = $sharedPrintersB | foreach {$_ + "<br />"}
# list documents
dir C:\Users\* -Recurse | Select Name, CreationTime, LastAccessTime, Attributes | ConvertTo-Html -Fragment > PSRecon\documents\documents.html
$documents = type PSRecon\documents\documents.html
# list downloaded files
dir C:\Users\*\Downloads\* -Recurse | Select Name, CreationTime, LastAccessTime, Attributes | ConvertTo-Html -Fragment > PSRecon\web\downloads.html
$downloads = type PSRecon\web\downloads.html
# list sidebar gadgets
dir "C:\Program Files\Windows Sidebar\Gadgets\*" | Select Name, CreationTime, LastAccessTime, Attributes | ConvertTo-Html -Fragment > PSRecon\system\gadgets.html
$gadgets = type PSRecon\system\gadgets.html
# Extract Prefetch File Listing
# script stolen from:
# https://github.com/davehull/Kansa/blob/master/Modules/Process/Get-PrefetchListing.ps1
$pfconf = (Get-ItemProperty "hklm:\system\currentcontrolset\control\session manager\memory management\prefetchparameters").EnablePrefetcher
Switch -Regex ($pfconf) {
"[1-3]" {
$o = "" | Select-Object FullName, CreationTimeUtc, LastAccessTimeUtc, LastWriteTimeUtc
ls $env:windir\Prefetch\*.pf | % {
$o.FullName = $_.FullName;
$o.CreationTimeUtc = Get-Date($_.CreationTimeUtc) -format o;
$o.LastAccesstimeUtc = Get-Date($_.LastAccessTimeUtc) -format o;
$o.LastWriteTimeUtc = Get-Date($_.LastWriteTimeUtc) -format o;
$o
} | ConvertTo-Html -Fragment >> PSRecon\process\prefetch.html
}
default {
echo "" >> PSRecon\process\prefetch.html
echo "Prefetch not enabled on ${env:COMPUTERNAME}" >> PSRecon\process\prefetch.html
echo "" >> PSRecon\process\prefetch.html
}
}
$prefetch = type PSRecon\process\prefetch.html
# Extract Internet Explorer History
# script stolen from:
# https://richardspowershellblog.wordpress.com/2011/06/29/ie-history-to-csv/
function get-iehistory {
[CmdletBinding()]
param ()
$shell = New-Object -ComObject Shell.Application
$hist = $shell.NameSpace(34)
$folder = $hist.Self
$hist.Items() |
foreach {
if ($_.IsFolder) {
$siteFolder = $_.GetFolder
$siteFolder.Items() |
foreach {
$site = $_
if ($site.IsFolder) {
$pageFolder = $site.GetFolder
$pageFolder.Items() |
foreach {
$visit = New-Object -TypeName PSObject -Property @{
Site = $($site.Name)
URL = $($pageFolder.GetDetailsOf($_,0))
Date = $( $pageFolder.GetDetailsOf($_,2))
}
$visit
}
}
}
}
}
}
get-iehistory | select Date, URL | ConvertTo-Html -Fragment > PSRecon\web\ie-history.html
$ieHistory = type PSRecon\web\ie-history.html
# Take a screenshot of the current desktop
# script stolen from:
# https://gallery.technet.microsoft.com/scriptcenter/eeff544a-f690-4f6b-a586-11eea6fc5eb8
Function Take-ScreenShot {
#Requires -Version 2
[cmdletbinding(
SupportsShouldProcess = $True,
DefaultParameterSetName = "screen",
ConfirmImpact = "low"
)]
Param (
[Parameter(
Mandatory = $False,
ParameterSetName = "screen",
ValueFromPipeline = $True)]
[switch]$screen,
[Parameter(
Mandatory = $False,
ParameterSetName = "window",
ValueFromPipeline = $False)]
[switch]$activewindow,
[Parameter(
Mandatory = $False,
ParameterSetName = "",
ValueFromPipeline = $False)]
[string]$file,
[Parameter(
Mandatory = $False,
ParameterSetName = "",
ValueFromPipeline = $False)]
[string]
[ValidateSet("bmp","jpeg","png")]
$imagetype = "bmp",
[Parameter(
Mandatory = $False,
ParameterSetName = "",
ValueFromPipeline = $False)]
[switch]$print
)
# C# code
$code = @'
using System;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Drawing.Imaging;
namespace ScreenShotDemo
{
/// <summary>
/// Provides functions to capture the entire screen, or a particular window, and save it to a file.
/// </summary>
public class ScreenCapture
{
/// <summary>
/// Creates an Image object containing a screen shot the active window
/// </summary>
/// <returns></returns>
public Image CaptureActiveWindow()
{
return CaptureWindow( User32.GetForegroundWindow() );
}
/// <summary>
/// Creates an Image object containing a screen shot of the entire desktop
/// </summary>
/// <returns></returns>
public Image CaptureScreen()
{
return CaptureWindow( User32.GetDesktopWindow() );
}
/// <summary>
/// Creates an Image object containing a screen shot of a specific window
/// </summary>
/// <param name="handle">The handle to the window. (In windows forms, this is obtained by the Handle property)</param>
/// <returns></returns>
private Image CaptureWindow(IntPtr handle)
{
// get te hDC of the target window
IntPtr hdcSrc = User32.GetWindowDC(handle);
// get the size
User32.RECT windowRect = new User32.RECT();
User32.GetWindowRect(handle,ref windowRect);
int width = windowRect.right - windowRect.left;
int height = windowRect.bottom - windowRect.top;
// create a device context we can copy to
IntPtr hdcDest = GDI32.CreateCompatibleDC(hdcSrc);
// create a bitmap we can copy it to,
// using GetDeviceCaps to get the width/height
IntPtr hBitmap = GDI32.CreateCompatibleBitmap(hdcSrc,width,height);
// select the bitmap object
IntPtr hOld = GDI32.SelectObject(hdcDest,hBitmap);
// bitblt over
GDI32.BitBlt(hdcDest,0,0,width,height,hdcSrc,0,0,GDI32.SRCCOPY);
// restore selection
GDI32.SelectObject(hdcDest,hOld);
// clean up
GDI32.DeleteDC(hdcDest);
User32.ReleaseDC(handle,hdcSrc);
// get a .NET image object for it
Image img = Image.FromHbitmap(hBitmap);
// free up the Bitmap object
GDI32.DeleteObject(hBitmap);
return img;
}
/// <summary>
/// Captures a screen shot of the active window, and saves it to a file
/// </summary>
/// <param name="filename"></param>
/// <param name="format"></param>
public void CaptureActiveWindowToFile(string filename, ImageFormat format)
{
Image img = CaptureActiveWindow();
img.Save(filename,format);
}
/// <summary>
/// Captures a screen shot of the entire desktop, and saves it to a file
/// </summary>
/// <param name="filename"></param>
/// <param name="format"></param>
public void CaptureScreenToFile(string filename, ImageFormat format)
{
Image img = CaptureScreen();
img.Save(filename,format);
}
/// <summary>
/// Helper class containing Gdi32 API functions
/// </summary>
private class GDI32
{
public const int SRCCOPY = 0x00CC0020; // BitBlt dwRop parameter
[DllImport("gdi32.dll")]
public static extern bool BitBlt(IntPtr hObject,int nXDest,int nYDest,
int nWidth,int nHeight,IntPtr hObjectSource,
int nXSrc,int nYSrc,int dwRop);
[DllImport("gdi32.dll")]
public static extern IntPtr CreateCompatibleBitmap(IntPtr hDC,int nWidth,
int nHeight);
[DllImport("gdi32.dll")]
public static extern IntPtr CreateCompatibleDC(IntPtr hDC);
[DllImport("gdi32.dll")]
public static extern bool DeleteDC(IntPtr hDC);
[DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);
[DllImport("gdi32.dll")]
public static extern IntPtr SelectObject(IntPtr hDC,IntPtr hObject);
}
/// <summary>
/// Helper class containing User32 API functions
/// </summary>
private class User32
{
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
}
[DllImport("user32.dll")]
public static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll")]
public static extern IntPtr GetWindowDC(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern IntPtr ReleaseDC(IntPtr hWnd,IntPtr hDC);
[DllImport("user32.dll")]
public static extern IntPtr GetWindowRect(IntPtr hWnd,ref RECT rect);
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
}
}
}
'@
#User Add-Type to import the code
add-type $code -ReferencedAssemblies 'System.Windows.Forms','System.Drawing'
#Create the object for the Function
$capture = New-Object ScreenShotDemo.ScreenCapture
#Take screenshot of the entire screen
If ($Screen) {
Write-Verbose "Taking screenshot of entire desktop"
#Save to a file
If ($file) {
If ($file -eq "") {
$file = "$pwd\image.bmp"
}
Write-Verbose "Creating screen file: $file with imagetype of $imagetype"
$capture.CaptureScreenToFile($file,$imagetype)
}
ElseIf ($print) {
$img = $Capture.CaptureScreen()
$pd = New-Object System.Drawing.Printing.PrintDocument
$pd.Add_PrintPage({$_.Graphics.DrawImage(([System.Drawing.Image]$img), 0, 0)})
$pd.Print()
}
Else {
$capture.CaptureScreen()
}
}
}
Take-ScreenShot -screen -file "c:\screenshot.png" -imagetype png
# convert the image to Base64 for inclusion in the HTML report
$path = "c:\screenshot.png"
$screenshot = [convert]::ToBase64String((get-content $path -encoding byte))
move $path .\PSRecon\config\screenshot.png
# Capture Log and Registry Data using cmdlets from Get-ComputerDetails
# Awesome cmdlets stolen from:
# https://raw.githubusercontent.com/clymb3r/PowerShell/master/Get-ComputerDetails/Get-ComputerDetails.ps1
if ( $remote -eq $true ) {
# I Suck at PowerShell, anyone know how to mitigate the memory issue so that Kansa cmdlets can run remotely?
$RDPconnections = "<p>Unfortunately his data cannot be pulled when PSRecon is run remotely<br />
Unless the shell memory is expanded...<br /><br />
The workaround is to set the Shell Memory Limit using the following command on the target host:<br />
PS C:\> Set-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB 1024 -force</p>"
$psscripts = "<p>Unfortunately his data cannot be pulled when PSRecon is run remotely<br />
Unless the shell memory is expanded...<br /><br />
The workaround is to set the Shell Memory Limit using the following command on the target host:<br />
PS C:\> Set-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB 1024 -force</p>"
$4624 = "<p>Unfortunately his data cannot be pulled when PSRecon is run remotely<br />
Unless the shell memory is expanded...<br /><br />
The workaround is to set the Shell Memory Limit using the following command on the target host:<br />
PS C:\> Set-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB 1024 -force</p>"
$4648 = "<p>Unfortunately his data cannot be pulled when PSRecon is run remotely<br />
Unless the shell memory is expanded...<br /><br />
The workaround is to set the Shell Memory Limit using the following command on the target host:<br />
PS C:\> Set-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB 1024 -force</p>"
} Else {
function Find-4648Logons
{
Param(
$SecurityLog
)
$ExplicitLogons = $SecurityLog | Where {$_.InstanceID -eq 4648}
$ReturnInfo = @{}
foreach ($ExplicitLogon in $ExplicitLogons)
{
$Subject = $false
$AccountWhosCredsUsed = $false
$TargetServer = $false
$SourceAccountName = ""
$SourceAccountDomain = ""
$TargetAccountName = ""
$TargetAccountDomain = ""
$TargetServer = ""
foreach ($line in $ExplicitLogon.Message -split "\r\n")
{
if ($line -cmatch "^Subject:$")
{
$Subject = $true
}
elseif ($line -cmatch "^Account\sWhose\sCredentials\sWere\sUsed:$")
{
$Subject = $false
$AccountWhosCredsUsed = $true
}
elseif ($line -cmatch "^Target\sServer:")
{
$AccountWhosCredsUsed = $false
$TargetServer = $true
}
elseif ($Subject -eq $true)
{
if ($line -cmatch "\s+Account\sName:\s+(\S.*)")
{
$SourceAccountName = $Matches[1]
}
elseif ($line -cmatch "\s+Account\sDomain:\s+(\S.*)")
{
$SourceAccountDomain = $Matches[1]
}
}
elseif ($AccountWhosCredsUsed -eq $true)
{
if ($line -cmatch "\s+Account\sName:\s+(\S.*)")
{
$TargetAccountName = $Matches[1]
}
elseif ($line -cmatch "\s+Account\sDomain:\s+(\S.*)")
{
$TargetAccountDomain = $Matches[1]
}
}
elseif ($TargetServer -eq $true)
{
if ($line -cmatch "\s+Target\sServer\sName:\s+(\S.*)")
{
$TargetServer = $Matches[1]
}
}
}
#Filter out logins that don't matter
if (-not ($TargetAccountName -cmatch "^DWM-.*" -and $TargetAccountDomain -cmatch "^Window\sManager$"))
{
$Key = $SourceAccountName + $SourceAccountDomain + $TargetAccountName + $TargetAccountDomain + $TargetServer
if (-not $ReturnInfo.ContainsKey($Key))
{
$Properties = @{
LogType = 4648
LogSource = "Security"
SourceAccountName = $SourceAccountName
SourceDomainName = $SourceAccountDomain
TargetAccountName = $TargetAccountName
TargetDomainName = $TargetAccountDomain
TargetServer = $TargetServer
Count = 1
Times = @($ExplicitLogon.TimeGenerated)
}
$ResultObj = New-Object PSObject -Property $Properties
$ReturnInfo.Add($Key, $ResultObj)
}
else
{
$ReturnInfo[$Key].Count++
$ReturnInfo[$Key].Times += ,$ExplicitLogon.TimeGenerated
}
}
}
return $ReturnInfo
}
function Find-4624Logons
{
Param (
$SecurityLog
)
$Logons = $SecurityLog | Where {$_.InstanceID -eq 4624}
$ReturnInfo = @{}
foreach ($Logon in $Logons)
{
$SubjectSection = $false
$NewLogonSection = $false
$NetworkInformationSection = $false
$AccountName = ""
$AccountDomain = ""
$LogonType = ""
$NewLogonAccountName = ""
$NewLogonAccountDomain = ""
$WorkstationName = ""
$SourceNetworkAddress = ""
$SourcePort = ""
foreach ($line in $Logon.Message -Split "\r\n")
{
if ($line -cmatch "^Subject:$")
{
$SubjectSection = $true
}
elseif ($line -cmatch "^Logon\sType:\s+(\S.*)")
{
$LogonType = $Matches[1]
}
elseif ($line -cmatch "^New\sLogon:$")
{
$SubjectSection = $false
$NewLogonSection = $true
}
elseif ($line -cmatch "^Network\sInformation:$")
{
$NewLogonSection = $false
$NetworkInformationSection = $true
}
elseif ($SubjectSection)
{
if ($line -cmatch "^\s+Account\sName:\s+(\S.*)")
{
$AccountName = $Matches[1]
}
elseif ($line -cmatch "^\s+Account\sDomain:\s+(\S.*)")
{
$AccountDomain = $Matches[1]
}
}
elseif ($NewLogonSection)
{
if ($line -cmatch "^\s+Account\sName:\s+(\S.*)")
{
$NewLogonAccountName = $Matches[1]
}
elseif ($line -cmatch "^\s+Account\sDomain:\s+(\S.*)")
{
$NewLogonAccountDomain = $Matches[1]
}
}
elseif ($NetworkInformationSection)
{
if ($line -cmatch "^\s+Workstation\sName:\s+(\S.*)")
{
$WorkstationName = $Matches[1]
}
elseif ($line -cmatch "^\s+Source\sNetwork\sAddress:\s+(\S.*)")
{
$SourceNetworkAddress = $Matches[1]
}
elseif ($line -cmatch "^\s+Source\sPort:\s+(\S.*)")
{
$SourcePort = $Matches[1]
}
}
}
#Filter out logins that don't matter
if (-not ($NewLogonAccountDomain -cmatch "NT\sAUTHORITY" -or $NewLogonAccountDomain -cmatch "Window\sManager"))
{
$Key = $AccountName + $AccountDomain + $NewLogonAccountName + $NewLogonAccountDomain + $LogonType + $WorkstationName + $SourceNetworkAddress + $SourcePort
if (-not $ReturnInfo.ContainsKey($Key))
{
$Properties = @{
LogType = 4624
LogSource = "Security"
SourceAccountName = $AccountName
SourceDomainName = $AccountDomain
NewLogonAccountName = $NewLogonAccountName
NewLogonAccountDomain = $NewLogonAccountDomain
LogonType = $LogonType
WorkstationName = $WorkstationName
SourceNetworkAddress = $SourceNetworkAddress
SourcePort = $SourcePort
Count = 1
Times = @($Logon.TimeGenerated)
}
$ResultObj = New-Object PSObject -Property $Properties
$ReturnInfo.Add($Key, $ResultObj)
}
else
{
$ReturnInfo[$Key].Count++
$ReturnInfo[$Key].Times += ,$Logon.TimeGenerated
}
}
}
return $ReturnInfo
}
Function Find-PSScriptsInPSAppLog {
$ReturnInfo = @{}
$Logs = Get-WinEvent -LogName "Microsoft-Windows-PowerShell/Operational" -ErrorAction SilentlyContinue | Where {$_.Id -eq 4100}
foreach ($Log in $Logs)
{
$ContainsScriptName = $false
$LogDetails = $Log.Message -split "`r`n"
$FoundScriptName = $false
foreach($Line in $LogDetails)
{
if ($Line -imatch "^\s*Script\sName\s=\s(.+)")
{
$ScriptName = $Matches[1]
$FoundScriptName = $true
}
elseif ($Line -imatch "^\s*User\s=\s(.*)")
{
$User = $Matches[1]
}
}
if ($FoundScriptName)
{
$Key = $ScriptName + "::::" + $User
if (!$ReturnInfo.ContainsKey($Key))
{
$Properties = @{
ScriptName = $ScriptName
UserName = $User
Count = 1
Times = @($Log.TimeCreated)
}
$Item = New-Object PSObject -Property $Properties
$ReturnInfo.Add($Key, $Item)
}
else
{
$ReturnInfo[$Key].Count++
$ReturnInfo[$Key].Times += ,$Log.TimeCreated
}
}
}
return $ReturnInfo
}
Function Find-RDPClientConnections {
$ReturnInfo = @{}
New-PSDrive -Name HKU -PSProvider Registry -Root Registry::HKEY_USERS | Out-Null
#Attempt to enumerate the servers for all users
$Users = Get-ChildItem -Path "HKU:\"
foreach ($UserSid in $Users.PSChildName)
{
$Servers = Get-ChildItem "HKU:\$($UserSid)\Software\Microsoft\Terminal Server Client\Servers" -ErrorAction SilentlyContinue
foreach ($Server in $Servers)
{
$Server = $Server.PSChildName
$UsernameHint = (Get-ItemProperty -Path "HKU:\$($UserSid)\Software\Microsoft\Terminal Server Client\Servers\$($Server)").UsernameHint
$Key = $UserSid + "::::" + $Server + "::::" + $UsernameHint
if (!$ReturnInfo.ContainsKey($Key))
{
$SIDObj = New-Object System.Security.Principal.SecurityIdentifier($UserSid)
$User = ($SIDObj.Translate([System.Security.Principal.NTAccount])).Value
$Properties = @{
CurrentUser = $User
Server = $Server
UsernameHint = $UsernameHint
}
$Item = New-Object PSObject -Property $Properties
$ReturnInfo.Add($Key, $Item)
}
}
}
return $ReturnInfo
}
# Extract data from Get-ComputerDetails suite of cmdlets
Find-RDPClientConnections | Format-List > PSRecon\registry\RDPconnections.html
$RDPconnectionsA = Get-Content PSRecon\registry\RDPconnections.html
$RDPconnections = $RDPconnectionsA | foreach {$_ + "<br />"}
Find-PSScriptsInPSAppLog | Format-List > PSRecon\registry\psscripts.html
$psscriptsA = Get-Content PSRecon\registry\psscripts.html
$psscripts = $psscriptsA | foreach {$_ + "<br />"}
$SecurityLog = Get-EventLog -LogName Security
Find-4624Logons $SecurityLog | Format-List > PSRecon\registry\4624logons.html
$4624A = Get-Content PSRecon\registry\4624logons.html
$4624 = $4624A | foreach {$_ + "<br />"}
Find-4648Logons $SecurityLog | Format-List > PSRecon\registry\4648logons.html
$4648A = Get-Content PSRecon\registry\4648logons.html
$4648 = $4648A | foreach {$_ + "<br />"}
#>
}
# Extract Email Details
if(-Not ($email)) {
echo "<p><strong>emails not extracted...</strong><br /><br />" >> PSRecon\web\email-subjects.html
echo " To extract emails, run PSRecon with the [email] command-line switch:<br /><br />" >> PSRecon\web\email-subjects.html
echo " PS C:\> .\PSRecon.ps1 -email" >> PSRecon\web\email-subjects.html
echo "<br /><br />" >> PSRecon\web\email-subjects.html
echo " This was skipped because email extraction takes a very long time.<br />" >> PSRecon\web\email-subjects.html
echo " This also closes the user's email client and tends to leave the Outlook process hanging...</strong></p><br />" >> PSRecon\web\email-subjects.html
copy PSRecon\web\email-subjects.html PSRecon\web\email-links.html
$emailSubjects = get-content PSRecon\web\email-subjects.html
$emailLinks = get-content PSRecon\web\email-links.html
} else {
if ($email -eq $true) {
# Close outlook, so we can extract the emails
Get-Process OUTLOOK | Foreach-Object { $_.CloseMainWindow() | Out-Null } | stop-process �force > $null 2>&1
Write-Host "Extracting emails... This may take a few minutes!"
Function Get-OutlookInBox {
Add-type -assembly "Microsoft.Office.Interop.Outlook" | out-null
$olFolders = "Microsoft.Office.Interop.Outlook.olDefaultFolders" -as [type]
$outlook = new-object -comobject outlook.application
$namespace = $outlook.GetNameSpace("MAPI")
$folder = $namespace.getDefaultFolder($olFolders::olFolderInBox)
$folder.items |
Select-Object -Property * -Last 50
}
$inbox = Get-OutlookInBox
$inbox | Select-Object -Property SenderName, Subject, ReceivedTime > PSRecon\web\email-subjects.html
$inbox | Select Body | findstr http > PSRecon\web\email-links.html
$getEmailLinks = 'PSRecon\web\email-links.html'
$emailLinkRegex = "([a-zA-Z]{3,})://([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*).*?"
$emailLinksA = select-string -Path $getEmailLinks -Pattern $emailLinkRegex -AllMatches | % { $_.Matches } | % { $_.Value }
$emailSubjectsA = Get-Content PSRecon\web\email-subjects.html
$emailSubjects = $emailSubjectsA | foreach {$_ + "<br />"}
$emailLinks = $emailLinksA | foreach {$_ + "<br />"}
Stop-Process -Name OUTLOOK -Force
Write-EventLog -LogName Application -Source "PSInspect" -EntryType Information -EventId 1234 -Message "Optional : Client Email Data Extracted"
} Else {
Write-Host "Missing Required Parameter [email]"
Write-Host " This option was specified "
Write-Host "PS C:\> .\PSRecon.ps1 -email"
Write-EventLog -LogName Application -Source "PSInspect" -EntryType Information -EventId 34404 -Message "Forensic Data Acquisition Failure : Missing Required Parameter"
Exit 1
}
}
# PowerShell Profile
if ( Test-Path $profile ) {
$PSprofileA = type $profile
$PSProfile = $PSProfileA | foreach {$_ + "<br />"}
} else {
$PSprofile = "<br />No PowerShell Profile File Found:<br /><br />"
}
#=======================================================================================
# Evidence Verification
#=======================================================================================
# Hash collected evidence files to verify authenticity
# script stolen from:
# https://gallery.technet.microsoft.com/scriptcenter/Get-Hashes-of-Files-1d85de46
function Get-FileHash {
[CmdletBinding()]
Param(
[Parameter(Position=0,Mandatory=$true, ValueFromPipelineByPropertyName=$true,ValueFromPipeline=$True)]
[Alias("PSPath","FullName")]
[string[]]$Path,
[Parameter(Position=1)]
[ValidateSet("MD5","SHA1","SHA256","SHA384","SHA512","RIPEMD160")]
[string[]]$Algorithm = "SHA256"
)
Process {
ForEach ($item in $Path) {
$item = (Resolve-Path $item).ProviderPath
If (-Not ([uri]$item).IsAbsoluteUri) {
Write-Verbose ("{0} is not a full path, using current directory: {1}" -f $item,$pwd)
$item = (Join-Path $pwd ($item -replace "\.\\",""))
}
If(Test-Path $item -Type Container) {
Write-Warning ("Cannot calculate hash for directory: {0}" -f $item)
Return
}
$object = New-Object PSObject -Property @{
Path = $item
}
#Open the Stream
$stream = ([IO.StreamReader]$item).BaseStream
foreach($Type in $Algorithm) {
[string]$hash = -join ([Security.Cryptography.HashAlgorithm]::Create( $Type ).ComputeHash( $stream ) |
ForEach { "{0:x2}" -f $_ })
$null = $stream.Seek(0,0)
#If multiple algorithms are used, then they will be added to existing object
$object = Add-Member -InputObject $Object -MemberType NoteProperty -Name $Type -Value $Hash -PassThru
}
$object.pstypenames.insert(0,'System.IO.FileInfo.Hash')
#Output an object with the hash, algorithm and path
Write-Output $object
#Close the stream
$stream.Close()
}
}
}
Get-Process | Where-Object {-not [string]::IsNullOrEmpty($_.Path)} | Select-Object Path -Unique | sort | Get-FileHash -Algorithm SHA256 | ConvertTo-Html -Fragment >> PSRecon\process\process-hashes.html
$processHashes = Get-Content PSRecon\process\process-hashes.html
"$env:windir\System32\WindowsPowerShell\v1.0\powershell.exe" | Get-FileHash -Algorithm SHA256 | ConvertTo-Html -Fragment > PSRecon\config\powershell-hashes.html
$powershellHashes = type PSRecon\config\powershell-hashes.html
Get-ChildItem C:\Users\* -Recurse | Get-FileHash -Algorithm SHA256 | ConvertTo-Html -Fragment > PSRecon\documents\document-hashes.html
$documentHashes = type PSRecon\documents\document-hashes.html
Get-ChildItem C:\Users\*\Downloads\ -Recurse | Get-FileHash -Algorithm SHA256 | ConvertTo-Html -Fragment > PSRecon\web\download-hashes.html
$downloadHashes = type PSRecon\web\download-hashes.html
Get-ChildItem PSRecon\ -Recurse -Filter *.html | Get-FileHash -Algorithm SHA256 | ConvertTo-Html -Fragment > PSRecon\config\e-hashes.html
Get-Content PSRecon\config\e-hashes.html | Select-String -pattern 'e-hashes' -notmatch | Out-File PSRecon\config\evidence-hashes.html
rm PSRecon\config\e-hashes.html -Force
$evidenceHashes = type PSRecon\config\evidence-hashes.html
#=======================================================================================
# Report Generation
#=======================================================================================
# Create system profile report in HTML
$html = $("PSRecon\PSRecon_" + $dateString + "_" + $computerName + ".html")
$htmlHead = @"
<!-- © hopliteindustries.com - 2015 -->
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="user-scalable=yes, width=1000px" />
<title>PSRecon Report - $computerName</title>
"@
$htmlJS = @"
<script type="text/javascript">//<![CDATA[
/*! jQuery v2.1.3 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */
!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l=a.document,m="2.1.3",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+`$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return!n.isArray(a)&&a-parseFloat(a)+1>=0},isPlainObject:function(a){return"object"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=l.createElement("script"),b.text=a,l.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:k}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=hb(),z=hb(),A=hb(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N=M.replace("w","w#"),O="\\["+L+"*("+M+")(?:"+L+"*([*^`$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+N+"))|)"+L+"*\\]",P=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+O+")*)|.*)\\)|)",Q=new RegExp(L+"+","g"),R=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+`$","g"),S=new RegExp("^"+L+"*,"+L+"*"),T=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),U=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),V=new RegExp(P),W=new RegExp("^"+N+"`$"),X={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+O),PSEUDO:new RegExp("^"+P),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")`$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|`$)","i")},Y=/^(?:input|select|textarea|button)`$/i,Z=/^h\d`$/i,`$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))`$/,ab=/[+~]/,bb=/'|\\/g,cb=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),db=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},eb=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(fb){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function gb(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],k=b.nodeType,"string"!=typeof a||!a||1!==k&&9!==k&&11!==k)return d;if(!e&&p){if(11!==k&&(f=_.exec(a)))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return H.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName)return H.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=1!==k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(bb,"\`$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+rb(o[l]);w=ab.test(a)&&pb(b.parentNode)||b,x=o.join(",")}if(x)try{return H.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"`$1"),b,d,e)}function hb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ib(a){return a[u]=!0,a}function jb(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function kb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function lb(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function mb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function nb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function ob(a){return ib(function(b){return b=+b,ib(function(c,d