Главная » Хабрахабр » Стеганография в IP-пакетах

Стеганография в IP-пакетах

Однажды, перед защитой очередной лабораторной работы мне задали вопрос: какие поля IP-пакета можно использовать для стегано? Я не знал и лишь пожал плечами. Но вскоре я всё же решил изучить этот вопрос.

Под катом вас ждёт изучение заголовков IP-пакетов, собственная утилита ping на Python и несколько способов передать данные, не привлекая внимания.

  1. Структура IP-пакета
  2. Настройка окружения
  3. Ping: Лёгкий вариант
  4. Ping: Сложный вариант
  5. Доработки?

Выделим поля, изменение которых не сильно повлияет на пакет:

Чаще всего это поле 0. IHL может изменяться от 5 до 15.
Поле ToS используется для приоритезации трафика и уведомлениях о заторах без отбрасывания пакетов. Необходимо знать количество хопов до принимающего и учитывать это. Теоретически можно использовать для передачи целого байта информации.
Длина пакета прекрасное поле для передачи чисел от 20 до 65535.
TTL может передавать до 7 бит информации.

Для повторения эксперимента потребуются две машины с Python и фреймворком scapy.

В моём случае это были два дроплета на DO со включенной локальной сетью. Установить оный можно следуя инструкции из документации. Для проверки работоспособности стегано были выбраны два маршрута: через локальную сеть за 1 хоп и через интернет за 2 хопа.

Сначала реализуем sender.py, который будет отправлять ICMP пакеты без скрытых сообщений.

from scapy.all import * # Создаём пакет для 10.0.0.2 с icmp-type 8 (echo-request)
pkt = IP(src="10.0.0.1", dst="10.0.0.2") / ICMP(type = 8)
# Отправляем пакет и ждём ответа
sr1(pkt)

Scapy перед отправкой заполнит остальные поле значениями по умолчанию и подсчитает контрольную сумму.

На стороне принимающего напишем listener.py, который будет прослушивать и выводить на экран все приходящие ICMP-пакеты.

from scapy.all import * # Настраиваем прослушивание пакетов
# filter -- только icmp
# timeout -- слушаем только 10 секунд
# count -- ждём не больше 100 пакетов # iface -- только на интерфейсе eth1
packets = sniff(filter = "icmp", timeout = 10, count = 100, iface = "eth1") # Итерируемся по всем полученным пакетам
for pkt in packets: # Нас интересуют только пришедшие echo-request if pkt[ICMP].type != 8: continue # Просим красиво напечатать pkt.show()

Примерный вывод слушателя

###[ Ethernet ]### dst = hh:hh:hh:hh:hh:hh src = gg:gg:gg:gg:gg:gg type = 0x800
###[ IP ]### version = 4 ihl = 5 tos = 0x0 len = 28 id = 24923 flags = frag = 0 ttl = 64 proto = icmp chksum = 0x4364 src = 10.0.0.1 dst = 10.0.0.2 \options \
###[ ICMP ]### type = echo-request code = 0 chksum = 0xf7ff id = 0x0 seq = 0x0
###[ Padding ]### load = '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

В заголовке IP-пакета есть поле «идентификатор». Заполним его символами «A» и «B»:

payload = ord("A") * 0x100 + ord("B")
pkt = IP(src="10.0.0.1", dst="10.0.0.2", id = payload) / ICMP(type = 8)

Более того, в заголовке ICMP есть точно такое же поле, в которое так же можно загрузить два байта.

Изменим слушателя для вывода на экран полученных данных:

from scapy.all import *
import sys packets = sniff(filter="icmp", timeout = 10, count = 100, iface="eth0") for pkt in packets: if pkt[ICMP].type != 8: continue # Разделяем два символа a, b = divmod(pkt[IP].id, 0x100) sys.stdout.write(chr(a)) sys.stdout.write(chr(b)) sys.stdout.flush()

По образу и подобию можно заполнить практически любое поле, отмеченное ранее как пригодное для стегано.
Передача данных из предыдущего пункта была не самая очевидная, но мы можем сделать ещё более неочевидной. Можно спрятать данные в поле для контрольной суммы. Согласно RFC1071 контрольная сумма является (внезапно!) побитовой инверсией чуть более сложной арифметической суммы.

