У «Казаков» секретов нет

image

Думаю, многие из читателей с добрым словом вспомнят серию игр «Казаки», многочасовые баталии, военные хитрости и бесподобное звуковое сопровождение — отличная стратегия своего времени.

Спустя 15 лет они вернулись, и теперь уже в режиме онлайн, о проблемах и уязвимостях новой версии и пойдет речь в данной статье.

Дисклеймер


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

Структура сетевых пакетов


Все началось с простого вопроса — «как шифруются сетевые данные?», после отлова первого же пакета ответ стал очевиден — «никак». Никаких xor операций, никаких подписей, честные и правдивые байты.

В этот же момент (после осознания простоты возможного анализа), было принято решение идти дальше и попытаться воссоздать примитивную работу сервера (вход, регистрация, восстановление пароля), для этого требовалось понять:

  1. Что представляет из себя «шапка» пакета
  2. Как происходит сериализация и десериализация данных
  3. Где хранятся данные о серверах (IP, порт)

После отлова очередного десятка пакетов «шапка» стала яркой и весьма четкой:

struct NetPacketHeader {
    unsigned int Size; // размер чистых (без учета самой шапки) данных пакета
    unsigned char Direction; // уникальный идентификатор пакета
    unsigned char Mode; // предположительно описывает режим передачи пакета - броуд или приват 
    unsigned int SessionId0; // идентификатор пользователя, выданный сервером при входе
    unsigned int SessionId1; // тоже самое, но для цели, например: приватного чата
};

Тем же путем были выявлены основные черты сериализации:

  • Запись и чтение выполняется с помощью специальных классов (изначальная догадка была о том, что это просто приведенные структуры)
  • Строки бывают двух типов — малые и большие, размер строки указывается перед ее началом, у первых он — один байт, у вторых — два байта
  • Числовые данные записываются как есть (плавающие встретить не удалось)
  • Имеются специальные блоки, заполненные строго нулями, служат разделителем: 4 байта — конец массива, 6 байт — конец записи (изначально казалось, что это смещения структуры от компилятора)

А список серверов был найден обычным поиском по названию (data\resources\servers.dat), хранителем данных оказался читабельный скрипт собственного производства.

По завершению этих шагов разложение пакетов начало сводиться только к усидчивости и внимательности, например:

3D 00 00 00 9A 01 00 00 00 00 00 00 00 00 07 31 2E 30 2E 30 2E 37 05 31 2E 32 2E 33 14 61 61 61 61 61 61 61 61 61 61 40 67 6D 61 69 6C 2E 63 6F 6D 0A 61 61 61 61 61 61 61 61 61 61 0E 39 30 30 30 2D 38 30 30 30 2D 35 30 30 30
Size: 3D 00 00 00 (всего в пакете 75 байт, но 61 байт является телом, а 14 других шапкой)
Direction: 9A
Mode: 01
SessionId0: 00 00 00 00
SessionId1: 00 00 00 00

VersionStringSize: 07
VersionString: 31 2E 30 2E 30 2E 37 (1.0.0.7)
UpdateStringSize: 05
UpdateString: 31 2E 32 2E 33 (1.2.3)
EmailStringSize: 14
EmailString: 61 61 61 61 61 61 61 61 61 61 40 67 6D 61 69 6C 2E 63 6F 6D (aaaaaaaaaa@gmail.com)
PasswordStringSize: 0A
PasswordString: 61 61 61 61 61 61 61 61 61 61 (aaaaaaaaaa)
GameKeyStringSize: 0E
GameKeyString: 39 30 30 30 2D 38 30 30 30 2D 35 30 30 30 (9000-8000-5000)

Таков пакет запроса входа на сервер от клиента.

Работа с лобби


За короткий промежуток времени был сделан примитивный сервер (на основе asio), способный общаться с оригинальным клиентом, за час с небольшим он безупречно мог:

  1. Обработать запрос входа
  2. Обработать запрос регистрации
  3. Обработать запрос восстановления пароля
  4. Обработать приватное и публичное чат сообщение
  5. Выдать список пользователей на сервере, а так же лобби

