Здравствуйте друзья!
Сегодня я попробую написать свою первую программу на замечательном, по моему мнению, языке программирования Ruby.
Эксперимент буду проводить на Debian 8.

Сканируем сеть


Итак, для начала давайте просканируем нужный нам диапазон.
Для своих целей я буду использовать Гем ipscanner.
Установим Гем (или Gem, для тех кому режет слух):
# gem install ipscanner

Ipscanner выдает нам массив с перечисленными доступными IP.
Синтаксис у него следующий:
IPScanner.scan(ip_base = ip_first, range = range1..range2, timeout)

Тут я думаю все понятно без слов, сделаю лишь акцент на поле ip_first — это должен быть строковый параметр, range1, range2 и timeout — это integer.

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

Функция сканирования диапазона IP адресов и создания потоков, будет выглядеть вот так:
def scanner (ip,range1,range2,start_port,end_port)
  scanned_ip=IPScanner.scan(ip_base = ip, range = range1..range2, t = 5)
  allports=*(start_port..end_port)
  pc_thread = []
  scanned_ip.each do |ip|
    pc_thread << Thread.new(ip) do
      scanports ip, allports
      end
    end
  pc_thread.each {|t| t.join }
end

Все доступные IP будут в массиве scanned_ip.
Дополнительно я создам массив allports, в который запишу весь диапазон необходимых для сканирования портов.
Ну и для каждого IP адреса создам поток pc_thread, в который передам функцию сканирования портов и все необходимые порты строкой:
scanports ip, allports


Сканируем порты


Функция сканирования портов будет выглядеть следующим образом:
def scanports(host, ports)
  ports.each do |tryport|
    begin
    sock = Socket.new(:INET, :STREAM)
    puts "#{host}: #{tryport} open." if sock.connect(Socket.sockaddr_in(tryport, host))
    rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ETIMEDOUT
    end
  end
end

Результатом выполнения данной функции будет надпись о том что порт открыт, если он открыт, и отвечает за это:
puts "#{host}: #{tryport} open." if sock.connect(Socket.sockaddr_in(tryport, host))


Код целиком


#Подключаем необходимые компоненты
#В Ruby это не обязательно делать, но все же, для полноты картины
require 'ipscanner'
require 'socket'
require 'thread'

#Функция сканирования портов
def scanports(host, ports)
  #Для каждого порта запускаем процедуру
  ports.each do |tryport|
    begin
    #Создаем сокет
    sock = Socket.new(:INET, :STREAM)
    #Если сокет подключается к порту выводим на экран сообщение
    puts "#{host}: #{tryport} open." if sock.connect(Socket.sockaddr_in(tryport, host))
    #Закрывать сокет нет необходимости, в Ruby это происходит автоматически
    rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ETIMEDOUT
    end
  end
end

#Функция сбора ip
def scanner (ip,range1,range2,start_port,end_port)
  #Запускаем процедуру сканирования, и все ip адреса записываем в массив scanned_ip
  scanned_ip=IPScanner.scan(ip_base = ip, range = range1..range2, t = 5)
  #Создаем массив с необходимыми портами
  allports=*(start_port..end_port)
  pc_thread = []
  #Для каждого элемента массива scanned_ip запускаем процедуру
  scanned_ip.each do |ip|
    #Записываем в массив  pc_thread класс потока, в котором идет ссылка на функцию scanports
    pc_thread << Thread.new(ip) do
      scanports ip, allports
      end
    end
  #Запускаем потоки
  pc_thread.each {|t| t.join }
end

#Запуск программы, путем передачи в функцию scanner необходимых параметров
scanner '10.166.10.',1,254,1,70000

Согласитесь, что это очень краткий и я надеюсь симпатичный код :).

Результатом выполнения данного кода будет:
...
10.166.10.200: 21 open.
10.166.10.110: 22 open.
10.166.10.2: 22 open.
10.166.10.171: 111 open.
10.166.10.1: 22 open.
10.166.10.151: 80 open.
10.166.10.128: 80 open.
10.166.10.137: 80 open.
10.166.10.170: 80 open.
10.166.10.159: 80 open.
...


Ну и видео о том как работает программа:

