Мы живём в логах, но что если их можно было бы увидеть?

🕵️‍♂️ Сегодня мы превратим скучные журналы Windows Firewall в красивые сетевые графы, используя только PowerShell и щепотку техномагии. Будь вы участником синей команды, исследователем лабораторий или просто любителем превращать текст в визуализацию — это для вас.


Сначала нужны данные. И под “данными” я имею в виду логи, полные шума и тайн. Чтобы получить свои, следуйте этой статье на Microsoft Technet и включите журналирование Windows Firewall. Я обычно складываю логи в отдельную папку, например, C:\Temp, потому что… даже хаосу нужны границы.

Вот как это выглядит:

fwlog

Структурировано? ✅
Читаемо? 🤕
Пора превратить это в то, что сможет переварить PowerShell.


Разбор логов

Мы используем гигантское регулярное выражение (не пугайтесь), чтобы сопоставить каждую строку и создать структурированные объекты. Вот как это делается:

$f = gc "C:\Temp\pfirewall_public.log"
$regex = '^(?\<datetime\>\d{4,4}-\d{2,2}-\d{2,2}\s\d{2}:\d{2}:\d{2})\s(?\<action\>\w+)\s(?\<protocol\>\w+)\s(?\<srcip\>\b(?:\d{1,3}\.){3}\d{1,3}\b)\s(?\<dstip\>\b(?:\d{1,3}\.){3}\d{1,3}\b)\s(?\<srcport\>\d{1,5})\s(?\<dstport\>\d{1,5})\s(?\<size\>\d+|-)\s(?\<tcpflags\>\d+|-)\s(?\<tcpsyn\>\d+|-)\s(?\<tcpack\>\d+|-)\s(?\<tcpwin\>\d+|-)\s(?\<icmptype\>\d+|-)\s(?\<icmpcode\>\d+|-)\s(?\<info\>\d+|-)\s(?\<path\>.+)$'

$log =
$f | % {
    $_ -match $regex | Out-Null
    if ($Matches) {
    [PSCustomObject]@{
        action   = $Matches.action
        srcip    = [ipaddress]$Matches.srcip
        dstport  = $Matches.dstport
        tcpflags = $Matches.tcpflags
        dstip    = [ipaddress]$Matches.dstip
        info     = $Matches.info
        size     = $Matches.size
        protocol = $Matches.protocol
        tcpack   = $Matches.tcpack
        srcport  = $Matches.srcport
        tcpsyn   = $Matches.tcpsyn
        datetime = [datetime]$Matches.datetime
        icmptype = $Matches.icmptype
        tcpwin   = $Matches.tcpwin
        icmpcode = $Matches.icmpcode
        path     = $Matches.path
    }
    }
} 

Да, это монстр-regex. Но, укрощённый, он превращает файл в массив структурированных записей. Обратите внимание, что мы приводим типы datetime и IP, чтобы позже можно было фильтровать и сортировать как взрослые.

Вот как выглядит результат:

fwlog3

Из строк лога — в граф

Каждая строка — это ребро. Но чтобы построить граф, нужны вершины и рёбра. PowerShell делает это удивительно просто, если вы используете модуль графов:

$g = new-graph -Type BidirectionalGraph
$log | ? {$_.srcip -and $_.dstip} | % {
    Add-Edge -From $_.srcip -To $_.dstip -Graph $g | out-null
}

И вуаля — теперь $g это ваш граф.

Хотите увидеть его?

Show-GraphLayout -Graph $g
fwlog4

Это просто мой ноутбук, но суть ясна. Если бы у нас были логи с нескольких машин или сегмента сети — вы бы начали видеть форму и замысел сети.


Временные фильтры для фокусированных просмотров

Допустим, вы хотите видеть только трафик за последний час. Отфильтруем так:

$d = ($log | sort datetime -Descending | select -First 1).datetime.addhours(-1)
$twoHrsLog = $log.Where({$_.datetime -gt $d})
$g1 = new-graph -Type BidirectionalGraph
$twoHrsLog | ? {$_.srcip -and $_.dstip} | % {
    Add-Edge -From $_.srcip -To $_.dstip -Graph $g1 | out-null
}
Show-GraphLayout -Graph $g1

