Приветствую,
уважаемый читатель!
Хотел бы
сегодня поделится опытом решения следующей задачи: запрет соединения с
ресурсами сети Интернет, при установлении соединения с конкретным IP-адресом.
Не скажу, что данная задача является типовой и очень востребованной, но процесс
её решения заставил придумывать нестандартные ходы. Будут освещены вопросы
архитектуры скрипта, который сможет выполнять данную задачу, и показаны примеры
работы с брандмауэром Windows из powershell. Кто заинтересовался, прошу ниже.
Начнем с
постановки задачи и описания последовательности действий. Предположим, что
соединение с указанным IP-адресом
происходит с использованием специального программного обеспечения.
Задача: Контроль
сетевой активности Windows-машины,
с целью выявления конкретного TCP-соединения с указанным IP-адресом и
блокирование всех остальных соединений. Как только соединение с конкретным
адресом прекратилось, блокировка остальных соединений должна сниматься.
Средство реализации задачи: Учитывая,
что в последнее время мне крайне полюбился powershell, программное обеспечение
решающее данную задачу будет реализовано на нём. Исходя из постановки задачи
понятно, что скрипт на powershell должен выполняться в операционной системе с
определённой периодичностью.
Обобщённый алгоритм работы: Скрипт
просматривает список всех установленных соединений в системе, с целью поиска
интересующего. После нахождения такого соединения, скрипт разрывает все
остальные соединения, кроме интересующего. После окончания работы с указанным
IP-адресом обычная работа пользователя с ресурсами сети Интернет восстанавливается.
Теперь,
последовательность действий.
- Получение
списка всех текущих TCP-соединений, с указанием приложения их создавшего,
и поиск нужного соединения.
- Определение
списка приложений, которым мы запрещаем сетевую активность и добавляем
запрещающие правила брандмауера.
- Отмена
запрещающих правил после окончания конкретного соединения.
- Настройка
скрипта на постоянное выполнение.
Приступим к
первому пункту. Получение списка всех текущих TCP-соединений, с указанием
приложения их создавшего, и поиск нужного соединения.
Частичное решение
этой задачи я описывал в этом посте - http://unitybas.blogspot.ru/2015/11/powershell-netstat.html
Всё просто,
получаем список установленных TCP-соединений с помощью Netstat, приводим его в удобный вид и связываем по полю ProcessID со списком запущенных в операционной системе
процессов.
Хотел бы сделать небольшое
отступление. Изначально я не планировал использовать брандмауер Windows для блокирования соединений, и собирался просто
разрывать установленные соединения (ESTABLISHED) с
удаленными IP-адресами.
Но как оказалось, в Powershell нет
командлетов или функций, которым можно сказать: Разорви конкретное соединение с
этим IP-адресом.
Немного
погуглив, я нашел несколько решений данной проблем, но оба они использовали
отдельное консольное приложение. Вот список приложений:
- Wkillcx (http://wkillcx.sourceforge.net/)
- Cports (http://www.nirsoft.net/utils/cports.html#DownloadLinks)
Использование приложений:
Разорвать
соединение по 22 порту с IP-адресом 100.120.200.18
при помощи wkillcx
C:\scripts\wkillcx-1.0.2\wkillcx.exe 100.120.200.18:22
Разорвать
соединение по 22 порту с IP-адресом 100.120.200.18
при помощи cports
C:\scripts\cports\cports.exe /close 192.168.1.30 47000 100.120.200.18
22
Варианты
использования сторонних приложений мне изначально были не интересны, и я решил ограничиться
только средствами powershell.
Но хватит с лирикой. Код, реализующий первый пункт
приведен ниже:
#Получаем список текущих TCP/UDP-соединений и список
запущенных в системе процессов
$sockets_all
= netstat -ano | Select-String -Pattern
'\s+(TCP|UDP)' |
Select-String -Pattern
'\:\:' -notmatch
$process_list =
Get-WmiObject -Class
Win32_Process
#Создаём пустую
коллекцию для списка соединений
$ProcessCollection=@()
foreach($socket
in $sockets_all)
{
$parse = $socket.line.split(' ',[System.StringSplitOptions]::RemoveEmptyEntries)
$ID_tcp = $parse[4]
$ID_udp = $parse[3]
$local = $parse[1] -split ":"
$remote = $parse[2] -split ":"
$local_ip = $local[0]
$local_port =
$local[1]
$remote_ip =
$remote[0]
$remote_port
= $remote[1]
#Соединяем
по ProcessID между собой список TCP-сессий и список процессов в системе
foreach($process in $process_list)
{
if (($process.ProcessID -eq
$ID_tcp) -and
($parse[0] -eq "TCP"))
{
#Данные
о сессии будем хранить в PS объектах и добавлять их в коллекцию
$obj_proc = New-Object psobject
$obj_proc |
Add-Member -type
noteproperty -name
Protocol -Value
$parse[0]
$obj_proc |
Add-Member -type
noteproperty -name
LocalIP -Value
$local_ip
$obj_proc |
Add-Member -type
noteproperty -name
LocalPort -Value
$local_port
$obj_proc |
Add-Member -type
noteproperty -name
RemoteIP -Value
$remote_ip
$obj_proc |
Add-Member -type
noteproperty -name
RemotePort -Value
$remote_port
$obj_proc |
Add-Member -type
noteproperty -name
PID -Value
$ID_tcp
$obj_proc
| Add-Member
-type noteproperty
-name ProcName
-Value $process.ProcessName
$obj_proc |
Add-Member -type
noteproperty -name
procPath -Value
$process.Path
$obj_proc
| Add-Member
-type noteproperty
-name CompName
-Value $process.CSName
$ProcessCollection +=
$obj_proc
}
}
}
#IP-адрес,
соединение с которым будет контролироваться
$ControlIP
= '100.120.220.18'
#Ищем, есть
ли соединение с данным адресом в текущий момент
$ResMonitor
= Get-NetTcpConnection
| where {$_.RemoteAddress
-eq $ControlIP}
Приступаем
ко второму пункту. Определение списка приложений, которым мы запрещаем сетевую
активность и добавляем запрещающие правила брандмауера.
Вообще,
управление правилами брандмауера из powershell оказалось очень удобным. Есть
целая куча командлетов для управления брандмауером, приведу пару наиболее
полезных:
Get-NetFirewallRule (получить
список правил МЭ)
New-NetFirewallRule (создать новое правило МЭ)
Set-NetFirewallRule (изменить
правило МЭ)
Remove-NetFirewallRule (удалить правило
МЭ)
Примеры:
Получить
список активных исходящих правил МЭ.
Get—NetFirewallRule -Direction Outbound -Enabled True
Добавить новое
запрещающее исходящее правило для исполняемого файла.
New-NetFirewallRule -Program “C:\Temp\Test.exe” -Action Block -DisplayName “Test”
-Description “Block”
-Direction Outbound
Отключить правило.
Set-NetFirewallRule –DisplayName "Test" -Enabled False
Удалить правило.
Remove-NetFirewallRule -DisplayName "Test"
На основе
этих базовых правил для любого МЭ, мы и реализуем блокировку. Общий алгоритм,
как был описан выше: блокируем активность для всех приложений, за исключением породившего
сессию с контролируемым IP-адресом.
Код реализующий данную задачу приведен ниже:
#IP-адрес, соединение с
которым будет котролироваться
$ControlIP
= '100.120.220.18'
#Ищем, есть
ли соединение с данным адресом в текущий момент
$ResMonitor =
Get-NetTcpConnection |
where {$_.RemoteAddress -eq
$ControlIP}
#Если
соединение с контролируемым IP-адресом установлено
if ($ResMonitor.count
-ne 0) {
#Просматриваем
все установленные соединения с IP-адресами в сети Интернет, и получаем для
каждого соединения имя породившего его процесса, и путь с исполняемому файлу
foreach($proc in ($ProcessCollection |
where {($_.RemoteIP -ne
"0.0.0.0") -and ($_.RemoteIP -ne
"127.0.0.1") -and ($_.RemoteIP -ne
$ControlIP)} |
select PID,ProcName,procPath -Unique)) {
$RuleCaption = "Block App -
" + $proc.ProcName
#Если
процесс породивший соединение уже заблокирован брандмауером Windows, то выводим
сообщение
if (Get-NetFirewallRule |
Where {$_.DisplayName -eq
$RuleCaption}) {Write-Host
"Already ban"}
else {
#Если процесс породивший соединение не заблокирован брандмауером
Windows, то добавляем новое правило
New-NetFirewallRule -Program $proc.procPath -Action
Block -DisplayName
$RuleCaption -Description
“Automatical Block” -Direction
Outbound
}
}
}
Приступаем к
третьему пункту. Отмена запрещающих правил после окончания конкретного
соединения.
Тут всё
просто, как только соединение с контролируемым IP-адресом разорвано,
то мы удаляем блокирующие правила из МЭ. Код представлен ниже:
#Если соединение с контролируемым IP-адресом не установлено
elseif
($ResMonitor.count
-eq 0) {
#Получаем
список текущий блокирующих правил
$blockRule = Get-NetFirewallRule
| Where {$_.DisplayName
-like "Block
App - *"}
foreach ($br in $blockRule)
{
#Удаляем блокирующее правило
Remove-NetFirewallRule -DisplayName $br.DisplayName
}
}
}
Приступаем к
четвертому пункту. Настройка скрипта на постоянное выполнение.
Есть несколько вариантов решения данной задачи, а
именно:
- Добавить запуск скрипта в планировщик заданий и настройка периодичности запуска. Прочитать поподробнее про это можно тут - http://windowsnotes.ru/powershell-2/zapusk-powershell-skripta-po-raspisaniyu/
- Добавить в скрипте бесконечный цикл и постоянно выполнять код с использованием командлета Start-Sleep. Учитывая, что нам нужно запускать скрипт каждые 5-10 секунд, этот вариант вполне подходит
#Запускаем
скрипт в бесконечном цикле
$count
= 0
while ($count -le 1) {
Block
Start-Sleep -Seconds 5
}
Таким
образом, общая финальная версия скрипта приведена ниже:
function
Block {
#Получаем
список текущих TCP/UDP-соединений и список запущенных в системе процессов
$sockets_all =
netstat -ano | Select-String -Pattern '\s+(TCP|UDP)'
| Select-String
-Pattern '\:\:'
-notmatch
$process_list =
Get-WmiObject -Class
Win32_Process
#Создаём
пустую коллекцию для списка соединений
$ProcessCollection=@()
foreach($socket
in $sockets_all)
{
$parse = $socket.line.split(' ',[System.StringSplitOptions]::RemoveEmptyEntries)
$ID_tcp = $parse[4]
$ID_udp = $parse[3]
$local = $parse[1] -split ":"
$remote = $parse[2] -split ":"
$local_ip = $local[0]
$local_port =
$local[1]
$remote_ip =
$remote[0]
$remote_port =
$remote[1]
#Соединяем
по ProcessID между собой список TCP-сессий и список процессов в системе
foreach($process in $process_list) {
if (($process.ProcessID -eq
$ID_tcp) -and
($parse[0] -eq "TCP"))
{
#Данные
о сессии будем хранить в PS объектах и добавлять их в коллекцию
$obj_proc = New-Object psobject
$obj_proc |
Add-Member -type
noteproperty -name
Protocol -Value
$parse[0 ]
$obj_proc |
Add-Member -type
noteproperty -name
LocalIP -Value
$local_ip
$obj_proc |
Add-Member -type
noteproperty -name
LocalPort -Value
$local_port
$obj_proc |
Add-Member -type
noteproperty -name
RemoteIP -Value
$remote_ip
$obj_proc |
Add-Member -type
noteproperty -name
RemotePort -Value
$remote_port
$obj_proc |
Add-Member -type
noteproperty -name
PID -Value
$ID_tcp
$obj_proc |
Add-Member -type
noteproperty -name
ProcName -Value
$process.ProcessName
$obj_proc |
Add-Member -type
noteproperty -name
procPath -Value
$process.Path
$obj_proc |
Add-Member -type
noteproperty -name
CompName -Value
$process.CSName
$ProcessCollection += $obj_proc
}
}
}
#IP-адрес,
соединение с которым будет котролироваться
$ControlIP
= '100.120.220.18'
#Ищем, есть
ли соединение с данным адресом в текущий момент
$ResMonitor =
Get-NetTcpConnection |
where {$_.RemoteAddress -eq
$ControlIP}
#Если
соединение с контролируемым IP-адресом установлено
if ($ResMonitor.count
-ne 0) {
#Просматриваем
все установленные соединения с IP-адресами в сети Интернет, и получаем для
каждого соединения имя породившего его процееса, и путь с исполняемому файлу
foreach($proc in ($ProcessCollection |
where {($_.RemoteIP -ne
"0.0.0.0") -and ($_.RemoteIP -ne
"127.0.0.1") -and ($_.RemoteIP -ne
$ControlIP)} |
select PID,ProcName,procPath -Unique)) {
$RuleCaption = "Block App -
" + $proc.ProcName
#Если
процесс породивший соединение уже заблокирован брандмауером Windows, то выводим
сообщение
if (Get-NetFirewallRule |
Where {$_.DisplayName -eq
$RuleCaption}) {Write-Host
"Already ban"}
else {
#Если процесс породивший соединение не заблокирован брандмауером
Windows, то добавляем новое правило
New-NetFirewallRule -Program $proc.procPath -Action
Block -DisplayName
$RuleCaption -Description
“Automatical Block” -Direction
Outbound
}
}
}
#Если
соединение с контролируемым IP-адресом не установлено
elseif
($ResMonitor.count
-eq 0) {
#Получаем
список текущий блокирующих правил
$blockRule = Get-NetFirewallRule
| Where {$_.DisplayName
-like "Block
App - *"}
foreach ($br
in $blockRule)
{
#Удаляем блокирующее правило
Remove-NetFirewallRule -DisplayName
$br.DisplayName
}
}
}
#Запускаем
скрипт в бесконечном цикле
$count
= 0
while ($count -le 1) {
Block
Start-Sleep -Seconds 5
}
Предвижу,
что может появиться закономерный вопрос: Что делать если контролируемое
соединение породило не отдельное приложение, а, например, браузер Chrome? Для данной ситуации код скрипта необходимо доработать, и блокирующие
правила в МЭ должны добавляться на основании внешних IP-адресов в
соединении, а не по приложениям. Делается это не трудно, позднее напишу.
На этом пока всё, может кому пригодится.
Интересно.
ОтветитьУдалитьЯ на той неделе тоже парсил netstat
https://www.powershellgallery.com/packages/Get-NetStat/1.0.0/DisplayScript
В последнее время использую Add-Member только если нужно добавить метод или скрытое свойство, присмотритесь к формированию объектов через Select-String.
И еще про запутку while:
Вы создаете переменную со значением 0
Потом вы говорите выполнять цикл, пока эта переменная меньше 1
Вы усложняете, поэтому перед началом такого цикла обязательно нужно писать коментарий "#Запускаем скрипт в бесконечном цикле"
Но, как известно, хороший код сам по себе является коментарием))
Так вот можно было бы обойтись и без коментария, если бы вы написали
while ($true) {
Block
Start-Sleep -Seconds 5
}
Благодарю за комментарий) по предложению с while согласен, так по понятнее.
Удалить