Главная

Sunday, 20 March 2022

Пишем диссектор Wireshark на Lua.

Всем привет.

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

К счастью, можно использовать плагин Wireshark Protocol Dissectors, чтобы добавить в Wireshark анализ дополнительного протокола. Раньше для этого требовалось создание диссектора на языке С, чтобы работать с конкретной версией Wireshark, но современные версии Wireshark поддерживают язык сценариев Lua. Сценарии, которые вы пишете на Lua, также будут работать с инструментом командной строки tshark.

Давайте рассмотрим как разработать простой диссектор на Lua для протокола SuperFunkyChat, который поддерживает открытый чат по протоколу 12345.

Перед разработкой диссектора убедитесь, что ваша копия Wireshark поддерживает Lua, проверив диалоговое окно "О программе" Wireshark в разделе Help → About Wireshark. Если вы запускаете Wireshark от имени привилегированного пользователя в Unix-подобной системе, то обычно поддержка Lua отключена по соображениям безопасности, и вам нужно будет настроить Wireshark для запуска от имени непривилегированного пользователя, чтобы перехватывать и запускать сценарии на Lua. Можно разрабатывать диссекторы практически для любого протокола, с которым будет работать Wireshark, включая TCP и UDP. Гораздо проще разработать диссекторы для протоколов UDP, чем для TCP, потому что каждый перехваченный пакет UDP обычно имеет все, что нужно диссектору. 

SuperFunkyChat поддерживает режим UDP, передавая клиенту параметр командной строки --udp при запуске, что довольно удобно. Отправьте этот параметр во время перехвата и увидите пакеты, подобные тем, что показаны на скриншоте. Обратите внимание, что Wireshark по ошибке пытается проанализировать трафик, используя протокол GVSP, как показано в столбце Protocol. Реализация собственного диссектора исправит эту ошибку.


Один из способов загрузить файлы Lua – поместить свои сценарии в каталог %APPDATA%\ireshark\plugins в Windows или каталог ~/.config/wirehark/plugins в Linux и macOS. Также можно загрузить сценарий Lua, указав его в командной строке следующим образом, заменив информацию о пути на расположение сценария:

Wireshark.exe -X lua_script:chat2.lua

Есть и другой способ - это в файле Init.lua проверить значение параметра

disable_lua = false

а в конце того же файла добавить ссылку на сам скрипт:

dofile(DATA_DIR.."chat2.lua")

 

Базовый диссектор Wireshark на Lua.

Чтобы создать диссектор для протокола SuperFunkyChat, сначала создайте базовую оболочку диссектора и зарегистрируйте ее в списке диссекторов Wireshark для UDP-порта 12345 (chat2.lua): 

-- Объявляем протокол для разбора

1) chat_proto = Proto("chat","SuperFunkyChat Protocol")

-- Указываем поля протокола

2) chat_proto.fields.chksum = ProtoField.uint32("chat.chksum", "Checksum",

base.HEX)

chat_proto.fields.command = ProtoField.uint8("chat.command", "Command")

chat_proto.fields.data = ProtoField.bytes("chat.data", "Data")

-- Функция диссектора

-- buffer: данные пакета UDP в виде «тестового виртуального буфера».

-- pinfo: информация о пакете

-- tree: корень дерева пользовательского интерфейса

3) function chat_proto.dissector(buffer, pinfo, tree)

-- Задаем имя в столбце протокола в пользовательском интерфейсе

4) pinfo.cols.protocol = " FunkyChat"

-- Создаем вложенное дерево, которое представляет весь буфер

5) local subtree = tree:add(chat_proto, buffer(),

"SuperFunkyChat Protocol Data")

subtree:add(chat_proto.fields.chksum, buffer(0, 4))

subtree:add(chat_proto.fields.command, buffer(4, 1))

subtree:add(chat_proto.fields.data, buffer(5))

end

-- Получаем таблицу диссектора UDP и добавляем ее для порта 12345

6) udp_table = DissectorTable.get("udp.port")

udp_table:add(12345, chat_proto)

При первоначальной загрузке сценария создается новый экземпляр класса Proto (1), который представляет собой экземпляр протокола Wireshark, и ему присваивается имя chat_proto. Хотя можно создать это дерево вручную, я решил определить конкретные поля для протокола (2), чтобы они были добавлены в механизм фильтров отображения и вы смогли задать для фильтра отображения chat.command значение 0, (chat.command == 0), поэтому Wireshark будет показывать только пакеты с командой 0. (Этот метод очень полезен для анализа, потому что вы можете легко фильтровать определенные пакеты и разбирать их по отдельности.)

На этапе сценарий (3) создает функцию dissector() экземпляра объекта класса Proto, которая будет вызываться для анализа пакета.

Она принимает три параметра:

- буфер, содержащий данные пакета, который является экземпляром того, что Wireshark называет Testy Virtual Buffer (TVB);

- экземпляр информации о пакете, представляющий отображаемую информацию для разбора;

- объект дерева root для пользовательского интерфейса. Можно присоединить к нему подузлы, чтобы сгенерировать отображение пакетных данных.

На этапе мы (4) задаем имя протокола в столбце пользовательского интерфейса: FunkyChat. Затем создаем дерево элементов протокола (5), которые разбираем. Поскольку UDP не имеет явного поля длины, не нужно принимать это во внимание; мы должны извлечь только поле контрольной суммы. Мы используем параметр buffer для создания диапазона, который принимает начальный индекс в буфер и необязательную длину. Если длина не указана, то используется остальная часть буфера.

