├── PPRT.psd1 ├── PPRT.history.zip ├── Private ├── shorturls.xml ├── Write-LogEntry.ps1 ├── Create-PPRTDatabase.ps1 ├── Create-PPRTDatabaseTables.ps1 ├── Add-ObjectDetail.ps1 └── Parse-EmailHeader.ps1 ├── DRAFT ├── Untitled9.ps1 └── Get-FirstReceivedIPMapInfo.ps1 ├── PPRT.psm1 ├── Public ├── Connect-RIPE.ps1 ├── Connect-APNIC.ps1 ├── Connect-LACNIC.ps1 ├── Get-ParsedURL.ps1 ├── Send-ToAntiPhishingGroup.ps1 ├── Send-ToIronPort.ps1 ├── Get-URLFromMessage.ps1 ├── Get-PhishingGeoLocationAllIPs.ps1 ├── Send-ToAbuseContact.ps1 ├── Get-PhishingGeoLocationStartingIPs.ps1 ├── Get-IPaddress.ps1 ├── Get-LongUrl.ps1 ├── Get-WhichWHOIS.ps1 ├── Expand-ShortURL.ps1 ├── Check-ARIN.ps1 ├── Connect-ARIN.ps1 ├── New-AllReceivedFromIPObject.ps1 ├── Get-AbsoluteUri.ps1 ├── New-PPRTAbuseContactObject.ps1 ├── Invoke-VTAttachment.ps1 ├── Send-PPRTNotification.ps1 ├── Get-PhishingGeoLocationHeatMap.ps1 ├── New-FirstReceivedFromIPObject.ps1 ├── New-PPRTNotificationObject.ps1 ├── Send-PhishingNotifications.ps1 ├── Export-MessageAttachment.ps1 ├── New-MessageObject.ps1 ├── Parse-EmailHeader.ps1 ├── Invoke-PhishingResponse.ps1 └── Get-PhishingGeoLocation.ps1 └── README.md /PPRT.psd1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSAdministrator/PPRT/HEAD/PPRT.psd1 -------------------------------------------------------------------------------- /PPRT.history.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSAdministrator/PPRT/HEAD/PPRT.history.zip -------------------------------------------------------------------------------- /Private/shorturls.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSAdministrator/PPRT/HEAD/Private/shorturls.xml -------------------------------------------------------------------------------- /DRAFT/Untitled9.ps1: -------------------------------------------------------------------------------- 1 | 2 | #check and see if Database has previously been created 3 | #Check the registry for information about the database 4 | #If it has been created, then open connection 5 | #if it has not been created 6 | #then create the database 7 | Create-PPRTDatabase -Server $Serverz 8 | #then create the Tables 9 | Create-PPRTDatabaseTables -Server $Server 10 | #add this information to the Registry 11 | 12 | 13 | $cmd.commandtext = "INSERT INTO servers (servername,username,spversion,reason) VALUES('{0}','{1}','{2}','{3}')" -f $os.__SERVER,$env.username,$os.servicepackmajorversion,$reason 14 | $cmd.executenonquery() 15 | $conn.close() -------------------------------------------------------------------------------- /PPRT.psm1: -------------------------------------------------------------------------------- 1 | #requires -Version 2 2 | #Get public and private function definition files. 3 | $Public = @( Get-ChildItem -Path $PSScriptRoot\Public\*.ps1 -ErrorAction SilentlyContinue ) 4 | $Private = @( Get-ChildItem -Path $PSScriptRoot\Private\*.ps1 -ErrorAction SilentlyContinue ) 5 | 6 | #Dot source the files 7 | Foreach($import in @($Public + $Private)) 8 | { 9 | Try 10 | { 11 | . $import.fullname 12 | } 13 | Catch 14 | { 15 | Write-Error -Message "Failed to import function $($import.fullname): $_" 16 | } 17 | } 18 | 19 | #download and import POSH-VirusTotal 20 | if (!(Test-Path -Path "$Home\Documents\WindowsPowerShell\Modules\Posh-VirusTotal")) 21 | { 22 | Write-Verbose -Message 'Downloading Posh-VirusTotal PowerShell Module....' 23 | Invoke-Expression -Command (New-Object -TypeName Net.WebClient).DownloadString('https://gist.githubusercontent.com/darkoperator/9138373/raw/22fb97c07a21139a398c2a3d6ca7e3e710e476bc/PoshVTInstall.ps1') 24 | } 25 | 26 | Export-ModuleMember -Function $Public.Basename -------------------------------------------------------------------------------- /Public/Connect-RIPE.ps1: -------------------------------------------------------------------------------- 1 | #requires -Version 3 2 | function Connect-RIPE () 3 | { 4 | [CmdletBinding()] 5 | param ( 6 | [parameter(Mandatory = $true,Position = 1,HelpMessage = 'Please provide a IP address')] 7 | $ipaddress 8 | ) 9 | <# 10 | .SYNOPSIS 11 | Takes IP address as input and queries RIPE's WHOIS implementation for the IP addresses abuse contact email - RESTFul API 12 | 13 | .DESCRIPTION 14 | Takes a IP Address and searches for RIPE's WHOIS abuse contact email for that IP based on registration data 15 | Returns this contact email address 16 | 17 | .PARAMETER ipaddress 18 | Specifices the specific IP address belonging to RIPE 19 | 20 | .EXAMPLE 21 | C:\PS> Check-RIPE -ipaddress '195.42.65.82' 22 | 23 | #> 24 | 25 | 26 | $abusecontact = Invoke-RestMethod -Uri "http://rest.db.ripe.net/abuse-contact/$ipaddress" 27 | 28 | $result = $abusecontact.'abuse-resources'.'abuse-contacts'.email 29 | 30 | return $result 31 | } 32 | -------------------------------------------------------------------------------- /Public/Connect-APNIC.ps1: -------------------------------------------------------------------------------- 1 | #requires -Version 3 2 | function Connect-APNIC () 3 | { 4 | [CmdletBinding()] 5 | param ( 6 | [parameter(Mandatory = $true,Position = 1,HelpMessage = 'Please provide a IP address')] 7 | $ipaddress 8 | ) 9 | <# 10 | .SYNOPSIS 11 | Takes IP address as input and queries APNIC's RDAP implementation for the IP addresses abuse contact email - RESTFul API 12 | 13 | .DESCRIPTION 14 | Takes a IP Address and searches for APNIC's RDAP abuse contact email for that IP based on registration data 15 | Returns this contact email address 16 | 17 | .PARAMETER ipaddress 18 | Specifices the specific IP address belonging to APNIC 19 | 20 | .EXAMPLE 21 | C:\PS> Check-APNIC -ipaddress '150.42.65.82' 22 | 23 | #> 24 | 25 | $regx2 = "[a-z0-9!#\$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#\$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?" 26 | 27 | $rawdata = Invoke-WebRequest -Uri "http://rdap.apnic.net/ip/$ipaddress" | ConvertFrom-Json 28 | 29 | for($i = 0;$i -lt ($rawdata.entities.vcardArray).count; $i++) 30 | { 31 | foreach ($item in $rawdata.entities.vcardArray.SyncRoot[$i]) 32 | { 33 | [array]$result += $item 34 | } 35 | } 36 | 37 | $result | Select-String -Pattern $regx2 38 | 39 | return $result 40 | } 41 | -------------------------------------------------------------------------------- /Public/Connect-LACNIC.ps1: -------------------------------------------------------------------------------- 1 | #requires -Version 3 2 | function Connect-LACNIC () 3 | { 4 | [CmdletBinding()] 5 | param ( 6 | [parameter(Mandatory = $true,Position = 1,HelpMessage = 'Please provide a IP address')] 7 | $ipaddress 8 | ) 9 | <# 10 | .SYNOPSIS 11 | Takes IP address as input and queries LACNIC's RDAP implementation for the IP addresses abuse contact email - RESTFul API 12 | 13 | .DESCRIPTION 14 | Takes a IP Address and searches for LACNIC's RDAP abuse contact email for that IP based on registration data 15 | Returns this contact email address 16 | 17 | .PARAMETER ipaddress 18 | Specifices the specific IP address belonging to LACNIC 19 | 20 | .EXAMPLE 21 | C:\PS> Check-LACNIC -ipaddress '190.42.65.82' 22 | 23 | #> 24 | 25 | 26 | $regx = "[a-z0-9!#\$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#\$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?" 27 | 28 | $rawdata = Invoke-WebRequest -Uri "http://rdap.lacnic.net/rdap/ip/$ipaddress" | ConvertFrom-Json 29 | 30 | for($i = 0;$i -lt ($rawdata.entities.vcardArray).count; $i++) 31 | { 32 | foreach ($item in $rawdata.entities.vcardArray.SyncRoot[$i]) 33 | { 34 | [array]$result += $item 35 | } 36 | } 37 | $parsedresult = $result | Select-String -Pattern $regx 38 | Write-Debug -Message 'parsed result: ' $parsedresult 39 | if ($parsedresult.count -gt 0) 40 | { 41 | return $parsedresult 42 | } 43 | return 'NO POC FOR LACNIC' 44 | } 45 | -------------------------------------------------------------------------------- /Private/Write-LogEntry.ps1: -------------------------------------------------------------------------------- 1 | #requires -Version 1 2 | Function Write-LogEntry 3 | { 4 | param ( 5 | [string]$type, 6 | [string]$message, 7 | [string]$Folder, 8 | [string]$CustomMessage 9 | ) 10 | 11 | [bool]$CustomMessage 12 | 13 | $mutex = New-Object -TypeName 'Threading.Mutex' -ArgumentList $false, 'MyInterprocMutex' 14 | 15 | switch ($type){ 16 | 'Error' 17 | { 18 | $mutex.waitone() 19 | "$((Get-Date).ToString('yyyyMMddThhmmss')) [ERROR]: $message" >> "$($Folder)\log.log" 20 | if ($CustomMessage) 21 | { 22 | "$((Get-Date).ToString('yyyyMMddThhmmss')) [CUSTOM MESSAGE]: $CustomMessage" >> "$($Folder)\log.log" 23 | } 24 | $mutex.ReleaseMutex() 25 | } 26 | 'Info' 27 | { 28 | $mutex.waitone() 29 | "$((Get-Date).ToString('yyyyMMddThhmmss')) [INFO]: $message" >> "$($Folder)\log.log" 30 | if ($CustomMessage) 31 | { 32 | "$((Get-Date).ToString('yyyyMMddThhmmss')) [CUSTOM MESSAGE]: $CustomMessage" >> "$($Folder)\log.log" 33 | } 34 | $mutex.ReleaseMutex() 35 | } 36 | 'Debug' 37 | { 38 | $mutex.waitone() 39 | "$((Get-Date).ToString('yyyyMMddThhmmss')) [DEBUG]: $message" >> "$($Folder)\log.log" 40 | if ($CustomMessage) 41 | { 42 | "$((Get-Date).ToString('yyyyMMddThhmmss')) [CUSTOM MESSAGE]: $CustomMessage" >> "$($Folder)\log.log" 43 | } 44 | $mutex.ReleaseMutex() 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Public/Get-ParsedURL.ps1: -------------------------------------------------------------------------------- 1 | #requires -Version 2 2 | function Get-ParsedURL () 3 | { 4 | [CmdletBinding()] 5 | param ( 6 | [parameter(Mandatory = $true, 7 | HelpMessage = 'Please provide a URL to parse')] 8 | $url, 9 | 10 | [parameter(Mandatory = $true, 11 | HelpMessage = 'Please provide a log path.')] 12 | $logpath 13 | ) 14 | <# 15 | .SYNOPSIS 16 | Takes a URL as input and splits the URL down to just the hostname. This function returns parsed URL 17 | 18 | .DESCRIPTION 19 | Takes a URL as input and splits the URL down to just the hostname. 20 | This function returns parsed URL 21 | 22 | .PARAMETER url 23 | Specifices the specific URL 24 | 25 | .EXAMPLE 26 | C:\PS> Get-ParsedURL -url 'http://outlookadminmailaccess.bravesites.com/' 27 | 28 | #> 29 | 30 | $ReturnObject = @() 31 | 32 | $regexipv4 = '\b((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3} (25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b' 33 | 34 | if ($url -like $regexipv4) 35 | { 36 | $log = Write-LogEntry -type 'INFO' -message "URL is a IP Address - URL = $url" -Folder $logpath 37 | return $url 38 | } 39 | 40 | $ParsedURL = [system.net.webrequest]::Create($url) 41 | 42 | $AbsoluteURL = $ParsedURL.GetResponse().ResponseUri.AbsoluteUri 43 | $URLAuthoirty = $ParsedURL.GetResponse().ResponseUri.Authority 44 | 45 | $props = @{ 46 | OriginalURL = $url 47 | AbsoluteURL = $AbsoluteURL 48 | URLAuthority = $URLAuthoirty 49 | } 50 | 51 | $ReturnObject = New-Object -TypeName PSObject -Property $props 52 | 53 | return $ReturnObject 54 | } 55 | -------------------------------------------------------------------------------- /Public/Send-ToAntiPhishingGroup.ps1: -------------------------------------------------------------------------------- 1 | #requires -Version 2 2 | function Send-ToAntiPhishingGroup 3 | { 4 | [CmdletBinding()] 5 | param ( 6 | [parameter(Mandatory = $true, 7 | HelpMessage = 'Please the trimmed phishing url link')] 8 | [string]$trimmedlink, 9 | 10 | [parameter(Mandatory = $true, 11 | HelpMessage = 'Please provide th Send On Behalf email address.')] 12 | $From, 13 | 14 | [parameter(Mandatory = $true, 15 | HelpMessage = 'Please provide a logging location')] 16 | $LogLocation 17 | ) 18 | 19 | $date = Get-Date -Format yyyyMMdd 20 | "$trimmedlink" + ',' + "$date" 21 | 22 | try 23 | { 24 | $outlook = New-Object -ComObject Outlook.Application 25 | $Mail = $outlook.CreateItem(0) 26 | $Mail.To = 'anti-phishing-email-reply-discuss@googlegroups.com' 27 | $Mail.Sentonbehalfofname = "$($From)" 28 | $Mail.Subject = ('Phishing Links ' + $date) 29 | $Mail.Body = "$trimmedlink" + ',' + "$date" 30 | $Mail.Send() 31 | Write-LogEntry -type Info -message 'Sucessfully sent notification to Abuse Contact' -Folder $LogLocation -CustomMessage "Sent $("$trimmedlink" + ',' + "$date") to: anti-phishing-email-reply-discuss@googlegroups.com" 32 | return $true 33 | } 34 | catch 35 | { 36 | $msg = ('An error occurred that could not be resolved: {0}' -f $_.Exception.Message) 37 | Write-LogEntry -type ERROR -message 'Error Sending to Abuse Contact' -Folder $LogLocation -CustomMessage "$msg" 38 | Write-LogEntry -type ERROR -message 'Exception' -Folder $LogLocation -CustomMessage "$($_.Exception)" 39 | Write-LogEntry -type ERROR -message 'Unknown Exception' -Folder $LogLocation -CustomMessage "$($_)" 40 | return $false 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Public/Send-ToIronPort.ps1: -------------------------------------------------------------------------------- 1 | #requires -Version 2 2 | function Send-ToIronPort 3 | { 4 | [CmdletBinding()] 5 | param ( 6 | [parameter(Mandatory = $true, 7 | HelpMessage = 'Please the original phishing url link')] 8 | [string]$originallink, 9 | 10 | [parameter(Mandatory = $true, 11 | HelpMessage = 'Please provide the original message to attach to email. ')] 12 | $messagetoattach, 13 | 14 | [parameter(Mandatory = $true, 15 | HelpMessage = 'Please provide th Send On Behalf email address.')] 16 | $From, 17 | 18 | [parameter(Mandatory = $true, 19 | HelpMessage = 'Please provide a logging location')] 20 | $LogLocation 21 | ) 22 | 23 | try 24 | { 25 | $outlook = New-Object -ComObject Outlook.Application 26 | $Mail = $outlook.CreateItem(0) 27 | $Mail.To = 'spam@access.ironport.com;phishing-report@us-cert.gov;spam@uce.gov' 28 | $Mail.Attachments.Add($messagetoattach) 29 | $Mail.Sentonbehalfofname = "$($From)" 30 | $Mail.Subject = 'Phishing E-Mail' 31 | $Mail.Body = "The attached email is a phishing email: $originallink" 32 | $Mail.Send() 33 | Write-LogEntry -type Info -message 'Sucessfully sent notification to Abuse Contact' -Folder $LogLocation -CustomMessage 'Sent to: spam@access.ironport.com;phishing-report@us-cert.gov;spam@uce.gov' 34 | return $true 35 | } 36 | catch 37 | { 38 | $msg = ('An error occurred that could not be resolved: {0}' -f $_.Exception.Message) 39 | Write-LogEntry -type ERROR -message 'Error Sending to Abuse Contact' -Folder $LogLocation -CustomMessage "$msg" 40 | Write-LogEntry -type ERROR -message 'Exception' -Folder $LogLocation -CustomMessage "$($_.Exception)" 41 | Write-LogEntry -type ERROR -message 'Unknown Exception' -Folder $LogLocation -CustomMessage "$($_)" 42 | return $false 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Public/Get-URLFromMessage.ps1: -------------------------------------------------------------------------------- 1 | #requires -Version 2 2 | function Get-URLFromMessage 3 | { 4 | [CmdletBinding()] 5 | param ( 6 | [parameter(Mandatory = $true, 7 | HelpMessage = 'Please provide a .MSG file.')] 8 | [PSTypeName('PPRT.Message')] 9 | $Message, 10 | 11 | [Parameter(Mandatory = $true)] 12 | [ValidateScript({ if (Test-Path $_){$true}else{ throw 'Please provide a valid path for LogPath' }})] 13 | $LogPath 14 | ) 15 | <# 16 | .SYNOPSIS 17 | Takes a .MSG file and parses the links from the message. This function returns the full URL within an email. 18 | 19 | .DESCRIPTION 20 | Takes a .MSG file and parses the links from the message. 21 | This function returns the full URL within an email. 22 | 23 | .PARAMETER inputtext 24 | Specifices the specific .MSG to parse 25 | 26 | .EXAMPLE 27 | C:\PS> Get-URLFromMessage 'C:\Users\UserName\Desktop\PHISING_EMAILS\Dear Email User.msg' 28 | 29 | #> 30 | 31 | Begin 32 | { 33 | $URLPattern = '(?:(?:https?|ftp|file)://|www\.|ftp\.)(?:\([-A-Z0-9+&@#/%=~_|$?!:,.]*\)|[-A-Z0-9+&@#/%=~_|$?!:,.])*(?:\([-A-Z0-9+&@#/%=~_|$?!:,.]*\)|[A-Z0-9+&@#/%=~_|$])' 34 | 35 | $URLObject = @() 36 | } 37 | Process 38 | { 39 | $log = Write-LogEntry -type Info -message "Get-URLFromMessage: Getting URL from $($Message.Subject)" -Folder $LogPath 40 | 41 | $URL = $Message.body | Select-String -AllMatches $URLPattern | Select-Object -ExpandProperty Matches | Select-Object -ExpandProperty Value 42 | 43 | $props = @{ 44 | URL = $URL 45 | Name = $Message.Subject 46 | } 47 | 48 | $URLObject = New-Object -TypeName PSObject -Property $props 49 | 50 | $log = Write-LogEntry -type Info -message 'Get-URLFromMessage: Getting URL complete!' -Folder $LogPath 51 | 52 | Add-ObjectDetail -InputObject $URLObject -TypeName PPRT.PhishingURL 53 | } 54 | End 55 | { 56 | 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Public/Get-PhishingGeoLocationAllIPs.ps1: -------------------------------------------------------------------------------- 1 | #requires -Version 2 2 | function Get-PhishingGeoLocationAllIPs 3 | { 4 | [CmdletBinding()] 5 | Param 6 | ( 7 | # Param1 help description 8 | [Parameter(Mandatory = $true)] 9 | [ValidateNotNull()] 10 | [ValidateNotNullOrEmpty()] 11 | [object[]]$AllIPData, 12 | [parameter(HelpMessage = 'Please provide a folder path for ouputting generated maps')] 13 | [ValidateNotNullOrEmpty()] 14 | [string]$FolderPath 15 | ) 16 | 17 | $OutStartingIPMap = "$FolderPath\PPRT_MapAllIPs.html" 18 | 19 | 20 | $html = @" 21 | 22 | 23 | 24 | 25 | 26 | Complex icons 27 | 37 | 38 | 39 |
40 | 78 | 80 | 81 | 82 | "@ | Out-File $OutStartingIPMap 83 | } 84 | -------------------------------------------------------------------------------- /Public/Send-ToAbuseContact.ps1: -------------------------------------------------------------------------------- 1 | #requires -Version 2 2 | function Send-ToAbuseContact 3 | { 4 | [CmdletBinding()] 5 | param ( 6 | [parameter(Mandatory = $true, 7 | HelpMessage = 'Please the original phishing url link')] 8 | [string]$originallink, 9 | 10 | [parameter(Mandatory = $true, 11 | HelpMessage = 'Please provide the abuse contact to send email to.')] 12 | $abusecontact, 13 | 14 | [parameter(Mandatory = $true, 15 | HelpMessage = 'Please provide the original message to attach to email. ')] 16 | $messagetoattach, 17 | 18 | [parameter(Mandatory = $true, 19 | HelpMessage = 'Please provide the Send On Behalf email address.')] 20 | $From, 21 | 22 | [parameter(Mandatory = $true, 23 | HelpMessage = 'Please provide a logging location')] 24 | $LogLocation 25 | ) 26 | 27 | try 28 | { 29 | $outlook = New-Object -ComObject Outlook.Application 30 | $Mail = $outlook.CreateItem(0) 31 | $Mail.To = "$abusecontact" 32 | $Mail.Attachments.Add($messagetoattach) 33 | $Mail.Sentonbehalfofname = "$($From)" 34 | $Mail.Subject = 'Remove Phishing Website' 35 | $Mail.Body = "We have received a phishing attempt (attached) that is using an IP registered to this contact. Please remove this site as soon as you can: $originallink'.' `n`nIn addition, any logs you can provide surrounding the registration or usage of this site would help us understand who is targeting our environment.`n`n Thank you!" 36 | $Mail.Send() 37 | Write-LogEntry -type Info -message 'Sucessfully sent notification to Abuse Contact' -Folder $LogLocation -CustomMessage "Sent to: $abusecontact" 38 | return $true 39 | } 40 | catch 41 | { 42 | $msg = ('An error occurred that could not be resolved: {0}' -f $_.Exception.Message) 43 | Write-LogEntry -type ERROR -message 'Error Sending to Abuse Contact' -Folder $LogLocation -CustomMessage "$msg" 44 | Write-LogEntry -type ERROR -message 'Exception' -Folder $LogLocation -CustomMessage "$($_.Exception)" 45 | Write-LogEntry -type ERROR -message 'Unknown Exception' -Folder $LogLocation -CustomMessage "$($_)" 46 | return $false 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Public/Get-PhishingGeoLocationStartingIPs.ps1: -------------------------------------------------------------------------------- 1 | #requires -Version 2 2 | function Get-PhishingGeoLocationStartingIPs 3 | { 4 | [CmdletBinding()] 5 | Param 6 | ( 7 | # Param1 help description 8 | [Parameter(Mandatory = $true)] 9 | [ValidateNotNull()] 10 | [ValidateNotNullOrEmpty()] 11 | [object[]]$StartingIPData, 12 | [parameter(HelpMessage = 'Please provide a folder path for ouputting generated maps')] 13 | [ValidateNotNullOrEmpty()] 14 | [string]$FolderPath 15 | ) 16 | 17 | $OutStartingIPMap = "$FolderPath\PPRT_StartingIPs.html" 18 | 19 | 20 | $html = @" 21 | 22 | 23 | 24 | 25 | 26 | Complex icons 27 | 37 | 38 | 39 |
40 | 75 | 77 | 78 | 79 | "@ | Out-File $OutStartingIPMap 80 | } 81 | -------------------------------------------------------------------------------- /Public/Get-IPaddress.ps1: -------------------------------------------------------------------------------- 1 | #requires -Version 2 2 | function Get-IPaddress () 3 | { 4 | [CmdletBinding()] 5 | param ( 6 | [parameter(Mandatory = $true, 7 | HelpMessage = 'Please provide a valid HOSTNAME')] 8 | [string]$hostname, 9 | 10 | [Parameter(Mandatory = $true)] 11 | [ValidateScript({ if (Test-Path $_){$true}else{ throw 'Please provide a valid path for LogPath' }})] 12 | $LogPath 13 | ) 14 | <# 15 | .SYNOPSIS 16 | Takes HOSTNAME (DNS Name) as input and does a reverse DNS lookup on that HOSTNAME. This function returns the IP address(es) associated with it. 17 | 18 | .DESCRIPTION 19 | Takes a HOSTNAME (DNS Name) and does a reverse DNS lookup on that HOSTNAME 20 | Returns IP Address(es) associated with that HOSTNAME 21 | 22 | .PARAMETER hostname 23 | Specifices the specific HOSTNAME (DNS Name) 24 | 25 | .EXAMPLE 26 | C:\PS> Get-IPAddress -hostname wix.com 27 | 28 | #> 29 | try 30 | { 31 | $log = Write-LogEntry -type Info -message "Get-IPAddress: Attempting GetHostAddresses for $hostname" -Folder $LogPath 32 | $ipaddresses = [System.Net.Dns]::GetHostAddresses("$hostname").IPAddressToString 33 | $log = Write-LogEntry -type Info -message "Get-IPAddress: GetHostAddresses resolved $hostname to the following ipaddress(es)" -Folder $LogPath -CustomMessage $ipaddresses 34 | 35 | return $ipaddresses 36 | } 37 | catch 38 | { 39 | $log = Write-LogEntry -type Error -message "Get-IPAddress: GetHostAddresses could not resolve this host name: $hostname" -Folder $LogPath 40 | } 41 | 42 | try 43 | { 44 | $log = Write-LogEntry -type Info -message "Get-IPAddress: Attempting Test-Connection for $hostname" -Folder $LogPath 45 | $ipaddresses = (Test-Connection $hostname -Count 1).IPV4Address 46 | $log = Write-LogEntry -type Info -message "Get-IPAddress: Test-Connection resolved $hostname to the following ipaddress(es)" -Folder $LogPath -CustomMessage $ipaddresses 47 | 48 | return $ipaddresses 49 | } 50 | catch 51 | { 52 | $log = Write-LogEntry -type Error -message "Get-IPAddress: Test-Connection could not resolve this host name: $hostname" -Folder $LogPath 53 | } 54 | 55 | 56 | return $null 57 | } 58 | -------------------------------------------------------------------------------- /Public/Get-LongUrl.ps1: -------------------------------------------------------------------------------- 1 | #requires -Version 2 2 | function Expand-ShortURL 3 | { 4 | [CmdletBinding()] 5 | param ( 6 | [parameter(Mandatory = $true, 7 | HelpMessage = 'Please provide a .MSG file.')] 8 | [PSTypeName('PPRT.PhishingURL')] 9 | $URLObject 10 | ) 11 | <# 12 | .SYNOPSIS 13 | Takes a short url and converts it to a long url 14 | 15 | .DESCRIPTION 16 | This function takes a tinyurl and converts it to a long/normal URL using the System.Net.WebRequest class. 17 | This function will continue to call itself until the URL has been expanded successfully. 18 | 19 | .PARAMETER shorturl 20 | This parameter needs to be a TinyUrl at this time. 21 | 22 | .PARAMETER logpath 23 | This parameter is a folder path that you want to log to 24 | 25 | .EXAMPLE 26 | C:\PS> Get-LongUrl -shorturl $shortUrlVariable -logpath $logpath 27 | 28 | #> 29 | 30 | Add-Type -AssemblyName System.Web 31 | 32 | if ($URLObject | Select-Object -Property URL) 33 | { 34 | $url = $URLObject.URL -as [system.URI] 35 | 36 | if (!($uri.AbsoluteURI -ne $null -and $url.Scheme -match '[http|https]')) 37 | { 38 | exit 39 | } 40 | 41 | $encodedurl = [system.net.webrequest]::Create($url) 42 | } 43 | else 44 | { 45 | $url = $URLObject.URL -as [system.URI] 46 | 47 | if (!($uri.AbsoluteURI -ne $null -and $url.Scheme -match '[http|https]')) 48 | { 49 | exit 50 | } 51 | 52 | $encodedurl = [system.net.webrequest]::Create($url) 53 | } 54 | 55 | $AbsoluteURL = $encodedurl.GetResponse().ResponseUri.AbsoluteUri 56 | $URLAuthority = $encodedurl.GetResponse().ResponseUri.Authority 57 | 58 | Import-Clixml -Path "$(Split-Path -Path $Script:MyInvocation.MyCommand.Path)\Private\shorturls.xml" | ForEach-Object -Process { 59 | if ($AbsoluteURL -like '$_') 60 | { 61 | #call Get-LongUrl to call API to resolve to the normal/long url 62 | $ExpandedURL = Add-ObjectDetail -InputObject $AbsoluteURL -TypeName PPRT.PhishingURL 63 | $FinalURL = Expand-ShortURL $ExpandedURL 64 | } 65 | } 66 | 67 | $props = @{ 68 | OriginalURL = $URLObject 69 | EncodedURL = $encodedurl 70 | AbsoluteURL = $AbsoluteURL 71 | URLAuthority = $URLAuthority 72 | } 73 | 74 | $ReturnObject = New-Object -TypeName PSObject -Property $props 75 | 76 | Add-ObjectDetail -InputObject $ReturnObject -TypeName PPRT.ExpandedURL 77 | } 78 | -------------------------------------------------------------------------------- /Public/Get-WhichWHOIS.ps1: -------------------------------------------------------------------------------- 1 | #requires -Version 2 2 | function Get-WhichWHOIS () 3 | { 4 | [CmdletBinding()] 5 | param ( 6 | [parameter(Mandatory = $true,Position = 1,HelpMessage = 'Please provide a valid IP Address')] 7 | [string]$ipaddress, 8 | 9 | [Parameter(Mandatory = $true)] 10 | [ValidateScript({ if (Test-Path $_){$true}else{ throw 'Please provide a valid path for LogPath' }})] 11 | $LogPath 12 | ) 13 | <# 14 | .SYNOPSIS 15 | Takes IPAddress as input and finds which whois should be used. 16 | 17 | .DESCRIPTION 18 | Takes an IPAddress and splits the first octect of the IP address 19 | Takes the first octect and compares against arrays of registrars 20 | Returns which whois should be used 21 | 22 | .PARAMETER ipaddress 23 | Specifies the ipdadress we are wanting information on 24 | 25 | .EXAMPLE 26 | C:\PS> Get-WhichWHOIS -ipaddress '189.84.54.56' 27 | 28 | #> 29 | 30 | $RegistryObject = @() 31 | 32 | try 33 | { 34 | $RegistryData = Invoke-RestMethod -Uri 'http://www.iana.org/assignments/ipv4-address-space/ipv4-address-space.xml' 35 | } 36 | catch 37 | { 38 | Write-LogEntry -type ERROR -message 'UNABLE TO REACH IANA.org' -Folder $LogPath 39 | 40 | throw 'Unable to reach http://www.iana.org/assignments/ipv4-address-space/ipv4-address-space.xml at this time' 41 | } 42 | 43 | for ($i = 1; $i -le $RegistryData.registry.record.Count; $i++) 44 | { 45 | if ($null -ne $RegistryData.registry.record[$i].prefix) 46 | { 47 | $TrimmedIP = $(($RegistryData.registry.record[$i].prefix).TrimStart("0")) 48 | 49 | $ComparisonIP = $TrimmedIP.Substring(0,$TrimmedIP.Length - 2) 50 | 51 | if ($($ipaddress.Split('{.}')[0]) -eq $ComparisonIP) 52 | { 53 | if ($RegistryData.registry.record[$i].whois -ne $null) 54 | { 55 | $WHOIS = ($RegistryData.registry.record[$i].whois) 56 | 57 | $props = @{ 58 | IPAddress = $ipaddress 59 | WHOIS = $(($WHOIS).Split('{.}')[1]) 60 | OriginalWHOIS = $WHOIS 61 | } 62 | 63 | $TempObject = New-Object -TypeName PSCustomObject -Property $props 64 | 65 | Add-ObjectDetail -InputObject $TempObject -TypeName PPRT.WHOIS 66 | } 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Public/Expand-ShortURL.ps1: -------------------------------------------------------------------------------- 1 | #requires -Version 2 2 | function Get-AbsoluteUri 3 | { 4 | [CmdletBinding()] 5 | param ( 6 | [parameter(Mandatory = $true, 7 | HelpMessage = 'Please provide a .MSG file.')] 8 | [PSTypeName('PPRT.PhishingURL')] 9 | $URLObject 10 | ) 11 | <# 12 | .SYNOPSIS 13 | Takes a short url and converts it to a long url 14 | 15 | .DESCRIPTION 16 | This function takes a tinyurl and converts it to a long/normal URL using the System.Net.WebRequest class. 17 | This function will continue to call itself until the URL has been expanded successfully. 18 | 19 | .PARAMETER shorturl 20 | This parameter needs to be a TinyUrl at this time. 21 | 22 | .PARAMETER logpath 23 | This parameter is a folder path that you want to log to 24 | 25 | .EXAMPLE 26 | C:\PS> Get-LongUrl -shorturl $shortUrlVariable -logpath $logpath 27 | 28 | #> 29 | 30 | Add-Type -AssemblyName System.Web 31 | 32 | if ($URLObject | Select-Object -Property URL) 33 | { 34 | $url = $URLObject.URL -as [system.URI] 35 | 36 | if (!($url.AbsoluteURI -ne $null -and $url.Scheme -match '[http|https]')) 37 | { 38 | Write-Error -Message 'URL is not formatted correctly' 39 | } 40 | 41 | $encodedurl = [system.net.webrequest]::Create($url) 42 | } 43 | else 44 | { 45 | $url = $URLObject.URL -as [system.URI] 46 | 47 | if (!($url.AbsoluteURI -ne $null -and $url.Scheme -match '[http|https]')) 48 | { 49 | Write-Error -Message 'URL is not formatted correctly' 50 | } 51 | 52 | $encodedurl = [system.net.webrequest]::Create($url) 53 | } 54 | 55 | $AbsoluteURL = $encodedurl.GetResponse().ResponseUri.AbsoluteUri 56 | $URLAuthority = $encodedurl.GetResponse().ResponseUri.Authority 57 | 58 | Import-Clixml -Path "$(Split-Path -Path $Script:MyInvocation.MyCommand.Path)\Private\shorturls.xml" | ForEach-Object -Process { 59 | if ($AbsoluteURL -like '$_') 60 | { 61 | #call Get-LongUrl to call API to resolve to the normal/long url 62 | $ExpandedURL = Add-ObjectDetail -InputObject $AbsoluteURL -TypeName PPRT.PhishingURL 63 | $FinalURL = Expand-ShortURL $ExpandedURL 64 | } 65 | } 66 | 67 | $props = @{ 68 | OriginalURL = $URLObject 69 | EncodedURL = $encodedurl 70 | AbsoluteURL = $AbsoluteURL 71 | URLAuthority = $URLAuthority 72 | } 73 | 74 | $ReturnObject = New-Object -TypeName PSObject -Property $props 75 | 76 | Add-ObjectDetail -InputObject $ReturnObject -TypeName PPRT.ExpandedURL 77 | } 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PPRT 2 | ============= 3 | 4 | This PowerShell Module is designed to send notifications to hosting companies that host phishing URLs by utilizing the major WHOIS/RDAP Abuse Point of Contact (POC) information. 5 | 6 | 0. This function takes in a .msg file and strips links from a phishing URL. 7 | 0. After getting the phishig email, it is then converted to it's IP Address. 8 | 0. Once the IP Address of the hosting website is identified, then we check which WHOIS/RDAP to search. 9 | 0. Each major WHOIS/RDAP is represented: ARIN, APNIC, AFRNIC, LACNIC, & RIPE. 10 | 0. We call the specific WHOIS/RDAP's API to determine the Abuse POC. 11 | 0. Once we have the POC, we send them an email telling them to shut the website down. This email contains the original email as an attachment, the original phishing link, and verbage telling them to remove the website. 12 | 13 | This Module came out of necessity. I was sick of trying to contact these individual sites, so I have began automating our response time to these events. 14 | 15 | The next steps for this project are to fully intergrate into Outlook and automate this even further by enabling a simple text search or based on a selected 'folder' event. 16 | 17 | Pull requests and other contributions would be welcome! 18 | 19 | # Instructions 20 | 21 | ```powershell 22 | # One time setup 23 | # Download the repository 24 | # Unblock the zip 25 | # Extract the PPRT folder to a module path (e.g. $env:USERPROFILE\Documents\WindowsPowerShell\Modules\) 26 | 27 | # Import the module. 28 | Import-Module PPRT #Alternatively, Import-Module \\Path\To\PPRT 29 | 30 | # Get commands in the module 31 | Get-Command -Module PPRT 32 | 33 | # Get help 34 | Get-Help New-MessageObject -Full 35 | Get-Help Invoke-PhishingResponse 36 | ``` 37 | 38 | ### Prerequisites 39 | 40 | * PowerShell 3 or later 41 | * A valid VirsuTotal API token (if using this feature) 42 | * This module using Posh-VirusTotal (https://github.com/darkoperator/Posh-VirusTotal) 43 | 44 | # Examples 45 | 46 | ### Create a New-MessageObject 47 | 48 | ```powershell 49 | # This example creates a new PPRT.Message Object 50 | 51 | $msgobj= New-MessageObject -Message C:\PHISHING_EMAILS -FullDetails -LogPath C:\PHISHING_EMAILS 52 | 53 | 54 | ```powershell 55 | # This example creates a new PPRT.Message Object 56 | 57 | #A folder that contains a single or multiple Phishing Emails 58 | $Message = C:\PHISHING_EMAILS 59 | 60 | #A folder that you want the log file to be created 61 | $LogPath = C:\PHISHING_EMAILS 62 | 63 | $MsgObject = New-MessageObject -Uri $Message ` 64 | -LogPath $LogPath ` 65 | -FullDetails 66 | 67 | ### Invoke-PhishingResponse 68 | 69 | ```powershell 70 | # This example calls Invoke-PhishingResponse 71 | 72 | #A PPRT.Message Object 73 | $Message = $MsgObject 74 | 75 | #A From address to send Phishing Notification to Abuse Contact 76 | $From = 'abuse@company.com' 77 | 78 | #The From Addresses SMTP Server 79 | $SMTPServer = 'smtp.office365.com' 80 | 81 | #Credentials for Send-MailMessage 82 | $Cred = (Get-Credential) 83 | 84 | #A folder that you want the log file to be created 85 | $LogPath = C:\PHISHING_EMAILS 86 | 87 | $PhishingResponse = Invoke-PhishingResponse -Message $MsgObject ` 88 | -From $From ` 89 | -SMTPServer $SMTPServer ` 90 | -Credential $cred ` 91 | -LogPath $LogPath 92 | 93 | 94 | -------------------------------------------------------------------------------- /Public/Check-ARIN.ps1: -------------------------------------------------------------------------------- 1 | function Check-ARIN (){ 2 | 3 | param ( 4 | [parameter(Mandatory=$true,Position=1,HelpMessage="Please provide a IP address")] 5 | $ipaddress, 6 | 7 | [Parameter(Mandatory = $true)] 8 | [ValidateScript({ if (Test-Path $_){$true}else{ throw 'Please provide a valid path for LogPath' }})] 9 | $LogPath 10 | ) 11 | <# 12 | .SYNOPSIS 13 | Takes IP address as input and queries ARIN's WHOIS implementation for the IP addresses abuse contact email - RESTFul API 14 | 15 | .DESCRIPTION 16 | Takes a IP Address and searches for ARIN's WHOIS abuse contact email for that IP based on registration data 17 | Returns this contact email address 18 | 19 | .PARAMETER ipaddress 20 | Specifices the specific IP address belonging to ARIN 21 | 22 | 23 | .EXAMPLE 24 | C:\PS> Check-ARIN 146.42.65.82 25 | 26 | #> 27 | Begin 28 | { 29 | try 30 | { 31 | $TestingWebRequest = Invoke-WebRequest -Uri 'https://www.google.com' 32 | } 33 | catch 34 | { 35 | throw 'Unable to connect to google.com or Internet Explorer has not be used on this system.` 36 | Please correct this before proceeding.' 37 | break 38 | } 39 | } 40 | Process 41 | { 42 | try 43 | { 44 | $ipdata = Invoke-RestMethod -Uri "http://whois.arin.net/rest/ip/$ipaddress" -UseBasicParsing 45 | } 46 | catch 47 | { 48 | Write-LogEntry -type ERROR ` 49 | -message 'Unable to Invoke-RestMethod against whois.arin.net' ` 50 | -CustomMessage $($Error[0] | Format-List -Property * -Force) ` 51 | -Folder $LogPath 52 | } 53 | 54 | try 55 | { 56 | $statusCode = Invoke-WebRequest $(($ipdata.net.orgRef.'#text')+'/pocs') | % {$_.StatusCode} 57 | } 58 | catch 59 | { 60 | Write-LogEntry -type ERROR ` 61 | -message 'Unable to Invoke-WebRequest to get Status code of site' ` 62 | -CustomMessage $($Error[0] | Format-List -Property * -Force) ` 63 | -Folder $LogPath 64 | } 65 | 66 | $orgdata = Invoke-RestMethod -Uri $(($ipdata.net.orgRef.'#text')+'/pocs') 67 | #$parentnetref = Invoke-RestMethod -Uri $(($ipdata.net.parentNetRef.'#text')+'/org/pocs') 68 | 69 | # $parentpocdata = Invoke-RestMethod -Uri ("http://whois.arin.net/rest/poc/"+$($parentnetref.pocs.pocLinkRef | ?{$_.description -eq 'Abuse'}).handle) 70 | # $parentpocdata 71 | $orgDataObject = @() 72 | 73 | foreach ($item in $orgdata.pocs.pocLinkRef) 74 | { 75 | $pocdata = @() 76 | $pocdata = Invoke-RestMethod -Uri ("http://whois.arin.net/rest/poc/"+$($item.handle))# 77 | 78 | $props = @{ 79 | Type = $item.Description 80 | Function = $item.Function 81 | Handle = $item.Handle 82 | URL = $item.'#text' 83 | POC = $pocdata.poc 84 | } 85 | 86 | $tempObject = New-Object -TypeName PSCustomObject -Property $props 87 | $orgDataObject += $tempObject 88 | } 89 | 90 | Add-ObjectDetail -InputObject $orgDataObject -TypeName PPRT.ARIN 91 | 92 | 93 | 94 | # $pocdata = Invoke-RestMethod -Uri ("http://whois.arin.net/rest/poc/"+$($orgdata.pocs.pocLinkRef)) #| ?{$_.description -eq 'Abuse'}).handle) 95 | # $pocdata 96 | # If ($pocdata.poc.emails.InnerText -ne ""){return $pocdata.poc.emails.InnerText} 97 | # If ($parentpocdata.poc.emails.InnerText -ne ""){return $parentpocdata.poc.emails.InnerText} 98 | } 99 | End 100 | { 101 | # return "NO ABUSE POC ON RECORD" 102 | } 103 | } 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /Public/Connect-ARIN.ps1: -------------------------------------------------------------------------------- 1 | function Connect-ARIN (){ 2 | 3 | param ( 4 | [parameter(Mandatory=$true,Position=1,HelpMessage="Please provide a IP address")] 5 | $ipaddress, 6 | 7 | [Parameter(Mandatory = $true)] 8 | [ValidateScript({ if (Test-Path $_){$true}else{ throw 'Please provide a valid path for LogPath' }})] 9 | $LogPath 10 | ) 11 | <# 12 | .SYNOPSIS 13 | Takes IP address as input and queries ARIN's WHOIS implementation for the IP addresses abuse contact email - RESTFul API 14 | 15 | .DESCRIPTION 16 | Takes a IP Address and searches for ARIN's WHOIS abuse contact email for that IP based on registration data 17 | Returns this contact email address 18 | 19 | .PARAMETER ipaddress 20 | Specifices the specific IP address belonging to ARIN 21 | 22 | 23 | .EXAMPLE 24 | C:\PS> Check-ARIN 146.42.65.82 25 | 26 | #> 27 | Begin 28 | { 29 | try 30 | { 31 | $TestingWebRequest = Invoke-WebRequest -Uri 'https://www.google.com' | Out-Null 32 | } 33 | catch 34 | { 35 | throw 'Unable to connect to google.com or Internet Explorer has not be used on this system.` 36 | Please correct this before proceeding.' 37 | break 38 | } 39 | } 40 | Process 41 | { 42 | try 43 | { 44 | $ipdata = Invoke-RestMethod -Uri "http://rdap.arin.net/bootstrap/ip/$ipaddress" -UseBasicParsing 45 | } 46 | catch 47 | { 48 | Write-LogEntry -type ERROR ` 49 | -message 'Unable to Invoke-RestMethod against whois.arin.net' ` 50 | -CustomMessage $($Error[0] | Format-List -Property * -Force) ` 51 | -Folder $LogPath 52 | } 53 | 54 | try 55 | { 56 | $statusCode = Invoke-WebRequest $(($ipdata.net.orgRef.'#text')+'/pocs') | % {$_.StatusCode} 57 | } 58 | catch 59 | { 60 | Write-LogEntry -type ERROR ` 61 | -message 'Unable to Invoke-WebRequest to get Status code of site' ` 62 | -CustomMessage $($Error[0] | Format-List -Property * -Force) ` 63 | -Folder $LogPath 64 | } 65 | 66 | $orgdata = Invoke-RestMethod -Uri $(($ipdata.net.orgRef.'#text')+'/pocs') 67 | #$parentnetref = Invoke-RestMethod -Uri $(($ipdata.net.parentNetRef.'#text')+'/org/pocs') 68 | 69 | # $parentpocdata = Invoke-RestMethod -Uri ("http://whois.arin.net/rest/poc/"+$($parentnetref.pocs.pocLinkRef | ?{$_.description -eq 'Abuse'}).handle) 70 | # $parentpocdata 71 | $orgDataObject = @() 72 | 73 | foreach ($item in $orgdata.pocs.pocLinkRef) 74 | { 75 | $pocdata = @() 76 | $pocdata = Invoke-RestMethod -Uri ("http://whois.arin.net/rest/poc/"+$($item.handle))# 77 | 78 | $props = @{ 79 | Type = $item.Description 80 | Function = $item.Function 81 | Handle = $item.Handle 82 | URL = $item.'#text' 83 | POC = $pocdata.poc 84 | } 85 | 86 | $tempObject = New-Object -TypeName PSCustomObject -Property $props 87 | $orgDataObject += $tempObject 88 | } 89 | 90 | Add-ObjectDetail -InputObject $orgDataObject -TypeName PPRT.ARIN 91 | 92 | 93 | 94 | # $pocdata = Invoke-RestMethod -Uri ("http://whois.arin.net/rest/poc/"+$($orgdata.pocs.pocLinkRef)) #| ?{$_.description -eq 'Abuse'}).handle) 95 | # $pocdata 96 | # If ($pocdata.poc.emails.InnerText -ne ""){return $pocdata.poc.emails.InnerText} 97 | # If ($parentpocdata.poc.emails.InnerText -ne ""){return $parentpocdata.poc.emails.InnerText} 98 | } 99 | End 100 | { 101 | # return "NO ABUSE POC ON RECORD" 102 | } 103 | } 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /Public/New-AllReceivedFromIPObject.ps1: -------------------------------------------------------------------------------- 1 | #requires -Version 3 2 | <# 3 | .Synopsis 4 | Short description 5 | .DESCRIPTION 6 | Long description 7 | .EXAMPLE 8 | Example of how to use this cmdlet 9 | .EXAMPLE 10 | Another example of how to use this cmdlet 11 | #> 12 | function New-AllReceivedFromIPObject 13 | { 14 | [CmdletBinding()] 15 | Param 16 | ( 17 | [Parameter(Mandatory = $true, 18 | ValueFromPipelineByPropertyName = $true, 19 | ParameterSetName = 'MessageObject')] 20 | [PSTypeName('PPRT.Message')] 21 | $MessageObject, 22 | 23 | [Parameter(Mandatory = $true, 24 | ValueFromPipelineByPropertyName = $true, 25 | ParameterSetName = 'EmailHeader')] 26 | $EmailHeader, 27 | 28 | [Parameter(Mandatory = $true, 29 | ValueFromPipelineByPropertyName = $true)] 30 | $SavePath, 31 | 32 | [Parameter(Mandatory = $false, 33 | ValueFromPipelineByPropertyName = $true)] 34 | [switch]$HeatMap 35 | ) 36 | 37 | Begin 38 | { 39 | #regex is used for getting IPs from String 40 | $regex = '\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b' 41 | 42 | $polyline = @() #used for array of PolyLines 43 | $originalPolyline = @() 44 | $ReceivedFromIP = @() 45 | $ReturnObject = @() 46 | } 47 | Process 48 | { 49 | 50 | switch ($PSBoundParameters.Keys) 51 | { 52 | 'MessageObject' 53 | { 54 | $msg = $MessageObject.Headers 55 | } 56 | 'EmailHeader' 57 | { 58 | if($null -ne $EmailHeader) 59 | { 60 | $msg = $EmailHeader 61 | } 62 | else 63 | { 64 | Write-Warning -Message 'Please provide Email Headers' 65 | break 66 | } 67 | } 68 | } 69 | 70 | foreach ($item in $MessageObject) 71 | { 72 | $ReceivedFromIP = (Parse-EmailHeader -InputFileName $item.Headers).From | 73 | Select-String -Pattern $regex -AllMatches | 74 | ForEach-Object -Process { 75 | $_.Matches 76 | } | 77 | ForEach-Object -Process { 78 | $_.Value 79 | } 80 | 81 | 82 | foreach ($ip in $ReceivedFromIP) 83 | { 84 | $IpLocation = '' 85 | $IpLocation = Invoke-RestMethod -Uri "http://freegeoip.net/xml/$($ip)" 86 | 87 | if (($IpLocation.Response.Latitude -ne 0) -or ($IpLocation.Response.Longitude -ne 0)) 88 | { 89 | if (![string]::IsNullOrWhiteSpace($IpLocation.Response.Latitude)) 90 | { 91 | if (![string]::IsNullOrWhiteSpace($IpLocation.Response.Longitude)) 92 | { 93 | $originalPolyline = "{lat: $($IpLocation.Response.Latitude), lng: $($IpLocation.Response.Longitude)}" 94 | $polyline += $originalPolyline 95 | } 96 | } 97 | } 98 | } 99 | 100 | $props = @{ 101 | marker = "[$($polyline -join ',')]" 102 | subject = $item.Subject 103 | SentFromAddress = $item.SenderEmailAddress 104 | SentFromType = $item.SenderEmailType 105 | ReceivedTime = $item.ReceivedTime 106 | EmailBody = $item.Body 107 | } 108 | 109 | $tempAllIPObject = New-Object -TypeName PSObject -Property $props 110 | $AllIPObject += $tempAllIPObject 111 | 112 | $polyline = @() 113 | } 114 | 115 | $ReturnObject = $AllIPObject 116 | } 117 | End 118 | { 119 | return $ReturnObject 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /Public/Get-AbsoluteUri.ps1: -------------------------------------------------------------------------------- 1 | #requires -Version 2 2 | function Get-AbsoluteUri 3 | { 4 | [CmdletBinding()] 5 | param ( 6 | [parameter(Mandatory = $true, 7 | HelpMessage = 'Please provide a .MSG file.')] 8 | #[PSTypeName('PPRT.PhishingURL')] 9 | $URLObject, 10 | 11 | [Parameter(Mandatory = $true)] 12 | [ValidateScript({ if (Test-Path $_){$true}else{ throw 'Please provide a valid path for LogPath' }})] 13 | $LogPath 14 | ) 15 | <# 16 | .SYNOPSIS 17 | This function will get the Absolute Uri for a given URLObject 18 | 19 | .DESCRIPTION 20 | This function checks and verifies that URL passed in is formed for the correct scheme 21 | We will get the response from contacting the website and return detailed information about 22 | the given URL. 23 | 24 | .PARAMETER URLObject 25 | A PPRT.PhishingURL Object Type that will checked and more detail returned. 26 | 27 | .PARAMETER LogPath 28 | This parameter is a folder path that you want to log to 29 | 30 | .EXAMPLE 31 | C:\PS> Get-AbsoluteUri -URLObject $URL -LogPath $LogPath 32 | 33 | #> 34 | 35 | $ReturnObject = @() 36 | $AbsoluteURL = @() 37 | $URLAuthority = @() 38 | 39 | Add-Type -AssemblyName System.Web 40 | 41 | $TempURL = $URLObject.URL 42 | 43 | if (($null -eq $TempURL.AbsoluteURI -and $TempURL.Scheme -match '[http|https]')) 44 | { 45 | $log = Write-LogEntry -type Error -message 'Get-AbsoluteUri: URL is not the correct scheme' -Folder $LogPath 46 | } 47 | 48 | $log = Write-LogEntry -type Info -message 'Get-AbsoluteUri: Creating WebRequest' -Folder $LogPath 49 | 50 | 51 | $encodedurl = [system.net.webrequest]::Create($($TempURL)) 52 | $Response = $null 53 | 54 | try 55 | { 56 | $log = Write-LogEntry -type Info -message 'Get-AbsoluteUri: Getting Response' -Folder $LogPath 57 | $Response = $encodedurl.GetResponse() 58 | 59 | $AbsoluteURL = $Response.ResponseUri.AbsoluteUri 60 | $URLAuthority = $Response.ResponseUri.Authority 61 | } 62 | catch 63 | { 64 | $log = Write-LogEntry -type Error -message 'Get-AbsoluteUri: ERROR GETTING RESPONSE!!!' -Folder $LogPath -CustomMessage 'BREAK!' 65 | continue 66 | } 67 | finally 68 | { 69 | # Clear the response, otherwise the next HttpWebRequest may fail... (don't know why) 70 | if ($Response -ne $null) 71 | { 72 | $Response.Close() 73 | } 74 | } 75 | 76 | try 77 | { 78 | Import-Clixml -Path "$(Split-Path -Path $Script:MyInvocation.MyCommand.Path)\Private\shorturls.xml" | ForEach-Object -Process { 79 | if ($AbsoluteURL -like '$_') 80 | { 81 | $log = Write-LogEntry -type Info -message "Get-AbsoluteUri: URL matches a Short URL - $AbsoluteURL = $($_)" -Folder $LogPath 82 | 83 | #call Get-LongUrl to call API to resolve to the normal/long url 84 | $ExpandedURL = Add-ObjectDetail -InputObject $AbsoluteURL -TypeName PPRT.PhishingURL 85 | 86 | $log = Write-LogEntry -type Info -message 'Get-AbsoluteUri: Calling Get-AbsoluteUri Again' -Folder $LogPath 87 | 88 | $FinalURL = Get-AbsoluteUri $ExpandedURL 89 | } 90 | } 91 | } 92 | catch 93 | { 94 | $log = Write-LogEntry -type Error -message 'Get-AbsoluteUri: Unable to find shorturls.xml!' -Folder $LogPath 95 | } 96 | 97 | $props = @{ 98 | OriginalURL = $URLObject 99 | EncodedURL = $($encodedurl) 100 | AbsoluteURL = $AbsoluteURL 101 | URLAuthority = $URLAuthority 102 | } 103 | 104 | $ReturnObject = New-Object -TypeName PSObject -Property $props 105 | 106 | $log = Write-LogEntry -type Info -message 'Get-AbsoluteUri: Completed Successfully!' -Folder $LogPath 107 | 108 | Add-ObjectDetail -InputObject $ReturnObject -TypeName PPRT.Uri 109 | } 110 | -------------------------------------------------------------------------------- /Private/Create-PPRTDatabase.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Short description 4 | .DESCRIPTION 5 | Long description 6 | .EXAMPLE 7 | Example of how to use this cmdlet 8 | .EXAMPLE 9 | Another example of how to use this cmdlet 10 | #> 11 | function Create-PPRT2Database 12 | { 13 | [CmdletBinding()] 14 | Param 15 | ( 16 | # Param1 help description 17 | [Parameter(Mandatory=$true, 18 | ValueFromPipelineByPropertyName=$true 19 | )] 20 | $Server, 21 | 22 | [Parameter(Mandatory=$true, 23 | ValueFromPipelineByPropertyName=$true 24 | )] 25 | $DatabaseLocation, 26 | 27 | [Parameter(Mandatory=$true, 28 | ValueFromPipelineByPropertyName=$true 29 | )] 30 | $LoggingLocation 31 | ) 32 | 33 | Begin 34 | { 35 | $User = 'Administrator' 36 | $PWord = '!QAZ2wsx' 37 | 38 | 39 | $conn = New-Object System.Data.SqlClient.SqlConnection 40 | $conn.ConnectionString = "Data Source=172.20.1.117,1433;Network Library=DBMSSOCN;Initial Catalog=myDataBase;User ID=$($User);Password=$($PWord);" 41 | $conn.open() 42 | 43 | $CreatePPRT2Database = @" 44 | USE [master] 45 | GO 46 | 47 | /****** Object: Database [PPRT2] Script Date: 5/31/2016 8:29:38 PM ******/ 48 | CREATE DATABASE [PPRT22] 49 | CONTAINMENT = NONE 50 | ON PRIMARY 51 | ( NAME = N'PPRT2', FILENAME = N'$($DatabaseLocation)\PPRT22.mdf' , SIZE = 4096KB , MAXSIZE = UNLIMITED, FILEGROWTH = 1024KB ) 52 | LOG ON 53 | ( NAME = N'PPRT2_log', FILENAME = N'$($LoggingLocation)\PPRT22_log.ldf' , SIZE = 1024KB , MAXSIZE = 2048GB , FILEGROWTH = 10%) 54 | GO 55 | 56 | ALTER DATABASE [PPRT2] SET COMPATIBILITY_LEVEL = 120 57 | GO 58 | 59 | IF (1 = FULLTEXTSERVICEPROPERTY('IsFullTextInstalled')) 60 | begin 61 | EXEC [PPRT2].[dbo].[sp_fulltext_database] @action = 'enable' 62 | end 63 | GO 64 | 65 | ALTER DATABASE [PPRT2] SET ANSI_NULL_DEFAULT OFF 66 | GO 67 | 68 | ALTER DATABASE [PPRT2] SET ANSI_NULLS OFF 69 | GO 70 | 71 | ALTER DATABASE [PPRT2] SET ANSI_PADDING OFF 72 | GO 73 | 74 | ALTER DATABASE [PPRT2] SET ANSI_WARNINGS OFF 75 | GO 76 | 77 | ALTER DATABASE [PPRT2] SET ARITHABORT OFF 78 | GO 79 | 80 | ALTER DATABASE [PPRT2] SET AUTO_CLOSE OFF 81 | GO 82 | 83 | ALTER DATABASE [PPRT2] SET AUTO_SHRINK OFF 84 | GO 85 | 86 | ALTER DATABASE [PPRT2] SET AUTO_UPDATE_STATISTICS ON 87 | GO 88 | 89 | ALTER DATABASE [PPRT2] SET CURSOR_CLOSE_ON_COMMIT OFF 90 | GO 91 | 92 | ALTER DATABASE [PPRT2] SET CURSOR_DEFAULT GLOBAL 93 | GO 94 | 95 | ALTER DATABASE [PPRT2] SET CONCAT_NULL_YIELDS_NULL OFF 96 | GO 97 | 98 | ALTER DATABASE [PPRT2] SET NUMERIC_ROUNDABORT OFF 99 | GO 100 | 101 | ALTER DATABASE [PPRT2] SET QUOTED_IDENTIFIER OFF 102 | GO 103 | 104 | ALTER DATABASE [PPRT2] SET RECURSIVE_TRIGGERS OFF 105 | GO 106 | 107 | ALTER DATABASE [PPRT2] SET DISABLE_BROKER 108 | GO 109 | 110 | ALTER DATABASE [PPRT2] SET AUTO_UPDATE_STATISTICS_ASYNC OFF 111 | GO 112 | 113 | ALTER DATABASE [PPRT2] SET DATE_CORRELATION_OPTIMIZATION OFF 114 | GO 115 | 116 | ALTER DATABASE [PPRT2] SET TRUSTWORTHY OFF 117 | GO 118 | 119 | ALTER DATABASE [PPRT2] SET ALLOW_SNAPSHOT_ISOLATION OFF 120 | GO 121 | 122 | ALTER DATABASE [PPRT2] SET PARAMETERIZATION SIMPLE 123 | GO 124 | 125 | ALTER DATABASE [PPRT2] SET READ_COMMITTED_SNAPSHOT OFF 126 | GO 127 | 128 | ALTER DATABASE [PPRT2] SET HONOR_BROKER_PRIORITY OFF 129 | GO 130 | 131 | ALTER DATABASE [PPRT2] SET RECOVERY FULL 132 | GO 133 | 134 | ALTER DATABASE [PPRT2] SET MULTI_USER 135 | GO 136 | 137 | ALTER DATABASE [PPRT2] SET PAGE_VERIFY CHECKSUM 138 | GO 139 | 140 | ALTER DATABASE [PPRT2] SET DB_CHAINING OFF 141 | GO 142 | 143 | ALTER DATABASE [PPRT2] SET FILESTREAM( NON_TRANSACTED_ACCESS = OFF ) 144 | GO 145 | 146 | ALTER DATABASE [PPRT2] SET TARGET_RECOVERY_TIME = 0 SECONDS 147 | GO 148 | 149 | ALTER DATABASE [PPRT2] SET DELAYED_DURABILITY = DISABLED 150 | GO 151 | 152 | ALTER DATABASE [PPRT2] SET READ_WRITE 153 | GO 154 | "@ 155 | } 156 | Process 157 | { 158 | $cmd = New-Object System.Data.SqlClient.SqlCommand 159 | $cmd.connection = $conn 160 | $cmd.Commandtext = "$CreatePPRT2Database" 161 | $command.ExecuteNonQuery() 162 | } 163 | End 164 | { 165 | $conn.close() 166 | } 167 | } 168 | 169 | 170 | -------------------------------------------------------------------------------- /Public/New-PPRTAbuseContactObject.ps1: -------------------------------------------------------------------------------- 1 | #requires -Version 2 2 | <# 3 | .Synopsis 4 | New-PPRTAbuseContactObject is a function to gather information about the abuse/registration POC 5 | .DESCRIPTION 6 | New-PPRTAbuseContactObject is a function to gather information about the abuse/registration POC. 7 | Additionally, this function will: 8 | Check to see if the URL is alive 9 | Get detailed URL information (AbsoluteUri) 10 | Get the IP Address of the host/URL 11 | Find out which WHOIS owns this IP 12 | Run against the owner/registrars RDAP/WHOIS API 13 | .EXAMPLE 14 | New-PPRTAbuseContactObject -URLObject $URLObject -LogPath $LogPath 15 | .EXAMPLE 16 | $URLObject | New-PPRTAbuseContactObject -LogPath $LogPath 17 | #> 18 | function New-PPRTAbuseContactObject 19 | { 20 | [CmdletBinding()] 21 | [OutputType([System.Collections.Hashtable],[String])] 22 | Param 23 | ( 24 | [Parameter(Mandatory = $true, 25 | ValueFromPipeline = $true, 26 | ValueFromPipelineByPropertyName = $true)] 27 | # [PSTypeName('PPRT.PhishingURL')] 28 | $URLObject, 29 | 30 | [Parameter(Mandatory = $true)] 31 | [ValidateScript({ if (Test-Path $_){$true}else{ throw 'Please provide a valid path for LogPath' }})] 32 | $LogPath 33 | ) 34 | Begin 35 | { 36 | $AllAttachments = @() 37 | $ReturnOjbect = @() 38 | 39 | $object = @{} 40 | } 41 | Process 42 | { 43 | foreach ($url in $URLObject) 44 | { 45 | $Body = @{} 46 | 47 | $Body.URL = $url 48 | 49 | $URLAlive = @() 50 | 51 | # try 52 | # { 53 | 54 | $log = Write-LogEntry -type Info -message "New-PPRTAbuseContactObject: Verifying that URL is alive - $($url)" -Folder $LogPath 55 | 56 | $log = Write-LogEntry -type Info -message 'New-PPRTAbuseContactObject: Getting AbsoluteUri' -Folder $LogPath 57 | 58 | $AbsoluteUri = Get-AbsoluteUri -URLObject $url -LogPath $LogPath 59 | 60 | if ($AbsoluteUri -eq $null) 61 | { 62 | $log = Write-LogEntry -type Error -message 'New-PPRTAbuseContactObject: AbsoluteUri is Null' -Folder $LogPath 63 | $AbsoluteUri = $null 64 | } 65 | 66 | $Body.AbsoluteUri = $AbsoluteUri 67 | 68 | $AbsoluteUri.URLAuthority 69 | 70 | [array]$ipaddress = Get-IPaddress -hostname $($AbsoluteUri.URLAuthority) -LogPath $LogPath 71 | if ($ipaddress -eq $null) 72 | { 73 | $log = Write-LogEntry -type Error -message 'New-PPRTAbuseContactObject: IPAddress is Null' -Folder $LogPath 74 | $ipaddress = $null 75 | } 76 | 77 | $Body.IPAddress = $ipaddress 78 | 79 | foreach ($ip in $ipaddress) 80 | { 81 | #based on the ipaddress we are going to get which WHOIS/RDAP to use 82 | $whoisdb = Get-WhichWHOIS -ipaddress $ip 83 | 84 | if ($whoisdb.WHOIS -eq $null ) 85 | { 86 | $log = Write-LogEntry -type Error -message 'New-PPRTAbuseContactObject: WHOIS is Null' -Folder $LogPath 87 | $whoisdb = $null 88 | } 89 | 90 | $Body.WHOIS = $whoisdb 91 | 92 | #based on info from Get-WhichWHOIS we will then begin those specific API calls 93 | switch ($whoisdb){ 94 | 'arin' 95 | { 96 | [array]$abusecontact = Check-ARIN $ip 97 | } 98 | 'ripe' 99 | { 100 | [array]$abusecontact = Check-RIPE $ip 101 | } 102 | 'apnic' 103 | { 104 | $abusecontact = Check-APNIC $ip 105 | } 106 | 'lacnic' 107 | { 108 | [array]$abusecontact = Check-LACNIC $ip 109 | } 110 | 'afrnic' 111 | { 112 | $abusecontact = 'NOCONTACT' 113 | } 114 | $null 115 | { 116 | 117 | } 118 | } 119 | 120 | $Body.AbuseContact = $abusecontact 121 | } 122 | 123 | 124 | Add-ObjectDetail -InputObject $Body -TypeName PPRT.AbuseContact 125 | } 126 | } 127 | End 128 | { 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /Private/Create-PPRTDatabaseTables.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Short description 4 | .DESCRIPTION 5 | Long description 6 | .EXAMPLE 7 | Example of how to use this cmdlet 8 | .EXAMPLE 9 | Another example of how to use this cmdlet 10 | #> 11 | function Create-PPRTDatabaseTables 12 | { 13 | [CmdletBinding()] 14 | Param 15 | ( 16 | # Param1 help description 17 | [Parameter(Mandatory=$true, 18 | ValueFromPipelineByPropertyName=$true 19 | )] 20 | $Server 21 | ) 22 | 23 | Begin 24 | { 25 | [assembly.reflection]::loadwithpartialname('System.Data') 26 | $conn = New-Object System.Data.SqlClient.SqlConnection 27 | $conn.ConnectionString = "Server=$($Server);Database=PPRT;Trusted_Connection=True;" 28 | $conn.open() 29 | } 30 | Process 31 | { 32 | 33 | $email = @" 34 | USE [PPRT] 35 | GO 36 | 37 | /****** Object: Table [dbo].[Email] Script Date: 5/31/2016 8:31:16 PM ******/ 38 | SET ANSI_NULLS ON 39 | GO 40 | 41 | SET QUOTED_IDENTIFIER ON 42 | GO 43 | 44 | CREATE TABLE [dbo].[Email]( 45 | [ID] [int] NOT NULL, 46 | [FullName] [nvarchar](max) NULL, 47 | [Subject] [nvarchar](max) NULL, 48 | [PhishingURL] [int] NULL, 49 | [Body] [nvarchar](max) NULL, 50 | [HTMLBody] [nvarchar](max) NULL, 51 | [BCC] [nvarchar](max) NULL, 52 | [CC] [nvarchar](max) NULL, 53 | [ReceivedOnBehalfOfEntryID] [nvarchar](max) NULL, 54 | [ReceivedOnBehalfOfName] [nvarchar](max) NULL, 55 | [ReceivedTime] [nvarchar](max) NULL, 56 | [Receipents] [nvarchar](max) NULL, 57 | [ReplyRecipientsName] [nvarchar](max) NULL, 58 | [SenderName] [nvarchar](max) NULL, 59 | [SentOnDate] [nvarchar](max) NULL, 60 | [SentOnBehalfOfName] [nvarchar](max) NULL, 61 | [SentTo] [nvarchar](max) NULL, 62 | [SenderEmailAddress] [nvarchar](max) NULL, 63 | [SenderEmailType] [nvarchar](max) NULL, 64 | [SendUsingAccount] [nvarchar](max) NULL, 65 | [Headers] [nvarchar](max) NULL, 66 | [Attachments] [int] NULL, 67 | [DateProcessed] AS (getdate()), 68 | PRIMARY KEY CLUSTERED 69 | ( 70 | [ID] ASC 71 | )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 72 | ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] 73 | 74 | GO 75 | 76 | ALTER TABLE [dbo].[Email] WITH CHECK ADD CONSTRAINT [FK_Email_Attachments] FOREIGN KEY([Attachments]) 77 | REFERENCES [dbo].[Attachments] ([ID]) 78 | GO 79 | 80 | ALTER TABLE [dbo].[Email] CHECK CONSTRAINT [FK_Email_Attachments] 81 | GO 82 | 83 | ALTER TABLE [dbo].[Email] WITH CHECK ADD CONSTRAINT [FK_Email_PhishingURL] FOREIGN KEY([PhishingURL]) 84 | REFERENCES [dbo].[PhishingURL] ([ID]) 85 | GO 86 | 87 | ALTER TABLE [dbo].[Email] CHECK CONSTRAINT [FK_Email_PhishingURL] 88 | GO 89 | 90 | "@ 91 | 92 | $Attachments = @" 93 | USE [PPRT] 94 | GO 95 | 96 | /****** Object: Table [dbo].[Attachments] Script Date: 5/31/2016 8:30:22 PM ******/ 97 | SET ANSI_NULLS ON 98 | GO 99 | 100 | SET QUOTED_IDENTIFIER ON 101 | GO 102 | 103 | CREATE TABLE [dbo].[Attachments]( 104 | [ID] [int] NOT NULL, 105 | [OriginalAttachmentName] [nvarchar](max) NULL, 106 | [NewAttachmentName] [nvarchar](max) NULL, 107 | [AttachmentSavePath] [nvarchar](max) NULL, 108 | [AttachmentHash] [nvarchar](max) NULL, 109 | [VirusTotalResults] [nvarchar](max) NULL, 110 | [ProcessedDate] AS (getdate()), 111 | PRIMARY KEY CLUSTERED 112 | ( 113 | [ID] ASC 114 | )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 115 | ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] 116 | 117 | GO 118 | 119 | "@ 120 | 121 | $phishingURL = @" 122 | USE [PPRT] 123 | GO 124 | 125 | /****** Object: Table [dbo].[PhishingURL] Script Date: 5/31/2016 8:32:18 PM ******/ 126 | SET ANSI_NULLS ON 127 | GO 128 | 129 | SET QUOTED_IDENTIFIER ON 130 | GO 131 | 132 | CREATE TABLE [dbo].[PhishingURL]( 133 | [ID] [int] NOT NULL, 134 | [RawPhishingLink] [nvarchar](max) NULL, 135 | [PhishingLinkStatus] [bit] NULL, 136 | [ShortenedURL] [nvarchar](max) NULL, 137 | [ParsedURL] [nvarchar](max) NULL, 138 | [URLIPAddress] [nvarchar](max) NULL, 139 | [WHOIS] [nvarchar](50) NULL, 140 | [AbuseNotificationStatus] [bit] NULL, 141 | [AdditionalNotifications] [nvarchar](50) NULL, 142 | [DateProcessed] AS (getdate()), 143 | PRIMARY KEY CLUSTERED 144 | ( 145 | [ID] ASC 146 | )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 147 | ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] 148 | 149 | GO 150 | 151 | "@ 152 | 153 | 154 | $cmd = New-Object System.Data.SqlClient.SqlCommand 155 | $cmd.connection = $conn 156 | $cmd.Commandtext = "$email" 157 | $command.ExecuteNonQuery() 158 | 159 | $cmd.Commandtext = "$Attachments" 160 | $command.ExecuteNonQuery() 161 | 162 | $cmd.Commandtext = "$phishingURL" 163 | $command.ExecuteNonQuery() 164 | } 165 | End 166 | { 167 | $conn.close() 168 | } 169 | } 170 | 171 | 172 | -------------------------------------------------------------------------------- /Public/Invoke-VTAttachment.ps1: -------------------------------------------------------------------------------- 1 | #requires -Version 2 -Modules Posh-VirusTotal 2 | <# 3 | .Synopsis 4 | Short description 5 | .DESCRIPTION 6 | Long description 7 | .EXAMPLE 8 | Example of how to use this cmdlet 9 | .EXAMPLE 10 | Another example of how to use this cmdlet 11 | #> 12 | function Invoke-VTAttachment 13 | { 14 | [CmdletBinding()] 15 | Param 16 | ( 17 | [Parameter(Mandatory = $true, 18 | ValueFromPipelineByPropertyName = $true, 19 | Helpmessage = 'Please provide a message file')] 20 | [PSTypeName('PPRT.Attachment')] 21 | $AttachmentHash, 22 | 23 | [parameter(Mandatory = $true, 24 | ValueFromPipelineByPropertyName = $true, 25 | HelpMessage = 'Please provide your Virus Total API Key')] 26 | $VTAPIKey, 27 | 28 | [Parameter(Mandatory = $true)] 29 | $LogPath 30 | ) 31 | 32 | Begin 33 | { 34 | $ReturnObject = @() 35 | 36 | $log = Write-LogEntry -type Info -message 'Invoke-VTAttachment: Checking to see if Posh-VirusTotal is installed' -Folder $LogPath 37 | 38 | #download and import POSH-VirusTotal 39 | if (!(Test-Path -Path "$Home\Documents\WindowsPowerShell\Modules\Posh-VirusTotal")) 40 | { 41 | $log = Write-LogEntry -type Error -message 'Invoke-VTAttachment: Unable to find Posh-VirusTotal' -Folder $LogPath 42 | 43 | $result = [System.Windows.Forms.MessageBox]::Show('You must have the Posh-VirusTotal PowerShell Module installed. Do you want to download Posh-VirusTotal now?', 'Warning', 'YesNo', 'Warning') 44 | if ($result -eq 'Yes') 45 | { 46 | $log = Write-LogEntry -type Info -message 'Invoke-VTAttachment: Begin Downloading of Posh-VirusTotal' -Folder $LogPath 47 | 48 | Invoke-Expression -Command (New-Object -TypeName Net.WebClient).DownloadString('https://gist.githubusercontent.com/darkoperator/9138373/raw/22fb97c07a21139a398c2a3d6ca7e3e710e476bc/PoshVTInstall.ps1') 49 | } 50 | else 51 | { 52 | $log = Write-LogEntry -type Error -message 'Invoke-VTAttachment: You must have Posh-VirusTotal installed before continuing' -Folder $LogPath -CustomMessage 'Break' 53 | break 54 | } 55 | } 56 | } 57 | Process 58 | { 59 | foreach ($hash in $AttachmentHash.Hash) 60 | { 61 | $log = Write-LogEntry -type Info -message "Invoke-VTAttachment: Getting VirusTotal File Report for $hash" -Folder $LogPath 62 | 63 | $VTFileReport = @() 64 | $VTFileReport = Get-VTFileReport -Resource $hash -APIKey $VTAPIKey 65 | 66 | if ($VTFileReport.ResponseCode -eq 1) 67 | { 68 | $result = [System.Windows.Forms.MessageBox]::Show("The following SHA256 hash was already been submitted to VirusTotal.`n $hash", 'Warning', 'Ok', 'Warning') 69 | 70 | $log = Write-LogEntry -type Info -message 'Invoke-VTAttachment: VirusTotal Submission' -Folder $LogPath -CustomMessage "Hash has been previously submitted to VirusTotal: $hash" 71 | 72 | $SubmissionStatus = 'Previously Submitted' 73 | 74 | $props = { 75 | AttachmentHash = $hash 76 | SubmissionStatus = $SubmissionStatus 77 | VTFileReport = $VTFileReport 78 | VTSubmissionResult = $null 79 | } 80 | 81 | $VTObject = New-Object -TypeName PSObject -Property $props 82 | 83 | Add-ObjectDetail -InputObject $VTObject -TypeName PPRT.VTResults 84 | } 85 | if ($VTFileReport.ResponseCode -eq 0) 86 | { 87 | $log = Write-LogEntry -type Info -message 'Invoke-VTAttachment: VirusTotal Submission' -Folder $LogPath -CustomMessage "Hash has NOT been previously submitted to VirusTotal: $hash" 88 | 89 | $result = [System.Windows.Forms.MessageBox]::Show("The following SHA256 hash has NOT been submitted to VirusTotal. Do you want to upload this file to VirusTotal Now?`n $hash", 'Warning', 'YesNo', 'Warning') 90 | 91 | if ($result -eq $true) 92 | { 93 | $log = Write-LogEntry -type Info -message 'Invoke-VTAttachment: Submitting File to VirusTotal' -Folder $LogPath 94 | 95 | $SubmitToVT = Submit-VTFile -File $AttachmentHash.Path -APIKey $VTAPIKey 96 | 97 | $log = Write-LogEntry -type Info -message 'Invoke-VTAttachment: File Submitted Successfully' -Folder $LogPath 98 | 99 | $log = Write-LogEntry -type Info -message 'Invoke-VTAttachment: Getting VirusTotal File Report' -Folder $LogPath 100 | 101 | $VTFileReport = Get-VTFileReport -Resource $AttachmentHash.Path -APIKey $VTAPIKey 102 | $SubmissionStatus = 'Hash Submitted' 103 | } 104 | else 105 | { 106 | $log = Write-LogEntry -type Error -message "Invoke-VTAttachment: Hash not Found or Submitted - $hash" -Folder $LogPath 107 | $SubmissionStatus = 'Hash not Found or Submitted' 108 | } 109 | 110 | $props = { 111 | AttachmentHash = $hash 112 | SubmissionStatus = $SubmissionStatus 113 | VTFileReport = $VTFileReport 114 | VTSubmissionResult = $SubmitToVT 115 | } 116 | 117 | $VTObject = New-Object -TypeName PSObject -Property $props 118 | 119 | Add-ObjectDetail -InputObject $VTObject -TypeName PPRT.VTResults 120 | } 121 | } 122 | } 123 | End 124 | { 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /Public/Send-PPRTNotification.ps1: -------------------------------------------------------------------------------- 1 | #requires -Version 2 2 | <# 3 | .Synopsis 4 | Short description 5 | .DESCRIPTION 6 | Long description 7 | .EXAMPLE 8 | Example of how to use this cmdlet 9 | .EXAMPLE 10 | Another example of how to use this cmdlet 11 | #> 12 | function Send-PPRTNotification 13 | { 14 | [CmdletBinding(DefaultParameterSetName = 'Full')] 15 | [OutputType([System.Collections.Hashtable],[String])] 16 | Param 17 | ( 18 | #[PSTypeName('PPRT.Message')] 19 | [Parameter(Mandatory = $true, 20 | ValueFromPipeline = $true)] 21 | $To, 22 | 23 | [Parameter(Mandatory = $true, 24 | ValueFromPipeline = $true)] 25 | $Attachment, 26 | 27 | [Parameter(ParameterSetName = 'Single', 28 | Mandatory = $true, 29 | ValueFromPipelineByPropertyName = $true)] 30 | [string]$From, 31 | 32 | [Parameter(ParameterSetName = 'Single', 33 | Mandatory = $false, 34 | ValueFromPipelineByPropertyName = $true)] 35 | [string]$CC, 36 | 37 | [Parameter(ParameterSetName = 'Single', 38 | Mandatory = $false, 39 | ValueFromPipelineByPropertyName = $true)] 40 | [string]$BCC, 41 | 42 | [Parameter(ParameterSetName = 'Single', 43 | Mandatory = $false, 44 | ValueFromPipelineByPropertyName = $true)] 45 | [string]$Subject, 46 | 47 | [Parameter(ParameterSetName = 'Single', 48 | Mandatory = $true, 49 | ValueFromPipelineByPropertyName = $true)] 50 | $Body, 51 | 52 | [Parameter(ParameterSetName = 'Single', 53 | Mandatory = $false, 54 | ValueFromPipelineByPropertyName = $true)] 55 | [switch]$HTMLBody, 56 | 57 | [Parameter(ParameterSetName = 'Single', 58 | Mandatory = $false, 59 | ValueFromPipelineByPropertyName = $true)] 60 | [switch]$IncludeAttachment, 61 | 62 | 63 | [ValidateSet('Normal','High','Low')] 64 | [Parameter(ParameterSetName = 'Single', 65 | Mandatory = $false, 66 | ValueFromPipelineByPropertyName = $true)] 67 | [string]$Priority = 'Normal', 68 | 69 | [Parameter(ParameterSetName = 'Single', 70 | Mandatory = $false, 71 | ValueFromPipelineByPropertyName = $true)] 72 | [switch]$UseSSL, 73 | 74 | 75 | [ValidateSet('ASCII','UTF8','UTF7','UTF32','Unicode','BigEndianUnicode','Default','OEM')] 76 | [Parameter(ParameterSetName = 'Single', 77 | Mandatory = $false, 78 | ValueFromPipelineByPropertyName = $true)] 79 | [string]$Encoding, 80 | 81 | [Parameter(ParameterSetName = 'Single', 82 | Mandatory = $true, 83 | ValueFromPipelineByPropertyName = $true)] 84 | [System.Management.Automation.PSCredential]$Credential, 85 | 86 | [Parameter(ParameterSetName = 'Single', 87 | Mandatory = $true, 88 | ValueFromPipelineByPropertyName = $true)] 89 | [Alias('PSEmailServer')] 90 | [string]$SMTPServer = $PSEmailServer, 91 | 92 | [Parameter(ParameterSetName = 'Single', 93 | Mandatory = $true, 94 | ValueFromPipelineByPropertyName = $true)] 95 | [int]$SMTPPort = '25' 96 | ) 97 | Begin 98 | { 99 | if ($null -eq $SMTPServer) 100 | { 101 | if ($null -eq $PSEmailServer) 102 | { 103 | Write-Error -Message 'Your $PSEmailServer variable is null. You must either set this variable or provide a SMTP Server address.' 104 | Write-Error -Message 'Exiting.....' 105 | return 106 | } 107 | } 108 | } 109 | Process 110 | { 111 | foreach ($msg in $MessageObject) 112 | { 113 | $Obj = @{} 114 | 115 | Add-Member -InputObject $Obj -MemberType NoteProperty -Name To -Value $(($AbuseContactObject.AbuseContact) -join ';') -Force 116 | 117 | if ($IncludeAttachment) 118 | { 119 | $Attachment = $msg.FullName 120 | 121 | Add-Member -InputObject $Obj -MemberType NoteProperty -Name Attachments -Value $Attachment -Force 122 | } 123 | 124 | switch ($psboundparameters.keys) 125 | { 126 | 'From' 127 | { 128 | $Obj.From = $From 129 | } 130 | 'Body' 131 | { 132 | $Obj.Body = $Body 133 | } 134 | 'HTMLBody' 135 | { 136 | $Obj.HTMLBody = $BodyAsHtml 137 | } 138 | 'BCC' 139 | { 140 | $Obj.BCC = $BCC 141 | } 142 | 'CC' 143 | { 144 | $Obj.CC = $CC 145 | } 146 | 'Subject' 147 | { 148 | $Obj.Subject = $Subject 149 | } 150 | 'Priority' 151 | { 152 | $Obj.Priority = $Priority 153 | } 154 | 'UseSSL' 155 | { 156 | $Obj.UseSSL = $UseSSL 157 | } 158 | 'Encoding' 159 | { 160 | $Obj.Encoding = $Encoding 161 | } 162 | 'Credential' 163 | { 164 | $Obj.Credential = $Credential 165 | } 166 | 'SMTPServer' 167 | { 168 | $Obj.SMTPServer = $SMTPServer 169 | } 170 | 'SMTPPort' 171 | { 172 | $Obj.SMTPPort = $SMTPPort 173 | } 174 | 'URL' 175 | { 176 | $Obj.URL = $AbuseContactObject.URL 177 | } 178 | } 179 | 180 | Send-MailMessage @Obj 181 | } 182 | } 183 | End 184 | { 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /Private/Add-ObjectDetail.ps1: -------------------------------------------------------------------------------- 1 | #requires -Version 2 2 | function Add-ObjectDetail 3 | { 4 | <# 5 | .SYNOPSIS 6 | Decorate an object with 7 | - A TypeName 8 | - New properties 9 | - Default parameters 10 | 11 | .DESCRIPTION 12 | Helper function to decorate an object with 13 | - A TypeName 14 | - New properties 15 | - Default parameters 16 | 17 | .PARAMETER InputObject 18 | Object to decorate. Accepts pipeline input. 19 | 20 | .PARAMETER TypeName 21 | Typename to insert. 22 | 23 | This will show up when you use Get-Member against the resulting object. 24 | 25 | .PARAMETER PropertyToAdd 26 | Add these noteproperties. 27 | 28 | Format is a hashtable with Key (Property Name) = Value (Property Value). 29 | 30 | Example to add a One and Date property: 31 | 32 | -PropertyToAdd @{ 33 | One = 1 34 | Date = (Get-Date) 35 | } 36 | 37 | .PARAMETER DefaultProperties 38 | Change the default properties that show up 39 | 40 | .PARAMETER Passthru 41 | Whether to pass the resulting object on. Defaults to true 42 | 43 | .EXAMPLE 44 | # 45 | # Create an object to work with 46 | $Object = [PSCustomObject]@{ 47 | First = 'Cookie' 48 | Last = 'Monster' 49 | Account = 'CMonster' 50 | } 51 | 52 | #Add a type name and a random property 53 | Add-ObjectDetail -InputObject $Object -TypeName 'ApplicationX.Account' -PropertyToAdd @{ AnotherProperty = 5 } 54 | 55 | # First Last Account AnotherProperty 56 | # ----- ---- ------- --------------- 57 | # Cookie Monster CMonster 5 58 | 59 | #Verify that get-member shows us the right type 60 | $Object | Get-Member 61 | 62 | # TypeName: ApplicationX.Account ... 63 | 64 | .EXAMPLE 65 | # 66 | # Create an object to work with 67 | $Object = [PSCustomObject]@{ 68 | First = 'Cookie' 69 | Last = 'Monster' 70 | Account = 'CMonster' 71 | } 72 | 73 | #Add a random property, set a default property set so we only see two props by default 74 | Add-ObjectDetail -InputObject $Object -PropertyToAdd @{ AnotherProperty = 5 } -DefaultProperties Account, AnotherProperty 75 | 76 | # Account AnotherProperty 77 | # ------- --------------- 78 | # CMonster 5 79 | 80 | #Verify that the other properties are around 81 | $Object | Select -Property * 82 | 83 | # First Last Account AnotherProperty 84 | # ----- ---- ------- --------------- 85 | # Cookie Monster CMonster 5 86 | 87 | .NOTES 88 | This breaks the 'do one thing' rule from certain perspectives... 89 | The goal is to decorate an object all in one shot 90 | 91 | This abstraction simplifies decorating an object, with a slight trade-off in performance. For example: 92 | 93 | 10,000 objects, add a property and typename: 94 | Add-ObjectDetail: ~4.6 seconds 95 | Add-Member + PSObject.TypeNames.Insert: ~3 seconds 96 | 97 | Initial code borrowed from Shay Levy: 98 | http://blogs.microsoft.co.il/scriptfanatic/2012/04/13/custom-objects-default-display-in-powershell-30/ 99 | 100 | .LINK 101 | http://ramblingcookiemonster.github.io/Decorating-Objects/ 102 | 103 | .FUNCTIONALITY 104 | PowerShell Language 105 | #> 106 | [CmdletBinding()] 107 | param( 108 | [Parameter( Mandatory = $true, 109 | Position = 0, 110 | ValueFromPipeline = $true )] 111 | [ValidateNotNullOrEmpty()] 112 | [psobject[]]$InputObject, 113 | 114 | [Parameter( Mandatory = $false, 115 | Position = 1)] 116 | [string]$TypeName, 117 | 118 | [Parameter( Mandatory = $false, 119 | Position = 2)] 120 | [System.Collections.Hashtable]$PropertyToAdd, 121 | 122 | [Parameter( Mandatory = $false, 123 | Position = 3)] 124 | [ValidateNotNullOrEmpty()] 125 | [Alias('dp')] 126 | [System.String[]]$DefaultProperties, 127 | 128 | [boolean]$Passthru = $true 129 | ) 130 | 131 | Begin 132 | { 133 | if($PSBoundParameters.ContainsKey('DefaultProperties')) 134 | { 135 | # define a subset of properties 136 | $ddps = New-Object -TypeName System.Management.Automation.PSPropertySet -ArgumentList DefaultDisplayPropertySet, $DefaultProperties 137 | $PSStandardMembers = [System.Management.Automation.PSMemberInfo[]]$ddps 138 | } 139 | } 140 | Process 141 | { 142 | foreach($Object in $InputObject) 143 | { 144 | switch ($PSBoundParameters.Keys) 145 | { 146 | 'PropertyToAdd' 147 | { 148 | foreach($Key in $PropertyToAdd.Keys) 149 | { 150 | #Add some noteproperties. Slightly faster than Add-Member. 151 | $Object.PSObject.Properties.Add( ( New-Object -TypeName System.Management.Automation.PSNoteProperty -ArgumentList ($Key, $PropertyToAdd[$Key]) ) ) 152 | } 153 | } 154 | 'TypeName' 155 | { 156 | #Add specified type 157 | [void]$Object.PSObject.TypeNames.Insert(0,$TypeName) 158 | } 159 | 'DefaultProperties' 160 | { 161 | # Attach default display property set 162 | Add-Member -InputObject $Object -MemberType MemberSet -Name PSStandardMembers -Value $PSStandardMembers 163 | } 164 | } 165 | if($Passthru) 166 | { 167 | $Object 168 | } 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /Public/Get-PhishingGeoLocationHeatMap.ps1: -------------------------------------------------------------------------------- 1 | #requires -Version 2 2 | function Get-PhishingGeoLocationHeatMap 3 | { 4 | [CmdletBinding()] 5 | Param 6 | ( 7 | # Param1 help description 8 | [Parameter(Mandatory = $true)] 9 | [ValidateNotNull()] 10 | [ValidateNotNullOrEmpty()] 11 | $HeatMapData, 12 | [parameter(HelpMessage = 'Please provide a folder path for ouputting generated maps')] 13 | [ValidateNotNullOrEmpty()] 14 | [string]$FolderPath 15 | ) 16 | 17 | $OutHeatMap = "$FolderPath\PPRT_HeatMap.html" 18 | 19 | $webdata = @" 20 | 21 | 22 | 23 | Complex icons 24 | 57 | 58 | 59 |
60 | 61 | 62 |
63 |
64 | 265 | 267 | 268 | 269 | "@ | Out-File $OutHeatMap 270 | } 271 | -------------------------------------------------------------------------------- /Public/New-FirstReceivedFromIPObject.ps1: -------------------------------------------------------------------------------- 1 | #requires -Version 3 2 | <# 3 | .Synopsis 4 | Short description 5 | .DESCRIPTION 6 | Long description 7 | .EXAMPLE 8 | Example of how to use this cmdlet 9 | .EXAMPLE 10 | Another example of how to use this cmdlet 11 | #> 12 | function New-FirstReceivedFromIPObject 13 | { 14 | [CmdletBinding()] 15 | [Alias()] 16 | [OutputType([int])] 17 | Param 18 | ( 19 | [Parameter(Mandatory = $true, 20 | ValueFromPipelineByPropertyName = $true, 21 | ParameterSetName = 'MessageObject')] 22 | [PSTypeName('PPRT.Message')] 23 | $MessageObject, 24 | 25 | [Parameter(Mandatory = $true, 26 | ValueFromPipelineByPropertyName = $true, 27 | ParameterSetName = 'EmailHeader')] 28 | $EmailHeader, 29 | 30 | [Parameter(Mandatory = $true, 31 | ValueFromPipelineByPropertyName = $true)] 32 | $SavePath, 33 | 34 | [Parameter(Mandatory = $false, 35 | ValueFromPipelineByPropertyName = $true)] 36 | [switch]$HeatMap 37 | ) 38 | 39 | Begin 40 | { 41 | #regex is used for getting IPs from String 42 | $regex = '\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b' 43 | 44 | $firstReceivedFromIP = @() 45 | $originalIpLocation = @() 46 | $originalmarker = @() 47 | $ReturnObject = @() 48 | } 49 | Process 50 | { 51 | switch ($PSBoundParameters.Keys) 52 | { 53 | 'MessageObject' 54 | { 55 | $msg = $MessageObject 56 | } 57 | 'EmailHeader' 58 | { 59 | if($null -ne $EmailHeader) 60 | { 61 | $msg = $EmailHeader 62 | } 63 | else 64 | { 65 | Write-Warning -Message 'Please provide Email Headers' 66 | break 67 | } 68 | } 69 | } 70 | 71 | foreach($item in $msg) 72 | { 73 | $firstReceivedFromIP = (Parse-EmailHeader -InputFileName $item.Header).From | 74 | ` 75 | Select-String -Pattern $regex -AllMatches | 76 | ` 77 | ForEach-Object -Process { 78 | $_.Matches 79 | } | 80 | ` 81 | ForEach-Object -Process { 82 | $_.Value 83 | } 84 | 85 | #calling first received from header returned from parse-emailheader. Location is [0] 86 | $originalIpLocation = Invoke-RestMethod -Uri "http://freegeoip.net/xml/$($firstReceivedFromIP[0])" 87 | 88 | $tempStartingIPObject = @() 89 | 90 | #getting all first received from IP from headers and creating markers 91 | if (($originalIpLocation.Response.Latitude -ne 0) -or ($originalIpLocation.Response.Longitude -ne 0)) 92 | { 93 | if (![string]::IsNullOrWhiteSpace($originalIpLocation.Response.Latitude)) 94 | { 95 | if (![string]::IsNullOrWhiteSpace($originalIpLocation.Response.Longitude)) 96 | { 97 | #adding json markup data to object. This will be passed to Get-PhishingGeoLocationStartingIps cmdlet 98 | $props = @{ 99 | marker = "`{'title': '$($item.subject -replace "'",' ')', ` 100 | 'lat': '$($originalIpLocation.Response.Latitude)', ` 101 | 'lng': '$($originalIpLocation.Response.Longitude)', ` 102 | 'description': '

$($item.Subject -replace "'",' ')

` 103 | Subject: $($item.Subject -replace "'",' ')

` 104 | Received Time: $($item.ReceivedTime)

` 105 | Sender Email Address: $($item.SenderEmailAddress)

` 106 | Sender Email Type: $($item.SenderEmailType)

` 107 | Phishing URL: $($item.URL.RawPhishingLink)

' ` 108 | }" 109 | subject = $item.Subject 110 | SentFromAddress = $item.SenderEmailAddress 111 | SentFromType = $item.SenderEmailType 112 | ReceivedTime = $item.ReceivedTime 113 | EmailBody = $item.Body 114 | } 115 | 116 | $tempStartingIPObject = New-Object -TypeName PSObject -Property $props 117 | } 118 | } 119 | } 120 | 121 | $tempHeatMapObject = @() 122 | 123 | if ($HeatMap) 124 | { 125 | #getting heat map markers, even though they switch may not be called 126 | if (($originalIpLocation.Response.Latitude -ne 0) -or ($originalIpLocation.Response.Longitude -ne 0)) 127 | { 128 | if (![string]::IsNullOrWhiteSpace($originalIpLocation.Response.Latitude)) 129 | { 130 | if (![string]::IsNullOrWhiteSpace($originalIpLocation.Response.Longitude)) 131 | { 132 | $props = @{ 133 | marker = "new google.maps.LatLng($($originalIpLocation.Response.Latitude), $($originalIpLocation.Response.Longitude))" 134 | subject = $item.Subject 135 | SentFromAddress = $item.SenderEmailAddress 136 | SentFromType = $item.SenderEmailType 137 | ReceivedTime = $item.ReceivedTime 138 | EmailBody = $item.Body 139 | } 140 | 141 | $tempHeatMapObject = New-Object -TypeName PSObject -Property $props 142 | } 143 | } 144 | } 145 | } 146 | 147 | $props = @{ 148 | FirstReceivedFromIP = $tempStartingIPObject 149 | FirstReceivedFromIPHeatMap = $tempHeatMapObject 150 | } 151 | 152 | $TempObject = New-Object -TypeName PSObject -Property $props 153 | $ReturnObject += $TempObject 154 | } 155 | } 156 | End 157 | { 158 | return $ReturnObject 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /Public/New-PPRTNotificationObject.ps1: -------------------------------------------------------------------------------- 1 | #requires -Version 2 2 | <# 3 | .Synopsis 4 | Short description 5 | .DESCRIPTION 6 | Long description 7 | .EXAMPLE 8 | Example of how to use this cmdlet 9 | .EXAMPLE 10 | Another example of how to use this cmdlet 11 | #> 12 | function New-PPRTNotificationObject 13 | { 14 | [CmdletBinding(DefaultParameterSetName = 'Full')] 15 | [OutputType([System.Collections.Hashtable],[String])] 16 | Param 17 | ( 18 | #[PSTypeName('PPRT.Message')] 19 | [Parameter(Mandatory = $true, 20 | ValueFromPipeline = $true)] 21 | $MessageObject, 22 | 23 | [PSTypeName('PPRT.AbuseContact')] 24 | [Parameter(Mandatory = $true, 25 | ValueFromPipeline = $true)] 26 | $AbuseContactObject, 27 | 28 | [Parameter(Mandatory = $true, 29 | ValueFromPipelineByPropertyName = $true)] 30 | [string]$From, 31 | 32 | [Parameter(Mandatory = $false, 33 | ValueFromPipelineByPropertyName = $true)] 34 | [string]$CC, 35 | 36 | [Parameter(Mandatory = $false, 37 | ValueFromPipelineByPropertyName = $true)] 38 | [string]$BCC, 39 | 40 | [Parameter(Mandatory = $true, 41 | ValueFromPipelineByPropertyName = $true)] 42 | [string]$Subject, 43 | 44 | [Parameter(Mandatory = $false, 45 | ValueFromPipelineByPropertyName = $true)] 46 | [string]$Body, 47 | 48 | [Parameter(Mandatory = $false, 49 | ValueFromPipelineByPropertyName = $true)] 50 | [switch]$HTMLBody, 51 | 52 | [Parameter(Mandatory = $false, 53 | ValueFromPipelineByPropertyName = $true)] 54 | [switch]$IncludeAttachment, 55 | 56 | [ValidateSet('Normal','High','Low')] 57 | [Parameter(Mandatory = $false, 58 | ValueFromPipelineByPropertyName = $true)] 59 | [string]$Priority = 'Normal', 60 | 61 | [Parameter(Mandatory = $false, 62 | ValueFromPipelineByPropertyName = $true)] 63 | [switch]$UseSSL, 64 | 65 | [ValidateSet('ASCII','UTF8','UTF7','UTF32','Unicode','BigEndianUnicode','Default','OEM')] 66 | [Parameter(Mandatory = $false, 67 | ValueFromPipelineByPropertyName = $true)] 68 | [string]$Encoding, 69 | 70 | [Parameter(Mandatory = $false, 71 | ValueFromPipelineByPropertyName = $true)] 72 | [System.Management.Automation.PSCredential]$Credential, 73 | 74 | [Parameter(Mandatory = $false, 75 | ValueFromPipelineByPropertyName = $true)] 76 | [Alias('PSEmailServer')] 77 | [string]$SMTPServer = $PSEmailServer, 78 | 79 | [Parameter(Mandatory = $false, 80 | ValueFromPipelineByPropertyName = $true)] 81 | [int]$SMTPPort = '25' 82 | ) 83 | Begin 84 | { 85 | if ($null -eq $SMTPServer) 86 | { 87 | if ($null -eq $PSEmailServer) 88 | { 89 | Write-Error -Message 'Your $PSEmailServer variable is null. You must either set this variable or provide a SMTP Server address.' 90 | Write-Error -Message 'Exiting.....' 91 | return 92 | } 93 | } 94 | } 95 | Process 96 | { 97 | foreach ($msg in $MessageObject) 98 | { 99 | $Obj = @{} 100 | $Splat = @{} 101 | 102 | Add-Member -InputObject $Obj -MemberType NoteProperty -Name To -Value $(($AbuseContactObject.AbuseContact) -join ';') -Force 103 | $Splat += "-To $(($AbuseContactObject.AbuseContact) -join ';')" 104 | 105 | if ($IncludeAttachment) 106 | { 107 | $Attachment = $msg.FullName 108 | 109 | Add-Member -InputObject $Obj -MemberType NoteProperty -Name Attachments -Value $Attachment -Force 110 | 111 | $Splat += "-Attachments $Attachment" 112 | } 113 | 114 | switch ($psboundparameters.keys) 115 | { 116 | 'From' 117 | { 118 | $Obj.From = $From 119 | $Splat += "-From $From" 120 | } 121 | 'Body' 122 | { 123 | $Obj.Body = $Body 124 | $Splat += "-Body $Body" 125 | } 126 | 'HTMLBody' 127 | { 128 | $Obj.HTMLBody = $BodyAsHtml 129 | $Splat += '-BodyAsHtml' 130 | } 131 | 'BCC' 132 | { 133 | $Obj.BCC = $BCC 134 | $Splat += "-BCC $BCC" 135 | } 136 | 'CC' 137 | { 138 | $Obj.CC = $CC 139 | $Splat += "-CC $CC" 140 | } 141 | 'Subject' 142 | { 143 | $Obj.Subject = $Subject 144 | $Splat += "-Subject $Subject" 145 | } 146 | 'Priority' 147 | { 148 | $Obj.Priority = $Priority 149 | $Splat += "-Priority $Priority" 150 | } 151 | 'UseSSL' 152 | { 153 | $Obj.UseSSL = $UseSSL 154 | $Splat += '-UseSSL' 155 | } 156 | 'Encoding' 157 | { 158 | $Obj.Encoding = $Encoding 159 | $Splat += "-Encoding $Encoding" 160 | } 161 | 'Credential' 162 | { 163 | $Obj.Credential = $Credential 164 | $Splat += "-Credential $Credential" 165 | } 166 | 'SMTPServer' 167 | { 168 | $Obj.SMTPServer = $SMTPServer 169 | $Splat += "-SMTPServer $SMTPServer" 170 | } 171 | 'SMTPPort' 172 | { 173 | $Obj.SMTPPort = $SMTPPort 174 | $Splat += "-SMTPPort $SMTPPort" 175 | } 176 | 'URL' 177 | { 178 | $Obj.URL = $AbuseContactObject.URL 179 | } 180 | } 181 | 182 | 183 | Send-MailMessage -To "$($Splat -join ' ')" 184 | } 185 | } 186 | End 187 | { 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /DRAFT/Get-FirstReceivedIPMapInfo.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Short description 4 | .DESCRIPTION 5 | Long description 6 | .EXAMPLE 7 | Example of how to use this cmdlet 8 | .EXAMPLE 9 | Another example of how to use this cmdlet 10 | #> 11 | function Get-FirstReceivedIPMapInfo 12 | { 13 | [CmdletBinding()] 14 | [Alias()] 15 | [OutputType([int])] 16 | Param 17 | ( 18 | # Param1 help description 19 | [Parameter(Mandatory=$true, 20 | ValueFromPipelineByPropertyName=$true, 21 | Position=0)] 22 | $headers, 23 | 24 | # Param2 help description 25 | [int] 26 | $Param2 27 | ) 28 | 29 | Begin 30 | { 31 | $firstReceivedFromIP = @() 32 | $originalIpLocation = @() 33 | $originalmarker = @() 34 | $tempHeatMapMarkers = @() 35 | } 36 | Process 37 | { 38 | $firstReceivedFromIP = (Parse-EmailHeader -InputFileName $headers).From | 39 | Select-String -Pattern $regex -AllMatches | ForEach-Object -Process { $_.Matches } | 40 | ForEach-Object -Process { $_.Value } 41 | 42 | #calling first received from header returned from parse-emailheader. Location is [0] 43 | $originalIpLocation = Invoke-RestMethod -Uri "http://freegeoip.net/xml/$($firstReceivedFromIP[0])" 44 | 45 | #getting all first received from IP from headers and creating markers 46 | if (($originalIpLocation.Response.Latitude -ne 0) -or ($originalIpLocation.Response.Longitude -ne 0)) 47 | { 48 | if (![string]::IsNullOrWhiteSpace($originalIpLocation.Response.Latitude)) 49 | { 50 | if (![string]::IsNullOrWhiteSpace($originalIpLocation.Response.Longitude)) 51 | { 52 | #adding json markup data to object. This will be passed to Get-PhishingGeoLocationStartingIps cmdlet 53 | $props = @{ 54 | marker = "`{'title': '$($msg.subject -replace "'",' ')', 'lat': '$($originalIpLocation.Response.Latitude)', 'lng': '$($originalIpLocation.Response.Longitude)', 'description': '

