#24 Gray Hat C#. Руководство для хакера по созданию и автоматизации инструментов безопасности. Написание обратного подключения (connect-back binding) и полезных нагрузок Metasploit.
Здравствуйте, дорогие друзья.
Для тестера на проникновение или инженера по безопасности, очень полезно иметь возможность писать и настраивать полезные нагрузки на лету. Часто корпоративные среды сильно отличаются друг от друга, а «готовые» полезные нагрузки таких фреймворков, как Metasploit, просто блокируются системами обнаружения/предотвращения вторжений, сетевым контролем доступа или другими переменными сети. Однако на машинах Windows в корпоративных сетях почти всегда установлена платформа .NET, что делает C# отличным языком для написания полезных нагрузок. Базовые библиотеки, доступные для C#, также содержат отличные сетевые классы, которые позволяют Вам приступить к делу в любой среде.
Лучшие тестеры на проникновение знают, как создавать специальные полезные нагрузки, адаптированные для конкретных сред, чтобы дольше оставаться вне поля зрения, сохранять устойчивость или обходить систему обнаружения вторжений или брандмауэр. В этом разделе показано, как написать набор полезных нагрузок для использования через TCP (протокол управления передачей) и UDP (протокол пользовательских дейтаграмм). Мы создадим кроссплатформенную полезную нагрузку обратного соединения UDP для обхода слабых правил брандмауэра и обсудим, как запускать произвольные полезные нагрузки сборки Metasploit, чтобы помочь ей в обходе антивируса.
Создание полезной нагрузки обратного подключения
Первый тип полезной нагрузки, который мы напишем, — это обратное соединение, которое позволяет злоумышленнику прослушивать обратное соединение от цели. Этот тип полезных данных полезен, если у Вас нет прямого доступа к машине, на которой выполняется полезная нагрузка. Например, если Вы находитесь за пределами сети и проводите фишинговую кампанию с помощью Metasploit Pro, этот тип полезной нагрузки позволяет целям выйти за пределы сети и связаться с Вами. Альтернатива, которую мы вскоре обсудим, заключается в том, что полезная нагрузка прослушивает соединение злоумышленника на целевой машине. Связывание полезных данных, подобных этим, наиболее полезно для поддержания устойчивости, когда Вы можете получить доступ к сети.
Сетевой поток
Мы будем использовать утилиту netcat, доступную в большинстве Unix-подобных операционных систем, для проверки полезных данных привязки и обратного подключения. Большинство операционных систем Unix поставляются с предустановленным netcat, но если Вы хотите использовать его в Windows, Вам необходимо загрузить утилиту вместе с Cygwin или в виде независимого двоичного файла (или собрать из исходного кода!). Сначала настройте netcat на прослушивание обратного соединения от нашей цели, как показано в листинге ниже.
$ nc -l 4444
Наша полезная нагрузка обратного подключения должна создать сетевой поток для чтения и записи. Как Вы можете видеть в листинге ниже, первые строки метода Main() полезной нагрузки создают этот поток для последующего использования на основе аргументов, переданных полезной нагрузке.
Конструктор класса TcpClient принимает два аргумента: хост для подключения в виде строки и порт для подключения на хосте в виде целого числа. Используя аргументы, переданные в полезные данные, и предполагая, что первым аргументом является хост, к которому нужно подключиться, мы передаем аргументы конструктору TcpClient [1]. Поскольку по умолчанию аргументами являются строки, нам не нужно приводить хост к какому-либо специальному типу, а только к порту.
Второй аргумент, указывающий порт для подключения, должен быть задан как int. Для этого мы используем статический метод int.Parse() [2], чтобы преобразовать второй аргумент из строки в целое число. (Многие типы в C# имеют статический метод Parse(), который преобразует один тип в другой.) После создания экземпляра TcpClient, мы вызываем клиентский метод GetStream() [3] и присваиваем его потоку переменных, из которого мы будем читать и записывать в него. Наконец, мы передаем поток конструктору класса StreamReader [4], чтобы мы могли легко прочитать команды, поступающие от злоумышленника.
Далее нам нужно, чтобы полезная нагрузка считывалась из потока, пока мы отправляем команды от нашего прослушивателя netcat. Для этого мы будем использовать поток, созданный в листинге выше, как показано в листинге ниже.
В бесконечном цикле while метод StreamReader ReadLine() [1] считывает строку данных из потока, которая затем присваивается переменной cmd. Мы определяем, что представляет собой строка данных, на основе того, где в потоке данных появляется символ новой строки (\n или 0x0a в шестнадцатеричном формате). Если строка, возвращаемая ReadLine(), пуста или равна нулю, мы закрываем [2] средство чтения потока, поток и клиент, а затем возвращаемся из программы. Если строка содержит только пробелы [3], мы начинаем цикл заново, используя continue, что возвращает нас к методу ReadLine(), чтобы начать заново.
После чтения команды, которую нужно выполнить, из сетевого потока, мы отделяем аргументы команды от самой команды. Например, если злоумышленник отправляет команду ls -a, это будет команда ls, а аргументом команды будет -a.
Чтобы разделить аргументы, мы используем метод Split() [4], чтобы разделить всю команду на каждое пространство в строке и затем вернуть массив строк. Массив строк является результатом разделения всей командной строки по разделителю, переданному в качестве аргумента метода Split(), который в нашем случае представляет собой пробел. Далее мы используем метод First() [5], который доступен в пространстве имен System.Linq для перечисляемых типов, таких как массивы, чтобы выбрать первый элемент в массиве строк, возвращаемом разделением, и назначаем его имени строкового файла для хранения нашей базовой команды. Это должно быть фактическое имя команды. Затем метод Join() [6] объединяет все строки в разделенном массиве, кроме первой, с пробелом в качестве объединяющего символа. Мы также используем метод LINQ Skip() [7], чтобы пропустить первый элемент массива, который был сохранен в переменной имени файла. Результирующая строка должна содержать все аргументы, переданные команде. Эта новая строка присваивается строке arg.
Запуск команды
Теперь нам нужно запустить команду и вернуть вывод злоумышленнику. Как показано в листинге ниже, мы используем классы Process и ProcessStartInfo для настройки и запуска команды, а затем записываем выходные данные обратно злоумышленнику.
После создания экземпляра нового класса Process [1] мы присваиваем новый класс ProcessStartInfo свойству StartInfo [2] класса Process, что позволяет нам определить определенные параметры для команды, чтобы мы могли получить выходные данные. Присвоив свойству StartInfo новому классу ProcessStartInfo, мы затем присваиваем значения свойствам StartInfo: свойству FileName [3], которое представляет собой команду, которую мы хотим запустить, и свойству Arguments [4], которое содержит любые аргументы для команды.
Мы также присваиваем свойству UseShellExecute [5] значение false и свойству RedirectStandardOutput [6] значение true. Если для UseShellExecute установлено значение true, команда будет запускаться в контексте другой системной оболочки, а не непосредственно текущим исполняемым файлом. Если для RedirectStandardOutput установлено значение true, мы можем использовать свойство StandardOutput класса Process для чтения выходных данных команды.
После установки свойства StartInfo мы вызываем Start() [7] в процессе, чтобы начать выполнение команды. Пока процесс выполняется, мы копируем его стандартный вывод непосредственно в сетевой поток для отправки злоумышленнику с помощью CopyTo() [8] в свойстве BaseStream потока StandardOutput. Если во время выполнения возникает ошибка, Encoding.ASCII.GetBytes() [9] преобразует строку «Ошибка выполнения команды» в массив байтов, который затем записывается в сетевой поток для злоумышленника с использованием метода Write() потока [10].
Запуск полезной нагрузки
Запуск полезной нагрузки с 127.0.0.1 и 4444 в качестве аргументов должен подключиться обратно к нашему прослушивателю netcat, чтобы мы могли запускать команды на локальном компьютере и отображать их в терминале, как показано в листинге ниже.
1 2 3 4 5 |
$ nc -l 4444 whoami bperry uname Linux |
На этом все. Всем хорошего дня!