ThreatHunting/Find-LateralMovement.ps1

function Find-LateralMovement {
    <#
    .SYNOPSIS
        Detects lateral movement indicators in the environment
    .DESCRIPTION
        Identifies signs of lateral movement including remote connections,
        pass-the-hash, PSRemoting, RDP, SMB shares, and credential access
    .PARAMETER CheckNetworkConnections
        Check for suspicious network connections
    .PARAMETER CheckRemoteExecution
        Check for remote execution indicators
    .PARAMETER CheckAuthentication
        Check authentication logs for lateral movement
    .PARAMETER CheckSMBShares
        Check SMB share access
    .PARAMETER Days
        Number of days to check in event logs (default: 7)
    .EXAMPLE
        Find-LateralMovement -CheckNetworkConnections -CheckAuthentication
        Find-LateralMovement -CheckNetworkConnections -CheckRemoteExecution -CheckAuthentication -Days 1
    .OUTPUTS
        PSCustomObject with lateral movement indicators
    #>

    
    [CmdletBinding()]
    param(
        [Parameter()]
        [switch]$CheckNetworkConnections,
        
        [Parameter()]
        [switch]$CheckRemoteExecution,
        
        [Parameter()]
        [switch]$CheckAuthentication,
        
        [Parameter()]
        [switch]$CheckSMBShares,
        
        [Parameter()]
        [int]$Days = 7
    )
    
    begin {
        Write-Host "=== LATERAL MOVEMENT DETECTION ===" -ForegroundColor Cyan
        Write-Host "Start Time: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" -ForegroundColor Yellow
        Write-Host "Analysis Period: Last $Days days" -ForegroundColor Yellow
        
        if (-not ($CheckNetworkConnections -or $CheckRemoteExecution -or $CheckAuthentication -or $CheckSMBShares)) {
            $CheckNetworkConnections = $true
            $CheckAuthentication = $true
            Write-Host "No specific check selected - enabling default checks" -ForegroundColor Yellow
        }
        
        $results = [PSCustomObject]@{
            ScanDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
            ComputerName = $env:COMPUTERNAME
            AnalysisPeriod = $Days
            Indicators = @{
                NetworkConnections = @()
                RemoteExecution = @()
                SuspiciousAuthentication = @()
                SMBAccess = @()
            }
            Summary = @{
                TotalIndicators = 0
                CriticalFindings = 0
                HighFindings = 0
                MediumFindings = 0
                LowFindings = 0
            }
            Recommendations = @()
        }
        
        $startDate = (Get-Date).AddDays(-$Days)
    }
    
    process {
        try {
            # 1. Check Network Connections for Lateral Movement
            if ($CheckNetworkConnections) {
                Write-Host "`n[1/4] Checking network connections..." -ForegroundColor Cyan
                
                try {
                    # Check for established connections to internal IPs on suspicious ports
                    $connections = Get-NetTCPConnection -State Established -ErrorAction SilentlyContinue
                    
                    $suspiciousPorts = @{
                        445 = 'SMB'
                        135 = 'RPC'
                        139 = 'NetBIOS'
                        5985 = 'WinRM HTTP'
                        5986 = 'WinRM HTTPS'
                        3389 = 'RDP'
                    }
                    
                    $foundCount = 0
                    
                    foreach ($conn in $connections) {
                        if ($conn.RemotePort -in $suspiciousPorts.Keys) {
                            $process = Get-Process -Id $conn.OwningProcess -ErrorAction SilentlyContinue
                            
                            if (-not $process) {
                                continue
                            }
                            
                            # Skip if it's a standard system process in expected scenarios
                            if ($process.Name -in @('svchost', 'System')) {
                                continue
                            }
                            
                            $indicator = [PSCustomObject]@{
                                Type = 'NetworkConnection'
                                Process = $process.Name
                                ProcessId = $conn.OwningProcess
                                LocalAddress = "$($conn.LocalAddress):$($conn.LocalPort)"
                                RemoteAddress = "$($conn.RemoteAddress):$($conn.RemotePort)"
                                Protocol = $suspiciousPorts[$conn.RemotePort]
                                Severity = 'Medium'
                                Description = "Connection to $($suspiciousPorts[$conn.RemotePort]) port from $($process.Name)"
                                Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
                            }
                            
                            $results.Indicators.NetworkConnections += $indicator
                            $results.Summary.MediumFindings++
                            $foundCount++
                            
                            if ($foundCount -le 5) {
                                Write-Host " [!] $($process.Name) -> $($conn.RemoteAddress):$($conn.RemotePort) [$($suspiciousPorts[$conn.RemotePort])]" -ForegroundColor Yellow
                            }
                        }
                    }
                    
                    if ($foundCount -gt 5) {
                        Write-Host " ... and $($foundCount - 5) more connections" -ForegroundColor Gray
                    }
                    
                    if ($foundCount -eq 0) {
                        Write-Host " No suspicious network connections detected" -ForegroundColor Green
                    }
                }
                catch {
                    Write-Warning "Error checking network connections: $_"
                }
            }
            
            # 2. Check Remote Execution Indicators
            if ($CheckRemoteExecution) {
                Write-Host "`n[2/4] Checking remote execution indicators..." -ForegroundColor Cyan
                
                try {
                    # Check for PSRemoting activity (Event ID 4648 - Explicit credential logon)
                    $remoteEvents = Get-WinEvent -FilterHashtable @{
                        LogName = 'Security'
                        ID = 4648
                        StartTime = $startDate
                    } -MaxEvents 100 -ErrorAction SilentlyContinue
                    
                    if ($remoteEvents) {
                        $groupedEvents = $remoteEvents | Group-Object -Property { 
                            $_.Properties[8].Value 
                        } | Sort-Object Count -Descending
                        
                        foreach ($group in $groupedEvents | Select-Object -First 5) {
                            $event = $group.Group[0]
                            
                            $indicator = [PSCustomObject]@{
                                Type = 'RemoteExecution'
                                Process = 'N/A'
                                ProcessId = 'N/A'
                                TargetServer = $event.Properties[8].Value
                                Account = $event.Properties[1].Value
                                Count = $group.Count
                                Severity = if ($group.Count -gt 10) { 'High' } else { 'Medium' }
                                Description = "Explicit credential logon to $($event.Properties[8].Value) - $($group.Count) times"
                                Timestamp = $event.TimeCreated.ToString("yyyy-MM-dd HH:mm:ss")
                            }
                            
                            $results.Indicators.RemoteExecution += $indicator
                            
                            if ($group.Count -gt 10) {
                                $results.Summary.HighFindings++
                            } else {
                                $results.Summary.MediumFindings++
                            }
                            
                            Write-Host " [!] Explicit credential logon: $($event.Properties[8].Value) ($($group.Count) times)" -ForegroundColor Yellow
                        }
                    }
                    else {
                        Write-Host " No remote execution indicators detected" -ForegroundColor Green
                    }
                }
                catch {
                    Write-Warning "Error checking remote execution: $_"
                }
            }
            
            # 3. Check Authentication Logs for Lateral Movement
            if ($CheckAuthentication) {
                Write-Host "`n[3/4] Checking authentication logs..." -ForegroundColor Cyan
                
                try {
                    # Event ID 4624 - Successful logon (Type 3 = Network, Type 10 = RemoteInteractive/RDP)
                    $logonEvents = Get-WinEvent -FilterHashtable @{
                        LogName = 'Security'
                        ID = 4624
                        StartTime = $startDate
                    } -MaxEvents 500 -ErrorAction SilentlyContinue
                    
                    if ($logonEvents) {
                        # Filter for Type 3 (Network) and Type 10 (RemoteInteractive)
                        $suspiciousLogons = $logonEvents | Where-Object {
                            $logonType = $_.Properties[8].Value
                            $logonType -in @(3, 10)
                        }
                        
                        if ($suspiciousLogons) {
                            # Group by source IP
                            $groupedByIP = $suspiciousLogons | Group-Object -Property { 
                                $_.Properties[18].Value 
                            } | Where-Object { $_.Name -ne '-' -and $_.Name -ne '127.0.0.1' } |
                            Sort-Object Count -Descending
                            
                            foreach ($group in $groupedByIP | Select-Object -First 5) {
                                $event = $group.Group[0]
                                $logonType = $event.Properties[8].Value
                                $logonTypeName = switch ($logonType) {
                                    3 { 'Network' }
                                    10 { 'RemoteInteractive/RDP' }
                                    default { "Type $logonType" }
                                }
                                
                                $indicator = [PSCustomObject]@{
                                    Type = 'SuspiciousAuthentication'
                                    Process = 'N/A'
                                    ProcessId = 'N/A'
                                    SourceIP = $group.Name
                                    Account = $event.Properties[5].Value
                                    LogonType = $logonTypeName
                                    Count = $group.Count
                                    Severity = if ($group.Count -gt 20) { 'High' } else { 'Medium' }
                                    Description = "$logonTypeName logon from $($group.Name) - $($group.Count) times"
                                    Timestamp = $event.TimeCreated.ToString("yyyy-MM-dd HH:mm:ss")
                                }
                                
                                $results.Indicators.SuspiciousAuthentication += $indicator
                                
                                if ($group.Count -gt 20) {
                                    $results.Summary.HighFindings++
                                } else {
                                    $results.Summary.MediumFindings++
                                }
                                
                                Write-Host " [!] $logonTypeName from $($group.Name): $($group.Count) times" -ForegroundColor Yellow
                            }
                        }
                        else {
                            Write-Host " No suspicious authentication events detected" -ForegroundColor Green
                        }
                    }
                    else {
                        Write-Host " No authentication events found in specified period" -ForegroundColor Gray
                    }
                }
                catch {
                    Write-Warning "Error checking authentication logs: $_"
                }
            }
            
            # 4. Check SMB Share Access
            if ($CheckSMBShares) {
                Write-Host "`n[4/4] Checking SMB share access..." -ForegroundColor Cyan
                
                try {
                    # Event ID 5140 - Network share object accessed
                    $shareEvents = Get-WinEvent -FilterHashtable @{
                        LogName = 'Security'
                        ID = 5140
                        StartTime = $startDate
                    } -MaxEvents 200 -ErrorAction SilentlyContinue
                    
                    if ($shareEvents) {
                        # Split share access: admin/hidden shares (IPC$, ADMIN$, C$, ...)
                        # are the PRIMARY lateral movement signal; other shares are noise
                        $adminShareEvents = @($shareEvents | Where-Object {
                            $shareName = $_.Properties[7].Value
                            $shareName -match 'IPC\$|ADMIN\$|[A-Za-z]\$'
                        })
                        $otherShareEvents = @($shareEvents | Where-Object {
                            $shareName = $_.Properties[7].Value
                            $shareName -notmatch 'IPC\$|ADMIN\$|[A-Za-z]\$'
                        })

                        $shareSets = @(
                            @{ Events = $adminShareEvents; Severity = 'High'; Label = 'Admin share' }
                            @{ Events = $otherShareEvents; Severity = 'Low';  Label = 'Share' }
                        )

                        $foundShares = $false

                        foreach ($shareSet in $shareSets) {
                            if ($shareSet.Events.Count -eq 0) {
                                continue
                            }

                            $foundShares = $true

                            $groupedShares = $shareSet.Events | Group-Object -Property {
                                "$($_.Properties[1].Value)|$($_.Properties[7].Value)"
                            } | Sort-Object Count -Descending

                            foreach ($group in $groupedShares | Select-Object -First 5) {
                                $parts = $group.Name -split '\|'
                                $event = $group.Group[0]

                                $indicator = [PSCustomObject]@{
                                    Type = 'SMBAccess'
                                    Process = 'N/A'
                                    ProcessId = 'N/A'
                                    Account = $parts[0]
                                    ShareName = $parts[1]
                                    Count = $group.Count
                                    Severity = $shareSet.Severity
                                    Description = "$($shareSet.Label) access: $($parts[1]) by $($parts[0]) - $($group.Count) times"
                                    Timestamp = $event.TimeCreated.ToString("yyyy-MM-dd HH:mm:ss")
                                }

                                $results.Indicators.SMBAccess += $indicator

                                if ($shareSet.Severity -eq 'High') {
                                    $results.Summary.HighFindings++
                                    Write-Host " [!] ADMIN SHARE: $($parts[1]) by $($parts[0]) ($($group.Count) times)" -ForegroundColor Red
                                }
                                else {
                                    $results.Summary.LowFindings++
                                    Write-Host " [!] Share: $($parts[1]) by $($parts[0]) ($($group.Count) times)" -ForegroundColor Yellow
                                }
                            }
                        }

                        if (-not $foundShares) {
                            Write-Host " No suspicious SMB share access detected" -ForegroundColor Green
                        }
                    }
                    else {
                        Write-Host " No SMB share access events found" -ForegroundColor Gray
                    }
                }
                catch {
                    Write-Warning "Error checking SMB share access: $_"
                }
            }
            
            # Calculate summary
            $results.Summary.TotalIndicators = 
                $results.Indicators.NetworkConnections.Count +
                $results.Indicators.RemoteExecution.Count +
                $results.Indicators.SuspiciousAuthentication.Count +
                $results.Indicators.SMBAccess.Count
            
            # Generate recommendations
            if ($results.Summary.TotalIndicators -gt 0) {
                if ($results.Summary.HighFindings -gt 0) {
                    $results.Recommendations += "HIGH: Investigate high-severity lateral movement indicators immediately"
                }
                
                $results.Recommendations += "Review source IPs and accounts for suspicious activity"
                $results.Recommendations += "Check for compromised credentials"
                $results.Recommendations += "Enable enhanced logging (Sysmon, PowerShell logging)"
                $results.Recommendations += "Implement network segmentation"
                $results.Recommendations += "Deploy EDR solution for behavioral detection"
                
                if ($results.Indicators.RemoteExecution.Count -gt 0) {
                    $results.Recommendations += "Review PSRemoting and WinRM usage policies"
                }
                
                if ($results.Indicators.SuspiciousAuthentication.Count -gt 0) {
                    $results.Recommendations += "Investigate authentication patterns for pass-the-hash indicators"
                }
            }
            
        }
        catch {
            Write-Error "Error during lateral movement detection: $_"
            throw
        }
    }
    
    end {
        Write-Host "`n=== SCAN COMPLETE ===" -ForegroundColor $(
            if ($results.Summary.HighFindings -gt 0) { 'Red' }
            elseif ($results.Summary.TotalIndicators -gt 0) { 'Yellow' }
            else { 'Green' }
        )
        
        Write-Host "`nDetection Summary:" -ForegroundColor Cyan
        Write-Host " Total Indicators: $($results.Summary.TotalIndicators)" -ForegroundColor White
        Write-Host " Critical: $($results.Summary.CriticalFindings)" -ForegroundColor Red
        Write-Host " High: $($results.Summary.HighFindings)" -ForegroundColor Yellow
        Write-Host " Medium: $($results.Summary.MediumFindings)" -ForegroundColor Yellow
        Write-Host " Low: $($results.Summary.LowFindings)" -ForegroundColor White
        
        if ($results.Summary.TotalIndicators -gt 0) {
            Write-Host "`nIndicator Breakdown:" -ForegroundColor Cyan
            if ($results.Indicators.NetworkConnections.Count -gt 0) {
                Write-Host " Network Connections: $($results.Indicators.NetworkConnections.Count)" -ForegroundColor White
            }
            if ($results.Indicators.RemoteExecution.Count -gt 0) {
                Write-Host " Remote Execution: $($results.Indicators.RemoteExecution.Count)" -ForegroundColor White
            }
            if ($results.Indicators.SuspiciousAuthentication.Count -gt 0) {
                Write-Host " Suspicious Authentication: $($results.Indicators.SuspiciousAuthentication.Count)" -ForegroundColor White
            }
            if ($results.Indicators.SMBAccess.Count -gt 0) {
                Write-Host " SMB Access: $($results.Indicators.SMBAccess.Count)" -ForegroundColor White
            }
        }
        else {
            Write-Host "`nNo lateral movement indicators detected" -ForegroundColor Green
        }
        
        if ($results.Recommendations.Count -gt 0) {
            Write-Host "`nRecommendations:" -ForegroundColor Cyan
            $results.Recommendations | Select-Object -Unique | ForEach-Object {
                Write-Host " ! $_" -ForegroundColor Yellow
            }
        }
        
        return $results
    }
}