$($msg.Subject -replace "'",' ')

Subject: $($msg.Subject -replace "'",' ')

Received Time: $($msg.ReceivedTime)

Sender Email Address: $($msg.SenderEmailAddress)

Sender Email Type: $($msg.SenderEmailType)

Phishing URL: $($phishingURL)

' }" 55 | subject = $msg.Subject 56 | SentFromAddress = $msg.SenderEmailAddress 57 | SentFromType = $msg.SenderEmailType 58 | ReceivedTime = $msg.ReceivedTime 59 | EmailBody = $msg.Body 60 | } 61 | 62 | $tempStartingIPObject = New-Object -TypeName PSObject -Property $props 63 | $StartingIPObject += $tempStartingIPObject 64 | } 65 | } 66 | } 67 | 68 | #getting heat map markers, even though they switch may not be called 69 | if (($originalIpLocation.Response.Latitude -ne 0) -or ($originalIpLocation.Response.Longitude -ne 0)) 70 | { 71 | if (![string]::IsNullOrWhiteSpace($originalIpLocation.Response.Latitude)) 72 | { 73 | if (![string]::IsNullOrWhiteSpace($originalIpLocation.Response.Longitude)) 74 | { 75 | $props = @{ 76 | marker = "new google.maps.LatLng($($originalIpLocation.Response.Latitude), $($originalIpLocation.Response.Longitude))" 77 | subject = $msg.Subject 78 | SentFromAddress = $msg.SenderEmailAddress 79 | SentFromType = $msg.SenderEmailType 80 | ReceivedTime = $msg.ReceivedTime 81 | EmailBody = $msg.Body 82 | } 83 | 84 | $tempHeatMapObject = New-Object -TypeName PSObject -Property $props 85 | $HeatMapObject += $tempHeatMapObject 86 | } 87 | } 88 | } 89 | } 90 | End 91 | { 92 | $props = @{ 93 | StartingIPObject = $StartingIPObject 94 | HeatMapObject = $HeatMapObject 95 | } 96 | 97 | $ReturnObject = New-Object -TypeName PSObject -Property $props 98 | 99 | return $ReturnObject 100 | } 101 | } 102 | 103 | 104 | 105 | 106 | 107 | $firstReceivedFromIP = (Parse-EmailHeader -InputFileName $headers).From | 108 | Select-String -Pattern $regex -AllMatches | ForEach-Object -Process { $_.Matches } | 109 | ForEach-Object -Process { $_.Value } 110 | 111 | #calling first received from header returned from parse-emailheader. Location is [0] 112 | 113 | $originalIpLocation = Invoke-RestMethod -Uri "http://freegeoip.net/xml/$($firstReceivedFromIP[0])" 114 | 115 | #getting all first received from IP from headers and creating markers 116 | if (($originalIpLocation.Response.Latitude -ne 0) -or ($originalIpLocation.Response.Longitude -ne 0)) 117 | { 118 | if (![string]::IsNullOrWhiteSpace($originalIpLocation.Response.Latitude)) 119 | { 120 | if (![string]::IsNullOrWhiteSpace($originalIpLocation.Response.Longitude)) 121 | { 122 | #adding json markup data to object. This will be passed to Get-PhishingGeoLocationStartingIps cmdlet 123 | $props = @{ 124 | marker = "`{'title': '$($msg.subject -replace "'",' ')', 'lat': '$($originalIpLocation.Response.Latitude)', 'lng': '$($originalIpLocation.Response.Longitude)', 'description': '