Столь быстрая реализация бодрила лучше любого кофе и было решено потратить остаток ночи на работу с лобби, а именно:

  1. Создание / обновление / удаление
  2. Синхронизация пользователей (цвет флага, страна и пр.)
  3. Открытие / закрытие слотов создателем

С первым же пунктом начались не совсем понятные (по началу) проблемы:

Получилось отловить и разложить три пары (запрос клиента и ответ сервера) пакетов — создание, обновление (что включало в себя и удаление), а так же вход в публичное лобби.

Именно последнее и вызывало головные боли, вход в публичное лобби проходил на «ура», а вот в приватное не совсем, было не ясно, где искать введенный пользователем пароль, что-бы проверить верность данных, ведь пакет один — как для входа в публичное, так и для входа в приватное лобби, и он содержит лишь шапку и одно целое (идентификатор сессии создателя лобби), за объяснениями пришлось лезть «под капот», но не привыкшие к результату компиляции Delphi кода глаза ничего толкового так и не нашли.

В конечном итоге стало очевидным — сервер никак не обрабатывает пароли лобби, от слова «совсем», а значит в теории было возможным зайти в любое лобби и на оригинальном сервере, ведь клиент получает пароль в чистом виде.

Теория была доказана в три шага:

Получение пароля нужной комнаты
image

Проверка связи
image

Реакция пользователей
image

Все верно — приватное == публичное.

Остальные пункты повестки ночи прошли относительно без особых затруднений, синхронизация пользователей — простое зеркало, от одного пользователя ко всем участникам лобби, закрытие слота (например, что-бы выгнать участника из лобби) вызвало небольшое беспокойство, сервер принимает запрос на закрытие слота, если слот был занят участником — выгоняет его, посылая оповещение, но, если игнорировать пакет на стороне клиента — визуально мы остаемся в лобби, не теряя связь, вызывает ли это проблемы с запуском игры у оставшихся участников — хороший вопрос, ответ на который получить в такое время суток не удалось.

Так же не малое беспокойство вызвало и выдача имени ПК создателя лобби, зачем оно вообще выдается участникам, если создатель != хост — вопрос, на который еще предстоит ответить.

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

Подводя итоги


Любой программист, которому приходилось доводить сетевое приложение до публики так или иначе ощутил на себе главное правило: никогда не доверять клиенту.

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

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

Пример на GitHub
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама
Комментарии 14
  • +3
    Ответ от разработчиков был?
    • +3
      К сожалению нет, возможно и без ответа после публикации наложат шифрование пакетов, во всяком случае хочется в это верить.
    • –2
      … но не привыкшие к результату компиляции Delphi кода глаза ничего толкового так и не нашли.

      А разве Казаки на Delphi написаны?
      • +2
        Склоняюсь к этому
        Заголовок спойлера
        image
      • +2
        Да, на dou.ua один из разработчиков сообщал об этом. К сожалению, ссылку дать не могу, тема удалена, а в архиве не сохранено.
      • НЛО прилетело и опубликовало эту надпись здесь
        • –4
          Сомневаюсь в правдивости данной заметки, учитывая что игра таки на C++
          https://habrahabr.ru/post/318870/#comment_9992962
          • +7
            И все же, лично мое мнение, что это не может быть Borland C++ Builder, как мы знаем у Delphi есть особенности, например — процедуры, а так же разница между названиями фундаментальных типов на лицо — Boolean vs bool, Integer vs int, String vs char* (или std::string), поэтому как и написал выше, склоняюсь к правде о том, что это уникальная игра для 2016 года, написанная на Delphi и использующая Opengl.
            Отладочная информация, которая намекает
            image
            • +1
              Если она уникальная, а не «набор новых текстур» — как многие уверяют, то это же круто!
              А в том что на Delphi — не вижу совершенно никаких проблем, наоборот приятно.
          • 0
            >>>Другой разраб в том же топике поделился сокровенным: игра написана на древнем Delphi 7, и это в 2016 году, Карл, когда кругом полно бесплатных современных движков от Unreal Engine до Unity.

            У автора или язык косой или он не знает про что пишет.
          • +3
            Не понимаю источник сомнений. «Короткие» и «длинные» строки есть только в pascal/delphi, точка.

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