Затем мы регистрируем диссектор протокола с по мощью таблицы диссекторов UDP. (Обратите внимание, что функция, которую мы определили (3), на самом деле пока еще не выполняется.) Наконец, мы получаем таблицу UDP и добавляем объект chat_proto в таблицу с портом 12345 (6). Теперь можно приступить к разбору траффика.

Разбор пакета при помощи Lua.

Запустите Wireshark, используя сценарий из листинга chat2.lua, а затем загрузите перехват пакета трафика UDP. Следует убедиться, что диссектор загрузил и разобрал пакеты. На этапе (1) столбец Protocol изменен на FunkyChat. Это соответствует первой строке нашей функции диссектора и так нам проще понять, что мы имеем дело с правильным протоколом. На этапе (2) получившееся дерево показывает различные поля протокола с контрольной суммой в шестнадцатеричном формате, как мы указали. Если щелкнуть по полю Data в дереве, в отображении необработанных пакетов в нижней части окна должен быть выделен соответствующий диапазон байтов (3).

Давайте расширим диссектор для парсинга конкретного пакета. В качестве примера мы будем использовать команду 3, потому что определили, что она отмечает отправку или получение сообщения. Поскольку полученное сообщение должно отображать ID отправителя, а также текст сообщения, эти данные пакета должны содержать оба компонента, что делает его прекрасным примером.

Напишем функцию для парсинга одной строки из ее двоичного представления. Мы обновим листинг chat2.lua, чтобы добавить поддержку парсинга команды Message:

-- Объявляем протокол для разбора

chat_proto = Proto("chat","SuperFunkyChat Protocol")

-- Указываем поля протокола

chat_proto.fields.chksum = ProtoField.uint32("chat.chksum", "Checksum", base.HEX)

chat_proto.fields.command = ProtoField.uint8("chat.command", "Command")

chat_proto.fields.data = ProtoField.bytes("chat.data", "Data")

-- buffer: объект TVB, содержащий пакетные данные

-- start: смещение в виртуальный буфер для чтения строки

-- возвращает строку и используемую общую длину

1) function read_string(buffer, start)

local len = buffer(start, 1):uint()

local str = buffer(start + 1, len):string()

return str, (1 + len)

end

-- Функция диссектора

-- buffer: данные пакета UDP в виде «тестового виртуального буфера»

-- pinfo: информация о пакете

dissector_with

_commands.lua

-- tree: Root of the UI tree

function chat_proto.dissector(buffer, pinfo, tree)

-- Задаем имя в столбце протокола в пользовательском интерфейсе

pinfo.cols.protocol = "FunkyChat"

-- Создаем вложенное дерево, которое представляет весь буфер

local subtree = tree:add(chat_proto, buffer(),

"SuperFunkyChat Protocol Data")

subtree:add(chat_proto.fields.chksum, buffer(0, 4))

subtree:add(chat_proto.fields.command, buffer(4, 1))

-- Получаем объект TVB для компонента данных пакета

2) local data = buffer(5):tvb()

local datatree = subtree:add(chat_proto.fields.data, data())

local MESSAGE_CMD = 3

3) local command = buffer(4, 1):uint()

if command == MESSAGE_CMD then

local curr_ofs = 0

local str, len = read_string(data, curr_ofs)

4) datatree:add(chat_proto, data(curr_ofs, len), "Username: " .. str)

curr_ofs = curr_ofs + len

str, len = read_string(data, curr_ofs)

datatree:add(chat_proto, data(curr_ofs, len), "Message: " .. str)

end

end

-- Получаем таблицу диссектора UDP и добавляем ее для порта 12345

udp_table = DissectorTable.get("udp.port")

udp_table:add(12345, chat_proto)

В листинге добавленная функция read_string() (1) принимает объект TVB (buffer) и начальное смещение (start) и возвращает длину буфера, а затем строку. 

Что, если строка длиннее диапазона байтового значения? Это одна из проблем анализа протокола. Если что-то вам кажется простым, то это не означает, что все на самом деле просто. Мы не будем обращать внимания на такие вопросы, как длина, потому что это лишь пример, а игнорирование длины подходит для любых примеров, которые мы перехватили. Имея функцию парсинга двоичных строк, теперь мы можем добавить команду Message в дерево анализа. Код начинается с добавления исходного дерева данных и создает новый объект TVB (2), который содержит только данные пакета. Затем он извлекает поле команды как целое число и проверяет, наша ли это команда Message (3). Если это не так, то мы покидаем дерево данных, но если поле совпадает, то мы приступаем к парсингу двух строк и добавляем их в поддерево данных (4). Однако вместо определения конкретных полей можно добавить текстовые узлы, указав только объект proto, а не объект поля.

Если вы теперь перезагрузите этот файл в Wireshark, то должны увидеть, что строки разобраны, как показано на ниже:


Поскольку проанализированные данные оказались фильтруемыми значениями, мы можем выбрать команду Message, задав для chat. command значение 3 в качестве фильтра отображения. Видно, что строки Username и Message сообщений были правильно проанализированы в дереве (2).

На этом мы завершаем краткий обзор по написанию диссектора на языке Lua для Wireshark. 

Удачи всем и Слава Украине!


No comments:

Post a Comment

А что вы думаете по этому поводу?