$($msg.Subject -replace "'",' ')

Subject: $($msg.Subject -replace "'",' ')

Received Time: $($msg.ReceivedTime)

Sender Email Address: $($msg.SenderEmailAddress)

Sender Email Type: $($msg.SenderEmailType)

Phishing URL: $($phishingURL)

' }" 125 | subject = $msg.Subject 126 | SentFromAddress = $msg.SenderEmailAddress 127 | SentFromType = $msg.SenderEmailType 128 | ReceivedTime = $msg.ReceivedTime 129 | EmailBody = $msg.Body 130 | } 131 | 132 | $tempStartingIPObject = New-Object -TypeName PSObject -Property $props 133 | $StartingIPObject += $tempStartingIPObject 134 | } 135 | } 136 | } 137 | 138 | #getting heat map markers, even though they switch may not be called 139 | $tempHeatMapMarkers = @() 140 | if (($originalIpLocation.Response.Latitude -ne 0) -or ($originalIpLocation.Response.Longitude -ne 0)) 141 | { 142 | if (![string]::IsNullOrWhiteSpace($originalIpLocation.Response.Latitude)) 143 | { 144 | if (![string]::IsNullOrWhiteSpace($originalIpLocation.Response.Longitude)) 145 | { 146 | $props = @{ 147 | marker = "new google.maps.LatLng($($originalIpLocation.Response.Latitude), $($originalIpLocation.Response.Longitude))" 148 | subject = $msg.Subject 149 | SentFromAddress = $msg.SenderEmailAddress 150 | SentFromType = $msg.SenderEmailType 151 | ReceivedTime = $msg.ReceivedTime 152 | EmailBody = $msg.Body 153 | } 154 | 155 | $tempHeatMapObject = New-Object -TypeName PSObject -Property $props 156 | $HeatMapObject += $tempHeatMapObject 157 | } 158 | } 159 | } -------------------------------------------------------------------------------- /Public/Send-PhishingNotifications.ps1: -------------------------------------------------------------------------------- 1 | #requires -Modules Posh-VirusTotal 2 | #requires -Version 4 3 | function Send-PhishingNotifications () 4 | { 5 | [CmdletBinding()] 6 | param ( 7 | [parameter(Mandatory = $true, 8 | HelpMessage = 'Please provide a .MSG file.')] 9 | $messagetoparse, 10 | 11 | [parameter(Mandatory = $true, 12 | HelpMessage = 'Please provide a log path.')] 13 | $logpath, 14 | 15 | [parameter(Mandatory = $true, 16 | HelpMessage = "Please provide a 'Send On Behalf of' email address")] 17 | $From, 18 | 19 | [parameter(ParameterSetName = 'VT', 20 | HelpMessage = 'Please include the VirusTotal switch to scan files against VT API.')] 21 | [switch]$VirusTotal, 22 | 23 | [parameter(ParameterSetName = 'VT', 24 | HelpMessage = 'Please provide your Virus Total API Key')] 25 | $VTAPIKey 26 | ) 27 | 28 | <# 29 | .SYNOPSIS 30 | Takes a .msg file, find a phishing link, does reverse DNS for the IP, and queries whois Databases for abuse contact information 31 | 32 | .DESCRIPTION 33 | Takes a .MSG file and searches for a link based on a regex pattern 34 | Takes that link, parses it to find the root DNS name 35 | Takes the DNS name and finds the IP by doing a reverse DNS lookup 36 | Takes the IP of the server and parses it for the first octet 37 | Takes the first octet and finds which whois should be used 38 | Once it has the whois, it queries their API or scraps their website for their abuse contact information 39 | Once it has the abuse contact info, it sends them an email from abuse email account with the original attachment - asking them to remove the website 40 | Sends an email to spam@access.ironport.com 41 | Sends an email to the Google Anti-Phishing Group anti-phishing-email-reply-discuss@googlegroups.com 42 | Logs this in the running log file 43 | 44 | .PARAMETER messagetoparse 45 | Specifices the specific .MSG that someone wants to parse 46 | 47 | .PARAMETER logpath 48 | Sets the path to our log file 49 | 50 | .PARAMETER From 51 | This parameter is used to define who is sending these notificaitons. 52 | Currently, you must put an email address that you want to "Send on Behalf of". 53 | 54 | .EXAMPLE 55 | C:\PS> Send-PhishingNotification -meesagetoparse 'C:\Users\UserName\Desktop\PHISING_EMAILS\Dear Email User.msg' -logpath C:\users\username\desktop -From 'abuse@emailaddress.com' 56 | 57 | #> 58 | 59 | $ipaddress = @() 60 | $regexipv6 = '(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))' 61 | $regexipv4 = '\b((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3} (25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b' 62 | $shorturl = '' 63 | #Take in .msg file and strip the phishing url 64 | 65 | if ($VirusTotal) 66 | { 67 | $AttachmentHash = Expand-MsgAttachment -Path $messagetoparse | Get-FileHash 68 | 69 | foreach ($hash in $AttachmentHash.Hash) 70 | { 71 | $VTFileReport = Get-VTFileReport -Resource $hash -APIKey $VTAPIKey 72 | 73 | if ($VTFileReport.ResponseCode -eq 1) 74 | { 75 | $result = [System.Windows.Forms.MessageBox]::Show("The following SHA256 hash was already been submitted to VirusTotal.`n $hash", 'Warning', 'Ok', 'Warning') 76 | Write-LogEntry -type Info -message 'VirusTotal Submission' -Folder $logpath -CustomMessage "Hash has been previously submitted to VirusTotal: $hash" 77 | } 78 | if ($VTFileReport.ResponseCode -eq 0) 79 | { 80 | $result = [System.Windows.Forms.MessageBox]::Show("The following SHA256 hash has NOT been submitted to VirusTotal. Do you want to upload this file to VirusTotal Now?`n $hash", 'Warning', 'YesNo', 'Warning') 81 | 82 | if ($result -eq $true) 83 | { 84 | $SubmitToVT = Submit-VTFile -File $AttachmentHash.Path -APIKey $VTAPIKey 85 | } 86 | } 87 | } 88 | } 89 | 90 | $url = Get-URLFromMessage $messagetoparse 91 | 92 | #if the url string has a 'shorturl' from the "shorturls.xml" file, then process seperately 93 | #shorturls is a static list created from longurl.org\services 94 | Import-Clixml -Path "$(Split-Path -Path $Script:MyInvocation.MyCommand.Path)\Private\shorturls.xml" | ForEach-Object -Process { 95 | if ($url -like '$_') 96 | { 97 | #call Get-LongUrl to call API to resolve to the normal/long url 98 | $longurl = Get-LongUrl $url 99 | Write-Debug -Message "longurl: $longurl" 100 | [array]$ipaddress = ([System.Uri]$longurl).Authority 101 | $url = $longurl 102 | $shorturl = $true 103 | } 104 | else 105 | { 106 | $shorturl = $false 107 | } 108 | } 109 | 110 | if ($shorturl -eq $false) 111 | { 112 | #if no 'tinyurl' then parse as normal 113 | $parsedurl = Get-ParsedURL $url 114 | [array]$ipaddress = Get-IPaddress $parsedurl 115 | } 116 | 117 | #for each ipaddress returned from above else statement 118 | for ($ip = 0;$ip -lt $ipaddress.count;$ip++) 119 | { 120 | #based on the ipaddress we are going to get which WHOIS/RDAP to use 121 | $whoisdb = Get-WhichWHOIS $ipaddress[$ip] 122 | Write-Debug -Message "$whoisdb" 123 | Write-Debug -Message "IPADDRESS: $ipaddress[$ip]" 124 | 125 | #based on info from Get-WhichWHOIS we will then begin those specific API calls 126 | switch ($whoisdb){ 127 | 'arin' 128 | { 129 | [array]$abusecontact = Check-ARIN $ipaddress[$ip] 130 | } 131 | 'ripe' 132 | { 133 | [array]$abusecontact = Check-RIPE $ipaddress[$ip] 134 | } 135 | 'apnic' 136 | { 137 | $abusecontact = Check-APNIC $ipaddress[$ip] 138 | } 139 | 'lacnic' 140 | { 141 | [array]$abusecontact = Check-LACNIC $ipaddress[$ip] 142 | } 143 | 'afrnic' 144 | { 145 | $abusecontact = 'NOCONTACT' 146 | Write-Host -Object 'CANNOT PARSE AFRNIC' 147 | } 148 | $null 149 | { 150 | Write-Host -Object 'UNKNOWN WHOIS' 151 | } 152 | } 153 | } 154 | #as long as the abusecontact does not equal 'NOCONTACT', send email to that abuse contact 155 | for ($a = 0;$a -lt $abusecontact.count;$a++) 156 | { 157 | if ($abusecontact[$a] -ne 'NOCONTACT') 158 | { 159 | Send-ToAbuseContact -originallink $url -abusecontact $abusecontact[$a] -messagetoattach $messagetoparse -From $From 160 | } 161 | } 162 | #additionally, send to IronPort and Anti-Phishing Working Group email distribution list 163 | Send-ToIronPort -originallink $url -messagetoattach $messagetoparse -From $From 164 | 165 | Send-ToAntiPhishingGroup -trimmedlink $url.Trim('http://') -From $From 166 | 167 | $logpath = "$($logpath)\get_whois.log" 168 | $logvalue = "$(Get-Date);$url;$parsedurl;$([array]$ipaddress[0]);$whoisdb;$abusecontact;$messagetoparse;" 169 | Add-Content -Path $logpath -Value $logvalue 170 | 171 | #stop outlook process if still open from send emails using Outlook.Application COM Object 172 | Start-Sleep -Seconds 3 173 | Get-Process -Name Outlook | Stop-Process 174 | } 175 | -------------------------------------------------------------------------------- /Public/Export-MessageAttachment.ps1: -------------------------------------------------------------------------------- 1 | #requires -Version 4 2 | <# 3 | .Synopsis 4 | This function will export message attachments included in a PPRT.Message Object Type Name 5 | .DESCRIPTION 6 | This function will export attachments included in the a New-MessageObject. You need to provide ` 7 | a message object for this function to work. 8 | .EXAMPLE 9 | $AttachmentObject = Extract-MessageAttachment -MessageObject $msg -LogPath $LogPath -FullDetails -SavePath $SaveLocation 10 | #> 11 | function Export-MessageAttachment 12 | { 13 | [CmdletBinding(DefaultParameterSetName = 'Full')] 14 | [OutputType([System.Collections.Hashtable],[String])] 15 | Param 16 | ( 17 | [Parameter(Mandatory = $true)] 18 | [PSTypeName('PPRT.Message')] 19 | $MessageObject, 20 | 21 | [Parameter(Mandatory = $true)] 22 | $LogPath, 23 | 24 | [Parameter(Mandatory = $true)] 25 | $SavePath, 26 | 27 | [Parameter(ParameterSetName = 'Full')] 28 | [switch]$FullDetails, 29 | 30 | [Parameter(ParameterSetName = 'Partial')] 31 | [switch]$GetFileHash, 32 | 33 | [Parameter(ParameterSetName = 'Partial')] 34 | [switch]$DisplayName, 35 | [Parameter(ParameterSetName = 'Partial')] 36 | [switch]$FileName, 37 | [Parameter(ParameterSetName = 'Partial')] 38 | [switch]$Index, 39 | [Parameter(ParameterSetName = 'Partial')] 40 | [switch]$Position, 41 | [Parameter(ParameterSetName = 'Partial')] 42 | [switch]$Type, 43 | [Parameter(ParameterSetName = 'Partial')] 44 | [switch]$Size, 45 | [Parameter(ParameterSetName = 'Partial')] 46 | [switch]$MIMEType, 47 | [Parameter(ParameterSetName = 'Partial')] 48 | [switch]$AttachedMethod, 49 | [Parameter(ParameterSetName = 'Partial')] 50 | [switch]$AttachContentID 51 | ) 52 | Begin 53 | { 54 | $obj = New-Object -TypeName psobject 55 | 56 | $Outlook = New-Object -ComObject Outlook.Application 57 | 58 | if (!(Test-Path -Path "$SavePath")) 59 | { 60 | try 61 | { 62 | $log = Write-LogEntry -type Info -message "Extract-MessageAttachment: Creating new Attachment Save Path - $SavePath" -Folder $LogPath 63 | New-Item -Path "$SavePath" -ItemType Directory -Force 64 | } 65 | catch 66 | { 67 | $log = Write-LogEntry -type Error -message "Extract-MessageAttachment: Unable to create Save Path!!! - $SavePath" -Folder $LogPath -CustomMessage 'Break' 68 | Break 69 | } 70 | } 71 | } 72 | Process 73 | { 74 | $MessageObject | ForEach-Object -Process { 75 | $msgFn = $_.FullName 76 | 77 | $log = Write-LogEntry -type Info -message "Extract-MessageAttachment: Processing Message - $msgFn" -Folder $LogPath 78 | 79 | if ($msgFn -notlike '*.msg') 80 | { 81 | $log = Write-LogEntry -type Error -message 'Extract-MessageAttachment: MSG is not a .MSG file' -Folder $LogPath 82 | break 83 | } 84 | else 85 | { 86 | $msg = $Outlook.CreateItemFromTemplate($msgFn) 87 | 88 | $msg.Attachments | ForEach-Object -Process { 89 | $AttachmentPath = "$LogPath\$($_.FileName)" 90 | 91 | Add-Member -InputObject $obj -MemberType NoteProperty -Name Attachment -Value $AttachmentPath -Force 92 | 93 | if (!(Test-Path -LiteralPath $AttachmentPath)) 94 | { 95 | $_.SaveAsFile($AttachmentPath) 96 | } 97 | } 98 | 99 | if ($psboundparameters.Keys -contains 'FullDetails') 100 | { 101 | $log = Write-LogEntry -type Info -message 'Extract-MessageAttachment: Getting Full Details of Attachment' -Folder $LogPath 102 | 103 | $temp = $msg 104 | 105 | $propertyNames = $temp | 106 | Get-Member -MemberType Properties | 107 | Select-Object -ExpandProperty Name 108 | 109 | foreach ($property in $propertyNames) 110 | { 111 | $value = foreach ($t in $temp) 112 | { 113 | $t.$property 114 | } 115 | 116 | $obj | Add-Member -MemberType NoteProperty -Name $property -Value $value 117 | } 118 | 119 | $MIMEType = $msg.PropertyAccessor.GetProperty('http://schemas.microsoft.com/mapi/proptag/0x370E001F') 120 | Add-Member -InputObject $obj -MemberType NoteProperty -Name MIMEType -Value $MIMEType -Force 121 | 122 | $AttachedMethod = $msg.PropertyAccessor.GetProperty('http://schemas.microsoft.com/mapi/proptag/0x37050003') 123 | Add-Member -InputObject $obj -MemberType NoteProperty -Name AttachedMethod -Value $AttachedMethod -Force 124 | 125 | $AttachContentID = $msg.PropertyAccessor.GetProperty('http://schemas.microsoft.com/mapi/proptag/0x3712001E') 126 | Add-Member -InputObject $obj -MemberType NoteProperty -Name AttachContentID -Value $AttachContentID -Force 127 | 128 | Add-Member -InputObject $obj -MemberType NoteProperty -Name SavePath -Value $AttachmentPath -Force 129 | 130 | $AttachmentHash = Get-FileHash -Path $AttachmentPath 131 | Add-Member -InputObject $obj -MemberType NoteProperty -Name Hash -Value $AttachmentHash -Force 132 | 133 | $log = Write-LogEntry -type Info -message 'Extract-MessageAttachment: Processing of Full Details Complete!' -Folder $LogPath 134 | 135 | Add-ObjectDetail -InputObject $obj -TypeName PPRT.Attachment 136 | } 137 | else 138 | { 139 | $obj = @{} 140 | 141 | $psboundparameters.Keys 142 | switch ($psboundparameters.keys) 143 | { 144 | 'DisplayName' 145 | { 146 | $obj.DisplayName = $msg.DisplayName 147 | } 148 | 'FileName' 149 | { 150 | $obj.FileName = $msg.FileName 151 | } 152 | 'Index' 153 | { 154 | $obj.Index = $msg.Index 155 | } 156 | 'Position' 157 | { 158 | $obj.Position = $msg.Position 159 | } 160 | 'Type' 161 | { 162 | $obj.Type = $msg.Type 163 | } 164 | 'Size' 165 | { 166 | $obj.Size = $msg.Size 167 | } 168 | 'MIMEType' 169 | { 170 | $obj.MIMEType = $msg.PropertyAccessor.GetProperty('http://schemas.microsoft.com/mapi/proptag/0x370E001F') 171 | } 172 | 'AttachedMethod' 173 | { 174 | $obj.AttachedMethod = $msg.PropertyAccessor.GetProperty('http://schemas.microsoft.com/mapi/proptag/0x37050003') 175 | } 176 | 'AttachContentID' 177 | { 178 | $obj.AttachContentID = $msg.PropertyAccessor.GetProperty('http://schemas.microsoft.com/mapi/proptag/0x3712001E') 179 | } 180 | 'GetFileHash' 181 | { 182 | $obj.Hash = $(Get-FileHash -Path $AttachmentPath) 183 | } 184 | } 185 | 186 | $log = Write-LogEntry -type Info -message 'Extract-MessageAttachment: Getting Selected Details of Attachment' -Folder $LogPath 187 | 188 | Add-Member -InputObject $obj -MemberType NoteProperty -Name SavePath -Value $AttachmentPath -Force 189 | 190 | Add-ObjectDetail -InputObject $obj -TypeName PPRT.Attachment 191 | } 192 | } 193 | } 194 | } 195 | End 196 | { 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /Private/Parse-EmailHeader.ps1: -------------------------------------------------------------------------------- 1 | Function Parse-EmailHeader { 2 | [CmdletBinding()] 3 | Param 4 | ( 5 | [parameter(Mandatory=$true)] 6 | [String]$InputFileName 7 | ) 8 | Begin 9 | { 10 | Function Process-ReceivedBy 11 | { 12 | Param($text) 13 | $regexBy1 = 'Received: by ' 14 | $regexBy2 = 'Received: by ([\s\S]*?)with([\s\S]*?);([(\s\S)*]{32,36})(?:\s\S*?)' 15 | $regexBy3 = 'Received: by ([\s\S]*?);([(\s\S)*]{32,36})(?:\s\S*?)' 16 | $byMatches = $text | Select-String -Pattern $regexBy1 -AllMatches 17 | 18 | if ($byMatches) 19 | { 20 | $byMatches = $text | Select-String -Pattern $regexBy2 -AllMatches 21 | if($byMatches) 22 | { 23 | $rbArray = @() 24 | $byMatches.Matches | foreach{ 25 | $by = Clean-string $_.groups[1].value 26 | $with = Clean-string $_.groups[2].value 27 | Switch -wildcard ($with) 28 | { 29 | "SMTP*" {$with = "SMTP"} 30 | "ESMTP*" {$with = "ESMTP"} 31 | default{} 32 | } 33 | $time = Clean-string $_.groups[3].value 34 | $byhash = @{ 35 | ReceivedByBy = $by 36 | ReceivedByWith = $with 37 | ReceivedByTime = [Datetime]$time 38 | } 39 | $byArray = New-Object -TypeName PSObject -Property $byhash 40 | $rbArray += $byArray 41 | } 42 | $rbArray 43 | } 44 | else 45 | { 46 | $rbArray = @() 47 | $byMatches = $text | Select-String -Pattern $regexBy3 -AllMatches 48 | $byMatches.Matches | foreach{ 49 | $by = Clean-string $_.groups[1].value 50 | $with = "" 51 | $time = Clean-string $_.groups[2].value 52 | $byhash = @{ 53 | ReceivedByBy = $by 54 | ReceivedByWith = $with 55 | ReceivedByTime = [Datetime]$time 56 | } 57 | $byArray = New-Object -TypeName PSObject -Property $byhash 58 | $rbArray += $byArray 59 | } 60 | $rbArray 61 | } 62 | } 63 | else 64 | { 65 | return $null 66 | } 67 | } 68 | 69 | Function Process-ReceivedFrom 70 | { 71 | Param($text) 72 | $regexFrom1 = 'Received: from([\s\S]*?)by([\s\S]*?)with([\s\S]*?);([(\s\S)*]{32,36})(?:\s\S*?)' 73 | $fromMatches = $text | Select-String -Pattern $regexFrom1 -AllMatches 74 | write-host "From Matches: " $fromMatches 75 | if ($fromMatches) 76 | { 77 | $rfArray = @() 78 | $fromMatches.Matches | foreach{ 79 | $from = Clean-string $_.groups[1].value 80 | $by = Clean-string $_.groups[2].value 81 | $with = Clean-string $_.groups[3].value 82 | Switch -wildcard ($with) 83 | { 84 | "SMTP*" {$with = "SMTP"} 85 | "ESMTP*" {$with = "ESMTP"} 86 | "NNFMP*" {$with = "NNFMP"} 87 | default{} 88 | } 89 | $time = Clean-string $_.groups[4].value 90 | $fromhash = @{ 91 | ReceivedFromFrom = $from 92 | ReceivedFromBy = $by 93 | ReceivedFromWith = $with 94 | ReceivedFromTime = [Datetime]$time 95 | } 96 | $fromArray = New-Object -TypeName PSObject -Property $fromhash 97 | $rfArray += $fromArray 98 | } 99 | $rfArray 100 | } 101 | else 102 | { 103 | return $null 104 | } 105 | } 106 | 107 | Function Clean-String 108 | { 109 | Param([string]$inputString) 110 | $inputString = $inputString.Trim() 111 | $inputString = $inputString.Replace("`r`n","") 112 | $inputString = $inputString.Replace("`t"," ") 113 | $inputString 114 | } 115 | 116 | Function Process-FromByObject 117 | { 118 | Param([PSObject[]]$fromObjects,[PSObject[]]$byObjects) 119 | [int]$hop=0 120 | $delay="" 121 | $receivedfrom=$receivedby=$receivedtime=$receivedwith=$null 122 | $prevTime=$null 123 | $time=$null 124 | $finalArray = @() 125 | if($byObjects) 126 | { 127 | $byObjects = $byObjects[($byObjects.Length-1)..0] # Reversing the Array 128 | for($index = 0;$index -lt $byobjects.Count;$index++) 129 | { 130 | if($index -eq 0) 131 | { 132 | $hop=1 133 | $delay="*" 134 | $receivedfrom = "" 135 | $receivedby = $byobjects[$index].ReceivedByBy 136 | $with = $byobjects[$index].ReceivedByWith 137 | $time = $byobjects[$index].ReceivedBytime 138 | $time = $time.touniversaltime() 139 | $prevTime = $time 140 | $finalHash = @{ 141 | Hop = $hop 142 | Delay = $delay 143 | From = $receivedfrom 144 | By = $receivedby 145 | With = $with 146 | Time = $time 147 | } 148 | $obj = New-Object -TypeName PSObject -Property $finalHash 149 | $finalArray += $obj 150 | } 151 | else 152 | { 153 | $hop = $index+1 154 | $receivedfrom = "" 155 | $receivedby = $byobjects[$index].ReceivedByBy 156 | $with = $byobjects[$index].ReceivedByWith 157 | $time = $byobjects[$index].ReceivedBytime 158 | $time = $time.touniversaltime() 159 | $delay = $time - $prevTime 160 | $delay = $delay.totalseconds 161 | if ($delay -le -1) {$delay = 0} 162 | $prevTime = $time 163 | $finalHash = @{ 164 | Hop = $hop 165 | Delay = $delay 166 | From = $receivedfrom 167 | By = $receivedby 168 | With = $with 169 | Time = $time 170 | } 171 | $obj = New-Object -TypeName PSObject -Property $finalHash 172 | $finalArray += $obj 173 | } 174 | } 175 | $lastHop = $hop 176 | 177 | } 178 | $hop = $lastHop 179 | if($fromObjects) 180 | { 181 | $fromObjects = $fromObjects[($fromObjects.Length-1)..0] #Reversing the Array 182 | for($index = 0;$index -lt $fromobjects.Count;$index++) 183 | { 184 | 185 | $hop = $hop + 1 186 | $receivedfrom = $fromobjects[$index].ReceivedFromFrom 187 | $receivedby = $fromobjects[$index].ReceivedFromBy 188 | $with = $fromobjects[$index].ReceivedFromWith 189 | $time = $fromobjects[$index].ReceivedFromTime 190 | $time = $time.touniversaltime() 191 | if($prevTime) 192 | { 193 | $delay = $time - $prevTime 194 | $delay = $delay.totalseconds 195 | } 196 | else 197 | { 198 | $delay = "*" 199 | } 200 | $prevTime = $time 201 | $finalHash = @{ 202 | Hop = $hop 203 | Delay = $delay 204 | From = $receivedfrom 205 | By = $receivedby 206 | With = $with 207 | Time = $time 208 | } 209 | $obj = New-Object -TypeName PSObject -Property $finalHash 210 | $finalArray += $obj 211 | 212 | } 213 | 214 | } 215 | $finalArray 216 | } 217 | 218 | } 219 | 220 | Process 221 | { 222 | $text = $InputFileName 223 | $fromObject = Process-ReceivedFrom -text $text 224 | $byObject = Process-ReceivedBy -text $text 225 | 226 | $finalArray = Process-FromByObject $fromObject $byObject 227 | Write-Output $finalArray 228 | 229 | } 230 | <# 231 | .SYNOPSIS 232 | Parses Email Message Header and then provides the Email route information along with delay at each hop. 233 | 234 | .DESCRIPTION 235 | Parses Email Message Header and then returns a PSObject with following values. 236 | 1. HOP 237 | 2. DELAY 238 | 3. From [Received From Server] 239 | 4. By [Received By Server] 240 | 5. With [Protocol] 241 | 6. Time 242 | 243 | .PARAMETER 244 | Specify ComputerName the script should run against. 245 | 246 | .EXAMPLE 247 | .\Parse-EmailHeader.ps1 -InputFileName "C:\Scripts\MSGHeaderProcessor\msg6.txt" 248 | This will process the contents of the msg6.txt file and then output the PSobject which gets returned. 249 | 250 | .EXAMPLE 251 | .\Parse-EmailHeader.ps1 -InputFileName "C:\Scripts\MSGHeaderProcessor\msg6.txt" | Format-Table 252 | This will process the contents of the msg6.txt file and then output the PSobject which gets returned in table format. 253 | Output: 254 | From With Delay Hop By Time 255 | ---- ---- ----- --- -- ---- 256 | corp.red.com ([1... mapi id 14.01.03... * 1 singapore.red.co... 7/13/2011 10:50:... 257 | singapore.red.co... Microsoft SMTP S... 8 2 newyork.red.com ... 7/13/2011 10:50:... 258 | newyork.red.com ... Microsoft SMTP S... 5 3 outgoing.red.com... 7/13/2011 10:50:... 259 | outgoing.red.com... Microsoft SMTPSV... 6 4 incoming.green.com 7/13/2011 10:50:... 260 | 261 | .EXAMPLE 262 | .\Parse-EmailHeader.ps1 -InputFileName "C:\Scripts\MSGHeaderProcessor\msg6.txt" | Out-GridView 263 | This will process the contents of the msg6.txt file and then output the PSobject which gets returned in a GridView. 264 | 265 | .EXAMPLE 266 | .\Parse-EmailHeader.ps1 -InputFileName "C:\Scripts\MSGHeaderProcessor\msg6.txt" | select hop,@{n='Delay(Seconds)';e={$_.delay}},from,by,with,@{n='Time(UTC)';e={$_.time}} | Out-GridView 267 | 268 | .LINK 269 | www.myExchangeWorld.com 270 | #> 271 | } -------------------------------------------------------------------------------- /Public/New-MessageObject.ps1: -------------------------------------------------------------------------------- 1 | #requires -Version 2 2 | <# 3 | .Synopsis 4 | This function gathers details about a message. ` 5 | The default properties are the message fullname and the header(s) of that message 6 | .DESCRIPTION 7 | This function gathers details about a message that you can use for other purposes but ` 8 | it is the primary function for the rest of the Posh-PPRT PowerShell Module. 9 | .EXAMPLE 10 | PS C:\windows\system32> $MsgObject = @() 11 | PS C:\windows\system32> $MsgObject = New-MessageObject -Message C:\PHISHING_EMAILS -LogPath C:\PHISHING_EMAILS -FullDetails 12 | PS C:\windows\system32> $MsgObject.Header 13 | This property will display the email header of the message that has been processed 14 | PS C:\windows\system32> $MsgObject | Get-Member -MemberType NoteProperty 15 | 16 | .EXAMPLE 17 | PS C:\windows\system32> $MsgObject = @() 18 | PS C:\windows\system32> $MsgObject = New-MessageObject -Message C:\PHISHING_EMAILS -LogPath C:\PHISHING_EMAILS -FullDetails 19 | PS C:\windows\system32> $MsgObject | Invoke-PhishingResponse - 20 | #> 21 | function New-MessageObject 22 | { 23 | [CmdletBinding(DefaultParameterSetName = 'Full')] 24 | [OutputType([System.Collections.Hashtable],[String])] 25 | Param 26 | ( 27 | [Parameter(Mandatory = $true)] 28 | $Message, 29 | 30 | [Parameter(Mandatory = $true)] 31 | $LogPath, 32 | 33 | [Parameter(ParameterSetName = 'Full')] 34 | [switch]$FullDetails, 35 | 36 | [Parameter(ParameterSetName = 'Partial')] 37 | [switch]$FullName, 38 | [Parameter(ParameterSetName = 'Partial')] 39 | [switch]$Subject, 40 | [Parameter(ParameterSetName = 'Partial')] 41 | [switch]$Body, 42 | [Parameter(ParameterSetName = 'Partial')] 43 | [switch]$HTMLBody, 44 | [Parameter(ParameterSetName = 'Partial')] 45 | [switch]$BCC, 46 | [Parameter(ParameterSetName = 'Partial')] 47 | [switch]$CC, 48 | [Parameter(ParameterSetName = 'Partial')] 49 | [switch]$ReceivedOnBehalfOfEntryID, 50 | [Parameter(ParameterSetName = 'Partial')] 51 | [switch]$ReceivedOnBehalfOfName, 52 | [Parameter(ParameterSetName = 'Partial')] 53 | [switch]$ReceivedTime, 54 | [Parameter(ParameterSetName = 'Partial')] 55 | [switch]$Receipents, 56 | [Parameter(ParameterSetName = 'Partial')] 57 | [switch]$ReplyRecipientsName, 58 | [Parameter(ParameterSetName = 'Partial')] 59 | [switch]$SenderName, 60 | [Parameter(ParameterSetName = 'Partial')] 61 | [switch]$SentOnDate, 62 | [Parameter(ParameterSetName = 'Partial')] 63 | [switch]$SentOnBehalfOfName, 64 | [Parameter(ParameterSetName = 'Partial')] 65 | [switch]$SentTo, 66 | [Parameter(ParameterSetName = 'Partial')] 67 | [switch]$SenderEmailAddress, 68 | [Parameter(ParameterSetName = 'Partial')] 69 | [switch]$SenderEmailType, 70 | [Parameter(ParameterSetName = 'Partial')] 71 | [switch]$SendUsingAccount, 72 | [Parameter(ParameterSetName = 'Partial')] 73 | [switch]$Header, 74 | [Parameter(ParameterSetName = 'Partial')] 75 | $Attachments 76 | ) 77 | Begin 78 | { 79 | $AllAttachments = @() 80 | $ReturnOjbect = @() 81 | try 82 | { 83 | #Add-Type -AssemblyName 'Microsoft.Office.Interop.Outlook' 84 | $outlook = New-Object -ComObject outlook.application 85 | } 86 | catch 87 | { 88 | throw "Error opening Outlook.Application: $($Error[0] | Format-List -Property * -Force)" 89 | } 90 | 91 | #$object = @{} 92 | #$body = @{} 93 | 94 | $MainObject = @() 95 | } 96 | Process 97 | { 98 | Get-ChildItem $Message | ForEach-Object -Process { 99 | $Obj = @{} 100 | 101 | $msgFn = $_.FullName 102 | 103 | $log = Write-LogEntry -type Info -message "New-MessageObject: Processing Message - $msgFn" -Folder $LogPath 104 | 105 | # Skip non-.msg files 106 | if ($msgFn -like '*.msg') 107 | { 108 | $msg = $outlook.CreateItemFromTemplate($msgFn) 109 | 110 | if ($psboundparameters.Keys -contains 'FullDetails') 111 | { 112 | $temp = $msg 113 | 114 | $propertyNames = $temp | 115 | Get-Member -MemberType Properties | 116 | Select-Object -ExpandProperty Name 117 | 118 | foreach ($property in $propertyNames) 119 | { 120 | $value = foreach ($t in $temp) 121 | { 122 | $t.$property 123 | } 124 | 125 | if ($null -ne $value) 126 | { 127 | $Obj | Add-Member -MemberType NoteProperty -Name $property -Value $value 128 | } 129 | } 130 | 131 | $log = Write-LogEntry -type Info -message 'New-MessageObject: ComObject Properties sucessfully copied' -Folder $LogPath 132 | 133 | $Obj.FullName = $msgFn 134 | 135 | $HeaderDetails = @() 136 | 137 | $HeaderDetails = $msg.PropertyAccessor.GetProperty('http://schemas.microsoft.com/mapi/proptag/0x007D001E') 138 | $Obj.Header = $HeaderDetails 139 | 140 | $log = Write-LogEntry -type Info -message 'New-MessageObject: Email Header successfully copied' -Folder $LogPath 141 | 142 | $MainObject += $Obj 143 | } 144 | else 145 | { 146 | if($Attachments) 147 | { 148 | foreach($Attachment in $($msg.Attachments)) 149 | { 150 | $AllAttachments += $Attachments 151 | } 152 | } 153 | 154 | if ($headers) 155 | { 156 | $Header = $msg.PropertyAccessor.GetProperty('http://schemas.microsoft.com/mapi/proptag/0x007D001E') 157 | 158 | $log = Write-LogEntry -type Info -message 'New-MessageObject: Email Header successfully copied' -Folder $LogPath 159 | } 160 | 161 | switch ($psboundparameters.keys) 162 | { 163 | 'Subject' 164 | { 165 | $Obj.Subject = $msg.Subject 166 | } 167 | 'Body' 168 | { 169 | $Obj.Body = $msg.body 170 | } 171 | 'HTMLBody' 172 | { 173 | $Obj.HTMLBody = $msg.HTMLBody 174 | } 175 | 'BCC' 176 | { 177 | $Obj.BCC = $msg.BCC 178 | } 179 | 'CC' 180 | { 181 | $Obj.CC = $msg.CC 182 | } 183 | 'ReceivedOnBehalfOfEntryID' 184 | { 185 | $Obj.ReceivedOnBehalfOfEntryID = $msg.ReceivedOnBehalfOfEntryID 186 | } 187 | 'ReceivedOnBehalfOfName' 188 | { 189 | $Obj.ReceivedOnBehalfOfName = $msg.ReceivedOnBehalfOfName 190 | } 191 | 'ReceivedTime' 192 | { 193 | $Obj.ReceivedTime = $msg.ReceivedTime 194 | } 195 | 'Receipents' 196 | { 197 | $Obj.Receipents = $msg.Receipents 198 | } 199 | 'ReplyRecipientsName' 200 | { 201 | $Obj.ReplyRecipientsName = $msg.ReplyRecipientsName 202 | } 203 | 'SenderName' 204 | { 205 | $Obj.SenderName = $msg.SenderName 206 | } 207 | 'SentOnDate' 208 | { 209 | $Obj.SentOnDate = $msg.SentOnDate 210 | } 211 | 'SentOnBehalfOfName' 212 | { 213 | $Obj.SentOnBehalfOfName = $msg.SentOnBehalfOfName 214 | } 215 | 'SentTo' 216 | { 217 | $Obj.SentTo = $msg.SentTo 218 | } 219 | 'SenderEmailAddress' 220 | { 221 | $Obj.SenderEmailAddress = $msg.SenderEmailAddress 222 | } 223 | 'SenderEmailType' 224 | { 225 | $Obj.SenderEmailType = $msg.SenderEmailType 226 | } 227 | 'SendUsingAccount' 228 | { 229 | $Obj.SendUsingAccount = $msg.SendUsingAccount 230 | } 231 | 'Header' 232 | { 233 | $Obj.Header = $Header 234 | } 235 | 'Attachments' 236 | { 237 | $Obj.Attachments = $AllAttachments 238 | } 239 | } 240 | 241 | $Obj | Add-Member -MemberType NoteProperty -Name FullName -Value $msgFn -Force 242 | 243 | $log = Write-LogEntry -type Info -message 'New-MessageObject: ComObject Properties sucessfully copied' -Folder $LogPath 244 | 245 | $MainObject += $Obj 246 | } 247 | } 248 | else 249 | { 250 | $log = Write-LogEntry -type Error -message "New-MessageObject: Message is not a .MSG file - $msgFn" -Folder $LogPath -CustomMessage 'Break' 251 | #break 252 | } 253 | } 254 | } 255 | End 256 | { 257 | #stop outlook process if still open from send emails using Outlook.Application COM Object 258 | Start-Sleep -Seconds 3 259 | Get-Process -Name Outlook | Stop-Process 260 | 261 | $log = Write-LogEntry -type Info -message 'New-MessageObject: Adding Object Detail' -Folder $LogPath 262 | 263 | Add-ObjectDetail -InputObject $MainObject -TypeName PPRT.Message 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /Public/Parse-EmailHeader.ps1: -------------------------------------------------------------------------------- 1 | #requires -Version 2 2 | Function Parse-EmailHeader 3 | { 4 | [CmdletBinding()] 5 | Param 6 | ( 7 | [parameter(Mandatory = $true)] 8 | [String]$InputFileName 9 | ) 10 | Begin 11 | { 12 | Function Process-ReceivedBy 13 | { 14 | Param($text) 15 | $regexBy1 = 'Received: by ' 16 | $regexBy2 = 'Received: by ([\s\S]*?)with([\s\S]*?);([(\s\S)*]{32,36})(?:\s\S*?)' 17 | $regexBy3 = 'Received: by ([\s\S]*?);([(\s\S)*]{32,36})(?:\s\S*?)' 18 | $byMatches = $text | Select-String -Pattern $regexBy1 -AllMatches 19 | 20 | if ($byMatches) 21 | { 22 | $byMatches = $text | Select-String -Pattern $regexBy2 -AllMatches 23 | if($byMatches) 24 | { 25 | $rbArray = @() 26 | $byMatches.Matches | ForEach-Object -Process { 27 | $by = Clean-String $_.groups[1].value 28 | $with = Clean-String $_.groups[2].value 29 | Switch -wildcard ($with) 30 | { 31 | 'SMTP*' 32 | { 33 | $with = 'SMTP' 34 | } 35 | 'ESMTP*' 36 | { 37 | $with = 'ESMTP' 38 | } 39 | default 40 | { 41 | 42 | } 43 | } 44 | $time = Clean-String $_.groups[3].value 45 | $byhash = @{ 46 | ReceivedByBy = $by 47 | ReceivedByWith = $with 48 | ReceivedByTime = [Datetime]$time 49 | } 50 | $byArray = New-Object -TypeName PSObject -Property $byhash 51 | $rbArray += $byArray 52 | } 53 | $rbArray 54 | } 55 | else 56 | { 57 | $rbArray = @() 58 | $byMatches = $text | Select-String -Pattern $regexBy3 -AllMatches 59 | $byMatches.Matches | ForEach-Object -Process { 60 | $by = Clean-String $_.groups[1].value 61 | $with = '' 62 | $time = Clean-String $_.groups[2].value 63 | $byhash = @{ 64 | ReceivedByBy = $by 65 | ReceivedByWith = $with 66 | ReceivedByTime = [Datetime]$time 67 | } 68 | $byArray = New-Object -TypeName PSObject -Property $byhash 69 | $rbArray += $byArray 70 | } 71 | $rbArray 72 | } 73 | } 74 | else 75 | { 76 | return $null 77 | } 78 | } 79 | 80 | Function Process-ReceivedFrom 81 | { 82 | Param($text) 83 | $regexFrom1 = 'Received: from([\s\S]*?)by([\s\S]*?)with([\s\S]*?);([(\s\S)*]{32,36})(?:\s\S*?)' 84 | $fromMatches = $text | Select-String -Pattern $regexFrom1 -AllMatches 85 | Write-Host 'From Matches: ' $fromMatches 86 | if ($fromMatches) 87 | { 88 | $rfArray = @() 89 | $fromMatches.Matches | ForEach-Object -Process { 90 | $from = Clean-String $_.groups[1].value 91 | $by = Clean-String $_.groups[2].value 92 | $with = Clean-String $_.groups[3].value 93 | Switch -wildcard ($with) 94 | { 95 | 'SMTP*' 96 | { 97 | $with = 'SMTP' 98 | } 99 | 'ESMTP*' 100 | { 101 | $with = 'ESMTP' 102 | } 103 | 'NNFMP*' 104 | { 105 | $with = 'NNFMP' 106 | } 107 | default 108 | { 109 | 110 | } 111 | } 112 | $time = Clean-String $_.groups[4].value 113 | $fromhash = @{ 114 | ReceivedFromFrom = $from 115 | ReceivedFromBy = $by 116 | ReceivedFromWith = $with 117 | ReceivedFromTime = [Datetime]$time 118 | } 119 | $fromArray = New-Object -TypeName PSObject -Property $fromhash 120 | $rfArray += $fromArray 121 | } 122 | $rfArray 123 | } 124 | else 125 | { 126 | return $null 127 | } 128 | } 129 | 130 | Function Clean-String 131 | { 132 | Param([string]$inputString) 133 | $inputString = $inputString.Trim() 134 | $inputString = $inputString.Replace("`r`n",'') 135 | $inputString = $inputString.Replace("`t",' ') 136 | $inputString 137 | } 138 | 139 | Function Process-FromByObject 140 | { 141 | Param([PSObject[]]$fromObjects,[PSObject[]]$byObjects) 142 | [int]$hop = 0 143 | $delay = '' 144 | $receivedfrom = $receivedby = $receivedtime = $receivedwith = $null 145 | $prevTime = $null 146 | $time = $null 147 | $finalArray = @() 148 | if($byObjects) 149 | { 150 | $byObjects = $byObjects[($byObjects.Length-1)..0] # Reversing the Array 151 | for($index = 0;$index -lt $byObjects.Count;$index++) 152 | { 153 | if($index -eq 0) 154 | { 155 | $hop = 1 156 | $delay = '*' 157 | $receivedfrom = '' 158 | $receivedby = $byObjects[$index].ReceivedByBy 159 | $with = $byObjects[$index].ReceivedByWith 160 | $time = $byObjects[$index].ReceivedBytime 161 | $time = $time.touniversaltime() 162 | $prevTime = $time 163 | $finalHash = @{ 164 | Hop = $hop 165 | Delay = $delay 166 | From = $receivedfrom 167 | By = $receivedby 168 | With = $with 169 | Time = $time 170 | } 171 | $obj = New-Object -TypeName PSObject -Property $finalHash 172 | $finalArray += $obj 173 | } 174 | else 175 | { 176 | $hop = $index+1 177 | $receivedfrom = '' 178 | $receivedby = $byObjects[$index].ReceivedByBy 179 | $with = $byObjects[$index].ReceivedByWith 180 | $time = $byObjects[$index].ReceivedBytime 181 | $time = $time.touniversaltime() 182 | $delay = $time - $prevTime 183 | $delay = $delay.totalseconds 184 | if ($delay -le -1) 185 | { 186 | $delay = 0 187 | } 188 | $prevTime = $time 189 | $finalHash = @{ 190 | Hop = $hop 191 | Delay = $delay 192 | From = $receivedfrom 193 | By = $receivedby 194 | With = $with 195 | Time = $time 196 | } 197 | $obj = New-Object -TypeName PSObject -Property $finalHash 198 | $finalArray += $obj 199 | } 200 | } 201 | $lastHop = $hop 202 | } 203 | $hop = $lastHop 204 | if($fromObjects) 205 | { 206 | $fromObjects = $fromObjects[($fromObjects.Length-1)..0] #Reversing the Array 207 | for($index = 0;$index -lt $fromObjects.Count;$index++) 208 | { 209 | $hop = $hop + 1 210 | $receivedfrom = $fromObjects[$index].ReceivedFromFrom 211 | $receivedby = $fromObjects[$index].ReceivedFromBy 212 | $with = $fromObjects[$index].ReceivedFromWith 213 | $time = $fromObjects[$index].ReceivedFromTime 214 | $time = $time.touniversaltime() 215 | if($prevTime) 216 | { 217 | $delay = $time - $prevTime 218 | $delay = $delay.totalseconds 219 | } 220 | else 221 | { 222 | $delay = '*' 223 | } 224 | $prevTime = $time 225 | $finalHash = @{ 226 | Hop = $hop 227 | Delay = $delay 228 | From = $receivedfrom 229 | By = $receivedby 230 | With = $with 231 | Time = $time 232 | } 233 | $obj = New-Object -TypeName PSObject -Property $finalHash 234 | $finalArray += $obj 235 | } 236 | } 237 | $finalArray 238 | } 239 | 240 | } 241 | 242 | Process 243 | { 244 | $text = $InputFileName 245 | $fromObject = Process-ReceivedFrom -text $text 246 | $byObject = Process-ReceivedBy -text $text 247 | 248 | $finalArray = Process-FromByObject $fromObject $byObject 249 | Write-Output -InputObject $finalArray 250 | 251 | } 252 | <# 253 | .SYNOPSIS 254 | Parses Email Message Header and then provides the Email route information along with delay at each hop. 255 | 256 | .DESCRIPTION 257 | Parses Email Message Header and then returns a PSObject with following values. 258 | 1. HOP 259 | 2. DELAY 260 | 3. From [Received From Server] 261 | 4. By [Received By Server] 262 | 5. With [Protocol] 263 | 6. Time 264 | 265 | .PARAMETER 266 | Specify ComputerName the script should run against. 267 | 268 | .EXAMPLE 269 | .\Parse-EmailHeader.ps1 -InputFileName "C:\Scripts\MSGHeaderProcessor\msg6.txt" 270 | This will process the contents of the msg6.txt file and then output the PSobject which gets returned. 271 | 272 | .EXAMPLE 273 | .\Parse-EmailHeader.ps1 -InputFileName "C:\Scripts\MSGHeaderProcessor\msg6.txt" | Format-Table 274 | This will process the contents of the msg6.txt file and then output the PSobject which gets returned in table format. 275 | Output: 276 | From With Delay Hop By Time 277 | ---- ---- ----- --- -- ---- 278 | corp.red.com ([1... mapi id 14.01.03... * 1 singapore.red.co... 7/13/2011 10:50:... 279 | singapore.red.co... Microsoft SMTP S... 8 2 newyork.red.com ... 7/13/2011 10:50:... 280 | newyork.red.com ... Microsoft SMTP S... 5 3 outgoing.red.com... 7/13/2011 10:50:... 281 | outgoing.red.com... Microsoft SMTPSV... 6 4 incoming.green.com 7/13/2011 10:50:... 282 | 283 | .EXAMPLE 284 | .\Parse-EmailHeader.ps1 -InputFileName "C:\Scripts\MSGHeaderProcessor\msg6.txt" | Out-GridView 285 | This will process the contents of the msg6.txt file and then output the PSobject which gets returned in a GridView. 286 | 287 | .EXAMPLE 288 | .\Parse-EmailHeader.ps1 -InputFileName "C:\Scripts\MSGHeaderProcessor\msg6.txt" | select hop,@{n='Delay(Seconds)';e={$_.delay}},from,by,with,@{n='Time(UTC)';e={$_.time}} | Out-GridView 289 | 290 | .LINK 291 | www.myExchangeWorld.com 292 | #> 293 | } 294 | -------------------------------------------------------------------------------- /Public/Invoke-PhishingResponse.ps1: -------------------------------------------------------------------------------- 1 | #requires -Version 2 2 | function Invoke-PhishingResponse 3 | { 4 | <# 5 | .SYNOPSIS 6 | Takes a .msg file, find a phishing link, does reverse DNS for the IP, and queries whois Databases for abuse contact information 7 | 8 | .DESCRIPTION 9 | Takes a .MSG file and searches for a link based on a regex pattern 10 | Takes that link, parses it to find the root DNS name 11 | Takes the DNS name and finds the IP by doing a reverse DNS lookup 12 | Takes the IP of the server and parses it for the first octet 13 | Takes the first octet and finds which whois should be used 14 | Once it has the whois, it queries their API or scraps their website for their abuse contact information 15 | Once it has the abuse contact info, it sends them an email from abuse email account with the original attachment - asking them to remove the website 16 | Sends an email to spam@access.ironport.com 17 | Sends an email to the Google Anti-Phishing Group anti-phishing-email-reply-discuss@googlegroups.com 18 | Logs this in the running log file 19 | 20 | .PARAMETER messagetoparse 21 | Specifices the specific .MSG that someone wants to parse 22 | 23 | .PARAMETER logpath 24 | Sets the path to our log file 25 | 26 | .PARAMETER From 27 | This parameter is used to define who is sending these notificaitons. 28 | Currently, you must put an email address that you want to "Send on Behalf of". 29 | 30 | .EXAMPLE 1 31 | C:\PS> Invoke-PhishingResponse -Message $MessageObject ` 32 | -LogPath C:\PHISHING_EMAILS ` 33 | -From 'abuse@emailaddress.com' ` 34 | -ExtractAttachments ` 35 | -SaveLocation C:\PHISHING_EMAILS\EXTRACTED_ATTACHMENTS 36 | 37 | .EXAMPLE 2 38 | C:\PS> Invoke-PhishingResponse -Message $MessageObject ` 39 | -LogPath C:\PHISHING_EMAILS ` 40 | -From 'abuse@emailaddress.com' ` 41 | -ExtractAttachments ` 42 | -SaveLocation C:\PHISHING_EMAILS\EXTRACTED_ATTACHMENTS ` 43 | -SMTPServer smtp.office365.com ` 44 | -Credential $Cred 45 | 46 | #> 47 | 48 | [CmdletBinding()] 49 | param ( 50 | [parameter(Mandatory = $true, 51 | HelpMessage = 'Please provide a .MSG file.', 52 | ValueFromPipelineByPropertyName = $true, 53 | ValueFromPipeline = $true)] 54 | [PSTypeName('PPRT.Message')] 55 | $Message, 56 | 57 | [Parameter(Mandatory = $true)] 58 | $LogPath, 59 | 60 | [Parameter(Mandatory = $true, 61 | ParameterSetName = 'VT')] 62 | [switch]$ExtractAttachments, 63 | 64 | [Parameter(Mandatory = $true, 65 | ParameterSetName = 'VT')] 66 | [Parameter(Mandatory = $true, 67 | ParameterSetName = 'Map1')] 68 | [string]$SaveLocation, 69 | 70 | [parameter(Mandatory = $true, 71 | HelpMessage = 'Please provide a From email address', 72 | ParameterSetName = 'Email')] 73 | [string]$From, 74 | 75 | [parameter(Mandatory = $false, 76 | HelpMessage = 'Please provide a message Subject. Default uses the Phising Email subject.', 77 | ParameterSetName = 'Email')] 78 | $Subject, 79 | 80 | 81 | [Parameter(Mandatory = $true, 82 | ValueFromPipelineByPropertyName = $true, 83 | ParameterSetName = 'Email')] 84 | [Alias('PSEmailServer')] 85 | [string]$SMTPServer = $PSEmailServer, 86 | 87 | [Parameter(Mandatory = $false, 88 | ValueFromPipelineByPropertyName = $true, 89 | ParameterSetName = 'Email')] 90 | [int]$SMTPPort = '25', 91 | 92 | [Parameter(Mandatory = $false, 93 | ValueFromPipelineByPropertyName = $true, 94 | ParameterSetName = 'Email')] 95 | [switch]$UseSSL, 96 | 97 | [parameter(Mandatory = $false, 98 | HelpMessage = 'Please provide a message Body. Default uses just the phishing URL.', 99 | ParameterSetName = 'Email')] 100 | $Body, 101 | 102 | [parameter(Mandatory = $false, 103 | HelpMessage = 'Please indicate if you want the message body to be rendered as HTML.', 104 | ParameterSetName = 'Email')] 105 | [switch]$BodyAsHTML, 106 | 107 | [parameter(Mandatory = $true, 108 | HelpMessage = 'Please provide a message Body. Default uses just the phishing URL.', 109 | ParameterSetName = 'Email')] 110 | [System.Management.Automation.PSCredential]$Credential, 111 | 112 | [Parameter(ParameterSetName = 'VT')] 113 | [parameter(HelpMessage = 'Please provide your Virus Total API Key')] 114 | $VTAPIKey, 115 | 116 | [parameter(HelpMessage = 'Provide this switch if you want to send additional notifications')] 117 | [switch]$AdditionalNotifications, 118 | 119 | [parameter(HelpMessage = 'Please select either AllReceivedFromIPMap or FirstReceivedFromIPMap')] 120 | [ValidateNotNullOrEmpty()] 121 | [switch]$AllReceivedFromIPMap, 122 | 123 | [parameter(HelpMessage = 'Please select either AllReceivedFromIPMap or FirstReceivedFromIPMap')] 124 | [ValidateNotNullOrEmpty()] 125 | [Parameter(ParameterSetName = 'Map1')] 126 | [switch]$FirstReceivedFromIPMap, 127 | 128 | [parameter(HelpMessage = 'Please select either AllReceivedFromIPMap or FirstReceivedFromIPMap')] 129 | [ValidateNotNullOrEmpty()] 130 | [Parameter(ParameterSetName = 'Map1')] 131 | [switch]$FirstReceivedFromIPHeatMap 132 | ) 133 | 134 | Begin 135 | { 136 | $AttachmentObject = @() 137 | $VirusTotalResult = @() 138 | $URLObject = @() 139 | $NotificationObj = @() 140 | 141 | $MainObject = @() 142 | 143 | $PhishingResponseObject = @{} 144 | 145 | } 146 | Process 147 | { 148 | 149 | foreach ($msg in $Message) 150 | { 151 | if ($ExtractAttachments) 152 | { 153 | if ($msg.Attachments) 154 | { 155 | #Call Extract-MessageAttachment 156 | $AttachmentObject = Export-MessageAttachment -MessageObject $msg -LogPath $LogPath -FullDetails -SavePath $SaveLocation 157 | 158 | $log = Write-LogEntry -type Info -message 'Invoke-PhishingResponse: Attachment Extracted' -Folder $LogPath 159 | 160 | if ($VTAPIKey) 161 | { 162 | $log = Write-LogEntry -type Info -message 'Invoke-PhishingResponse: Calling Invoke-VTAttachment' -Folder $LogPath 163 | $VirusTotalResult = Invoke-VTAttachment -AttachmentHash $AttachmentObject -VTAPIKey $VTAPIKey 164 | } 165 | } 166 | } 167 | 168 | 169 | $URLObject = @() 170 | 171 | $log = Write-LogEntry -type Info -message "Invoke-PhishingResponse: Getting URL From $($msg.FullName)" -Folder $LogPath 172 | 173 | $URLObject = Get-URLFromMessage -Message $msg -LogPath $LogPath 174 | 175 | $AbuseContactObject = @() 176 | 177 | $log = Write-LogEntry -type Info -message "Invoke-PhishingResponse: Trying to identify Abuse Contact for $($msg.FullName)" -Folder $LogPath 178 | 179 | $AbuseContactObject = New-PPRTAbuseContactObject -URLObject $URLObject -LogPath $LogPath 180 | 181 | if ($AbuseContactObject.AbuseContact -notmatch 'NO POC FOR *') 182 | { 183 | if ($AbuseContactObject -eq $null) 184 | { 185 | $log = Write-LogEntry -type Info -message 'Invoke-PhishingResponse: New-PPRTAbuseContactObject did not return a value' -Folder $LogPath 186 | continue 187 | } 188 | 189 | $Obj = @{} 190 | 191 | if ($null -ne $AbuseContactObject) 192 | { 193 | $SendTo = @() 194 | 195 | foreach ($item in $AbuseContactObject.AbuseContact) 196 | { 197 | $SendTo += $($item) 198 | } 199 | 200 | $Obj.To = $($SendTo -join ',') 201 | } 202 | else 203 | { 204 | $log = Write-LogEntry -type Error -message 'Invoke-PhishingResponse: ABUSE CONTACT IS NULL' -Folder $LogPath -CustomMessage 'Break!' 205 | continue 206 | } 207 | 208 | if (!$Subject) 209 | { 210 | $Obj.Subject = $msg.Subject 211 | } 212 | 213 | if (!$Body) 214 | { 215 | $Obj.Body = $URLObject.URL 216 | } 217 | 218 | switch ($psboundparameters.keys) 219 | { 220 | 'From' 221 | { 222 | $Obj.From = $From 223 | } 224 | 'HTMLBody' 225 | { 226 | $Obj.BodyAsHTML = $BodyAsHTML 227 | } 228 | 'BCC' 229 | { 230 | $Obj.BCC = $BCC 231 | } 232 | 'CC' 233 | { 234 | $Obj.CC = $CC 235 | } 236 | 'Subject' 237 | { 238 | $Obj.Subject = $Subject 239 | } 240 | 'Priority' 241 | { 242 | $Obj.Priority = $Priority 243 | } 244 | 'UseSSL' 245 | { 246 | $Obj.UseSSL = $UseSSL 247 | } 248 | 'Encoding' 249 | { 250 | $Obj.Encoding = $Encoding 251 | } 252 | 'Credential' 253 | { 254 | $Obj.Credential = $Credential 255 | } 256 | 'SMTPServer' 257 | { 258 | $Obj.SMTPServer = $SMTPServer 259 | } 260 | 'SMTPPort' 261 | { 262 | $Obj.SMTPPort = $SMTPPort 263 | } 264 | 'URL' 265 | { 266 | $Obj.URL = $AbuseContactObject.URL 267 | } 268 | } 269 | 270 | $NotificationObject = @() 271 | 272 | $log = Write-LogEntry -type Info -message "Invoke-PhishingResponse: Attempting to send notifications - $($Obj)" -Folder $LogPath 273 | 274 | $NotificationObject = Send-MailMessage @Obj 275 | 276 | $PhishingResponseObject.Notification = $NotificationObj 277 | } 278 | 279 | $props = @{ 280 | MSG = $msg 281 | URL = $URLObject 282 | Abuse = $AbuseContactObject 283 | Notification = $Obj 284 | Attachment = $AttachmentObject 285 | VirusTotal = $VirusTotalResult 286 | } 287 | 288 | $TempMainObject = New-Object -TypeName PSObject -Property $props 289 | $MainObject += $TempMainObject 290 | 291 | $log = Write-LogEntry -type Info -message "Invoke-PhishingResponse: Message Successfully Processed - $($msg.FullName)" -Folder $LogPath 292 | } 293 | 294 | $log = Write-LogEntry -type Info -message 'All Phishing Emails Processed' -Folder $LogLocation 295 | 296 | if ($FirstReceivedFromIPMap) 297 | { 298 | if ($FirstReceivedFromIPHeatMap) 299 | { 300 | $log = Write-LogEntry -type Info -message 'Invoke-PhishingResponse: Creating New First Received From IP Heat Map Object' -Folder $LogLocation 301 | $FirstReceivedFromIPObject = New-FirstReceivedFromIPObject -MessageObject $Message -SavePath $SaveLocation -HeatMap 302 | } 303 | else 304 | { 305 | $log = Write-LogEntry -type Info -message 'Invoke-PhishingResponse: Creating New First Received From IP Map Object' -Folder $LogLocation 306 | $FirstReceivedFromIPObject = New-FirstReceivedFromIPObject -MessageObject $Message -SavePath $SaveLocation 307 | } 308 | 309 | $MainObject.FirstReceivedFromIPObject = $FirstReceivedFromIPObject 310 | } 311 | 312 | 313 | #getting data for MapAllIPs Google Maps API Polyline output 314 | if ($AllReceivedFromIPMap) 315 | { 316 | $AllReceivedFromIPObject = Create-AllReceivedFromIPObject -MessageObject $MainObject -SavePath $LogLocation 317 | 318 | $MainObject.AllReceivedFromIPObject = $AllReceivedFromIPObject 319 | } 320 | 321 | } 322 | End 323 | { 324 | return $MainObject 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /Public/Get-PhishingGeoLocation.ps1: -------------------------------------------------------------------------------- 1 | #requires -Version 3 2 | function Get-PhishingGeoLocation 3 | { 4 | param ( 5 | [parameter(Mandatory = $true, 6 | HelpMessage = 'Please provide a .msg file')] 7 | [ValidateNotNullOrEmpty()] 8 | [string[]]$email, 9 | [parameter(ParameterSetName = 'set2', 10 | HelpMessage = 'Please select either MapAllIPs or MapStartingIP')] 11 | [ValidateNotNullOrEmpty()] 12 | [switch]$MapAllIPs, 13 | [parameter(ParameterSetName = 'set1', 14 | HelpMessage = 'Please select either MapAllIPs or MapStartingIP')] 15 | [ValidateNotNullOrEmpty()] 16 | [switch]$MapStartingIP, 17 | [parameter(ParameterSetName = 'set1', 18 | HelpMessage = 'Please select either MapAllIPs or MapStartingIP')] 19 | [ValidateNotNullOrEmpty()] 20 | [switch]$HeatMap, 21 | [parameter(Mandatory = $true, 22 | HelpMessage = 'Please provide a folder path for ouputting generated maps')] 23 | [ValidateNotNullOrEmpty()] 24 | [string]$FolderPath 25 | 26 | ) 27 | <# 28 | .SYNOPSIS 29 | Map location of IPs from email headers of Phishing Emails received using Google Maps API 30 | 31 | 32 | .DESCRIPTION 33 | This function takes a .msg or .msg's as input and plots the email header IPs on local Google Map using the API. 34 | This function takes the .msg, get's the email headers, get's all the IPs, finds the Latitude and Longtitude (if available) 35 | and builds a HTML webpage plotting those IPs 36 | 37 | You have several options for different types of maps: 38 | MapStartingIP - This options places markers for the first IP listed in the Received From headers of the email 39 | MapAllIPs - This option maps all "Received From" IPs from email headers and maps their total path 40 | HeatMap - This option is similar to MapStartingIp, but instead of markers it provides a Heat Map of those first received from IPs 41 | 42 | .PARAMETER message 43 | Specifices the message or messages you are wanting to plot on a map. This messages need to be in a .msg format 44 | 45 | .PARAMETER MapAllIPs 46 | This switch is used when you want to map all IPs in a message header. This option will 47 | plot the path of the entire message header(s). 48 | 49 | .PARAMETER MapStartingIP 50 | This switch is used when you want to map the first "Received From:" IP. This option will 51 | place a marker at it's geolocation. The marker includes the following information: 52 | Subject 53 | Received Time 54 | Sender Email Address 55 | Phishing URL 56 | 57 | .PARAMETER HeatMap 58 | This switch can be used in conjunction with the MapStartingIP. It will produce a Heat Map of the first "Received From:" IP. Coloration/Radius 59 | will increase based on the number of IPs 60 | 61 | .PARAMETER FolderPath 62 | This parameter is mandatory and is needed to to output the generated maps. 63 | 64 | .EXAMPLE 65 | C:\PS> Get-PhishingGeoLocation -message 'C:\users\username\PHISHING_MESSAGES\*.msg' -MapStartingIP -HeatMap -FolderPath C:\users\username\Desktop 66 | 67 | .EXAMPLE 68 | C:\PS> Get-PhishingGeoLocation -message 'C:\users\username\PHISHING_MESSAGES\*.msg' -MapStartingIP -FolderPath C:\users\username\Desktop 69 | 70 | .EXAMPLE 71 | C:\PS> Get-PhishingGeoLocation -message 'C:\users\username\PHISHING_MESSAGES\*.msg' -MapAllIPs -FolderPath C:\users\username\Desktop 72 | 73 | .NOTES 74 | Currently all Outputs work best in Internet Explorer but they may work in Google Chrome and FireFox. 75 | #> 76 | 77 | #setting arrays to be used later in the script 78 | $polyline = @() #used for array of PolyLines 79 | $StartingIPObject = @() #used for array of First Received IPs 80 | $HeatMapObject = @() #used for HeatMap Object 81 | $AllIPObject = @() #used for MapAllIPs object 82 | $i = 0 #used in conjunction with write-progress functionality 83 | $message = @() #used in conjunction with write-progress functionality 84 | 85 | #regex is used for getting IPs from String 86 | $regex = '\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b' 87 | 88 | #used to strip http/https/ftp/file/www links from emails 89 | $PhishingURLRegEx = '(?:(?:https?|ftp|file)://|www\.|ftp\.)(?:\([-A-Z0-9+&@#/%=~_|$?!:,.]*\)|[-A-Z0-9+&@#/%=~_|$?!:,.])*(?:\([-A-Z0-9+&@#/%=~_|$?!:,.]*\)|[A-Z0-9+&@#/%=~_|$])' 90 | 91 | #gathering total count of emails to process and adding them to $message array for further processing 92 | foreach ($item in $email) 93 | { 94 | Get-ChildItem $item | ForEach-Object -Process { 95 | $message += $_ 96 | } 97 | } 98 | 99 | Write-Verbose -Message "Gathering Email Data from $(($message | Measure-Object).count) email messages" 100 | 101 | Get-ChildItem $message | ForEach-Object -Process { 102 | try 103 | { 104 | Add-Type -AssemblyName 'Microsoft.Office.Interop.Outlook' 105 | $outlook = New-Object -ComObject outlook.application 106 | } 107 | catch 108 | { 109 | Write-Debug -Message 'Error: Please try and shutdown Outlook' 110 | } 111 | 112 | $msg = $outlook.CreateItemFromTemplate($_.FullName) 113 | 114 | #generating Progress Bar/Activity 115 | Write-Progress -Activity "Gathering Email Data from $($message.count) email messages" -Status "Processing $($msg.Subject)" -PercentComplete ($i/$(($message | Measure-Object).count)*100) 116 | 117 | #getting phishing URL from the current processing email message 118 | $phishingURL = '' 119 | 120 | $phishingURL = $msg | 121 | Select-Object -Property body | 122 | Select-String -Pattern '(?:(?:https?|ftp|file)://|www\.|ftp\.)(?:\([-A-Z0-9+&@#/%=~_|$?!:,.]*\)|[-A-Z0-9+&@#/%=~_|$?!:,.])*(?:\([-A-Z0-9+&@#/%=~_|$?!:,.]*\)|[A-Z0-9+&@#/%=~_|$])' | 123 | ForEach-Object -Process { 124 | $_.Matches 125 | } | 126 | ForEach-Object -Process { 127 | $_.Value 128 | } 129 | 130 | Write-Verbose -Message "PhisingURL: $($phishingURL)" 131 | 132 | $headers = '' 133 | 134 | #getting mapi property descriptor from microsoft. This is needed to get the raw text email headers 135 | $headers = $msg.PropertyAccessor.GetProperty('http://schemas.microsoft.com/mapi/proptag/0x007D001E') 136 | 137 | if ($MapStartingIP) 138 | { 139 | $firstReceivedFromIP = @() 140 | $firstReceivedFromIP = (Parse-EmailHeader -InputFileName $headers).From | 141 | Select-String -Pattern $regex -AllMatches | 142 | ForEach-Object -Process { 143 | $_.Matches 144 | } | 145 | ForEach-Object -Process { 146 | $_.Value 147 | } 148 | 149 | #calling first received from header returned from parse-emailheader. Location is [0] 150 | $originalIpLocation = @() 151 | $originalIpLocation = Invoke-RestMethod -Uri "http://freegeoip.net/xml/$($firstReceivedFromIP[0])" 152 | 153 | #getting all first received from IP from headers and creating markers 154 | $originalmarker = @() 155 | if (($originalIpLocation.Response.Latitude -ne 0) -or ($originalIpLocation.Response.Longitude -ne 0)) 156 | { 157 | if (![string]::IsNullOrWhiteSpace($originalIpLocation.Response.Latitude)) 158 | { 159 | if (![string]::IsNullOrWhiteSpace($originalIpLocation.Response.Longitude)) 160 | { 161 | #adding json markup data to object. This will be passed to Get-PhishingGeoLocationStartingIps cmdlet 162 | $props = @{ 163 | marker = "`{'title': '$($msg.subject -replace "'",' ')', 'lat': '$($originalIpLocation.Response.Latitude)', 'lng': '$($originalIpLocation.Response.Longitude)', 'description': '

$($msg.Subject -replace "'",' ')

Subject: $($msg.Subject -replace "'",' ')

Received Time: $($msg.ReceivedTime)

Sender Email Address: $($msg.SenderEmailAddress)

Sender Email Type: $($msg.SenderEmailType)

Phishing URL: $($phishingURL)

' }" 164 | subject = $msg.Subject 165 | SentFromAddress = $msg.SenderEmailAddress 166 | SentFromType = $msg.SenderEmailType 167 | ReceivedTime = $msg.ReceivedTime 168 | EmailBody = $msg.Body 169 | } 170 | 171 | $tempStartingIPObject = New-Object -TypeName PSObject -Property $props 172 | $StartingIPObject += $tempStartingIPObject 173 | } 174 | } 175 | } 176 | 177 | #getting heat map markers, even though they switch may not be called 178 | $tempHeatMapMarkers = @() 179 | if (($originalIpLocation.Response.Latitude -ne 0) -or ($originalIpLocation.Response.Longitude -ne 0)) 180 | { 181 | if (![string]::IsNullOrWhiteSpace($originalIpLocation.Response.Latitude)) 182 | { 183 | if (![string]::IsNullOrWhiteSpace($originalIpLocation.Response.Longitude)) 184 | { 185 | $props = @{ 186 | marker = "new google.maps.LatLng($($originalIpLocation.Response.Latitude), $($originalIpLocation.Response.Longitude))" 187 | subject = $msg.Subject 188 | SentFromAddress = $msg.SenderEmailAddress 189 | SentFromType = $msg.SenderEmailType 190 | ReceivedTime = $msg.ReceivedTime 191 | EmailBody = $msg.Body 192 | } 193 | 194 | $tempHeatMapObject = New-Object -TypeName PSObject -Property $props 195 | $HeatMapObject += $tempHeatMapObject 196 | } 197 | } 198 | } 199 | } 200 | 201 | #getting data for MapAllIPs Google Maps API Polyline output 202 | if ($MapAllIPs) 203 | { 204 | $originalPolyline = @() 205 | $ReceivedFromIP = @() 206 | $ReceivedFromIP = (Parse-EmailHeader -InputFileName $headers).From | 207 | Select-String -Pattern $regex -AllMatches | 208 | ForEach-Object -Process { 209 | $_.Matches 210 | } | 211 | ForEach-Object -Process { 212 | $_.Value 213 | } 214 | 215 | foreach ($ip in $ReceivedFromIP) 216 | { 217 | $IpLocation = '' 218 | $IpLocation = Invoke-RestMethod -Uri "http://freegeoip.net/xml/$($ip)" 219 | 220 | if (($IpLocation.Response.Latitude -ne 0) -or ($IpLocation.Response.Longitude -ne 0)) 221 | { 222 | if (![string]::IsNullOrWhiteSpace($IpLocation.Response.Latitude)) 223 | { 224 | if (![string]::IsNullOrWhiteSpace($IpLocation.Response.Longitude)) 225 | { 226 | $originalPolyline = "{lat: $($IpLocation.Response.Latitude), lng: $($IpLocation.Response.Longitude)}" 227 | $polyline += $originalPolyline 228 | } 229 | } 230 | } 231 | } 232 | 233 | $props = @{ 234 | marker = "[$($polyline -join ',')]" 235 | subject = $msg.Subject 236 | SentFromAddress = $msg.SenderEmailAddress 237 | SentFromType = $msg.SenderEmailType 238 | ReceivedTime = $msg.ReceivedTime 239 | EmailBody = $msg.Body 240 | } 241 | 242 | $tempAllIPObject = New-Object -TypeName PSObject -Property $props 243 | $AllIPObject += $tempAllIPObject 244 | 245 | $polyline = @() 246 | } 247 | 248 | $i++ 249 | }#end of foreach message 250 | 251 | if ($MapStartingIP) 252 | { 253 | Write-Verbose -Message "Starting IP Object Count: $(($StartingIPObject.marker).count)" 254 | Get-PhishingGeoLocationStartingIPs -StartingIPData $StartingIPObject -FolderPath $FolderPath 255 | } 256 | 257 | if ($HeatMap) 258 | { 259 | Write-Verbose -Message "Heat Map Object Count: $(($HeatMapObject.marker).count)" 260 | Get-PhishingGeoLocationHeatMap -HeatMapData $HeatMapObject -FolderPath $FolderPath 261 | } 262 | 263 | if ($MapAllIPs) 264 | { 265 | Write-Verbose -Message "All IP Object Count: $(($AllIPObject.marker).count)" 266 | Get-PhishingGeoLocationAllIPs -AllIPData $AllIPObject -FolderPath $FolderPath 267 | } 268 | 269 | #closing outlook process if not done already 270 | Start-Sleep -Seconds 3 271 | Get-Process -Name Outlook | Stop-Process 272 | } 273 | --------------------------------------------------------------------------------