24.04.2017

Стеганография на базе стека протоколов TCP/IP. Часть 2

image

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

Автор: John Torakis

Основы стеганографии

Прежде, чем продолжать тему, начатую в первой части, поподробнее разберемся с понятием стеганографии.

В целом стеганография подразделяется на 2 категории:

Мы можем зашифровать произведение Шекспира внутри картинки, где изображено стадо зебр, и будем уверены, что никто не догадается, поскольку те, кто разглядывают картинку, не будут искать младшие биты в каждом байте каждого пикселя. Этот тип стеганографии, которому была посвящена первая часть, основан на сокрытии ценной информации в открытых источниках, когда мы передавали данные через инкапсуляцию в заголовки протокола TCP.

  • Второй тип стеганографии, на основе которого сделан пример Танетбаума, намного интереснее. Здесь мы используем шифрование, и даже если вы заметите, что в картинке есть секретное послание, то не сможете расшифровать без знания ключа.

Шифрование в стеганографии – очень перспективная затея, учитывая тот факт, что никто не догадывается о существовании канала коммуникации. Когда мы просто используем шифрование, никто не знает, о чем мы говорим (кроме абонента на другом конце провода), но все видят, что происходит коммуникация. И все понимают, что мы говорим на конфиденциальные темы (а иначе, зачем использовать шифрование).

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

Альтернативный вариант

Новый метод коммуникации во многом похож на тот, о котором говорилось в первой части, за исключением того, что новая схема станет менее явной. Мы все также будем передавать команды через поля Identification и ISN, но на этот раз уже более правильным образом.

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

Возможности альтернативного варианта:

  • Полноценный запуск команды в операционной системе (с наличием и отсутствием вывода результатов работы).
  • Удаленное выполнение шелл-кода на лету.
  • Закачка/загрузка файлов.
  • Полная устойчивость к анализу .pcap файлов, журналов фаервола и другим видам исследований, за исключением криминалистического анализа образа операционной системы.
  • Возможность имитации сканирования портов через приложение nmap с ключом –sS и любых других видов SYN-сканирования или SYN-спама с указанием целевых портов.
  • Совместимость с ОС Windows и Linux.
  • Отсутствие соединений. Каждый новый пакет в рамках одной «беседы» может быть отправлен с разных IP-адресов и на разные целевые порты.

Недостатки новой схемы:

  • Маленькая скорость обмена (пропускная способность – 5 байт/пакет). Запаситесь терпением.
  • Отсутствием механизмов сокрытия внутри системы. Требуется отдельный руткит.
  • Невозможность работы через прокси-сервер (но детектирования происходить не будет). Хотя в схеме с пробросом портов должно работать.
  • Есть зависимости… Требуется Scapy в Linux и Scapy с Wincap в Windows. Оба приложения возможно использовать в сочетании с утилитами PyInstaller-py2exe-nuitka (за исключением, возможно, библиотек .dll).

Условия для успешной реализации схемы:

  • Необходимы права суперпользователя / администратора для создания пакетов и сниффинга.
  • Необходимо находиться в одной подсети с жертвой (в случае с глобальным интернетом – это должны быть два хоста с публичными IP адресами). В крайнем случае,  должен быть прямой путь до TCP-портов жертвы (если жертва находится за фаерволом с переадресаций TCP порта с номером 21, схема будет работать, если управляющий сервер будет посылать пакеты по адресу <IP-адрес фаервола>:21).
  • Управляющему серверу нежелательно находиться за NAT, поскольку порт источника в исходящих пакетах важен для жертвы, а NAT меняет это поле (так как происходит перевод на другой порт источника перед передачей с IP-адресом шлюза).

Суть идеи

На машине жертвы запускается пакетный сниффер, который принимает все входящие пакеты и решает, какие из них созданы при помощи алгоритма, описанного в разделе «Решение проблемы».

Самым важным является то, что эти пакеты не создают никаких соединений к жертве (ни TCP, UDP). Это пакеты типа TCP SYN, которые почти не нарушают правила протокола TCP (подробнее этой темы коснемся в 3 части), и, как следствие, свободно проходят все проверки внутри протокола, выполняемыми системами безопасности и анализаторами пакетов. SYN пакеты нельзя просто взять и заблокировать (в отличие от ICMP), поскольку тогда все соединения будут запрещены, и сеть окажется бесполезной. Не будет работать ни SQL-сервер, ни веб-приложения, ни FTP и так далее.

Примечательным является то, что в каждом пакете может доставлять 6 байт информации через поле Identification протокола IP и поле Sequense Number протокола TCP (2 байта + 4 байта) в строго зашифрованном виде. На стороне жертвы происходит извлечение 6-байтовой полезной нагрузки, разделение на две части (1+5 байт), где первый байт представляет собой опкод команды, которая запускает содержимое последующих 5 байт.

