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

#27 Gray Hat C#. Руководство для хакера по созданию и автоматизации инструментов безопасности. Запуск полезных нагрузок Metasploit x86 и x86-64 из C#.

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

Набор инструментов для эксплуатации Metasploit Framework, начатый HD Moore и теперь разработанный Rapid7, стал де-факто средой тестирования на проникновение и разработки эксплойтов для специалистов по безопасности. Поскольку Metasploit написан на Ruby, он является кроссплатформенным и будет работать в Linux, Windows, OS X и множестве других операционных систем. На момент написания этой статьи существует более 1300 бесплатных эксплойтов Metasploit, написанных на языке программирования Ruby.
В дополнение к коллекции эксплойтов Metasploit содержит множество библиотек, предназначенных для того, чтобы сделать разработку эксплойтов быстрой и в целом безболезненной. Например, как Вы вскоре увидите, Вы можете использовать Metasploit для создания кросс-платформенной сборки .NET для определения типа и архитектуры вашей операционной системы и запуска на ней шелл-кода.


Настройка Metasploit

На момент написания этой статьи Rapid7 разрабатывает Metasploit на GitHub (https://github.com/rapid7/metasploit-framework/). В Ubuntu используйте git для клонирования главного репозитория Metasploit в Вашу систему, как показано в листинге ниже.

Примечание
Я рекомендую использовать Ubuntu при разработке следующей полезной нагрузки в этом разделе. Конечно, тестирование также необходимо будет провести на Windows, чтобы убедиться, что обнаружение вашей ОС и полезные нагрузки работают на обеих платформах.

Установка Руби

Metasploit Framework требует Ruby. Если после прочтения онлайн-инструкций по установке Metasploit Вы обнаружите, что Вам нужна другая версия Ruby, которая установлена в Вашей системе Linux, используйте RVM, менеджер версий Ruby (http://rvm.io/), чтобы установить его вместе с любой существующей версией Ruby. Установите ключ GNU Privacy Guard (GPG) сопровождающего RVM, а затем установите RVM в Ubuntu, как показано в листинге ниже.

После установки RVM определите, какая версия Ruby требуется для Metasploit Framework, просмотрев файл .ruby-version в корне Metasploit Framework, как показано в листинге ниже.

Теперь запустите команду rvm, чтобы скомпилировать и установить правильную версию Ruby, как показано в листинге ниже. Это может занять несколько минут, в зависимости от скорости Вашего Интернета и процессора.

После завершения установки Ruby настройте среду bash для его просмотра, как показано в листинге ниже.

Установка зависимостей Metasploit


Metasploit использует gem-пакет (пакет Ruby) для управления зависимостями. Перейдите в текущий каталог git checkout Metasploit Framework на своем компьютере и запустите команды, показанные в листинге ниже, чтобы установить библиотеки разработки, необходимые для сборки некоторых гемов, необходимых Metasploit Framework.

После установки всех зависимостей, Вы сможете запустить Metasploit Framework, как показано в листинге ниже.

Генерация пейлоадов


Мы будем использовать инструмент Metasploit msfvenom для создания необработанных пейлоадов сборки, для открытия программ в Windows или запуска команд в Linux. Например, в листинге ниже показано, как команды, отправленные в msfvenom, генерируют полезную нагрузку x86-64 (64-разрядную версию) для Windows, которая вызывает калькулятор Windows Calc.exe на отображаемом в данный момент рабочем столе. (Чтобы просмотреть полный список параметров инструмента msfvenom, запустите msfvenom —help из командной строки.)

Здесь мы передаем windows/x64/exec в качестве полезной нагрузки, csharp в качестве формата полезной нагрузки и опцию полезной нагрузки CMD=calc.exe. Вы также можете передать что-то вроде linux/x86/exec с CMD=whoami, чтобы сгенерировать полезную нагрузку, которая при запуске в 32-битной системе Linux запускает команду whoami.


Выполнение собственных полезных данных Windows как неуправляемого кода

Полезные данные Metasploit генерируются в 32- или 64-битном ассемблерном коде, который в мире .NET называется неуправляемым кодом. Когда Вы компилируете код C# в DLL или исполняемую сборку, этот код называется управляемым кодом. Разница между ними заключается в том, что для запуска управляемого кода требуется виртуальная машина .NET или Mono, тогда как неуправляемый код может запускаться непосредственно операционной системой.
Чтобы выполнить неуправляемый ассемблерный код в управляемой среде, мы будем использовать P/Invoke .NET для импорта и запуска функции VirtualAlloc() из ядра Microsoft Windows kernel32.dll. Это позволяет нам выделить необходимую память, доступную для чтения, записи и выполнения, как показано в листинге ниже.

Importing the VirtualAlloc() kernel32.dll function and defining a Windows-specific delegate

Importing the VirtualAlloc() kernel32.dll function and defining a Windows-specific delegate

В [2], мы импортируем VirtualAlloc() из kernel32.dll. Функция VirtualAlloc() принимает четыре аргумента типа IntPtr, который представляет собой класс C#, значительно упрощающий передачу данных между управляемым и неуправляемым кодом. Кстати, мы используем атрибут C# DllImport [1] (атрибут похож на аннотацию в Java или декоратор в Python), чтобы указать виртуальной машине искать эту функцию в библиотеке kernel32.dll во время выполнения. (Мы будем использовать атрибут DllImport для импорта функций из libc при выполнении полезных данных Linux.) В [4] мы объявляем делегат WindowsRun(), который имеет атрибут UnmanagedFunctionPointer [3], который сообщает виртуальной машине Mono/.NET запускать этот делегат как неуправляемую функцию. Передавая CallingConvention.StdCall в атрибут UnmanagedFunctionPointer, мы указываем виртуальной машине Mono/.NET вызывать VirtualAlloc(), используя соглашение о вызовах Windows StdCall.
Сначала нам нужно написать метод Main() для выполнения полезных данных в соответствии с архитектурой целевой системы, как показано в листинге ниже.

Small C# class wrapping two Metasploit payloads

Чтобы определить целевую операционную систему, мы фиксируем переменную Environment.OSVersion [1], которая имеет свойство Platform, идентифицирующее текущую систему (как используется в операторе if). Чтобы определить целевую архитектуру, мы сравниваем размер IntPtr с числом 4 [2], поскольку в 32-битной системе указатель имеет длину 4 байта, а в 64-битной системе — 8 байт. Мы знаем, что если размер IntPtr равен 4, мы находимся в 32-битной системе; в противном случае мы предполагаем, что система 64-битная. Мы также объявляем массив байтов, называемый полезной нагрузкой, для хранения сгенерированной полезной нагрузки.
Теперь мы можем настроить полезную нагрузку нашей собственной сборки. Если текущая операционная система соответствует Windows PlatformID [3] (список известных платформ и версий операционной системы), мы присваиваем массив байтов переменной полезной нагрузки в соответствии с архитектурой системы.
Чтобы выделить память, необходимую для выполнения исходного ассемблерного кода, мы передаем четыре аргумента в VirtualAlloc() [4]. Первый аргумент — IntPtr.Zero, который сообщает VirtualAlloc() о необходимости выделения памяти в первом подходящем месте. Второй аргумент — это объем выделяемой памяти, который будет равен длине текущей полезной нагрузки. Этот аргумент передается классу IntPtr, который понимает неуправляемая функция, чтобы выделить достаточно памяти для размещения нашей полезной нагрузки.
Третий аргумент — это магическое значение, определенное в kernel32.dll, которое соответствует опции MEM_COMMIT, указывая VirtualAlloc() немедленно выделить память. Этот аргумент определяет режим, в котором должна быть выделена память. Наконец, 0x40 — это магическое значение, определенное в kernel32.dll, которое соответствует режиму RWX (чтение, запись и выполнение), который нам нужен. Функция VirtualAlloc() вернет указатель на вновь выделенную память, чтобы мы знали, где начинается выделенная область памяти.
Теперь Marshal.Copy() [5] копирует нашу полезную нагрузку непосредственно в выделенное пространство памяти. Первый аргумент, передаваемый в Marshal.Copy(), — это массив байтов, который мы хотим скопировать в выделенную память. Второй — это индекс в массиве байтов, с которого следует начать копирование, а третий — с которого следует начать копирование (используя указатель, возвращаемый функцией VirtualAlloc()). Последний аргумент сколько байтов из массива байтов мы хотим скопировать в выделенную память (все).


Далее мы ссылаемся на ассемблерный код как указатель на неуправляемую функцию, используя делегат WindowsRun, который мы определили в верхней части MainClass. Мы используем метод Marshal.GetDelegateForFunctionPointer() [6] для создания нового делегата, передавая указатель на начало нашего ассемблерного кода и тип делегата в качестве первого и второго аргументов соответственно. Мы приводим делегат, возвращаемый этим методом, к нашему типу делегата WindowsRun, а затем присваиваем его новой переменной того же типа WindowsRun. Теперь осталось вызвать этот делегат, как если бы это была функция, и выполнить ассемблерный код, который мы скопировали в память.

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

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