Комментарии (18)


  1. YourChief
    02.09.2015 14:38
    -3

    Hello, Threads!


  1. artishok
    02.09.2015 14:56

    Хабракат.


    1. Boomburum
      02.09.2015 15:12
      +1

      Он стоял, но не в том месте :) Поправил.


  1. berez
    02.09.2015 15:09
    +2

    Можно просто взять диапазон адресов и сразу его весь по портам просканировать. А вы сначала все машинки из диапазона пытаетесь пинговать (это именно то, чем занимается ipScanner). Так вот, пинги могут быть заблокированы, а коннекты — нет, и тогда ваш чудо-сканер машинку не найдет.

    А еще вы порты перебираете на машинке без паузы и подряд — такое поведение сразу вызовет срабатывание всех возможных IDS в сети. Так что если поиграться со скриптом не в своей домашней сетке, а на работе, то можно и люлей огрести нешутейных.

                   begin
                            sock = Socket.new(:INET, :STREAM)
                            puts "#{host}: #{tryport} open." if sock.connect(Socket.sockaddr_in(tryport, host))
                            rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ETIMEDOUT
                    end
    

    Я не очень знаком с руби, поэтому сразу вопрос: а когда закроется открытый вами сокет? Если не сразу по выходу из области видимости, то ваш сканер при некотором везении сможет выкушать все дескрипторы и позорно умереть (а при удачном стечении обстоятельств еще и систему слегка ушатать).


    1. dimult
      02.09.2015 17:02
      -1

      Можно просто взять диапазон адресов и сразу его весь по портам просканировать. А вы сначала все машинки из диапазона пытаетесь пинговать (это именно то, чем занимается ipScanner). Так вот, пинги могут быть заблокированы, а коннекты — нет, и тогда ваш чудо-сканер машинку не найдет.

      да вы правы, но это самый простой вариант, и он скорее в качестве начального примера

      при некотором везении сможет выкушать все дескрипторы и позорно умереть

      нет, об этом речь не идет, все работает стабильно, единственное, что на Windows это раз в 10 медленнее, опять же из -за простоты и отсутствия оптимизации кода


    1. dimult
      02.09.2015 17:08
      -1

      А еще вы порты перебираете на машинке без паузы и подряд — такое поведение сразу вызовет срабатывание всех возможных IDS в сети. Так что если поиграться со скриптом не в своей домашней сетке, а на работе, то можно и люлей огрести нешутейных.

      пробовал как раз на работе, cisco не среагировала никак, больше там ни каких экранов и не было.

      чудо-сканер

      )))


      1. berez
        02.09.2015 17:59

        больше там ни каких экранов и не было.

        Так я не конкретно про экраны, а вообще про IDS.
        Они весьма во многих конторах стоят, ими рулят сисадмины, и пользователям об их наличии не докладывают. А настроены они могут быть сильно по-разному. Где-то сразу алярмы во все щели начинают рассылать, а где-то админам постфактум отчетик приходит: «в этом месяце такая-то и такая-то машины сканировали сеть коннектами. Столько-то тыщ коннектов в минуту, в течение стольки-то минут».
        В первом случае шершавый фитиль вставят сразу, прямо на месте преступления, в присутствии понятых и начальства. Во втором возможны варианты, вплоть до того, что админы забьют болт на одиночный инцидент.

        Но в любом случае массированное сканирование сети коннектами с одной машины — это то, на что любая IDS (если она есть и запущена) сработает обязательно.


  1. A1MaZ
    02.09.2015 15:13

    А зачем такие огромные отступы?
    И какая-то странная расстановка пробелов. Где-то присвоение отделено пробелами, а где-то нет. В блоке вон с одной стороны пробел есть, а с другой нет.


    1. dimult
      02.09.2015 17:15

      А зачем такие огромные отступы?

      извините, исправил (скопировал изначально из рабочего скрипта)


  1. Obramko
    02.09.2015 16:43
    +2

    Такие вещи нельзя писать с потоками. Нужен какой-нибудь event loop.


    1. dimult
      02.09.2015 17:04
      -1

      Такие вещи нельзя писать с потоками.

      почему?
      работает нормально, конечно контроля никакого нет, но в 20 строках кода этого и не могло быть.
      сканирование сети в маске /24 бита с 1 по 70000 порт происходит на ура.


      1. berez
        02.09.2015 18:13
        +2

        работает нормально,

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

        цитато
        Данный пример также иллюстрирует глюк. Внутри цикла предпочтительней использовать print для вывода числа, чем puts. Почему? Потому что puts тайно разбивает свою работу на две составляющие: выводит свой аргумент, а затем выводит символ новой строки. Между ними двумя может запуститься поток, и вывод будет чередоваться. Вызывая print одной строки, которая уже содержит символ новой строки, мы можем обойти данную проблему.


        1. zombopanda
          02.09.2015 18:43

          Да, вместо puts нужно писать print "#{string}\n", тогда будет нормально.

          UPD: написал не дочитав.


  1. ayurtaykin
    02.09.2015 18:19

    А вы можете сделать сравнение по скорости сканирования между «простым и быстрым сканером IP адресов» и nmap?
    Просто интереса ради :)


    1. openkazan
      02.09.2015 20:58

      Лучше уж тогда сразу с zmap :)


    1. dimult
      04.09.2015 07:36

      добавил видео


  1. dimult
    02.09.2015 18:36
    -1

    Ок, сделаю видео по его работе, чтобы прекратить эту охоту на ведьм


  1. Naftic
    06.09.2015 23:23

    Нормальная ведь статья. За что парня заминосавали?