Затем генерируется пакет RST-ACK, также не нарушающий правил протокола TCP, в который инжектируется в зашифрованном виде ответ выполненной команды, после чего происходит отсылка на управляющий сервер.
Схема SYN-RST намного более похожа на сканер портов, чем на шелл для удаленного выполнения команд и, следовательно, не возникает блокировок системами IDS/IPS, поскольку шифрование скрывает все явно видимые признаки (и такие системы редко смотрят на слои из 3-4 заголовков). Хорошо сконфигурированный фаервол, где прописано использование каждого хоста (например, гипотетическому SSH серверу разрешено работать только с портом 22), может противодействовать нашей схеме, но я редко встречал подобное.

Решение проблем

В первой части у нас возникли некоторые проблемы, и сейчас будут приведены методы решения.

Проблема, связанная с низкой энтропией

Низкая энтропия возникает в тот момент, когда мы используем только байты печатных символов ASCII, исключая цифры и знаки в верхней раскладке, в поле, где предусмотрено использование полного набора из 256 байт.
Решение этой проблемы – шифрование. Нам нужен алгоритм с использованием 6-байтовых блоков или поточное шифрование. И в большинстве случаев потребуется придерживаться определенного стиля. Учитывая вышесказанное, я решил выбрать схему с одноразовым ключом с использованием алгоритмов XOR и SHA512. Очень простое и стильное шифрование.

Шифрование с одноразовым ключом

Ключ создается посредством шифрования выбранной секретной фразы алгоритмом SHA512. При помощи этого ключа и алгоритма XOR мы обрабатываем 6-байтовые блоки данных. В итоге получаем надежное шифрование, поскольку ключ вычисляется посредством обработки секретной фразы необратимой функцией.  Чтобы зашифровать следующий 6-байтовый блок, мы обрабатываем текущий ключ алгоритмом SHA512 и еще раз выполняем операцию XOR. Таким образом, операция XOR каждый раз выполняется с разными ключами, что исключает возможность расшифровки при помощи простой и известной техники. Кроме того, эта схема исключает предсказуемость ключей, даже если мы постоянно шифруем один и тот же 6-байтовый блок (например, «ls -la»). Каждый раз можно извлечь лишь часть ключа размером 6 байт, и для предугадывания следующего ключа информации будет недостаточно, поскольку длина ключа 512 бит (64 байт).

Дополнительным плюсом является то, что мы выполняем операцию XOR вместе с полным набором байт (поскольку после обработки алгоритмом SHA512 получается последовательность, содержащая все возможные байты), равномерно распределенных по диапазону из 256 байт. В итоге получаем энтропию, близкую к 1, что означает практически случайную последовательность.

Проблема идентификации

На вопрос «Кто твой хозяин?» RCE-шелл (Remote Command Execution; Удаленное выполнение команд) должен знать ответ. Когда вы запускаете команды удаленно, это очень хорошо, но вы должны быть единственным, кто может выполнять подобные манипуляции. Шелл должен уметь отделять ваши пакеты от остальных. Для решения этой задачи необходимо жестко прописать в шелл либо ваш IP-адрес, либо имя домена. На самом деле, вам поймают, как только вы подумаете о таком приеме. Можно воспользоваться техниками, которые применяются в эксплоитах, например, быстрой сменой поддоменов и тому подобные приемы, у которых отсутствует изюминка. Но и в этом случае вас в скором времени вычислят.

В первой части мы совершенно забыли о поле Source Port (порт источника), которым можно управлять. Вам в голову могут прийти примерно следующие мысли: «Ну, ок, будем просто отсылать пакеты с определенного порта, а затем расшифровывать и запускать команды».

В этом подходе также отсутствует изюминка.

Решение проблемы «Кто твой хозяин?»

Идея о проверке поля Source Port верна до той поры, пока мы не будем углубляться в детали. Если аналитик начнет просматривать .pcap файл, где в поле Destination Port указаны разные порты, а в поле Source Port отображается  одно и то же значение, то сразу заподозрит неладное и задумываться над следующими вопросами?

Почему сканнер портов используется порт с номером 23456 во множестве систем?

  • Этот порт жестко прописан в настройках?
  • Существуют ли сканеры портов с такими настройками?
  • Является ли такое поведение нормальным?
  • Почему Гугл не выдает никакой информации относительно порта 23456?

И здесь возникает более интересная идея.

В нашей схеме мы все же будем проверять у пакетов поле Source Port, но это значение не обязательно должно быть постоянным. Для формирования содержимого этого поля также можно использовать циклический алгоритм, как и в случае со схемой шифрования.

