Главная » Хабрахабр » Стеганография в 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 Интересное!

В Госдуму внесен законопроект об автономной работе рунета

Источник: ТАСС Документы подготовлены группой депутатов во главе с Андреем Клишасом, главой комитета Совета Федерации по законодательству. Сегодня в Государственную думу внесен законопроект о необходимости обеспечения работы российского сегмента интернета в случае отключения от зарубежных серверов. С этой целью будут ...

Телепатические платежи, запись на слономойку и другие тестовые задания для UX-редакторов

Даже если совсем не собиралась менять работу — просто чтобы быть в курсе требований и технологий. Давным-давно, когда я занималась веб-разработкой больше, чем текстами, раз в полгода обязательно смотрела вакансии: кого ищут и что хотят. И однажды почти случайно наткнулась на ...