Объяснение с примером

Допустим, у нас есть заголовок, для которого мы хотим вычислить контрольную сумму. На время расчётов поле checksum обнуляется.

4500 003c 000a 0000 8001 [checksum] c0a8 000d c0a8 000d

1. Складываем все 16-битные слова, запоминая перенос из старшего разряда:

4500 + 003c + 000a + 0000 + 8001 + [checksum=0000] + c0a8 + 000d + c0a8 + 000e = = (2) 46b2

2. Складываем результат с переносами:

46b2 + 2 = 46b4

3. Инвертируем:

~(46b4) = b94b

b94b — искомая нами контрольная сумма. Для проверки можно подставить в заголовок и выполнить пункты 1 и 2. Если получится FFFF, то сумма найдена верна.

Проверка:

1. 4500 + 003c + 000a + 0000 + 8001 + [checksum=b94b] + c0a8 + 000d + c0a8 + 000e = = (2) FFFD
2. FFFD + 2 = FFFF

Нам известно, что контрольная сумма пакета изменяется при прохождении узлов в сети, так как изменяется TTL. Так же при прохождении NAT в пакете подменяется «адрес источника», что так же влияет на контрольную сумму. И на сколько уменьшится TTL при достижении нашего слушателя… Вишенкой на торте является то, что разрядность «идентификатора» совпадает с разрядностью контрольной суммы. Этот факт позволяет нам влиять на контрольную сумму и изменять её на любое значение из области определения. Так как контрольная сумма (полезная нагрузка) будет подсчитана только при прохождении последнего узла в маршруте, важно при расчётах учесть всё, что может быть изменено в пакете за время прохождения маршрута.

Алгоритм нахождения «идентификатора», который даст нам желаемую контрольную сумму:

  1. Настраиваем пакет как при прохождении последнего узла (IP, TTL, etc)
  2. В «идентификатор» записываем полезную нагрузку
  3. Подсчитываем контрольную сумму
  4. Результат необходимо записать в «идентификатор» отправляемого пакета

Напишем функцию, которая по количеству хопов, айпишникам за NAT'ом и двум байтам полезной нагрузки сформирует пакет.

# src - адрес отправителя
# src_nat - адрес отправителя за NAT
# dst - адрес получателя
# dttl - количество узлов на пути в получателю
# a, b -- по одному байту полезной информации
def send_stegano(src, src_nat, dst, dttl, a, b): # Формируем полезную нагрузку из двух байт payload = ord(a)*0x100 + ord(b) # Создаём состояние пакета при прохождении последнего узла маршрута pkt = IP(dst=dst, src=src_nat, ttl=64-dttl, id = payload) / ICMP(type=8) # Заставляем Scapy вычислить chksum pkt = IP(raw(pkt)) # Готовим пакет к отправке pkt[IP].src = src pkt[IP].ttl = 64 pkt[IP].id = pkt[IP].chksum # Стираем поле chksum, чтобы Scapy перерасчитал его del pkt[IP].chksum # Scapy вновь вычисляет все контрольные суммы pkt = IP(raw(pkt)) # Отправляем пакет и ждём ответ sr1(pkt)

  • поля chksum, seq, id в заголовке протокола ICMP так же могут использоваться для передачи данных
  • ToS можно использовать для идентификации пакетов «от своих» и игнорировать чужие echo-request.

Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

Выявление и классификация токсичных комментариев. Лекция в Яндексе

Во всех современных системах модерации используется либо краудсорсинг, либо уже ставшее классикой машинное обучение. На очередной тренировке по ML в Яндексе Константин Котик, Игорь Галицкий и Алексей Носков рассказали о своём участии в конкурсе по массовому выявлению оскорбительных комментариев. Конкурс ...

[Перевод] Мышление в стиле Ramda: Неизменяемость и массивы

Первые шаги2. 1. Частичное применение (каррирование)4. Сочетаем функции3. Бесточечная нотация6. Декларативное программирование5. Неизменяемость и массивы8. Неизменяемость и объекты7. Заключение10. Линзы9. Функциональные компоненты с React stateless функциями и Ramda12. Использование Ramda с Redux11. Модульные редюсеры и селекторы Данный пост — это ...