Поле Source Port содержит 2 байта или 4 шестнадцатеричные цифры. Мы инициализируем первую (самую старшую) цифру в зависимости от выбранного секретного ключа (эта цифра должна быть больше 8, чтобы получались порты с большим номером). Затем мы обрабатываем секретный ключ алгоритмом SHA512 и берем еще 3 шестнадцатеричные цифры полученного хеша. Далее собираем все цифры и получаем набор из 4 шестнадцатеричных цифр или 2 байта. После этого будет сгенерирован новый хеш и, соответственно, новый порт.

Используя эту технику, мы каждый раз будем получать новые номера портов в непредсказуемой последовательности для того, у кого нет секретного ключа. Только агент, установленная в системе жертвы, и клиент на управляющем будут знать следующий корректный порт, а вероятность получения неправильного пакета с корректным полем Source Port равна 1/65536, что вполне приемлемо.

Снижение количества ошибок

Несмотря на то, что каждый раз генерируется случайный порт, возможность получения «левого» пакета с корректным полем Source Port (который не был создан клиентом на управляющем сервере) существует. Если подобное происходит, агент переключится на следующий порт и ключ шифрования и будет пытаться расшифровать и запустить некорректную информацию, что приведет к хаосу.

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

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

Восстановление связи

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

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

Если полезная нагрузка более 5 байт

Существуют команды наподобие «find / -name ‘flag’ 2 > /dev/null», которые превышают лимит в 5 байт (+1 байт опкода) и не могут быть переданы одним пакетом. В этом случае следует делать разбивку на несколько порций. При получении строки “find ” (обратите внимание на однобайтовый пробел) агент понимает, что команда сформирована не полностью и ожидает следующих пакетов.

Другой пример: команда “head -1 /etc/shadow” (для получения хеша пароля суперпользователя), возвращающая содержимое, которое может превышать 100 байт. Эти байты должны быть доставлены на управляющий сервер, который, в свою очередь, должен понимать, что нужно ожидать следующие части полезной нагрузки и когда содержимое доставлено полностью. Кроме того, агент никогда не посылает пакеты, не являющиеся ответами на полученные пакеты (умеет только формировать пакеты типа RST-ACK).

Протокол внутри протокола

Если мы используем опкоды, то всегда знаем текущее положение вещей. Существуют опкоды, сигнализирующие о том, что в данный момент поступила лишь часть полезной нагрузки, и нужно ждать дальше. Другие опкоды означают, что поступившая информация завершает цикл передачи, и можно запускать команду. По сути, эта схема имеет сходство алгоритмом разделения, используемым в протоколе TCP, но без смещений данных, поскольку у нас нет достаточного времени и ресурсов канала. В нашей схеме предусмотрена ситуация, что если пакет теряется, дальнейшие пакеты расшифровываться не будут. Таким образом, исключается выполнение неполных команд и появление конфликтов.

Инерционность агента

Агент в системе жертвы никогда не отсылает пакеты, которые не являются ответом. Это означает, что управляющий сервер нужно подтолкнуть к началу «разговора». Далее управляющей сервер начинает отсылать случайную информацию (например, опкод «talk») и в ответ принимает осмысленные пакеты. Агент также информирует управляющий сервер о том, что больше информации не будет, после чего процесс общения прерывается до следующей команды.

Завершение агента после запуска шелл-кода

После доставки шелл-кода происходит запуск при помощи шаблона на основе ctypes:

libc = CDLL('libc.so.6')              # Loads libc
sc = c_char_p(shellcode)              # creates a C string with shellcode
size = len(shellcode)                 # gets shellcode's length (used later)
addr = c_void_p(libc.valloc(size))    # allocates bytes of heap memory equal                            to the shellcode length.
memmove(addr, sc, size)               # copies shellcode from stack variable(pointer) sc to heap memory that was just allocated
libc.mprotect(addr, size, 0x7)        # disables NX protection of data memory
run = cast(addr, CFUNCTYPE(c_void_p)) # casts the pointer to shellcode in heap to a function pointer
run()                                 # jumps shellcode function pointer - runs the shellcode

В коде выше происходит копирование шелл кода в кучу, изменение бита NX для нужной области памяти и переход к выполнению. В этот момент работа агента приостанавливается, поскольку регистр EIP указывает на шелл-код. Возврат невозможен. Агент прекратит работу в любом случае. 

Создание отдельного процесса

При помощи кода ниже, шелл-код запускается как отдельный процесс.

p = Process(target=run)     # run the shellcode as independent process
p.start()

Вместо стандартной схемы с использованием метода run().

