Расшифровка сессий OpenSSH для забавы и профита

Расшифровка сессий OpenSSH для забавы и профита

Этичный Хакер

Введение

Некоторое время назад у нас был случай судебной экспертизы, в котором сервер Linux был скомпрометирован, а модифицированный двоичный файл OpenSSH был загружен в память веб-сервера. Модифицированный бинарный файл OpenSSH использовался злоумышленниками в качестве бэкдора в систему. У клиента были файлы pcap и снимок гипервизора системы на момент взлома. Мы начали задаваться вопросом, можно ли расшифровать сеанс SSH и узнать о нем, восстановив ключевой материал из моментального снимка памяти. В этом блоге я расскажу о проведенных мною исследованиях OpenSSH и выпущу некоторые инструменты для выгрузки ключей сеанса OpenSSH из памяти, а также расшифровки и синтаксического анализа сеансов в сочетании с pcaps. Я также отправил свое исследование на конкурс плагинов Volatility 2020 .

SSH протокол

Во-первых, я начал читать об OpenSSH и его работе. К счастью, OpenSSH имеет открытый исходный код, поэтому мы можем легко загрузить и прочитать детали реализации. RFC, хоть и были немного скучными для чтения, содержали в себе много информации. С первого взгляда протокол SSH выглядит следующим образом:

1) Протокол SSH + обмен версией ПО

2) Согласование алгоритма (KEX INIT)

  • Алгоритмы обмена ключами
  • Алгоритмы шифрования
  • Алгоритмы MAC
  • Алгоритмы сжатия

3) Обмен ключами

4) Аутентификация пользователя

5) Клиент запрашивает канал типа «session»

6) Клиент запрашивает псевдотерминал

7) Клиент взаимодействует с сеансом

Начиная с начала, клиент подключается к серверу и посылает версию протокола и версию программного обеспечения:SSH-2.0-OpenSSH_8.3. Сервер отвечает своим протоколом и версией программного обеспечения. После этого первоначального обмена протоколом и версией программного обеспечения весь трафик упаковывается в кадры SSH. Кадры SSH существуют в основном из длины, длины заполнения, данных полезной нагрузки, содержимого заполнения и MAC. Пример фрейма SSH:

Перед согласованием алгоритма шифрования и генерацией сеансового ключа кадры SSH не будут зашифрованы, и даже когда кадр зашифрован, в зависимости от алгоритма, части кадра могут быть не зашифрованы. Например, aes256-gcm не зашифрует 4 байта в кадре, но chacha20-poly1305 зашифрует.

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

Восстановление ключей сеанса

Первым шагом в восстановлении ключей сеанса был анализ исходного кода OpenSSH и отладка существующих двоичных файлов OpenSSH. Я попробовал сам скомпилировать OpenSSH, записав где-нибудь сгенерированные ключи сеанса, подключив отладчик и выполнив поиск ключей в памяти программы. Успех! Ключи сеанса хранились в памяти в куче. Еще немного покопавшись в исходном коде, я обратил внимание на функции, отвечающие за отправку и получение кадра NEWKEYS. Я обнаружил, что существует структура «ssh», в которой хранится структура «session_state». Эта структура, в свою очередь, содержит все виды информации, относящиеся к текущему сеансу SSH, включая структуру newkeys, содержащую информацию, касающуюся шифрования, MAC и алгоритма сжатия. На один уровень ниже мы, наконец, находим структуру sshenc, содержащую имя шифра, ключ, IV и длину блока. Все, что нам нужно! Хороший обзор структуры OpenSSH показан ниже:

И определение структуры sshenc:

Трудно найти сам ключ в памяти (это просто строка случайных байтов), но структуры sshenc (и другие) более различимы, имея некоторые свойства, по которым мы можем найти их. Затем мы можем очистить все адресное пространство памяти программы и проверить каждое смещение на соответствие этим свойствам. Мы можем проверить следующие свойства:

  • name, cipher, key и iv являются действительными указателями
  • name указывает на допустимое имя шифра, которое равно cipher->name
  • key_len находится в допустимом диапазоне
  • iv_len находится в допустимом диапазоне
  • block_size находится в допустимом диапазоне

Если мы проверим все эти ограничения, мы сможем надежно найти структуру sshenc. Я начал создавать скрипт POC Python, который я мог запустить на живом хосте, который подключается к процессам и очищает память для этой структуры. Исходный код этого скрипта можно найти здесь . На самом деле он работает довольно хорошо и выводит json blob для каждого найденного ключа. Итак, я продемонстрировал, что могу восстановить ключи сеанса с живого хоста с помощью Python и ptrace, но как мы собираемся восстановить их из моментального снимка памяти? Вот где волатильность вступает в игру. Volatility - это фреймворк для криминалистической экспертизы памяти, написанный на Python с возможностью писать собственные плагины. Приложив некоторые усилия, я смог написать плагин Volatility 2 и смог проанализировать моментальный снимок памяти и выгрузить ключи сеанса! Для конкурса плагинов Volatility 3 я также перенес плагин на Volatility 3 и отправил плагин и исследования на конкурс. Скрестим пальцы!

Расшифровка и разбор трафика

Восстановление ключей сеанса, которые используются для шифрования и дешифрования трафика, было успешным. Следующее - расшифровка трафика! Я начал разбирать некоторые pcap-файлы с помощью pynids, библиотеки синтаксического анализа и повторной сборки TCP. Я использовал нашу собственную библиотеку dissect.cstruct для анализа структур данных и разработала структуру синтаксического анализатора, для анализа протоколов, таких как ssh. Фреймворк синтаксического анализа в основном подает пакеты синтаксическому анализатору протокола в правильном порядке, поэтому, если клиент отправляет 2 пакета, а сервер отвечает 3 пакетами, пакеты также будут доставлены синтаксическому анализатору в том же порядке. Это важно для сохранения общего состояния протокола. Анализатор в основном использует кадры SSH до тех пор, пока не встретится кадр NEWKEYS, указывающий, что следующий кадр зашифрован. Теперь парсер просматривает следующий кадр в потоке из этого источника и перебирает предоставленные ключи сеанса, пытаясь расшифровать кадр. В случае успеха парсер устанавливает сеансовый ключ в состоянии для расшифровки оставшихся в сеансе кадров. Парсер может обрабатывать практически все алгоритмы шифрования, поддерживаемые OpenSSH.

И, наконец, синтаксический анализатор в действии, где вы можете увидеть, как он расшифровывает и анализирует сеанс SSH, а также раскрывает пароль, используемый пользователем для аутентификации:

Вывод

Подводя итог, я исследовал протокол SSH, узнал как ключи сеанса хранятся в памяти для OpenSSH, нашел способ очистить их из памяти и использовать их в сетевом парсере для расшифровки и синтаксического анализа сеансов SSH с получением удобочитаемого вывода. Скрипты, использованные в этом исследовании, можно найти здесь:

Потенциальным следующим шагом, который было бы неплохо реализовать, было бы внедрение этого дешифратора и парсера в Wireshark.

Последние мысли

Как ни странно, во время своего исследования я также наткнулся на эти прокомментированные строки в функции ssh_set_newkeys в исходном коде OpenSSH. Какая ирония! Если бы эти строки были раскомментированы и скомпилированы в двоичных файлах OpenSSH, это исследование было бы намного сложнее...



Report Page