#28 Gray Hat C#. Руководство для хакера по созданию и автоматизации инструментов безопасности. Выполнение собственных полезных нагрузок Linux.
Здравствуйте, дорогие друзья.
В этом разделе мы рассмотрим, как определить полезные нагрузки, которые можно скомпилировать один раз и запустить как в Linux, так и в Windows. Но сначала нам нужно импортировать несколько функций из libc и определить делегат неуправляемой функции Linux, как показано в листинге ниже.
![Setting up the payload to run the generated Metasploit payloads](https://timcore.ru/wp-content/uploads/2023/10/116-1024x231.png)
![Setting up the payload to run the generated Metasploit payloads](https://timcore.ru/wp-content/uploads/2023/10/117.png)
Мы добавляем строки, показанные в листинге выше, в верхнюю часть MainClass рядом с импортом нашей функции Windows. Мы импортируем три функции из libc — mprotect(), posix_memalign() и free() — и определяем новый делегат под названием LinuxRun [2]. Он имеет атрибут UnmanagedFunctionPointer, как и наш делегат WindowsRun. Однако вместо передачи CallingConvention.StdCall, как это было в листинге ранее, мы передаем CallingConvention.Cdecl [1], поскольку cdecl — это соглашение о вызовах собственных функций в Unix-подобной среде.
В листинге ниже, мы теперь добавляем оператор else if в наш метод Main(), следующий за оператором if, который проверяет, находимся ли мы на машине Windows.
![Detecting the platform and assigning the appropriate payload](https://timcore.ru/wp-content/uploads/2023/10/118-1024x184.png)
Исходное перечисление PlatformID от Microsoft не включало значения для платформ, отличных от Windows. По мере развития Mono были введены неофициальные значения для свойств платформы Unix-подобной системы, поэтому мы проверяем значение Platform непосредственно по значениям магических целых чисел, а не по четко определенным значениям перечисления. Значения 4, 6 и 128 можно использовать, чтобы определить, используем ли мы Unix-подобную систему. Приведение свойства Platform к int позволяет нам сравнить значение Platform с целочисленными значениями 4, 16 и 128.
Как только мы определим, что работаем в Unix-подобной системе, мы можем установить значения, необходимые для выполнения наших собственных полезных нагрузок сборки. В зависимости от нашей текущей архитектуры массиву байтов полезной нагрузки будет назначена либо наша полезная нагрузка x86, либо x86-64.
Выделение памяти
Теперь мы начинаем выделять память для вставки нашей сборки в память, как показано в листинге ниже.
![Allocating the memory using posix_memalign()](https://timcore.ru/wp-content/uploads/2023/10/119.png)
![Allocating the memory using posix_memalign()](https://timcore.ru/wp-content/uploads/2023/10/120-1024x262.png)
Сначала мы определяем несколько переменных: ptr, которому с помощью posix_memalign() должен быть присвоен указатель на начало нашей выделенной памяти, если все идет хорошо; успех, которому будет присвоено значение, возвращаемое функцией posix_memalign(), если наше выделение будет успешным; и логическое значение freeMe, которое будет истинным в случае успешного выделения, чтобы мы знали, когда нам нужно освободить выделенную память. (Мы присваиваем freeMe значение false в случае сбоя распределения.)
Затем мы запускаем блок try, чтобы начать выделение, чтобы мы могли перехватить любые исключения и корректно завершить полезную нагрузку в случае возникновения ошибки. Мы установили для новой переменной pagesize значение 4096, что соответствует размеру страницы памяти по умолчанию в большинстве установок Linux.
После назначения новой переменной с именем length, которая содержит длину нашей полезной нагрузки, приведенной к IntPtr, мы вызываем posix_memalign() [1], передавая переменную ptr по ссылке, чтобы posix_memalign() мог изменить значение напрямую, без необходимости передавать его обратно. Мы также передаем выравнивание памяти (всегда кратное 2; 32 — хорошее значение) и объем памяти, который мы хотим выделить. Функция posix_memalign() вернет IntPtr.Zero, если выделение будет успешным, поэтому мы проверяем это. Если IntPtr.Zero не был возвращен, мы печатаем сообщение о сбое posix_memalign(), а затем возвращаемся и выходим из полезных данных. Если выделение прошло успешно, мы изменяем режим выделенной памяти на доступный для чтения, записи и выполнения, как показано в листинге ниже.
![Changing the mode of the allocated memory](https://timcore.ru/wp-content/uploads/2023/10/121-1024x265.png)
Примечание
Техника, используемая для выполнения шелл-кода в Linux, не будет работать в операционной системе, которая ограничивает выделение памяти RWX. Например, если в вашем дистрибутиве Linux используется SELinux, эти примеры могут не работать на вашем компьютере. По этой причине я рекомендую Ubuntu — поскольку SELinux отсутствует, примеры должны работать без проблем.
Чтобы в дальнейшем освободить выделенную память, мы устанавливаем для freeMe значение true. Затем мы берем указатель, который posix_memalign() установил во время выделения (переменная ptr), и создаем указатель с выравниванием по страницам, используя пространство памяти, которое мы выделили, выполняя побитовую операцию И над указателем с дополнением до единиц нашего размера страницы [1]. По сути, дополнение единиц фактически превращает адрес нашего указателя в отрицательное число, так что наши математические вычисления для установки прав доступа к памяти складываются.
Из-за того, как Linux распределяет память по страницам, мы должны изменить режим для всей страницы памяти, на которой была распределена наша память для полезных данных. Побитовое И с дополнением единиц к текущему размеру страницы округлит адрес памяти, данный нам posix_memalign(), до начала страницы памяти, где находится указатель. Это позволяет нам установить режим для полной страницы памяти, используемой памятью, выделенной posix_memalign().
Мы также создаем режим для установки памяти, выполняя операцию ИЛИ над значениями 0x04 (чтение), 0x02 (запись) и 0x01 (выполнение) и сохраняя значение из операций OR в переменной режима [2]. Наконец, мы вызываем mprotect() [3], передавая указатель страницы памяти, выравнивание памяти (передаваемое в функцию posix_memalign()) и режим, в который устанавливается память. Как и функция posix_memalign(), IntPtr.Zero возвращается, если mprotect() успешно меняет режим страницы памяти. Если IntPtr.Zero не возвращается, мы печатаем сообщение об ошибке и возвращаемся для выхода из полезных данных.
Копирование и выполнение полезной нагрузки
Теперь мы готовы скопировать полезные данные в пространство памяти и выполнить код, как показано в листинге ниже.
![Copying the payload to the allocated memory and executing the payload](https://timcore.ru/wp-content/uploads/2023/10/122-1024x253.png)
Последние несколько строк листинга выше должны выглядеть аналогично коду, который мы написали для выполнения полезных данных Windows. Метод Marshal.Copy() [1] копирует наши полезные данные в буфер выделенной памяти, а метод Marshal.GetDelegateForFunctionPointer() [2] превращает полезные данные в памяти в делегат, который мы можем вызвать из нашего управляемого кода. Как только у нас есть делегат, указывающий на наш код в памяти, мы вызываем его, чтобы выполнить код. Блок finally, следующий за блоком try, освобождает память, выделенную функцией posix_memalign(), если для freeMe установлено значение true [3].
Наконец, мы добавляем сгенерированные нами полезные нагрузки для Windows и Linux к кроссплатформенным полезным нагрузкам, что позволяет нам компилировать и запускать одну и ту же полезную нагрузку как в Windows, так и в Linux.
Заключение
В этом разделе мы обсудили несколько различных способов создания пользовательских полезных данных, которые будут полезны в различных обстоятельствах. Полезные нагрузки, использующие TCP, могут обеспечить преимущества при атаке на сеть: от получения оболочки из внутренней сети до поддержания устойчивости. Используя технику обратного подключения, Вы можете создать оболочку на удаленный ящик, что помогает, например, в фишинговой кампании, когда пентест является полностью внешним по отношению к сети. С другой стороны, техника связывания может помочь Вам поддерживать постоянство на ящиках без необходимости повторно использовать уязвимость на машине, если доступен внутренний доступ к сети.
Полезные данные, которые обмениваются данными через UDP, часто могут обойти плохо сконфигурированные межсетевые экраны и, возможно, смогут обойти систему обнаружения вторжений, ориентированную на TCP-трафик. Несмотря на то, что UDP менее надежен, чем TCP, он предлагает скорость и скрытность, которые обычно не может обеспечить тщательно изученный TCP. Используя полезную нагрузку UDP, которая прослушивает входящие широковещательные сообщения, пытается выполнить отправленные команды, а затем передает Вам результаты, Ваши атаки могут быть немного тише и, возможно, более скрытными за счет стабильности.
Metasploit позволяет злоумышленнику на лету создавать множество типов полезных данных, его легко установить и запустить. Metasploit включает в себя инструмент msfvenom, который создает и кодирует полезные данные для использования в эксплойтах. Используя инструмент msfvenom для создания собственных полезных данных сборки, Вы можете создать небольшой кросс-платформенный исполняемый файл для обнаружения и запуска шелл-кода для различных операционных систем. Это дает вам большую гибкость в использовании полезных данных, которые запускаются на целевом компьютере. Он также использует одну из самых мощных и полезных доступных функций Metasploit.
На этом все. Всем хорошего дня!