В ОС Windows метод CreateThread() также работает без сбоев. К тому же, в Windows нельзя отследить регистр EIP. Никто не сможет узнать, куда указывал регистр EIP в определенный момент времени. Даже те, кто придумал этот регистр.

Демонстрационный пример

Запускаем агента:

./lucky.py mypassphrase

Агент успешно запустился, готов к шифрованию ответов и терпеливо ожидает команд.
Управляющий сервер выходит на связь:

# ./pozzo.py target_ip mypassphrase

Реальный пример заражения системы

cp lucky.py /usr/sbin/X
printf "@reboot /usr/sbin/X --rootless -noreset\n" > /etc/crontab

Помните, что оригинальный исполняемый файл X находится в директории /usr/sbin/X. Лично я не думаю, что системный администратор сможет заподозрить неладное при помощи пристального просмотра списка, полученного командой “ps aux”. По самым оптимистичным подсчетам вероятность примерно 4 из 10. Чтобы повысить шансы на успех для не слишком наблюдательных персон, воспользуйтесь правильными утилитами.

Этот экземпляр агента используется секретную фразу (да, вы правильно догадались) “–rootless” (argv[1]). Можно использовать любую другую секретную фразу, похожую на параметр приложения X. Думаю, что в природе нет таких людей, которые знают все параметры X. И, скорее всего, никогда не появится смельчак, который прочитает всю справку по X.

Здесь уже затрагивается взлома человеческого разума, а не компьютера. Хотя, с моей точки зрения, «Хакерство» как раз относится именно ко взлому ума).

Секретную фразу можно прописать внутри файла lucky.py, однако этот метод не очень красивый. Но даже если не задумываться о красоте, основной плюс в том, что строковая команда не вернет ничего (в случае упаковки файла lycky.py утилитой PyInstaller), если секретная фраза передается как аргумент.

Видео демонстрация схемы

В первом видеоролике демонстрируется выполнение несколько команд и при одновременно запущенной утилите tcpdump.

A covert channel OS shell from John Torakis on Vimeo.

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

The covert ASM shell from John Torakis on Vimeo.

Заключение

На момент написания статьи этот проект был с закрытым исходным кодом, поскольку содержит часть моих личных исследований, которые еще не закончены. Изначально идея носила академическую направленность, поскольку в статьях наподобие “Embedding Covert Channels into TCP/IP” (Murdoch & Lewis, 2005), авторы предлагали алгоритмы, защищающие от стеганографии на базе стека TCP/IP. Мне хотелось проверить данную гипотезу.

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

  • Написать похожую утилиту на ассемблере или C++. Это будет убийственное приложение без зависимостей (даже если зависимости будут, вы всегда можете воспользоваться опцией --static).
  • Использовать более незаметный протокол. Как насчет ARP? ARP-пакеты блокируются только в том случае, если сумасшедший админ заблокировал ВСЕ связи между коммутируемыми портами и MAC-адресами. Даже если такое произошло, самопроизвольные ARP пакеты (Gratuitous ARP) принимаются всеми узлами внутри сети. Я вижу в этой теме большой потенциал.
  • Ознакомиться с псевдокодом в статье, приведенной выше, и реализовать фильтры и приложения для детектирования стеганографии на базе стека TCP/IP.
  • Я бы очень хотел, чтобы появился плагин к фаерволу PF-Sense для фильтрации стеганографии.
  • И так далее.

В третьей части будут продемонстрированы техники по детектированию и противодействию стеганографии на базе стека протоколов TCP/IP.

На данный момент в этой схеме существуют недостатки, которые могут привести обнаружению.

С одной стороны, энтропия поля ISN не превышает энтропию /dev/urandom, если брать одно и то же количество байт. Однако существует такое понятие как функция распределения созданных значений? В операционной системе содержимое полей ISN формируется на базе текущего времени как начального числа, используемого при генерации случайной последовательности. Эти значения не являются строго случайными и укладываются в определенную функцию распределения. Возникает закономерный вопрос: «Укладываются ли сгенерированные числа в нашей схеме в ту же самую функцию распределения?» Скорее всего, нет.

  • Можем ли мы обнаружить стеганографию на базе вышеприведенной информации?
  • Если да, то нам нужно много пакетов (значений) для вычисления функции распределения.
  • Сколько нужно пакетов?
  • Какой объем информации утечет прежде, чем мы раскроем эту схему?

В следующей части вместо утилиты Scapy мы будем использовать набор приложений Scipy. Кроме того, мы будем использовать кривые функций и интегралы в связке с логами фаерволов и других систем безопасности.
(Все о чем мы только можем мечтать, уже существует. Например, простенький класс Fitter позволяет вычислять функцию распределения! Именно сей факт заставляет меня любить Python).

Продолжение следует.