В локальной сети, обычно, достаточно много машин с операционной системой Windows. И почти у каждой из них включено сетевое обнаружение, что делает ее ресурсы выставленными напоказ всей сети. Есть небольшой трюк, когда вы не хотите, чтобы ваш сетевой ресурс был виден в проводнике, в конце, после его имени нужно поставить знак $. Именно так светит диски в сеть ОС Windows. То есть, к примеру, есть общий ресурс ADMIN$. Если вы зайдете с помощью проводника, то вы ничего не увидите. Но, стоит попробовать обратиться к ресурсу напрямую и у вас попросят пароль. Давайте напишем небольшой скрипт, который использует в своей работе smbclient для сканирования компьютеров в сети на наличие общедоступных ресурсов.
Что потребуется?
Так как мы будем использовать smbclient, по умолчанию он установлен далеко не во всех ОС Linux, а потому, нам либо нужно установить его вручную выполнив команду:
либо не устанавливать изначально, так как мы напишем небольшую функцию, которая будет проверять его наличие и устанавливать в систему для работы.
Понадобиться библиотека requests. С ее помощью мы будем выполнять запросы к сайтам для получения внешнего ip-адреса, а также библиотека ping3, для того, чтобы мы могли пинговать компьютеры. Конечно, в процессе работы данного скрипта установка библиотеки requests не обязательна, так как напрямую не взаимодействует в скрипте ни с одной функцией. Но, так как я написал небольшой модуль в который собрал те функции, которые чаще всего требуются при работе с сетью, такие как получение локального ip, внешнего ip или получение координат по внешнему ip-адресу, установить ее нужно. Пишем в терминале:
Импортировать на данном этапе мы ничего не будем, так как у нас будет три модуля, импорт в них будет немного различаться. Почему три? Это вы поймете в процессе создания скрипта.
Модуль для работы с IP
Создадим модуль для работы с часто требующимися функциями по работе с IP-адресом. Я назвал модуль definition.py.
Выполним в него импорт необходимых для работы библиотек.
Теперь создадим класс IPDefinition и инициализируем необходимые параметры. В самом классе, как таковых функций нет. Он просто служит для удобства вызова функций. Все функции, к которым обращаются переменные при инициализации, находятся вне класса. Поэтому, он будет совсем небольшой. Вот полный его код:
Теперь напишем функции, к которым обращаются переменные при инициализации класса.
Первой функцией будет получение локального IP-адреса. На самом деле, эту функцию я нашел на Stack Overflow. Точного объяснения, к какому именно адресу коннектится сокет я не нашел, но при коннекте, функция получает имя сокета. И именно оно является локальным адресом. Эта функция кросплатформенна, а потому я решил использовать ее, вместо использования ip или ipconfig. Так как, желательно, чтобы модуль работал в любой операционной системе.
Создадим функцию local(). Объявим переменную st и установим протокол, по которому будет коннектится сокет и тип соединения — дейтаграм. Устанавливаем соединение, после чего, получаем имя сокета и возвращаем его из функции. В случае возникновения исключения значению ip присваиваем локальный IP, закрываем сокет и возвращаем адрес.
Следующая функция, которую нужно создать, это функция для получения внешнего, публичного IP-адреса. Создадим функцию public(). С помощью функции get библиотеки requests отправим запрос на адрес api.ipify.org. API этого сайта возвращает в ответ внешний IP, его мы и вернем из функции. В случае ошибки соединения вернем локальный IP в виде 127.0.0.1.
Еще нам понадобиться функция, которая получает IP-адрес роутера или маршрутизатора. Так как она понадобиться в процессе получения общедоступных ресурсов. Создадим функцию router(). Здесь, с помощью функции check_output библиотеки subprocess выполняем команду и получаем ответ, который парсим для получения результата. Для того, чтобы функция была кросплатформенной, для начала определяется операционная система, а затем, в зависимости от этого выполняется команда. Выполняется проверка являются ли полученные данные цифровыми. Если нет, делаем попытку получить IP-адрес с помощью функции сокет, gethostbyname.
И еще одна функция, которая в данном контексте не обязательна, но присутствует в модуле, хотя, ее нужно импортировать напрямую, помимо класса. Так происходит потому, что класс не принимает никаких значений. А данная функция требует, чтобы в нее передали IP-адрес. Служит она для получения координат (широты и долготы) по IP-адресу.
Создадим функцию geo_ip(ip). На вход она получает IP-адрес, для которого нужно получить координаты. Конечно, адрес должен быть внешним, так как по локальному адресу определить координаты вряд ли получиться.
Формируем ссылку на сервис, который возвращает данные по IP в виде JSON.
Выполняем запрос и возвращаем полученные значения.
На самом деле, в JSON прилетают не только координаты, но также и дополнительная информация, вроде адреса. Но в данном контексте она не особо нужна, так как, определение координат по IP в принципе не особо точное занятие.
Вот, для примера, как можно получить координаты в контексте данного модуля для своего IP:
На этом, пока все. Функции в модуле закончились. А значит, нужно приступать к написанию модуля, с помощью которого будут сканироваться общедоступные ресурсы локальной сети.
Модуль для сканирования сети на наличие общедоступных ресурсов
Создадим модуль smb_scan.py. Импортируем в него необходимые библиотеки для работы:
Создадим класс SMBscan и функцию инициализации переменных класса. Класс требует передачи в него IP-адреса сканируемой машины, а также логина и пароля, если таковые известны для доступа к сетевым ресурсам. Если логин и пароль не известны, присваиваем переменной значение «-N». Это опция указывающая на то, что будет попытка осуществления доступа к ресурсам без логина и пароля.
Получение данных об общедоступных дисках компьютера в сети
Создадим функцию класса smb_get(self). С помощью функции check_output, которая позволяет получить данные из выполняемой команды, выполним сканирование ресурсов компьютера. Данная команда состоит из пути к smbclient, опции L(list), которая позволяет просмотреть общедоступные сервисы, ip-адреса компьютера, связки логин-пароль. Логин и пароль передаются в виде опции -U login%pass, так как, если передать только логин, пароль будет запрошен явно в процессе выполнения. Если пароль и логин не переданы, сюда подставляется опция -N, которая указывает, что данные о пароле и логине отсутствуют. И выводим сообщения не относящиеся к делу в null.
После чего разбиваем ответ на строки и парсим полученные значения. Убираем пустые символы, а также строку, которая указывает, какая версия протокола выполняется. Затем снова собираем строки в кучку с помощью join и возвращаем собранные данные из функции.
Сканирование сетевых дисков
Создадим функцию класса smb_access(self). С ее помощью мы будем пробовать сканировать найденные диски на наличие общедоступных ресурсов и возможность к ним доступа без логина и пароля. Выполним для начала ту же команду, что и в предыдущей функции, но с помощью grep отфильтруем только те записи, в которых содержится значение Disk, а также с помощью sed -e удалим все символы, которые идут после его названия. Таким образом, на выходе данной команды мы получим необработанную строку, содержащую имена сетевых дисков, для примера: C$.
Теперь разобьем эту полученные значения построчно, и сформируем список с очищенными от пробелов и символов именами.
Создадим словарь, в который будем добавлять полученные значения и ошибки в результате выполнения команды с помощью os.popen. В данной команде мы передаем путь к smbclient, путь к диску, логин-пароль, если таковые имеются, если нет, значение будет «-N» укажем опцию -c, которая позволяет указать через ; несколько команд: получение списка директории и выход.
Полученные значения разобьем на строки, сформируем список, при формировании которого обрежем лишние символы, и добавим полученные значения в словарь, где ключом будет имя диска.
Пробежимся в цикле по словарю. Сформируем строку, которая будет состоять из полученных значений и переводов каретки для корректного отображения в терминале, заменим полученные строки на более понятные значения. После чего вернем эту строку из функции.
На этом код модуля закончен. Можно приступать к написанию модуля, в котором мы будем обращаться к созданным ранее модулям и получать нужную информацию.
Скрипт для получения данных
Создадим модуль scan.py. Импортируем нужные библиотеки и модули, а также объявим глобальный словарь, в который будем складывать полученные при сканировании IP-адреса.
Пинг сети, проверка наличия машин на адресе
Создадим функцию ping_net(ip), которая на входе получает ip-адрес. С помощью данной функции можно выполнить проверку доступности машины в сети. Так как нам, для наших целей больше информации не требуется, то есть, нет необходимости получать MAC-адрес или NETBIOS-имя компьютера. Выполняем пинг, если ответ получен, проверяем, не равен ли адрес локальному IP, так как получать список ресурсов у своей машины нам нет необходимости, также проверяем, не является ли IP адресом роутера или маршрутизатора. Если нет, добавляем адрес в список. Также обработаем исключение, при котором доступ, то есть пинг адреса запрещен. К примеру, если вы захотите сделать пинг 192.168.1.255, получите ошибку доступа, так как вы попытаетесь пинговать адрес для широковещательной рассылки.
Запуск потоков для выполнения пинга
Создадим функцию thread_func(target), которая на вход получает адрес и маску сети, для примера, в виде 192.168.1.1/24. В цикле выполняем итерацию по списку адресов, который получаем с помощью ip_network. Запускаем потоки, в которых целевой функцией является функция пинга ip-адреса, в которую передаем итерированный ip. Ждем завершения потоков. Обрабатываем ошибку, когда неверно указан адрес или маска.
Проверка доступности 139 порта
Создадим функцию check_port(ip, port=139), в которую передается ip-адрес, а также порт для сканирования. По умолчанию порт 139-й. Это порт сеансовой службы NetBIOS, которая активизирует браузер поиска других компьютеров, службы совместного использования файлов, Net Logon и службу сервера.
Выполнять проверку будем с помощью сокетов. Создаем сокет, в котором указываем, что он будет использовать TCP для работы, выполним коннект по переданному в функцию адресу к порту 139. Если есть ответ, вернем True, нет, вернем False.
Установка smbclient при его отсутствии
Создадим функцию smb_install(), в которой проверим, доступен ли пакет по определенному пути. Если нет, запускаем команду установки пакета, запрашиваем пароль у пользователя.
Запуск полного сканирования сети
Создадим функцию network_scan(ip_addr, user_pass), которая на входе получает диапазон для сканирования в виде, для примера, 192.168.1.1/24, а также связку логин-пароль, если таковые были указаны пользователем. Создаем список, в который будем складывать ip-адреса, у которых открыт порт 139, после чего запускаем функцию пинга сети с помощью потоков, куда и передаем диапазон.
Проверяем глобальный список, есть ли в нем IP-адреса. Если есть, запускаем по ним цикл, и проверяем на каждом адресе, открыт ли 139-й порт. Если открыт, добавляем в созданный список. После чего выводим сообщение о том, сколько найдено компьютеров с отрытым 139-портом, а также, сколько их найдено вообще.
Проверяем список после сканирования 139-го порта. Если он не пуст, пробегаемся по нему циклом и запускаем поочередно функции smb_get и smb_access класса SMBscan(cln, user_pass), в который передаем ip-адрес и связку логин-пароль. После чего, выводим в терминал полученные значения.
Функция пользовательского выбора
Создадим функцию main(). В ней мы будем запрашивать у пользователя желаемое действие на выбор. Доступен: пинг сети, сканирование одного ip-адреса, сканирование всей сети и выход из скрипта.
Для начала запустим функцию проверки, установлен ли smbclient, smb_install(). Просим у пользователя выбрать действие. Если пользователь выбирает пинг сети, просим ввести ip-адрес и маску сети, после чего запускаем сканирование сети в потоках thread_func(ip_mask), проверяем, есть ли значения в глобальном списке, если есть, выводим их в терминал.
Если пользователь выбрал сканирование одного компьютера, запрашиваем ip-адрес, затем запрашиваем логин. Если логин введен, запрашиваем пароль, формируем опции для передачи в команду сканирования общедоступных ресурсов. То есть, U логин%пароль. Если пользователь не ввел ничего, просто передаем пустое значение и вызываем поочередно функции сканирования компьютера на наличие дисков, и на наличие ресурсов на этих дисках, куда передаем ip-адрес, и логин-пароль. После выполнения функций выводим полученные значения в терминал.
Если пользователь выбрал сканирование всей сети, просим ввести его адрес и маску, запрашиваем логин-пароль и запускаем функцию network_scan(ip_mask, user_pass), куда передаем полученные значения.
Если пользователь выбрал выход, завершаем работу скрипта.
Небольшое видео демонстрации работы сканера в Kali Linux:
А на этом, пожалуй, всё. Все модули будут прикреплены во вложении к статье.
Спасибо за внимание. Надеюсь, что данная информация будет вам полезна
Дисклеймер: Все данные, предоставленные в данной статье, взяты из открытых источников, не призывают к действию и являются только лишь данными для ознакомления, и изучения механизмов используемых технологий.
Что потребуется?
Так как мы будем использовать smbclient, по умолчанию он установлен далеко не во всех ОС Linux, а потому, нам либо нужно установить его вручную выполнив команду:
sudo apt install smbclient
либо не устанавливать изначально, так как мы напишем небольшую функцию, которая будет проверять его наличие и устанавливать в систему для работы.
Понадобиться библиотека requests. С ее помощью мы будем выполнять запросы к сайтам для получения внешнего ip-адреса, а также библиотека ping3, для того, чтобы мы могли пинговать компьютеры. Конечно, в процессе работы данного скрипта установка библиотеки requests не обязательна, так как напрямую не взаимодействует в скрипте ни с одной функцией. Но, так как я написал небольшой модуль в который собрал те функции, которые чаще всего требуются при работе с сетью, такие как получение локального ip, внешнего ip или получение координат по внешнему ip-адресу, установить ее нужно. Пишем в терминале:
pip install requests ping3
Импортировать на данном этапе мы ничего не будем, так как у нас будет три модуля, импорт в них будет немного различаться. Почему три? Это вы поймете в процессе создания скрипта.
Модуль для работы с IP
Создадим модуль для работы с часто требующимися функциями по работе с IP-адресом. Я назвал модуль definition.py.
Выполним в него импорт необходимых для работы библиотек.
Python:
from platform import system
from subprocess import check_output
from socket import gethostbyname, socket, AF_INET, SOCK_DGRAM
from requests import exceptions, get
Теперь создадим класс IPDefinition и инициализируем необходимые параметры. В самом классе, как таковых функций нет. Он просто служит для удобства вызова функций. Все функции, к которым обращаются переменные при инициализации, находятся вне класса. Поэтому, он будет совсем небольшой. Вот полный его код:
Python:
class IPDefinition:
def __init__(self):
"""
Класс для получения IP-адресов.
self.local_ip: локальный IP-адрес. Получает значение из функции.
self.public_ip: внешний IP-адрес. Получает значение из функции.
self.router_ip: IP-адрес роутера или маршрутизатора. Получает значение из функции.
"""
self.local_ip = local()
self.public_ip = public()
self.router_ip = router()
Теперь напишем функции, к которым обращаются переменные при инициализации класса.
Первой функцией будет получение локального IP-адреса. На самом деле, эту функцию я нашел на Stack Overflow. Точного объяснения, к какому именно адресу коннектится сокет я не нашел, но при коннекте, функция получает имя сокета. И именно оно является локальным адресом. Эта функция кросплатформенна, а потому я решил использовать ее, вместо использования ip или ipconfig. Так как, желательно, чтобы модуль работал в любой операционной системе.
Создадим функцию local(). Объявим переменную st и установим протокол, по которому будет коннектится сокет и тип соединения — дейтаграм. Устанавливаем соединение, после чего, получаем имя сокета и возвращаем его из функции. В случае возникновения исключения значению ip присваиваем локальный IP, закрываем сокет и возвращаем адрес.
Python:
def local():
"""
Получаем локальный IP-адрес с помощью коннекта
на адрес 10.255.255.255. В ответ получаем имя
сокета, которое и является адресом.
:return: возвращает локальный IP-адрес.
"""
st = socket(AF_INET, SOCK_DGRAM)
try:
st.connect(('10.255.255.255', 1))
ip = st.getsockname()[0]
except Exception:
ip = '127.0.0.1'
finally:
st.close()
return ip
Следующая функция, которую нужно создать, это функция для получения внешнего, публичного IP-адреса. Создадим функцию public(). С помощью функции get библиотеки requests отправим запрос на адрес api.ipify.org. API этого сайта возвращает в ответ внешний IP, его мы и вернем из функции. В случае ошибки соединения вернем локальный IP в виде 127.0.0.1.
Python:
def public():
"""
Получение внешнего, публичного IP-адреса.
:return: возвращает значение публичного IP-адреса.
"""
try:
return get('https://api.ipify.org/').text
except exceptions.ConnectionError:
return '127.0.0.1'
Еще нам понадобиться функция, которая получает IP-адрес роутера или маршрутизатора. Так как она понадобиться в процессе получения общедоступных ресурсов. Создадим функцию router(). Здесь, с помощью функции check_output библиотеки subprocess выполняем команду и получаем ответ, который парсим для получения результата. Для того, чтобы функция была кросплатформенной, для начала определяется операционная система, а затем, в зависимости от этого выполняется команда. Выполняется проверка являются ли полученные данные цифровыми. Если нет, делаем попытку получить IP-адрес с помощью функции сокет, gethostbyname.
Python:
def router():
"""
Получение IP-адреса роутера или маршрутизатора.
:return: возвращает IP-адрес роутера или маршрутизатора.
"""
ip_route = None
if system() == "Linux":
ip_route = str(check_output('route -n | grep UG', shell=True).decode()).split()[1]
elif system() == "Windows":
com = 'route PRINT 0* -4 | findstr 0.0.0.0'.split()
interface_temp = check_output(com, shell=True).decode('cp866')
ip_route = interface_temp.split()[-3]
if ip_route.isdigit():
return ip_route
else:
sock = gethostbyname(ip_route)
return sock
И еще одна функция, которая в данном контексте не обязательна, но присутствует в модуле, хотя, ее нужно импортировать напрямую, помимо класса. Так происходит потому, что класс не принимает никаких значений. А данная функция требует, чтобы в нее передали IP-адрес. Служит она для получения координат (широты и долготы) по IP-адресу.
Создадим функцию geo_ip(ip). На вход она получает IP-адрес, для которого нужно получить координаты. Конечно, адрес должен быть внешним, так как по локальному адресу определить координаты вряд ли получиться.
Формируем ссылку на сервис, который возвращает данные по IP в виде JSON.
url = f'[URL]http://ip-api.com/json/[/URL]{ip}'
Выполняем запрос и возвращаем полученные значения.
Python:
req = get(url=url).json()
return [req['lat'], req['lon']]
На самом деле, в JSON прилетают не только координаты, но также и дополнительная информация, вроде адреса. Но в данном контексте она не особо нужна, так как, определение координат по IP в принципе не особо точное занятие.
Python:
def geo_ip(ip):
"""
Функция для получения координат по внешнему IP-адресу.
Выполняет обращение к сервису, в который передает адрес.
В ответ получает JSON с координатами и не только (остальные данные не возвращаются из функции).
:param ip: внешний IP-адрес или любой адрес координаты которого нужно узнать.
:return: возвращает широту и долготу по IP.
"""
try:
url = f'http://ip-api.com/json/{ip}'
req = get(url=url).json()
return [req['lat'], req['lon']]
except Exception:
return 'Не удалось получить координаты'
Вот, для примера, как можно получить координаты в контексте данного модуля для своего IP:
geo_ip(IPDefinition().public_ip)
На этом, пока все. Функции в модуле закончились. А значит, нужно приступать к написанию модуля, с помощью которого будут сканироваться общедоступные ресурсы локальной сети.
Python:
"""
Модуль объединяет в себе несколько функций для получения различного
рода IP-адресов.
В данном случае это:
- локальный IP;
- IP-роутера(маршрутизатора);
- внешний IP-адрес.
Для работы данной функции нужно установить библиотеку requests:
pip install requests
"""
from platform import system
from subprocess import check_output
from socket import gethostbyname, socket, AF_INET, SOCK_DGRAM
from requests import exceptions, get
class IPDefinition:
def __init__(self):
"""
Класс для получения IP-адресов.
self.local_ip: локальный IP-адрес. Получает значение из функции.
self.public_ip: внешний IP-адрес. Получает значение из функции.
self.router_ip: IP-адрес роутера или маршрутизатора. Получает значение из функции.
"""
self.local_ip = local()
self.public_ip = public()
self.router_ip = router()
def local():
"""
Получаем локальный IP-адрес с помощью коннекта
на адрес 10.255.255.255. В ответ получаем имя
сокета, которое и является адресом.
:return: возвращает локальный IP-адрес.
"""
st = socket(AF_INET, SOCK_DGRAM)
try:
st.connect(('10.255.255.255', 1))
ip = st.getsockname()[0]
except Exception:
ip = '127.0.0.1'
finally:
st.close()
return ip
def public():
"""
Получение внешнего, публичного IP-адреса.
:return: возвращает значение публичного IP-адреса.
"""
try:
return get('https://api.ipify.org/').text
except exceptions.ConnectionError:
return '127.0.0.1'
def router():
"""
Получение IP-адреса роутера или маршрутизатора.
:return: возвращает IP-адрес роутера или маршрутизатора.
"""
ip_route = None
if system() == "Linux":
ip_route = str(check_output('route -n | grep UG', shell=True).decode()).split()[1]
elif system() == "Windows":
com = 'route PRINT 0* -4 | findstr 0.0.0.0'.split()
interface_temp = check_output(com, shell=True).decode('cp866')
ip_route = interface_temp.split()[-3]
if ip_route.isdigit():
return ip_route
else:
sock = gethostbyname(ip_route)
return sock
def geo_ip(ip):
"""
Функция для получения координат по внешнему IP-адресу.
Выполняет обращение к сервису, в который передает адрес.
В ответ получает JSON с координатами и не только (остальные данные не возвращаются из функции).
:param ip: внешний IP-адрес или любой адрес координаты которого нужно узнать.
:return: возвращает широту и долготу по IP.
"""
try:
url = f'http://ip-api.com/json/{ip}'
req = get(url=url).json()
return [req['lat'], req['lon']]
except Exception:
return 'Не удалось получить координаты'
geo_ip(IPDefinition().public_ip)
Модуль для сканирования сети на наличие общедоступных ресурсов
Создадим модуль smb_scan.py. Импортируем в него необходимые библиотеки для работы:
Python:
from os import popen
from shutil import which
from subprocess import check_output, CalledProcessError
Создадим класс SMBscan и функцию инициализации переменных класса. Класс требует передачи в него IP-адреса сканируемой машины, а также логина и пароля, если таковые известны для доступа к сетевым ресурсам. Если логин и пароль не известны, присваиваем переменной значение «-N». Это опция указывающая на то, что будет попытка осуществления доступа к ресурсам без логина и пароля.
Python:
class SMBscan:
def __init__(self, ip, user_pass):
"""
Инициализация класса для сканирования компьютера на наличие
общедоступных директорий и дисков.
:param ip: IP-адрес для сканирования.
:param user_pass: связка логин пароль, которая запрашивается у пользователя и
передается в виде login%pass. Если пользователь не вводит логин-пароль, по умолчанию
устанавливается значение -N, что означает отсутствие пароля.
"""
self.ip = ip
if user_pass == "" or user_pass == " ":
self.user_pass = "-N"
else:
self.user_pass = user_pass
Получение данных об общедоступных дисках компьютера в сети
Создадим функцию класса smb_get(self). С помощью функции check_output, которая позволяет получить данные из выполняемой команды, выполним сканирование ресурсов компьютера. Данная команда состоит из пути к smbclient, опции L(list), которая позволяет просмотреть общедоступные сервисы, ip-адреса компьютера, связки логин-пароль. Логин и пароль передаются в виде опции -U login%pass, так как, если передать только логин, пароль будет запрошен явно в процессе выполнения. Если пароль и логин не переданы, сюда подставляется опция -N, которая указывает, что данные о пароле и логине отсутствуют. И выводим сообщения не относящиеся к делу в null.
После чего разбиваем ответ на строки и парсим полученные значения. Убираем пустые символы, а также строку, которая указывает, какая версия протокола выполняется. Затем снова собираем строки в кучку с помощью join и возвращаем собранные данные из функции.
Python:
def smb_get(self):
"""
Сканирование компьютера с использованием smbclient. Получаем вывод команды с параметрами,
где: -L - опция для просмотра доступных сервисов компьютера.
:return: возвращает список общедоступных ресурсов.
"""
try:
view = check_output(f'{which("smbclient")} -L {self.ip} '
f'{self.user_pass} 2>/dev/null', shell=True).decode().splitlines()
view_smb = "\n".join([x.strip() for x in view if not x.startswith("SMB")])
return view_smb
except CalledProcessError:
return 'Ошибка соединения'
Сканирование сетевых дисков
Создадим функцию класса smb_access(self). С ее помощью мы будем пробовать сканировать найденные диски на наличие общедоступных ресурсов и возможность к ним доступа без логина и пароля. Выполним для начала ту же команду, что и в предыдущей функции, но с помощью grep отфильтруем только те записи, в которых содержится значение Disk, а также с помощью sed -e удалим все символы, которые идут после его названия. Таким образом, на выходе данной команды мы получим необработанную строку, содержащую имена сетевых дисков, для примера: C$.
Python:
disks = check_output(f'{which("smbclient")} -L '
f'{self.ip} {self.user_pass} '
f'2>/dev/null| grep " Disk " | sed -e "s/ Disk .*//"', shell=True).decode()
Теперь разобьем эту полученные значения построчно, и сформируем список с очищенными от пробелов и символов именами.
disks = [d.strip() for d in disks.splitlines()]
Создадим словарь, в который будем добавлять полученные значения и ошибки в результате выполнения команды с помощью os.popen. В данной команде мы передаем путь к smbclient, путь к диску, логин-пароль, если таковые имеются, если нет, значение будет «-N» укажем опцию -c, которая позволяет указать через ; несколько команд: получение списка директории и выход.
Python:
disks_list = dict()
for disk in disks:
f = popen(f'{which("smbclient")} //{self.ip}/"{disk}" {self.user_pass} -c "dir;exit" 2>/dev/null').readlines()
d = [x.strip() for x in f]
disks_list.update({disk: d})
Полученные значения разобьем на строки, сформируем список, при формировании которого обрежем лишние символы, и добавим полученные значения в словарь, где ключом будет имя диска.
Пробежимся в цикле по словарю. Сформируем строку, которая будет состоять из полученных значений и переводов каретки для корректного отображения в терминале, заменим полученные строки на более понятные значения. После чего вернем эту строку из функции.
Python:
for key in disks_list:
found = found + "Найден общий ресурс: %s\n%s\n\n" % (key, "\n".join(disks_list[key]))
found = found.replace("NT_STATUS_ACCESS_DENIED", "Общий ресурс виден, но защищен паролем"). \
replace("NT_STATUS_OBJECT_PATH_NOT_FOUND", "Не удается получить доступ к файлу"). \
replace("NT_STATUS_NO_MEDIA_IN_DEVICE", "Диск общий, но нет доступа к носителю"). \
replace("NT_STATUS_DEVICE_DATA_ERROR", "Не удается получить доступ к общему ресурсу")
Python:
def smb_access(self):
"""
Для начала функция получает данные о доступных сервисах, после чего, данные фильтруются
и выбираются только диски, для которых выполняется попытка чтения содержимого.
:return: возвращает список найденных ресурсов.
"""
disks = check_output(f'{which("smbclient")} -L '
f'{self.ip} {self.user_pass} '
f'2>/dev/null| grep " Disk " | sed -e "s/ Disk .*//"', shell=True).decode()
disks = [d.strip() for d in disks.splitlines()]
disks_list = dict()
for disk in disks:
f = popen(f'{which("smbclient")} //{self.ip}/"{disk}" {self.user_pass} -c "dir;exit" 2>/dev/null').readlines()
d = [x.strip() for x in f]
disks_list.update({disk: d})
if disks_list:
found = ''
for key in disks_list:
found = found + "Найден общий ресурс: %s\n%s\n\n" % (key, "\n".join(disks_list[key]))
found = found.replace("NT_STATUS_ACCESS_DENIED", "Общий ресурс виден, но защищен паролем"). \
replace("NT_STATUS_OBJECT_PATH_NOT_FOUND", "Не удается получить доступ к файлу"). \
replace("NT_STATUS_NO_MEDIA_IN_DEVICE", "Диск общий, но нет доступа к носителю"). \
replace("NT_STATUS_DEVICE_DATA_ERROR", "Не удается получить доступ к общему ресурсу")
return "\n" + found
else:
return 'Не могу получить доступ'
На этом код модуля закончен. Можно приступать к написанию модуля, в котором мы будем обращаться к созданным ранее модулям и получать нужную информацию.
Python:
"""
Модуль для сканирования сети
на наличие расшаренных(общедоступных)
директорий и дисков локальной сети.
"""
from os import popen
from shutil import which
from subprocess import check_output, CalledProcessError
class SMBscan:
def __init__(self, ip, user_pass):
"""
Инициализация класса для сканирования компьютера на наличие
общедоступных директорий и дисков.
:param ip: IP-адрес для сканирования.
:param user_pass: связка логин пароль, которая запрашивается у пользователя и
передается в виде login%pass. Если пользователь не вводит логин-пароль, по умолчанию
устанавливается значение -N, что означает отсутствие пароля.
"""
self.ip = ip
if user_pass == "" or user_pass == " ":
self.user_pass = "-N"
else:
self.user_pass = user_pass
def smb_get(self):
"""
Сканирование компьютера с использованием smbclient. Получаем вывод команды с параметрами,
где: -L - опция для просмотра доступных сервисов компьютера.
:return: возвращает список общедоступных ресурсов.
"""
try:
view = check_output(f'{which("smbclient")} -L {self.ip} '
f'{self.user_pass} 2>/dev/null', shell=True).decode().splitlines()
view_smb = "\n".join([x.strip() for x in view if not x.startswith("SMB")])
return view_smb
except CalledProcessError:
return 'Ошибка соединения'
def smb_access(self):
"""
Для начала функция получает данные о доступных сервисах, после чего, данные фильтруются
и выбираются только диски, для которых выполняется попытка чтения содержимого.
:return: возвращает список найденных ресурсов.
"""
disks = check_output(f'{which("smbclient")} -L '
f'{self.ip} {self.user_pass} '
f'2>/dev/null| grep " Disk " | sed -e "s/ Disk .*//"', shell=True).decode()
disks = [d.strip() for d in disks.splitlines()]
disks_list = dict()
for disk in disks:
f = popen(f'{which("smbclient")} //{self.ip}/"{disk}" {self.user_pass} -c "dir;exit" 2>/dev/null').readlines()
d = [x.strip() for x in f]
disks_list.update({disk: d})
if disks_list:
found = ''
for key in disks_list:
found = found + "Найден общий ресурс: %s\n%s\n\n" % (key, "\n".join(disks_list[key]))
found = found.replace("NT_STATUS_ACCESS_DENIED", "Общий ресурс виден, но защищен паролем"). \
replace("NT_STATUS_OBJECT_PATH_NOT_FOUND", "Не удается получить доступ к файлу"). \
replace("NT_STATUS_NO_MEDIA_IN_DEVICE", "Диск общий, но нет доступа к носителю"). \
replace("NT_STATUS_DEVICE_DATA_ERROR", "Не удается получить доступ к общему ресурсу")
return "\n" + found
else:
return 'Не могу получить доступ'
Скрипт для получения данных
Создадим модуль scan.py. Импортируем нужные библиотеки и модули, а также объявим глобальный словарь, в который будем складывать полученные при сканировании IP-адреса.
Python:
from shutil import which
from socket import socket, AF_INET, SOCK_STREAM
from subprocess import Popen, PIPE
from threading import Thread
from ipaddress import ip_network
from ping3 import ping
from definition import IPDefinition
from smb_scan import SMBscan
IP_SET = []
Пинг сети, проверка наличия машин на адресе
Создадим функцию ping_net(ip), которая на входе получает ip-адрес. С помощью данной функции можно выполнить проверку доступности машины в сети. Так как нам, для наших целей больше информации не требуется, то есть, нет необходимости получать MAC-адрес или NETBIOS-имя компьютера. Выполняем пинг, если ответ получен, проверяем, не равен ли адрес локальному IP, так как получать список ресурсов у своей машины нам нет необходимости, также проверяем, не является ли IP адресом роутера или маршрутизатора. Если нет, добавляем адрес в список. Также обработаем исключение, при котором доступ, то есть пинг адреса запрещен. К примеру, если вы захотите сделать пинг 192.168.1.255, получите ошибку доступа, так как вы попытаетесь пинговать адрес для широковещательной рассылки.
Python:
def ping_net(ip):
"""
Проверяет доступность компьютера в сети с помощью
пинга, если доступен, добавляет адрес в глобальный
список, если нет, выход из функции.
:param ip: IP-адрес для пинга компьютеро.
:return: выход из функции.
"""
try:
if ping(ip):
if ip == IPDefinition().local_ip:
return
elif ip == IPDefinition().router_ip:
return
IP_SET.append(ip)
except PermissionError:
return
Запуск потоков для выполнения пинга
Создадим функцию thread_func(target), которая на вход получает адрес и маску сети, для примера, в виде 192.168.1.1/24. В цикле выполняем итерацию по списку адресов, который получаем с помощью ip_network. Запускаем потоки, в которых целевой функцией является функция пинга ip-адреса, в которую передаем итерированный ip. Ждем завершения потоков. Обрабатываем ошибку, когда неверно указан адрес или маска.
Python:
def thread_func(target):
"""
Запуск потоков для пинга адресов в диапазоне, который
итерируется с помощью ip_network. Ожидание завершения потоков.
:param target: IP-адрес с маской сети.
:return: выход из функции.
"""
threads = []
try:
for ip in ip_network(target, False):
t = Thread(target=ping_net, kwargs={'ip': str(ip)})
t.daemon = True
t.start()
threads.append(t)
for num, thread in enumerate(threads):
thread.join()
except ValueError:
print('Неверно указан адрес или маска. Повторите ввод')
return
Проверка доступности 139 порта
Создадим функцию check_port(ip, port=139), в которую передается ip-адрес, а также порт для сканирования. По умолчанию порт 139-й. Это порт сеансовой службы NetBIOS, которая активизирует браузер поиска других компьютеров, службы совместного использования файлов, Net Logon и службу сервера.
Выполнять проверку будем с помощью сокетов. Создаем сокет, в котором указываем, что он будет использовать TCP для работы, выполним коннект по переданному в функцию адресу к порту 139. Если есть ответ, вернем True, нет, вернем False.
Python:
def check_port(ip, port=139):
"""
Проверка порта службы SMB.
:param ip: IP-адрес для проверки порта.
:param port: порт сеансовой службы NetBIOS, которая
активизирует браузер поиска других компьютеров,
службы совместного использования файлов, Net Logon и службу сервера.
По умолчанию 139.
:return: возвращает True, если порт открыт и
False, если порт недоступен.
"""
s = socket(AF_INET, SOCK_STREAM)
s.settimeout(2)
try:
s.connect((ip, int(port)))
s.shutdown(2)
return True
except Exception:
return False
Установка smbclient при его отсутствии
Создадим функцию smb_install(), в которой проверим, доступен ли пакет по определенному пути. Если нет, запускаем команду установки пакета, запрашиваем пароль у пользователя.
Python:
def smb_install():
"""
Проверка наличия smbclient на компьютере.
Если он не установлен в системе, пользователю
будет предложено его установить. Запрашивается пароль
для установки.
"""
if not which('smbclient'):
sudo_password = input("Внимание!\nНе установлен smbclient.\nДля его установки введите пароль sudo: ")
command1 = 'apt install smbclient -y'.split()
p = Popen(['sudo', '-S'] + command1, stdin=PIPE, stderr=PIPE,
universal_newlines=True)
p.communicate(sudo_password + '\n')
Запуск полного сканирования сети
Создадим функцию network_scan(ip_addr, user_pass), которая на входе получает диапазон для сканирования в виде, для примера, 192.168.1.1/24, а также связку логин-пароль, если таковые были указаны пользователем. Создаем список, в который будем складывать ip-адреса, у которых открыт порт 139, после чего запускаем функцию пинга сети с помощью потоков, куда и передаем диапазон.
Python:
set_smb = []
thread_func(ip_addr)
Проверяем глобальный список, есть ли в нем IP-адреса. Если есть, запускаем по ним цикл, и проверяем на каждом адресе, открыт ли 139-й порт. Если открыт, добавляем в созданный список. После чего выводим сообщение о том, сколько найдено компьютеров с отрытым 139-портом, а также, сколько их найдено вообще.
Python:
if len(IP_SET) > 0:
for ip in IP_SET:
if check_port(ip):
set_smb.append(ip)
print(f'\nНайдено компьютеров с SMB: {len(set_smb)}/{len(IP_SET)}')
Проверяем список после сканирования 139-го порта. Если он не пуст, пробегаемся по нему циклом и запускаем поочередно функции smb_get и smb_access класса SMBscan(cln, user_pass), в который передаем ip-адрес и связку логин-пароль. После чего, выводим в терминал полученные значения.
Python:
if len(set_smb) > 0:
for cln in set_smb:
print(f'\n[+] Сканирую: {cln}\n{"-" * 50}')
print(f'{SMBscan(cln, user_pass).smb_get()}')
print(SMBscan(cln, user_pass).smb_access())
else:
print('Сканировать нечего')
Python:
def network_scan(ip_addr, user_pass):
"""
Сканирование сети на наличие в ней компьютеров,
сканирование на каждом из них наличие общедоступных ресурсов.
Вывод полученных данных в терминал.
:param ip_addr: IP-адрес и маска сети.
:param user_pass: логин и пароль пользователя в виде: login%pass
"""
set_smb = []
thread_func(ip_addr)
if len(IP_SET) > 0:
for ip in IP_SET:
if check_port(ip):
set_smb.append(ip)
print(f'\nНайдено компьютеров с SMB: {len(set_smb)}/{len(IP_SET)}')
if len(set_smb) > 0:
for cln in set_smb:
print(f'\n[+] Сканирую: {cln}\n{"-" * 50}')
print(f'{SMBscan(cln, user_pass).smb_get()}')
print(SMBscan(cln, user_pass).smb_access())
else:
print('Сканировать нечего')
else:
print('Компьютеров в сети не обнаружено')
Функция пользовательского выбора
Создадим функцию main(). В ней мы будем запрашивать у пользователя желаемое действие на выбор. Доступен: пинг сети, сканирование одного ip-адреса, сканирование всей сети и выход из скрипта.
Для начала запустим функцию проверки, установлен ли smbclient, smb_install(). Просим у пользователя выбрать действие. Если пользователь выбирает пинг сети, просим ввести ip-адрес и маску сети, после чего запускаем сканирование сети в потоках thread_func(ip_mask), проверяем, есть ли значения в глобальном списке, если есть, выводим их в терминал.
Python:
smb_install()
user_ch = input('\nВыберите действие:\n [1] Пинг сети\n [2] Сканирование одного компьютера\n '
'[3] Сканирование сети\n [4] Выход\n >>> ')
if user_ch == "1":
ip_mask = input("\nВведите IP и маску сети (192.168.1.1/24): ")
thread_func(ip_mask)
if len(IP_SET) > 0:
print('\nНайдены компьютеры:')
for ip in IP_SET:
print(f' {ip}')
else:
print('Компьютеров не найдено')
Если пользователь выбрал сканирование одного компьютера, запрашиваем ip-адрес, затем запрашиваем логин. Если логин введен, запрашиваем пароль, формируем опции для передачи в команду сканирования общедоступных ресурсов. То есть, U логин%пароль. Если пользователь не ввел ничего, просто передаем пустое значение и вызываем поочередно функции сканирования компьютера на наличие дисков, и на наличие ресурсов на этих дисках, куда передаем ip-адрес, и логин-пароль. После выполнения функций выводим полученные значения в терминал.
Python:
elif user_ch == "2":
ip = input('Введите IP адрес: ')
user = input('Введите логин администратора, если он вам известен.\nЕсли нет, нажмите Enter: ')
if user != "":
pwd = input("Введите пароль администратора, если она вам известен.\nЕсли нет, нажмите Enter: ")
user_pass = f"-U {user}%{pwd}"
else:
user_pass = ""
if check_port(ip):
print(SMBscan(ip, user_pass).smb_get())
print(SMBscan(ip, user_pass).smb_access())
Если пользователь выбрал сканирование всей сети, просим ввести его адрес и маску, запрашиваем логин-пароль и запускаем функцию network_scan(ip_mask, user_pass), куда передаем полученные значения.
Python:
elif user_ch == "3":
ip_mask = input("\nВведите IP и маску сети (192.168.1.1/24): ")
user = input('Введите логин администратора, если он вам известен.\nЕсли нет, нажмите Enter: ')
if user != "":
pwd = input("Введите пароль администратора, если она вам известен.\nЕсли нет, нажмите Enter: ")
user_pass = f"-U {user}%{pwd}"
else:
user_pass = ""
network_scan(ip_mask, user_pass)
Если пользователь выбрал выход, завершаем работу скрипта.
Python:
elif user_ch == "4":
print('Good by...')
exit(0)
Python:
def main():
"""
Проверка пользовательского выбора. В зависимости
от выбора выполняется пинг сети, сканирование адреса или
сканирование всей сети.
"""
smb_install()
user_ch = input('\nВыберите действие:\n [1] Пинг сети\n [2] Сканирование одного компьютера\n '
'[3] Сканирование сети\n [4] Выход\n >>> ')
if user_ch == "1":
ip_mask = input("\nВведите IP и маску сети (192.168.1.1/24): ")
thread_func(ip_mask)
if len(IP_SET) > 0:
print('\nНайдены компьютеры:')
for ip in IP_SET:
print(f' {ip}')
else:
print('Компьютеров не найдено')
elif user_ch == "2":
ip = input('Введите IP адрес: ')
user = input('Введите логин администратора, если он вам известен.\nЕсли нет, нажмите Enter: ')
if user != "":
pwd = input("Введите пароль администратора, если она вам известен.\nЕсли нет, нажмите Enter: ")
user_pass = f"-U {user}%{pwd}"
else:
user_pass = ""
if check_port(ip):
print(SMBscan(ip, user_pass).smb_get())
print(SMBscan(ip, user_pass).smb_access())
elif user_ch == "3":
ip_mask = input("\nВведите IP и маску сети (192.168.1.1/24): ")
user = input('Введите логин администратора, если он вам известен.\nЕсли нет, нажмите Enter: ')
if user != "":
pwd = input("Введите пароль администратора, если она вам известен.\nЕсли нет, нажмите Enter: ")
user_pass = f"-U {user}%{pwd}"
else:
user_pass = ""
network_scan(ip_mask, user_pass)
elif user_ch == "4":
print('Good by...')
exit(0)
Python:
"""
Скрипт для пинга сети, проверки наличия в ней компьютеров
и сканирование каждого из них на наличие общедоступных ресурсов.
Для работы требует установки библиотеки ping3: pip install ping3.
Также, для работы скрипта на компьютере необходимо наличие
установленного пакета smbclient. Если данного пакета не установлено,
пользователю предлагается его установить.
Также в скрипт импортируются модули для работы с IP-адерсами, то есть
для определения локального, внешнего адреса, а также адреса роутера-маршрутизатора и
модуль для работы с smbclient и сканирования сети с его помощью.
"""
from shutil import which
from socket import socket, AF_INET, SOCK_STREAM
from subprocess import Popen, PIPE
from threading import Thread
from ipaddress import ip_network
from ping3 import ping
from definition import IPDefinition
from smb_scan import SMBscan
IP_SET = []
def ping_net(ip):
"""
Проверяет доступность компьютера в сети с помощью
пинга, если доступен, добавляет адрес в глобальный
список, если нет, выход из функции.
:param ip: IP-адрес для пинга компьютеро.
:return: выход из функции.
"""
try:
if ping(ip):
if ip == IPDefinition().local_ip:
return
elif ip == IPDefinition().router_ip:
return
IP_SET.append(ip)
except PermissionError:
return
def thread_func(target):
"""
Запуск потоков для пинга адресов в диапазоне, который
итерируется с помощью ip_network. Ожидание завершения потоков.
:param target: IP-адрес с маской сети.
:return: выход из функции.
"""
threads = []
try:
for ip in ip_network(target, False):
t = Thread(target=ping_net, kwargs={'ip': str(ip)})
t.daemon = True
t.start()
threads.append(t)
for num, thread in enumerate(threads):
thread.join()
except ValueError:
print('Неверно указан адрес или маска. Повторите ввод')
return
def check_port(ip, port=139):
"""
Проверка порта службы SMB.
:param ip: IP-адрес для проверки порта.
:param port: порт сеансовой службы NetBIOS, которая
активизирует браузер поиска других компьютеров,
службы совместного использования файлов, Net Logon и службу сервера.
По умолчанию 139.
:return: возвращает True, если порт открыт и
False, если порт недоступен.
"""
s = socket(AF_INET, SOCK_STREAM)
s.settimeout(2)
try:
s.connect((ip, int(port)))
s.shutdown(2)
return True
except Exception:
return False
def smb_install():
"""
Проверка наличия smbclient на компьютере.
Если он не установлен в системе, пользователю
будет предложено его установить. Запрашивается пароль
для установки.
"""
if not which('smbclient'):
sudo_password = input("Внимание!\nНе установлен smbclient.\nДля его установки введите пароль sudo: ")
command1 = 'apt install smbclient -y'.split()
p = Popen(['sudo', '-S'] + command1, stdin=PIPE, stderr=PIPE,
universal_newlines=True)
p.communicate(sudo_password + '\n')
def network_scan(ip_addr, user_pass):
"""
Сканирование сети на наличие в ней компьютеров,
сканирование на каждом из них наличие общедоступных ресурсов.
Вывод полученных данных в терминал.
:param ip_addr: IP-адрес и маска сети.
:param user_pass: логин и пароль пользователя в виде: login%pass
"""
set_smb = []
thread_func(ip_addr)
if len(IP_SET) > 0:
for ip in IP_SET:
if check_port(ip):
set_smb.append(ip)
print(f'\nНайдено компьютеров с SMB: {len(set_smb)}/{len(IP_SET)}')
if len(set_smb) > 0:
for cln in set_smb:
print(f'\n[+] Сканирую: {cln}\n{"-" * 50}')
print(f'{SMBscan(cln, user_pass).smb_get()}')
print(SMBscan(cln, user_pass).smb_access())
else:
print('Сканировать нечего')
else:
print('Компьютеров в сети не обнаружено')
def main():
"""
Проверка пользовательского выбора. В зависимости
от выбора выполняется пинг сети, сканирование адреса или
сканирование всей сети.
"""
smb_install()
user_ch = input('\nВыберите действие:\n [1] Пинг сети\n [2] Сканирование одного компьютера\n '
'[3] Сканирование сети\n [4] Выход\n >>> ')
if user_ch == "1":
ip_mask = input("\nВведите IP и маску сети (192.168.1.1/24): ")
thread_func(ip_mask)
if len(IP_SET) > 0:
print('\nНайдены компьютеры:')
for ip in IP_SET:
print(f' {ip}')
else:
print('Компьютеров не найдено')
elif user_ch == "2":
ip = input('Введите IP адрес: ')
user = input('Введите логин администратора, если он вам известен.\nЕсли нет, нажмите Enter: ')
if user != "":
pwd = input("Введите пароль администратора, если она вам известен.\nЕсли нет, нажмите Enter: ")
user_pass = f"-U {user}%{pwd}"
else:
user_pass = ""
if check_port(ip):
print(SMBscan(ip, user_pass).smb_get())
print(SMBscan(ip, user_pass).smb_access())
elif user_ch == "3":
ip_mask = input("\nВведите IP и маску сети (192.168.1.1/24): ")
user = input('Введите логин администратора, если он вам известен.\nЕсли нет, нажмите Enter: ')
if user != "":
pwd = input("Введите пароль администратора, если она вам известен.\nЕсли нет, нажмите Enter: ")
user_pass = f"-U {user}%{pwd}"
else:
user_pass = ""
network_scan(ip_mask, user_pass)
elif user_ch == "4":
print('Good by...')
exit(0)
if __name__ == "__main__":
main()
Небольшое видео демонстрации работы сканера в Kali Linux:
А на этом, пожалуй, всё. Все модули будут прикреплены во вложении к статье.
Спасибо за внимание. Надеюсь, что данная информация будет вам полезна
Вложения
Последнее редактирование: