Gray Hat C#, Программирование

#26 Gray Hat C#. Руководство для хакера по созданию и автоматизации инструментов безопасности. Использование UDP для атаки на сеть.

Здравствуйте, дорогие друзья.

Обсуждаемые до сих пор пэйлоады использовали TCP для связи; TCP — это протокол с отслеживанием состояния, который позволяет двум компьютерам поддерживать соединение друг с другом с течением времени. Альтернативным протоколом является UDP, который, в отличие от TCP, не сохраняет состояние: при обмене данными между двумя сетевыми машинами не поддерживается соединение. Вместо этого связь осуществляется посредством широковещательной рассылки по сети, при этом каждый компьютер прослушивает широковещательные сообщения на свой IP-адрес.


Еще одно очень важное различие между UDP и TCP заключается в том, что TCP пытается гарантировать, что пакеты, отправленные на машину, достигнут этой машины в том же порядке, в котором они были отправлены. Напротив, пакеты UDP могут быть получены в любом порядке или не получены вообще, что делает UDP менее надежным, чем TCP.


Однако UDP имеет некоторые преимущества. Во-первых, поскольку он не пытается гарантировать, что компьютеры получают отправляемые им пакеты, он работает невероятно быстро. Кроме того, в сетях его не так тщательно исследуют, как TCP, поскольку некоторые брандмауэры настроены только на обработку TCP-трафика. Это делает UDP отличным протоколом для использования при атаке на сеть, поэтому давайте посмотрим, как написать полезную нагрузку UDP для выполнения команды на удаленной машине и возврата результатов.


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


Код для целевой машины

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

First five lines of the Main() method for the target code

Прежде чем отправлять и получать данные, мы настраиваем переменную для порта, который будет прослушиваться. (Для простоты мы заставим и целевую, и атакующую машины прослушивать данные на одном и том же порту, но это предполагает, что мы атакуем отдельную виртуальную машину). Как показано в листинге выше, мы используем Parse() [1] для преобразования строки, переданной в качестве аргумента, в целое число, а затем передаем порт конструктору UdpClient [2], чтобы создать экземпляр нового UdpClient. Мы также настроим класс IPEndPoint [3], который включает в себя сетевой интерфейс и порт, передав IPAddress.Any в качестве первого аргумента и порт для прослушивания в качестве второго аргумента. Мы присваиваем новый объект переменной localEP (локальная конечная точка). Теперь мы можем начать получать данные из сетевых трансляций.


Основной цикл while

Как показано в листинге ниже, мы начинаем с цикла while, который выполняется непрерывно до тех пор, пока от злоумышленника не будет получена пустая строка.

Listening for UDP broadcasts with commands and parsing the command from the arguments
Listening for UDP broadcasts with commands and parsing the command from the arguments

В этом цикле while мы вызываем Listener.Receive(), передавая экземпляр класса IPEndPoint, который мы создали. Получая данные от злоумышленника, метод Receive() [1] заполняет свойство localEP Address IP-адресом атакующего хоста и другой информацией о соединении, чтобы мы могли использовать эти данные позже при ответе. Receive() также блокирует выполнение полезных данных до тех пор, пока не будет получено широковещательное сообщение UDP.
После получения широковещательной рассылки Encoding.ASCII.GetString() [2] преобразует данные в строку ASCII. Если строка равна нулю или пуста, мы выходим из цикла while и позволяем процессу полезной нагрузки завершиться и выйти. Если строка состоит только из пробелов, мы перезапускаем цикл, используя continue, чтобы получить новую команду от злоумышленника. Убедившись, что команда не является пустой строкой или пробелом, мы разделяем ее на любые пробелы [3] (так же, как мы это делали в полезных нагрузках TCP), а затем отделяем команду от массива строк, возвращаемого функцией Split [4]. Затем мы создаем строку аргумента, объединяя все строки в разделенном массиве после первого элемента массива [5].


Выполнение команды и возврат результата отправителю

Теперь мы можем выполнить команду и вернуть результат отправителю через широковещательную рассылку UDP, как показано в листинге ниже.

Executing the command received and broadcasting the output back to the attacker
Executing the command received and broadcasting the output back to the attacker

Как и в случае с предыдущими полезными нагрузками, мы используем классы Process и ProcessStartInfo для выполнения команды и возврата выходных данных. Мы настроили свойство StartInfo с переменными filename и arg, которые мы использовали для хранения команды и аргументов команды соответственно, а также установили свойство UseShellExecute и свойство RedirectStandardOutput. Мы начинаем новый процесс с вызова метода Start(), а затем ждем, пока процесс завершит выполнение, вызывая WaitForExit(). После завершения команды мы вызываем метод ReadToEnd() [1] для свойства потока StandardOutput процесса и сохраняем выходные данные в строке результатов, объявленной ранее. Если во время выполнения процесса произошла ошибка, мы вместо этого присваиваем переменной результатов строку «При выполнении команды произошла ошибка: ».
Теперь нам нужно настроить сокет, который будет использоваться для возврата вывода команды отправителю. Мы будем транслировать данные с помощью сокета UDP. Используя класс Socket, мы создаем экземпляр нового Socket [2] , передавая значения перечисления в качестве аргументов конструктору Socket. Первое значение, AddressFamily.InterNetwork, говорит, что мы будем общаться, используя адреса IPv4. Второе значение, SocketType.Dgram, означает, что мы будем обмениваться данными, используя дейтаграммы UDP (буква D в UDP) вместо пакетов TCP. Третье и последнее значение, ProtocolType.Udp, сообщает сокету, что мы будем использовать UDP для связи с удаленным хостом.
После создания сокета, который будет использоваться для связи, мы присваиваем новой переменной IPAddress значение свойства localEP.Address [3], которое ранее было заполнено IP-адресом злоумышленника при получении данных на нашем прослушивателе UDP. Мы создаем новый IPEndPoint [4] с IP-адресом злоумышленника и портом прослушивания, который был передан в качестве аргумента полезной нагрузке.
Как только мы настроим сокет и узнаем, куда возвращаем выходные данные нашей команды, Encoding.ASCII.GetBytes() преобразует выходные данные в массив байтов. Мы используем SendTo() [5] в сокете для передачи данных обратно злоумышленнику, передавая массив байтов, содержащий выходные данные команды, в качестве первого аргумента и передавая конечную точку отправителя в качестве второго аргумента. Наконец, мы возвращаемся к началу цикла while, чтобы прочитать другую команду.


Код злоумышленника

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

Setting up the UDP listener and other variables for the attacker-side code

Предполагая, что этот код будет принимать в качестве аргументов хост для отправки команд и порт для прослушивания, мы передаем порт для прослушивания функции Parse() [1], чтобы преобразовать строку в целое число, а затем передаем полученное целое число в конструктор UdpClient [2], чтобы создать экземпляр нового класса UdpClient. Затем мы передаем порт прослушивания конструктору класса IPEndPoint вместе со значением IPAddress.Any для создания экземпляра нового класса IPEndPoint [3]. После настройки IPEndPoint мы объявляем выходные данные и байты переменных для дальнейшего использования.


Создание переменных для отправки широковещательных сообщений UDP

В листинге ниже показано, как создать переменные, которые будут использоваться для отправки широковещательных сообщений UDP.

Creating the UDP socket and endpoint to communicate with

Для начала мы создаем экземпляр нового класса Socket [1] в контексте блока using. Значения перечисления, передаваемые в Socket, сообщают сокету, что мы будем использовать адресацию IPv4, дейтаграммы и UDP для связи через широковещательные рассылки. Мы создаем экземпляр нового IPAddress с помощью IPAddress.Parse() [2], чтобы преобразовать первый аргумент, переданный в код, в класс IPAddress. Затем мы передаем объект IPAddress и порт, на котором прослушиватель UDP цели будет прослушивать конструктор IPEndPoint, чтобы создать экземпляр нового класса IPEndPoint. [3]


Общение с целью

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

Main logic to send and receive data to and from the target’s UDP listener

Напечатав понятный текст справки о том, как использовать этот скрипт, мы начинаем отправлять команды цели в цикле while. Сначала Console.ReadLine() [1] считывает строку данных со стандартного ввода, которая станет командой для отправки на целевой компьютер. Затем Encoding.ASCII.GetBytes() преобразует эту строку в массив байтов, чтобы мы могли отправить ее по сети.
Затем в блоке try/catch мы пытаемся отправить массив байтов с помощью SendTo() [2], передавая массив байтов и конечную точку IP для отправки данных. После отправки командной строки мы выходим из цикла while, если строка, прочитанная из стандартного ввода, была пустой, поскольку мы встроили ту же логику в целевой код. Если строка не пуста, а содержит только пробелы, мы возвращаемся к началу цикла while. Затем мы вызываем Receive() [3], чтобы прослушиватель UDP заблокировал выполнение до тех пор, пока выходные данные команды не будут получены от цели, после чего Encoding.ASCII.GetString() преобразует полученные байты в строку, которая затем записывается на консоль злоумышленника. В случае возникновения ошибки мы выводим на экран сообщение об исключении.


Как показано в листинге ниже, после запуска полезных данных на удаленной машине, передачи 4444 в качестве единственного аргумента полезных данных и запуска приемника на машине злоумышленника, мы сможем выполнять команды и получать выходные данные от цели.

Communicating with the target machine over UDP in order to run arbitrary commands

На этом все. Всем хорошего дня!

Цикл статей по Gray Hat C#. Руководство для хакера по созданию и автоматизации инструментов безопасности.