Теперь у вас моментальный снимок по времени. Та же логика работает для диапазонов, окон, всплесков трафика.


Выборочные подграфы на основе степени вершин

Хотите посмотреть только на самых “болтливых”? Вот как:

$g2 = new-graph -Type BidirectionalGraph
$x = $g.Vertices.Where({$g.OutDegree($_) -gt 0})
$x | where {$_ -ne '192.168.0.107'} | % {$e = $g.InEdges($_); if ($e) {$e | % {add-edge -from $_.source -to $_.target -Graph $g2}}}
$x | where {$_ -ne '192.168.0.107'} | % {$e = $g.OutEdges($_); if ($e) {$e | % {add-edge -from $_.source -to $_.target -Graph $g2}}}

Show-GraphLayout -Graph $g2

Теперь вы не просто визуализируете трафик — вы анализируете поведенческие шаблоны. «Кто с кем говорит, и когда» — это уже полезная информация.


Полный скрипт

Если вы ленивы, как я (или просто эффективны), вот полный скрипт:

#file and regular expression
$f = gc "C:\Temp\pfirewall_public.log"
$regex = '^(?<datetime>\d{4,4}-\d{2,2}-\d{2,2}\s\d{2}:\d{2}:\d{2})\s(?<action>\w+)\s(?<protocol>\w+)\s(?<srcip>\b(?:\d{1,3}\.){3}\d{1,3}\b)\s(?<dstip>\b(?:\d{1,3}\.){3}\d{1,3}\b)\s(?<srcport>\d{1,5})\s(?<dstport>\d{1,5})\s(?<size>\d+|-)\s(?<tcpflags>\d+|-)\s(?<tcpsyn>\d+|-)\s(?<tcpack>\d+|-)\s(?<tcpwin>\d+|-)\s(?<icmptype>\d+|-)\s(?<icmpcode>\d+|-)\s(?<info>\d+|-)\s(?<path>.+)$'

#parsing
$log =
$f | % {
    $_ -match $regex | Out-Null
    if ($Matches) {
    [PSCustomObject]@{
        action   = $Matches.action
        srcip    = [ipaddress]$Matches.srcip
        dstport  = $Matches.dstport
        tcpflags = $Matches.tcpflags
        dstip    = [ipaddress]$Matches.dstip
        info     = $Matches.info
        size     = $Matches.size
        protocol = $Matches.protocol
        tcpack   = $Matches.tcpack
        srcport  = $Matches.srcport
        tcpsyn   = $Matches.tcpsyn
        datetime = [datetime]$Matches.datetime
        icmptype = $Matches.icmptype
        tcpwin   = $Matches.tcpwin
        icmpcode = $Matches.icmpcode
        path     = $Matches.path
    }
    }
}
 #whole graph
$g = new-graph -Type BidirectionalGraph
$log | ? {$_.srcip -and $_.dstip} | % {
    Add-Edge -From $_.srcip -To $_.dstip -Graph $g | out-null
}

Show-GraphLayout -Graph $g

#subset of log records filterd by time
$d = ($log | sort datetime -Descending | select -First 1).datetime.addhours(-1)
$twoHrsLog = $log.Where({$_.datetime -gt $d})
$g1 = new-graph -Type BidirectionalGraph
$twoHrsLog | ? {$_.srcip -and $_.dstip} | % {
    Add-Edge -From $_.srcip -To $_.dstip -Graph $g1 | out-null
}

Show-GraphLayout -Graph $g1

#subset of log records filtered by degree of edges
$g2 = new-graph -Type BidirectionalGraph
$x = $g.Vertices.Where({$g.OutDegree($_) -gt 0})
$x | where {$_ -ne '192.168.0.107'} | % {$e = $g.InEdges($_); if ($e) {$e | % {add-edge -from $_.source -to $_.target -Graph $g2}}}
$x | where {$_ -ne '192.168.0.107'} | % {$e = $g.OutEdges($_); if ($e) {$e | % {add-edge -from $_.source -to $_.target -Graph $g2}}}
Show-GraphLayout -Graph $g2

Если логи — ваша тема, то графы — ваша суперсила. 🕸️