Сканер локальной сети для получения списка IP и MAC адресов на Python
На данный момент в различных операционных системах существует довольно много самых разнообразных сканеров сети, которые не только составят вам ее карту, но, также покажут, какие сетевые ресурсы доступны на каждой из найденных машин, их сетевые имена. Но, почему бы не сделать свой сканер на Python, который будет составлять карту сети, хоть и без сетевых ресурсов на данном этапе, но, тем не менее, работать он будет довольно быстро и в качестве практики очень даже полезно.
Для того, чтобы создать быстрый сканер сети, с возможностью его дальнейшего расширения, нам понадобиться установить scapy. Это многофункциональная библиотека, которая позволяет отправлять, анализировать и подделывать сетевые пакеты, а также сканировать или атаковать сети. Установка ее достаточно простая. Нужно написать в терминале команду:
pip install --pre scapy[basic]
Однако, это не единственный, но рекомендуемый, вариант установки данной библиотеки. Вот небольшая табличка с командами по установке разных по компоновке пакетов. Для наших целей будет вполне достаточно рекомендуемой установки.
Если ваша операционная система отличается от Linux, а в данном случае имеется в виду Windows, то потребуется установить библиотеку захвата пакетов Npcap. Если же вы используете Windows XP, хотя на сегодняшнее время это вряд ли, то нужно использовать библиотеку WinPcap. Установить Npcap можно скачав его с официального сайта.
На этом подготовительный этап можно завершить. Давайте теперь импортируем scapy в наш скрипт.
from platform import system from socket import socket, AF_INET, SOCK_DGRAM from subprocess import check_output import os import scapy.all as sc
Как видите, помимо библиотеки scapy для скрипта нужно будет импортировать еще несколько модулей входящих в состав стандартных библиотек python. В данном случае библиотека platform будет нужна для определения операционной системы. Так как данный скрипт будет работать как в Windows, так и в Linux. Библиотека socket используется для получения локального IP-адреса компьютера, а subprocess для выполнения команды и тем самым получения IP-адреса роутера, используемого в системе по умолчанию.
Давайте приступим к созданию сканера сети. Для начала нам будет нужна функция, которая получает локальный IP-адрес машины. Создадим ее и назовем, к примеру: local_ipv4(). Данная функция устанавливает соединение с адресом 10.255.255.255, а затем с помощью getsockname возвращает адрес и порт сокета. В данном случае порт нам не нужен, а вот адрес вполне пригодится. Поэтому забираем его из кортежа и возвращаем из функции. Если же произошла ошибка и соединение не удалось, возвращается адрес 127.0.0.1. Что, в принципе, будет соответствовать действительности, так как в данном случае сетевого соединения не будет, а будет использоваться только localhost.
Код функции получения локального IP-адреса:
def local_ipv4(): st = socket(AF_INET, SOCK_DGRAM) try: st.connect(('10.255.255.255', 1)) ip_l = st.getsockname()[0] except Exception: ip_l = '127.0.0.1' finally: st.close() return ip_l
Дополнение: функцию получения локального адреса можно сделать немного по другому, но только в том случае, если у вас ОС Linux. Нужно дополнительно установить библиотеки getmac и netifaces.
from getmac.getmac import _get_default_iface_linux from netifaces import ifaddresses, AF_INET
Тогда функция будет выглядеть вот так:
def local_ipv4(): try: return ifaddresses(_get_default_iface_linux()).setdefault(AF_INET)[0]['addr'] except TypeError: return '127.0.0.1'
Получение IP-адреса шлюза используемого в системе по умолчанию
На следующем этапе нужно получить IP-адрес шлюза, который используется в системе по умолчанию. Для этого используем subprocess.check_output, которая запускает выполнение определенной команды в терминале и возвращает ее вывод. И для Linux, и для Windows будем использовать похожие команды. Для Linux это «route -n», а вот с Windows все чуточку посложнее, так как там при выполнении команды возвращается куча всяких не особо то нужных в данном случае параметров. Поэтому нужно будет выполнить среди всей этой информации поиск и вывести уже отфильтрованный результат.
Давайте сделаем первую функцию для Windows. Создадим get_gateway_win(). На вход она не получает никаких данных, а на выходе возвращает адрес шлюза. С помощью subprocess.check_output выполняется команда, после чего результат приводится к нужному виду и возвращается из функции.
def get_gateway_win(): # получаем адрес шлюза по умолчанию для текущего сетевого интерфейса com = f'route PRINT 0* | findstr {local_ipv4()}'.split() return check_output(com, shell=True).decode('cp866').split()[2]
И для Linux, создадим функцию get_gateway_linx(). Принцип получения адреса шлюза здесь тот же самый и на выходе получается нужный адрес шлюза.
def get_gateway_linx(): com = 'route -n'.split() ip_route = str(subprocess.check_output(com, shell=True)).split("\\n")[2].split()[1].strip() if ip_route.isdigit(): return ip_route else: sock = socket.gethostbyname(ip_route) return sock
Теперь самое время приступить к сканированию сети. Создадим функцию get_ip_mac_nework(ip), которая на входе будет получать начальный адрес для сканирования с маской подсети, а возвращать список из словарей с полученными значениями ip и mac адресов. Сделаем с помощью функции scapy.srp широковещательный ARP запрос, чтобы получить ответы от всех компьютеров, которые находятся с нами в одной сети. Для отправки запроса нужно сформировать пакет, который будет состоять из широковещательного адреса и ARP-пакета, в котором указывается адрес и маска подсети. Параметр timeout указывает на то, что между запросами нужно делать паузу в одну секунду, а параметр verbose позволяет не выводить результаты выполнения запроса в терминал.
answered_list = sc.srp(sc.Ether(dst='ff:ff:ff:ff:ff:ff') / sc.ARP(pdst=ip), timeout=1, verbose=False)[0]
С помощью данного запроса мы можем получить два списка. С запросами, на которые был получен ответ и с запросами, на которые ответ не был получен. Так как нам не отвеченные запросы вроде бы без надобности, забираем только те, ответ на которые был получен. Потому, указываем явно, что забираем 0 элемент.
Далее в цикле пробежимся по содержимому списка с запросами и получим из него нужные значения, которые добавим в список, возвращаемый из функции в виде словаря.
Код функции получения ip и mac адресов:
def get_ip_mac_nework(ip): answered_list = sc.srp(sc.Ether(dst='ff:ff:ff:ff:ff:ff') / sc.ARP(pdst=ip), timeout=1, verbose=False)[0] clients_list = [] for element in answered_list: clients_list.append({'ip': element[1].psrc, 'mac': element[1].hwsrc}) return clients_list
Печать результатов сканирования
Ну и осталось вывести полученные результаты на экран. В данном случае за это будет отвечать функция print_ip_mac(mac_ip_list), которая на входе получает сформированный ранее список со словарями. Тут все просто. Пробегаемся в цикле по списку и получаем определенные значений из словарей под нужным индексом. После чего выводим на экран.
def print_ip_mac(mac_ip_list): print(f"\nMachine in Network:\n\nIP\t\t\t\t\tMAC-address\n{'-' * 41}") for client in mac_ip_list: print(f'{client["ip"]}\t\t{client["mac"]}')
Для запуска сканирования сети, а также получения ip адресов будем использовать функцию main(). Для начала получим локальный ip, затем, в зависимости от системы ip шлюза. Сформируем из полученного локального IP-адреса адрес сети с маской и запустим функцию сканирования сети. После чего выведем на экран IP-адреса и результаты сканирования.
def main(): local_ip = local_ipv4() if system() == "Windows": gateway = get_gateway_win() elif system() == 'Linux': if not os.getuid() == 0: print('\n[+] Запустите скрипт с правами суперпользователя!\n') return gateway = get_gateway_linx() ip_mac_network = get_ip_mac_nework(f'{local_ip.split(".")[0]}.{local_ip.split(".")[1]}.{local_ip.split(".")[2]}.1/24') print(f'\n[+] Local IP: {local_ip}\n[+] Local Gateway: {gateway}') print_ip_mac(ip_mac_network)
А на этом, в данном случае все. Сканер сети готов, осталось только посмотреть на результаты его работы. Запускаем и видим, что в нашей локальной сети есть межсетевой экран. А те, кто понимает, даже узнали, что это pfSense, а также три машины ip и mac адреса которых были благополучно получены.
Полный код скрипта сканера сети:
from platform import system from socket import socket, AF_INET, SOCK_DGRAM from subprocess import check_output import os import scapy.all as sc # получаем локальный IP-адрес def local_ipv4(): st = socket(AF_INET, SOCK_DGRAM) try: st.connect(('10.255.255.255', 1)) ip_l = st.getsockname()[0] except Exception: ip_l = '127.0.0.1' finally: st.close() return ip_l def get_gateway_win(): # получаем адрес шлюза по умолчанию для текущего сетевого интерфейса в Windows com = f'route PRINT 0* | findstr {local_ipv4()}'.split() return check_output(com, shell=True).decode('cp866').split()[2] # получение ip адреса шлюза используемого по умолчанию в Linux def get_gateway_linx(): com = 'route -n'.split() ip_route = str(subprocess.check_output(com, shell=True)).split("\\n")[2].split()[1].strip() if ip_route.isdigit(): return ip_route else: sock = socket.gethostbyname(ip_route) return sock # сканируем сеть, получаем ip и mac сетевых машин def get_ip_mac_nework(ip): answered_list = sc.srp(sc.Ether(dst='ff:ff:ff:ff:ff:ff') / sc.ARP(pdst=ip), timeout=1, verbose=False)[0] clients_list = [] for element in answered_list: clients_list.append({'ip': element[1].psrc, 'mac': element[1].hwsrc}) return clients_list # функция печати сканированных ip и mac def print_ip_mac(mac_ip_list): print(f"\nMachine in Network:\n\nIP\t\t\t\t\tMAC-address\n{'-' * 41}") for client in mac_ip_list: print(f'{client["ip"]}\t\t{client["mac"]}') def is_root(): return os.getuid() == 0 def main(): local_ip = local_ipv4() if system() == "Windows": gateway = get_gateway_win() elif system() == 'Linux': if not os.getuid() == 0: print('\n[+] Запустите скрипт с правами суперпользователя!\n') return gateway = get_gateway_linx() ip_mac_network = get_ip_mac_nework(f'{local_ip.split(".")[0]}.{local_ip.split(".")[1]}.{local_ip.split(".")[2]}.1/24') print(f'\n[+] Local IP: {local_ip}\n[+] Local Gateway: {gateway}') print_ip_mac(ip_mac_network) if __name__ == "__main__": main()
Ну и да, не забывайте о том, что именно таким образом вам удастся просканировать только локальную сеть.
Спасибо за внимание. Надеюсь, что данная информация была кому-нибудь полезной
Один хакер может причинить столько же вреда, сколько 10 000 солдат! Подпишись на наш Телеграм канал, чтобы узнать первым, как выжить в цифровом кошмаре!