Реверсинг протокола Network Assistant 3.x — Архив WASM.RU
Содержание
Введение
В статье рассматривается реверсинг протокола чата Network Assistant 3.x. Это первая моя статья, поэтому можно считать, что она написана новичком для новичков, тем более что разработчики никакой защиты на программу не навешивали, код достаточно прост и ясен, а единственная проблема - его объём.
Идея поковыряться в чём-либо возникла, когда мне надоело писать свою собственную программу, поэтому выбор продукта по большому счёту случаен.
Network Assistant (далее сокращённо - Nassi) от Gracebyte Software представляет собой чат для локальной сети под Windows. Программа шароварная, однако в сети легко можно найти ключ. На настоящий момент актуальной является четвёртая версия, однако я буду рассматривать третью, т. к. именно этой версией пользуются в моей локалке
Версия 3.x поддерживает общие, приватные и защищённые паролем каналы, личные сообщения, доску рисования, смену состояний пользователей, передачу информации о версии чата, ОС, заголовке активного окна, просмотр и управление процессами, копии экрана, буфера обмена, статистики, сигнализаторы и т. д. К счастью, большую часть этих "возможностей" можно отключить. Чат не имеет сервера и работает через UDP-броадкаст (по умолчанию порты 50138/50139) и IP-мультикаст. Далее я буду ориентироваться на броадкаст т. к. его по историческим причинам используют в моей сети, хотя в общем-то формат пакетов Nassi от типа транспорта не зависит.
Прежде чем самому пытаться вскрыть протокол имеет смысл поискать в интернете открытую информацию. Побродив по сети можно выяснить следующее:
Во-первых. Nassi-пакет устроен следующим образом. Первый байт обозначает версию протокола: 0 для 2.x, 1 для 3.x. Далее идут два байта с длиной пакета. Это справедливо для 2.x и 3.x. Формат следующих далее данных зависит от версии чата. Структура пакета 2.x хорошо описывается в статье Эксплоит для сетевого чата в 48-м спецвыпуске Хакера (советую почитать, как очень неплохой туториал по реверсингу и написанию эксплоитов для начинающих). Для 3.x же все данные со смещения +3 зашифрованы.
Во-вторых. Для Nassi существует два бота: Vsbot и Krbot. Первый написан на Делфи (как, к слову, и сам чат) второй на Борланд С++, оба запакованы, однако распаковываются легко (самая длительная фаза - набор в адресной строке браузера http://cracklab.ru/download.php ). Боты работают только с запущенным чатом и используют сырые сокеты для перехвата его пакетов. Очевидно, что функционально они победнее чата, по объёму кода - всего на один десятичный порядок меньше, поэтому реверсить их особого смысла нет.
Из открытых источников известно, что разработчики чата помогли авторам ботов, а значит у последних должно быть точное описание формата пакетов. Мне удалось связаться с одним из них, однако поделиться информацией он отказался, сославшись на договорённости с Gracebyte. Сами же разработчики меня проигнорировали.
Итак. Для реверсинга нам потребуется собственно сам чат - я исследовал Network Assistant 3.2.5 build 2260, отладчик и дизассемблер - я использовал Ida Pro, что и вам советую
Первым делом скармливаем главный модуль Nassi.exe Иде (не забываем применить сигнатуры - Delphi, Borland VCL, CBuilder). Пока идёт анализ программы подумаем, откуда в листинге в несколько сот тысяч строк начинать поиск интересующего нас кода по формированию, шифровке, расшифровке и анализу пакета.
Во-первых, нам известен криптоалгоритм используемый чатом - Blowfish (об этом можно прочитать на SecurityLab и на форуме самой Gracebyte) и можно попытаться найти код, отвечающий за шифровку/дешифровку, если была использована более или менее стандартная реализация. Во-вторых, можно начинать с анализа вызовов Winsock API
Blowfish
Начнём с Blowfish. Я использовал вот это описание алгоритма: достаточно подробное, но без излишеств с примером реализации на Яве. Из него мы можем выделить следующие, предположительно полезные фрагменты:
- Blowfish — 64-битный блочный шифр с ключом переменной длины. Алгоритм включает в себя 2 части: часть расширения ключей и часть шифрования данных. Расширение ключа преобразует ключ, в большинстве 448-битный, в несколько суммированных массивов подключей в 4168 байт. Шифрование данных происходит через 16-итерационную сеть Feistel
- Blowfish использует большое количество подключей. Эти ключи должны быть предварительно вычислены перед любым шифрованием данных или расшифровкой
- P-массив включает 18 32-битных подключей
- Имеются четыре 32-битных S-блока с 256 входами каждый
- Подключи вычисляются, путем использования алгоритма Blowfish. Данный метод состоит в следующем:
- Сначала инициализируется P-массив и затем четыре S-блока, с фиксированной строкой. Эта строка состоит из шестнадцатеричных цифр pi (меньше начальной тройки).
- Произведите XOR P1 с первыми 32 битами ключа, XOR P2 со вторым 32-битами ключа, и так далее для всех битов ключа (возможно до P14). Циклически пройдите биты ключа, пока весь P-массив не будет "поXORен" с битами ключа. (Для каждой короткого ключа, имеется, по крайней мере, один эквивалентный более длинный ключ; например, если ключ 64-битный, тогда AA, AAA, и т.д., являются эквивалентными ключами.)
- Необходимо шифровать все пустые строки с помощью алгоритма Blowfish, используя подключи, описанные на шагах (1) и (2).
- Заменить P1 и P2 с использованием (3).
- Шифровать с изменяемым подключом, используя шаг (3).
- Заменить P3 и P4 с использованием шага (5).
- Продолжить процесс, заменяя все входы P- массива, и затем все четыре S-блока.
И так, попробуем найти Blowfish в Nassi. Для начала попробуем на удачу поискать в листинге дизассемблера строки типа blow, blowfish и т. п. К счастью, нам везёт и мы сразу находим приведённый ниже фрагмент кода (Примечание: его наличие показывает, что Gracebyte использовали при написании чата сторонний криптокомпонент, т. к. вывод такого сообщения Nassi не нужен в принципе - как мы увидим далее чат использует два типа ключей, длины которых - константы. Некоторое время, я искал его в сети и нашёл один фриварный, похожий на используемый в Nassi, однако т. к. он достаточно большой и поставляется без исходников, анализировать его не имеет смысла):
Код (Text):
CODE:004B476D jle short loc_4B4774 CODE:004B476F cmp ebx, 38h CODE:004B4772 jle short loc_4B478A CODE:004B4774 CODE:004B4774 loc_4B4774: ; CODE XREF: sub_4B4754+19.j CODE:004B4774 mov ecx, offset aBlowfishKeyMus ; "Blowfish: Key must be between 1 and 56 "... CODE:004B4779 mov dl, 1 CODE:004B477B mov eax, off_40860C CODE:004B4780 call unknown_libname_40 ; Delphi 5 Visual Component Library CODE:004B4785 call @System@@RaiseExcept$qqrv ; System::__linkproc__ RaiseExcept(void) CODE:004B478A CODE:004B478A loc_4B478A: ; CODE XREF: sub_4B4754+1E.jОчевидно, что перед нами проверка корректности Blowfish-ключа: если его длина (в ebx) ненулевая и меньше либо равна 56, исполнение продолжается, если нет - возбуждается исключение. Эта проверка - часть довольно большой функции sub_4B4754 (назовём её BlowInit), которая предположительно вычисляет подключи. Чтобы убедиться в этом проанализируем код в районе выше и ниже по листингу. При этом обнаруживаются следующие факты:
Во-первых, функция BlowInit вызывается согласно конвенкции thiscall и принимает четыре параметра (это видно по прологу и сгенерированному Идой заголовку функции и использованию регистров):
Аргументы функции:Код (Text):
CODE:004B4754 var_20 = dword ptr -20h CODE:004B4754 var_1C = dword ptr -1Ch CODE:004B4754 var_18 = dword ptr -18h CODE:004B4754 var_10 = dword ptr -10h CODE:004B4754 var_C = dword ptr -0Ch CODE:004B4754 var_8 = dword ptr -8 CODE:004B4754 var_4 = dword ptr -4 CODE:004B4754 arg_0 = dword ptr 8 CODE:004B4754 CODE:004B4754 push ebp CODE:004B4755 mov ebp, esp CODE:004B4757 add esp, 0FFFFFFE0h CODE:004B475A push ebx CODE:004B475B push esi CODE:004B475C push edi CODE:004B475D mov ebx, ecx CODE:004B475F mov [ebp+var_8], edx CODE:004B4762 mov [ebp+var_4], eax CODE:004B4765 mov esi, [ebp+arg_0] CODE:004B4768 lea edi, [ebp+var_18]
- eax - указатель на объект, назовём его TBlow
- edx - какой-то указатель
- ecx - длина ключа
- один аргумент в стеке - указатель
Во-вторых, функция BlowInit вызывается из двух мест в программе
- С адреса 004E19EF:
С, как мы видим, следующми аргументами:Код (Text):
CODE:004E19D9 mov eax, ds:off_52AC8C CODE:004E19DE push eax CODE:004E19DF mov edx, ds:off_52ADBC CODE:004E19E5 mov eax, ds:off_52AF7C CODE:004E19EA mov ecx, 0Ah CODE:004E19EF call BlowInit
- eax = off_52AF7C = offset dword_52D9A4
- edx = off_52ADBC
- ecx = 10
- off_52AC8C - через стек
- С адреса 00504EDB:
С аргументами:Код (Text):
CODE:00504EBD call GetTickCount_0 CODE:00504EC2 mov ds:dword_526644, eax CODE:00504EC7 push offset unk_526620 CODE:00504ECC mov edx, offset dword_526644 CODE:00504ED1 mov eax, offset unk_52D9A4 CODE:00504ED6 mov ecx, 4 CODE:00504EDB call BlowInit
- eax = dword unk_52D9A4
- edx = указатель на переменную хранящую текущий тик таймера
- ecx = 4
- offset unk_526620 в стеке
В-третьих, в районе адресов 004B478A - 004B4817 путем последовательных вызовов функции sub_4029C4 (копирует ecx байт с eax в edx) и sub_403324, которую у меня Ида распознаёт как @@FillChar (заполняет строку длиной edx по адресу eax байтом cl) инициализируются поля объекта TBlow. В итоге TBlow содержит следующие поля:
- Два блока по 8 байт, копируемых с буфера, на который указывает 4-й аргумент BlowInit
- 4096 байт, копируемых с адреса 0052472C
- 72 байта, копируемых с адреса 005246E4
Теперь самое время вспомнить, что для генерации подключей Blowfish использует P-массив размером в 72 байта и S-массив размером 4096 байт. В стандартной реализации для их инициализации используется число пи в двоичном виде без начальной тройки. Оно (точнее, первые 4096+72 байт) приведено в в этой статье. Как видим, реализация в Nassi не стала исключением. (К слову, если скормить Nassi.exe PEiD, он найдёт в ней несколько криптокомпонентов, причём в качестве сигнатур используются как раз эти четыре с небольшим килобайта - число пи)
И так, код инициализации Blowfish нами найден. Самое время найти функции шифровки и расшифровки. Очевидно, что они обращаются к полям объекта TBlow. Ставим бряк на 52D9A4 и аттачимся к уже запущенному процессу (чтоб не ловить инициализацию). После минутного эксперимента обнаруживается, что бряки часто срабатывают, когда программа обращается к TBlow с адреса 004B4A4E и иногда с 004029А3
Посмотри на код в районе 004B4A4E. Этот адрес находится в теле функции sub_4B4A30, которая вызывается по 16 раз из тел функций sub_4B4A78 и sub_4B4C58. В свою очередь sub_4B4A78 вызывается дважды с BlowInit и один раз с sub_4B4E38, которая в свою очередь вызывается в цикле практически сразу перед отправкой пакета функцией sendto:
Код (Text):
CODE:0050444B xor ebx, ebx CODE:0050444D CODE:0050444D loc_50444D: ; CODE XREF: sub_5042D4+199.j CODE:0050444D mov eax, ebx CODE:0050444F shl eax, 3 CODE:00504452 add eax, 3 CODE:00504455 lea ecx, buf[eax] CODE:0050445B lea edx, buf[eax] CODE:00504461 mov eax, offset dword_52D9A4 CODE:00504466 call sub_4B4E38 CODE:0050446B inc ebx CODE:0050446C dec esi CODE:0050446D jnz short loc_50444D CODE:0050446F CODE:0050446F loc_50446F: ; CODE XREF: sub_5042D4+174.j CODE:0050446F mov eax, offset dword_52D9A4 CODE:00504474 call sub_4B4EBC CODE:00504479 mov [ebp+to.sa_family], 2 CODE:0050447F mov eax, ds:off_52B3B8 CODE:00504484 mov ax, [eax] CODE:00504487 push eax ; hostshort CODE:00504488 call htons CODE:0050448D mov word ptr [ebp+to.sa_data], ax CODE:00504491 mov eax, [ebp+hostlong] CODE:00504494 push eax ; hostlong CODE:00504495 call htonl CODE:0050449A mov dword ptr [ebp+to.sa_data+2], eax CODE:0050449D push 10h ; tolen CODE:0050449F lea eax, [ebp+to] CODE:005044A2 push eax ; to CODE:005044A3 push 0 ; flags CODE:005044A5 mov eax, [ebp+len] CODE:005044A8 push eax ; len CODE:005044A9 push offset buf ; buf CODE:005044AE mov eax, ds:dword_526634 CODE:005044B3 push eax ; s CODE:005044B4 call sendtoФункция sub_4B4C58 вызывается из тела sub_4B4E6C которая вызывается в очень похожем цикле в теле sub_518534:
Код (Text):
CODE:005186A0 xor ebx, ebx CODE:005186A2 CODE:005186A2 loc_5186A2: ; CODE XREF: sub_518534+193.j CODE:005186A2 mov eax, ebx CODE:005186A4 shl eax, 3 CODE:005186A7 add eax, 3 CODE:005186AA mov edx, [ebp+var_11C] CODE:005186B0 lea ecx, [edx+eax] CODE:005186B3 mov edx, [ebp+var_11C] CODE:005186B9 add edx, eax CODE:005186BB mov eax, ds:off_52AF7C CODE:005186C0 call sub_4B4E6C CODE:005186C5 inc ebx CODE:005186C6 dec esi CODE:005186C7 jnz short loc_5186A2Данные циклы так и просятся, чтобы их назвали циклами кодирования и декодирования пакетов блоками по 8 байт со смещения +3. Однако в отличии от цикла loc_50446F, который находится практически сразу перед вызовом sendto, что выдаёт его "вину", никаких winsock выше loc_5186A2 не видно. Вместо них мы находим заголовок функции sub_518534 вызываемой из sub_512E44, которая в свою очередь вызывается по таблице. Однако, если посмотреть на код следующий за вызовом recvfrom по адресу 0050384B, обнаруживается, что в случае удачного вызова какому-то окну (с классом TfrmNassiMain, что, впрочем, не важно) отсылается сообщение 40Ah = WM_USER + 0Ah, wParam которого указывает на буфер содержащий в том числе и принятый пакет
Код (Text):
CODE:005038DB mov eax, [ebp+lParam] CODE:005038DE call sub_40278C CODE:005038E3 mov ebx, eax CODE:005038E5 mov edx, ebx CODE:005038E7 lea eax, [ebp+var_8] CODE:005038EA mov ecx, 4 CODE:005038EF call sub_4029C4 CODE:005038F4 lea edx, [ebx+4] CODE:005038F7 lea eax, [ebp+PerformanceCount] CODE:005038FA mov ecx, 8 CODE:005038FF call sub_4029C4 CODE:00503904 lea edx, [ebx+0Ch] CODE:00503907 lea eax, [ebp+buf] CODE:0050390D mov ecx, [ebp+wParam] CODE:00503910 call sub_4029C4 CODE:00503915 mov eax, [ebp+lParam] CODE:00503918 push eax ; lParam CODE:00503919 push ebx ; wParam CODE:0050391A push 40Ah ; Msg CODE:0050391F mov eax, ds:off_52B35C CODE:00503924 mov eax, [eax] CODE:00503926 call @Controls@TWinControl@GetHandle$qqrv ; Controls::TWinControl::GetHandle(void) CODE:0050392B push eax ; hWnd CODE:0050392C call PostMessageAЕсли поставить бряки на 0050392C и 00512E44 и посмотреть на значения в регистрах и памяти, на которую они ссылаются, то хорошо видно, что sub_512E44 не что иное как процедура обработки сообщения 40Ah
Теперь у нас есть практически все знания необходимые для создания собственного кодера/декодера пакетов в виде отдельной dll-ки. Осталось только узнать, каким ключем осуществляется шифрование. Для этого ставим бряк на функцию BlowInit и (на всякий случай) на объект TBlow. До отправки кодирования первого пакета бряк на BlowInit срабатывает дважды. Первый раз Nassi использует 4-байтный ключ - тик таймера, второй раз 10-байтный ключ с адреса 00526614. При этом параметр передающийся через стек указывает на 8-байтный блок по адресу 00526620. По-видимому, этот блок Очевидно, что подключи генерируемые первым вызовом игнорируются и шифровка/расшифровка использует только результат второго вызова.
Стартуя с BlowInit, loc_5186A2 и loc_50444D проходим по вложенным вызовам и выдираем из листинга данные и код (благо их немного), подчищаем, редактируем, создаём удобные нам входы для BlowInit, CryptPacket и DecryptPacket и компилируем. Подробнее я описывать не буду, т. к. это быстрее сделать, чем рассказать. В файлах идущих со статьёй есть dll-ка с "исходниками". В комментариях указаны адреса, с которых выдрана та или иная функция, так что разобраться думаю будет не сложно.
Заголовок
Начинать исследовать поля пакета можно тремя методами:
- Анализировать конструктор
- Анализировать получателя
- Связать вместе снифер и декодер и анализировать сами пакеты
Начнём с конструктора. Беглым просмотром от начала функции sub_5042D4 до цикла loc_50444D можно восстановить приблизительную структуру пакета:
Смещение Размер Назначение 0 1 Версия протокола; в нашем случае всегда 1 1 2 Размер пакета Далее все данные кодируются 3 1 Неизвестный байт 4 4 Неизвестный dword 8 1 Неизвестный байт 9 4 Неизвестный dword 13 ... Неизвестный блок ... ... Неизвестный блок ... 8 Время отправки пакета в формате TDateTime Далее предположительно идут собственно данные пакета Неизвестный байт по смещению +8 передается функции-сендеру (sub_5042D4), как первый параметр (в dl). Сама же функция вызывается с нескольких десятков мест с разными константными значениями dl. Предположительно этот байт определяет тип пакета
Неизвестные блоки, как это видно под отладчиком, - имя хоста и ник-нейм в виде pascal-строк, а двойное слово по +4 - инкрементальный счётчик.
Больше на поверхности здесь ничего вроде бы не лежит, поэтому можно перейти к анализатору пакетов - функции sub_518534. В ней мы найдём, что байт +3 должен быть меньше 2 (на практике - всегда 1), а байт +8 действительно определяет тип пакета (по адресу 005188CC и ниже распологается ветвление). Также легко обнаружить, что двойное слово +9 есть цвет пользователя в формате RGB
Итого заголовок Nassi-пакета имеет следующий вид:
Смещение Размер Назначение 0 1 Версия протокола; в нашем случае всегда 1 1 2 Размер пакета Далее все данные кодируются 3 1 Всегда 1 4 4 Счётчик 8 1 Тип пакета, Nassi-команда 9 4 Цвет пользователя 13 ... Pascal-строка, хост-нейм ... ... Pascal-строка, ник-нейм ... 8 Время отправки пакета в формате TDateTime Далее идут собственно данные пакета Тело
Чтоб выяснить ещё что-либо я написал снифер и прикрутил к нему декодер пакетов. Снифер вместе с исходниками приведён во вложении. Подробно описывать его не буду, скажу лишь, что он использует сырые сокеты для перехвата (информации об этом в сети достаточно), APC для корректного прерывания перехвата (см. статью Проблемы реализации многопоточных WinSockets-приложений в 11-м номере PC Magazine за 2005 год) и алгоритм приведённый в Зубкове для печати чисел в hex-формате. Остальное вроде бы вопросов вызывать не должно. Снифер игнорирует фрагментированные IP-пакеты, однако для исследования протокола это не важно
Написав снифер мы получаем относительно удобный инструмент для анализа пакетов и в короткое время можем узнать формат наиболее важных из них.
Например:
и т. д.
Смещение Размер Назначение Тип 36: Смена состояния 0 1 Номер состояние:
0.. 5 - встроенное (Свободен, Играю, Работаю, Не беспокоить, Секретное, Отошёл),
6.. 15 - определяемое пользователем1 1 Тип состояния: 1 для встроенного 2 ... Pascal-строка - название состояния ... ... Pascal-строка - описание состояния Тип 10: Сообщение в канал 0 1 Тип канала:
0 - обычный
1 - общий для всех
2 - запароленный
3 - приватный1 1 Pascal-строка, название канала ... ... Pascal-строка, пароль (только для 2-го типа) ... ... ASCIIZ-строка, сообщение в канал Всего типов сообщений - несколько десятков, для практических целей могут понадобиться:
2 - Запрос информации о пользователе
3 - Оповещение об активности окна
10 - Сообщение в канал
13 - Оповещение о присоединении к каналу
14 - Оповещение о выходе из канала
30 - Личное сообщение
35 - Оповещение о смене ника
36 - Оповещение о смене состояния
37 - Запрос состояния
130 - Сообщение информации о себе
и, возможно, некоторые другиеЯ не буду здесь приводить более подробное описание всех типов пакетов: любой желающий может восстановить его сам. Назову лишь варианты практического применения полученной информации:
- Написание собственного клиента или бота (Существующие боты работают через raw-сокеты только параллельно чату, а сам чат - лучший детектор ОС в сетях, где им активно пользуются )
- Написание флудилок
- Чтение сообщений из запароленных каналов
- Вместе с ARP-спуфером - перехват личных сообщений и сообщений из приватных каналов
- Написание nassi-шлюзов для общения в чате пользователей из различных сегментов
- Написание шлюзов а-ля nassi2irc
- Совместо с известным нюкером nassidos.pl написание полезных утилит (например: автокикалка любителей создавать общие для всех каналы, рассылать всплывающие сообщения, употребляющих "плохие слова", флудящих, имеющих ненравящийся вам цвет и т. п.)
(Примечание: В отличии от версии 2.х, насколько мне известно, серьёзной уязвимости, позволяющей написание эксплоита, в 3.х найдено не было. Однако 3.х подвержена DoS-атаке: программа не всегда корректно обрабатывает "мусорные пакеты" и может аварийно завершить работу. Подробнее об этом можно прочитать в частности на SecurityLab. Gracebyte выпустили два патча для устранения проблемы. При установке они обновляют версию Nassi до build 2261 (первый глючный патч) и 2262 соответственно, однако в настоящее время скачать их с сайта разработчиков нельзя - очевидно таким образом они пытаются убедить юзеров перейти на версию 4.х)Файлы к статье находятся здесь.
© Mescalito
Реверсинг протокола Network Assistant 3.x
Дата публикации 5 окт 2006