If you have Windows Firewall enabled and your have it logging all the packets that get either dropped or allowed, first you firewall has to be enabled as below:
Then you need to ensure that you have packet logging enabled, I would not recommend you log "Allowed" packets but that is your decision, here you can see I only log dropped packets to the file pfirewall.log in the Windows directory.
2024-12-12 13:32:25 DROP TCP 10.80.44.264 10.80.44.259 10054 80 52 S 2359260937 0 64240 - - - RECEIVE
2024-12-12 13:32:28 DROP TCP 10.80.44.264 10.80.44.259 10054 80 52 S 2359260937 0 64240 - - - RECEIVE
2024-12-12 13:32:34 DROP TCP 10.80.44.264 10.80.44.259 10054 443 52 S 2359260937 0 64240 - - - RECEIVE
Then the script would turn that into a website that is nicely presented in a html document as below:
# Create cache directory if it doesn't exist
$cacheDir = Join-Path $PSScriptRoot "port_cache"
$cachePath = Join-Path $cacheDir "port_descriptions.json"
$ianaPortsPath = Join-Path $cacheDir "iana_ports.json"
if (-not (Test-Path $cacheDir)) {
New-Item -ItemType Directory -Path $cacheDir | Out-Null
}
function Update-IANAPortDatabase {
Write-Host "Downloading IANA port database..."
try {
# Download IANA service names and port numbers
$url = "https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml"
$webRequest = Invoke-WebRequest -Uri $url -UseBasicParsing
if ($webRequest.StatusCode -eq 200) {
[xml]$portData = $webRequest.Content
$ports = @{}
Write-Host "Processing port database..."
# Get the correct namespace
$ns = New-Object Xml.XmlNamespaceManager($portData.NameTable)
$ns.AddNamespace("iana", "http://www.iana.org/assignments")
# Process each record with proper namespace
$records = $portData.SelectNodes("//iana:record", $ns)
$count = 0
foreach ($record in $records) {
$numberNode = $record.SelectSingleNode(".//iana:number", $ns)
$serviceNode = $record.SelectSingleNode(".//iana:name", $ns)
$descriptionNode = $record.SelectSingleNode(".//iana:description", $ns)
if ($numberNode -and $serviceNode) {
# Some port records have multiple ports separated by '-' or ','
$numbers = $numberNode.InnerText -split '[-,]' | ForEach-Object { $_.Trim() }
foreach ($num in $numbers) {
if ($num -match '^\d+$') {
$portNum = [int]$num
$service = $serviceNode.InnerText.Trim()
$description = if ($descriptionNode) { $descriptionNode.InnerText.Trim() } else { ""
}
# Create meaningful description
if ($description) {
$portDescription = "$service - $description"
} else {
$portDescription = $service
}
if (-not $ports.ContainsKey($portNum)) {
$ports[$portNum] = $portDescription
$count++
}
}
}
}
}
Write-Host "Processed $count port entries"
# Save to cache
$ports | ConvertTo-Json -Depth 10 | Set-Content $ianaPortsPath
Write-Host "Port database updated successfully."
return $ports
}
}
catch {
Write-Host "Failed to download IANA port database: $_"
return $null
}
}
# Test function to verify database content
function Test-PortDatabase {
Write-Host "`nTesting port database entries:"
Write-Host "Total ports in database: $($global:portDatabase.Count)"
# Check if port database exists and ask user about updating
if (Test-Path $ianaPortsPath) {
$lastUpdate = (Get-Item $ianaPortsPath).LastWriteTime
Write-Host "`nIANA port database last updated: $lastUpdate"
$updateChoice = Read-Host "Would you like to download a fresh copy of the port database? (y/n)"
if ($updateChoice.ToLower() -eq 'y') {
$global:portDatabase = Update-IANAPortDatabase
} else {
Write-Host "Using existing port database..."
$global:portDatabase = Get-Content $ianaPortsPath | ConvertFrom-Json -AsHashtable
}
} else {
Write-Host "`nNo port database found. Downloading for the first time..."
$global:portDatabase = Update-IANAPortDatabase
}
# Fallback common ports if IANA download fails
$commonPorts = @{
20 = "FTP Data Transfer"
21 = "FTP Control"
22 = "SSH"
23 = "Telnet"
25 = "SMTP"
53 = "DNS"
80 = "HTTP"
110 = "POP3"
123 = "NTP"
137 = "NetBIOS Name Service"
139 = "NetBIOS Session Service"
143 = "IMAP"
443 = "HTTPS"
445 = "Microsoft-DS"
3389 = "RDP"
49665 = "Windows RPC"
59701 = "Windows RPC Dynamic Port"
61948 = "DNS Zone Transfer"
61949 = "DNS Zone Transfer"
}
if (-not $global:portDatabase) {
Write-Host "Using fallback port database due to download failure..."
$global:portDatabase = $commonPorts
}
function Get-PortDescription {
param (
[string]$port
)
if ($port -eq "-") { return "N/A" }
try {
[int]$portNum = $port
if ($global:portDatabase.ContainsKey($portNum)) {
# Return both port number and description
return "$portNum - $($global:portDatabase[$portNum])"
}
return "$portNum - Unregistered Port"
}
catch {
return "Invalid Port"
}
}
function Convert-LogToHTML {
param (
[string]$LogPath
)
Write-Host "Processing log file..."
$progressCount = 0
$batchSize = 1000
$logContent = @(Get-Content $LogPath | Select-Object -Skip 4)
$totalLines = $logContent.Count
$processedData = @()
for ($i = 0; $i -lt $totalLines; $i += $batchSize) {
$batch = $logContent | Select-Object -Skip $i -First $batchSize
$batchData = $batch | ForEach-Object {
$progressCount++
if ($progressCount % 100 -eq 0) {
Write-Progress -Activity "Processing Log Entries" `
-Status "$progressCount of $totalLines entries" `
-PercentComplete (($progressCount / $totalLines) * 100)
}
$fields = $_ -split '\s+'
if ($fields.Count -ge 9) {
$srcPort = $fields[6]
$dstPort = $fields[7]
@{
Timestamp = "$($fields[0]) $($fields[1])"
Protocol = $fields[3]
SourceIP = $fields[4]
SourcePort = $srcPort
SourcePortDesc = (Get-PortDescription $srcPort)
DestinationIP = $fields[5]
DestinationPort = $dstPort
DestinationPortDesc = (Get-PortDescription $dstPort)
Action = $fields[2]
}
}
}
$processedData += $batchData
}
Write-Progress -Activity "Processing Log Entries" -Completed
$htmlHeader = @"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Firewall Log Analysis Report</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css"
rel="stylesheet">
<style>
.table-container {
overflow-x: auto;
max-width: 100%;
}
.custom-table {
min-width: 100%;
border-collapse: collapse;
table-layout: fixed;
}
.custom-table th, .custom-table td {
padding: 8px 16px;
text-align: left;
border-bottom: 1px solid #e2e8f0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.custom-table th {
background-color: #f7fafc;
font-weight: 600;
position: sticky;
top: 0;
z-index: 10;
}
.custom-table tr:hover { background-color: #f8fafc; }
.drop { color: #e53e3e; font-weight: 600; }
.allow { color: #38a169; font-weight: 600; }
@media (max-width: 768px) {
.custom-table {
font-size: 14px;
}
}
</style>
</head>
<body class="bg-gray-100">
<div class="container mx-auto px-4 py-8">
<div class="bg-white rounded-lg shadow-lg p-6">
<div class="flex flex-col md:flex-row justify-between mb-6">
<div>
<h1 class="text-2xl font-bold text-gray-800">Firewall Log Analysis Report</h1>
<p class="text-sm text-gray-600 mt-1">Processed Entries: $($processedData.Count)</p>
</div>
<p class="text-gray-600">Generated: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')</p>
<p class="text-sm text-gray-500">Log File: $([System.IO.Path]::GetFileName($LogPath)
</p>
</div>
</div>
<div class="table-container">
<table class="custom-table">
<thead>
<tr>
<th>Timestamp</th>
<th>Protocol</th>
<th>Source IP</th>
<th>Source Port</th>
<th>Source Service</th>
<th>Destination IP</th>
<th>Destination Port</th>
<th>Destination Service</th>
<th>Action</th>
</tr>
</thead>
<tbody>
"@
$htmlRows = $processedData | ForEach-Object {
$actionClass = if ($_.Action -eq "DROP") { "drop" } else { "allow" }
@"
<tr>
<td>$($_.Timestamp)</td>
<td>$($_.Protocol)</td>
<td>$($_.SourceIP)</td>
<td>$($_.SourcePort)</td>
<td>$($_.SourcePortDesc)</td>
<td>$($_.DestinationIP)</td>
<td>$($_.DestinationPort)</td>
<td>$($_.DestinationPortDesc)</td>
<td class="$actionClass">$($_.Action)</td>
</tr>
"@
}
$htmlFooter = @"
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>
"@
return $htmlHeader + $htmlRows + $htmlFooter
}
# Main script
$logFiles = Get-ChildItem -Filter "*.log"
if ($logFiles.Count -eq 0) {
Write-Host "No log files found in the current directory."
exit
}
Write-Host "`nAvailable log files:"
for ($i = 0; $i -lt $logFiles.Count; $i++) {
Write-Host "$($i + 1). $($logFiles[$i].Name)"
}
do {
$selection = Read-Host "`nEnter the number of the log file to analyze (1-$($logFiles.Count))"
$index = [int]$selection - 1
} while ($index -lt 0 -or $index -ge $logFiles.Count)
$selectedFile = $logFiles[$index].FullName
$outputFile = "firewall_report_$(Get-Date -Format 'yyyyMMdd_HHmmss').html"
# Generate and save the report
Write-Host "`nGenerating report..."
Convert-LogToHTML -LogPath $selectedFile | Out-File -FilePath $outputFile -Encoding UTF8
Write-Host "`nReport generated: $outputFile"