Статья Сканирование общедоступных ресурсов в локальной сети с помощью smbclient и Python

В локальной сети, обычно, достаточно много машин с операционной системой Windows. И почти у каждой из них включено сетевое обнаружение, что делает ее ресурсы выставленными напоказ всей сети. Есть небольшой трюк, когда вы не хотите, чтобы ваш сетевой ресурс был виден в проводнике, в конце, после его имени нужно поставить знак $. Именно так светит диски в сеть ОС Windows. То есть, к примеру, есть общий ресурс ADMIN$. Если вы зайдете с помощью проводника, то вы ничего не увидите. Но, стоит попробовать обратиться к ресурсу напрямую и у вас попросят пароль. Давайте напишем небольшой скрипт, который использует в своей работе smbclient для сканирования компьютеров в сети на наличие общедоступных ресурсов.

f6gfowAtovY.jpg

Дисклеймер: Все данные, предоставленные в данной статье, взяты из открытых источников, не призывают к действию и являются только лишь данными для ознакомления, и изучения механизмов используемых технологий.


Что потребуется?

Так как мы будем использовать 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:


А на этом, пожалуй, всё. Все модули будут прикреплены во вложении к статье.

Спасибо за внимание. Надеюсь, что данная информация будет вам полезна
 

Вложения

  • smb_scan.zip
    5,7 КБ · Просмотры: 241
Последнее редактирование:

dotru

Green Team
07.09.2019
36
2
BIT
3
pip3 install smbclient

ERROR: Could not find a version that satisfies the requirement smbclient (from versions: none)
ERROR: No matching distribution found for smbclient
 
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!