Как мы взломали шифрование пакетов в BattlEye

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Недавно Battlestate Games, разработчики Escape From Tarkov, наняли BattlEye для реализации шифрования сетевых пакетов, чтобы мошенники не могли перехватить эти пакеты, разобрать их и использовать в своих интересах в виде радарных читов или иным образом. Сегодня подробно расскажем, как мы взломали их шифрование спустя несколько часов.


Анализ EFT

Мы начали с анализа самого "Escape from Tarkov". В игре используется Unity Engine, который, в свою очередь, использует C# — промежуточный язык, а это означает, что можно очень легко просмотреть исходный код игры, открыв его в таких инструментах, как ILDasm или dnSpy. В этом анализе мы работали с dnSpy.

Unity Engine без опции IL2CPP генерирует игровые файлы и помещает их в GAME_NAME_Data\Managed, в нашем случае это EscapeFromTarkov_Data\Managed. Эта папка содержит все использующие движок зависимости, включая файл с кодом игры — Assembly-CSharp.dll, мы загрузили этот файл в dnSpy, а затем искали строку encryption и оказались здесь:

Этот сегмент находится в классе EFT.ChannelCombined, который, как можно судить по переданным ему аргументам, работает с сетью:

Правый клик по переменной channelCombined.bool_2, которая регистрируется как индикатор того, было ли включено шифрование, а затем клик по кнопке Analyze показывают нам, что на эту переменную ссылаются два метода:

Второй из них — тот, в котором мы сейчас находимся, так что, дважды щёлкнув по первому, мы окажемся здесь:

Вуаля! Есть вызов BEClient.EncryptPacket, клик по методу приведёт к классу BEClient, его мы можем препарировать и найти метод DecryptServerPacket. Этот метод вызывает функцию pfnDecryptServerPacket в библиотеке BEClient_x64.dll. Она расшифрует данные в пользовательском буфере и запишет размер расшифрованного буфера в предоставленный вызывающим методом указатель.

pfnDecryptServerPacket не экспортируется BattlEye и не вычисляется EFT, на самом деле он поставляется инициализатором BattlEye, который в какой-то момент вызывается игрой. Нам удалось вычислить RVA (Relative Virtual Address), загрузив BattlEye в свой процесс и скопировав то, как игра инициализирует его. Код этой программы лежит здесь.

Анализ BattlEye

В последнем разделе мы сделали вывод, что, чтобы выполнить все свои криптографические задачи, EFT вызывает BattlEye. Так что теперь речь идёт о реверс-инжинеринге не IL, а нативного кода, что значительно сложнее.

BattlEye использует защитный механизм под названием VMProtect, который виртуализирует и изменяет указанные разработчиком сегменты. Чтобы правильно выполнить реверс-инжинеринг защищённого этим обфускатором бинарника, нужно распаковать его.

Распаковка — это дамп образа процесса во время выполнения; мы сделали дамп, загрузив его в локальный процесс, а затем поработав в Scylla, чтобы сбросить его память на диск.

Открытие этого файла в IDA, а затем переход к процедуре DecryptServerPacket приведут нас к функции, которая выглядит так:

Это называется vmentry, она добавляет на стек vmkey, а затем вызывает обработчика виртуальной машины — vminit. Хитрость вот в чём: из-за того, что инструкции “виртуализированы” VMProtect, они понятны только самой программе.

К счастью для нас, участник Секретного Клуба can1357 сделал инструмент, который полностью ломает эту защиту, — VTIL; его вы найдёте здесь.

Выясняем алгоритм

Созданный VTIL файл сократил функцию с 12195 инструкций до 265, что значительно упростило проект. Некоторые процедуры VMProtect присутствовали в дизассемблированном коде, но они легко распознаются и их можно проигнорировать, шифрование начинается отсюда:

Вот эквивалент в псевдо-Си:

uint32_t flag_check = *(uint32_t*)(image_base + 0x4f8ac);

if (flag_check != 0x1b)
	goto 0x20e445;
else
	goto 0x20e52b;

VTIL использует свой собственный набор инструкций, чтобы ещё больше упростить код. Я перевёл его на псевдо-Си.

Мы анализируем эту процедуру, войдя в 0x20e445, который является переходом к 0x1a0a4a, в самом начале этой функции они перемещают sr12 — копию rcx (первый аргумент в соглашении о вызове x64 по умолчанию) — и хранят его на стеке в [rsp+0x68], а ключ xor — в [rsp+0x58]. Затем эта процедура переходит к 0x1196fd, вот он:

И вот эквивалент в псевдо-Си:

uint32_t xor_key_1 = *(uint32_t*)(packet_data + 3) ^ xor_key;
(void(*)(uint8_t*, size_t, uint32_t))(0x3dccb7)(packet_data, packet_len, xor_key_1);

Обратите внимание, что rsi — это rcx, а sr47 — это копия rdx. Так как это x64, они вызывают 0x3dccb7 с аргументами в таком порядке: (rcx, rdx, r8). К счастью для нас, vxcallq во VTIL означает вызов функции, приостановку виртуального выполнения, а затем возврат в виртуальную машину, так что 0x3dccb7 — не виртуализированная функция! Войдя в эту функцию в IDA и нажав F5, вы вызовете сгенерированный декомпилятором псевдокод:

Этот код выглядит непонятно, в нём какие-то случайные ассемблерные вставки, и они вообще не имеют значения. Как только мы отменим эти инструкции, изменим некоторые типы var, а затем снова нажмём F5, код будет выглядеть намного лучше:

Эта функция расшифровывает пакет в несмежные 4-байтовые блоки, начиная с 8-го байта, с помощью ключа шифра rolling xor.

Примечание от переводчика:

Rolling xor – шифр, при котором операция xor буквально прокатывается [отсюда rolling] по байтам:

  • Первый байт остаётся неизменным.

  • Второй байт — это результат xor первого и второго оригинальных байтов.

  • Третий байт — результат XOR изменённого второго и оригинального третьего байтов и так далее. Реализация здесь.

Продолжая смотреть на ассемблер, мы поймём, что она вызывает здесь другую процедуру:

Эквивалент на ассемблере x64:

mov t225, dword ptr [rsi+0x3]
mov t231, byte ptr [rbx]
add t231, 0xff ; uhoh, overflow

; the following is psuedo
mov [$flags], t231 u< rbx:8

not t231

movsx t230, t231
mov [$flags+6], t230 == 0
mov [$flags+7], t230 < 0

movsx t234, rbx
mov [$flags+11], t234 < 0
mov t236, t234 < 1
mov t235, [$flags+11] != t236

and [$flags+11], t235

mov rdx, sr46 ; sr46=rdx
mov r9, r8

sbb eax, eax ; this will result in the CF (carry flag) being written to EAX

mov r8, t225
mov t244, rax
and t244, 0x11 ; the value of t244 will be determined by the sbb from above, it'll be either -1 or 0 
shr r8, t244 ; if the value of this shift is a 0, that means nothing will happen to the data, otherwise it'll shift it to the right by 0x11

mov rcx, rsi
mov [rsp+0x20], r9
mov [rsp+0x28], [rsp+0x68]

call 0x3dce60

Прежде чем мы продолжим разбирать вызываемую им функцию, мы должны прийти к следующему выводу: сдвиг бессмыслен из-за того, что флаг переноса не установлен, а это приводит к возвращаемому из инструкции sbb значению 0; в свою очередь, это означает, что мы не на правильном пути.

Если поискать ссылки на первую процедуру 0x1196fd, то увидим, что на неё действительно ссылаются снова, на этот раз с другим ключом!

Это означает, что первый ключ на самом деле направлял по ложному следу, а второй, скорее всего, правильный. Хороший Бастиан!

Теперь, когда мы разобрались с реальным ключом xor и аргументами к 0x3dce60, которые расположены в таком порядке: (rcx, rdx, r8, r9, rsp+0x20, rsp+0x28). Переходим к этой функции в IDA, нажимаем F5 — и теперь прочитать её очень легко:

Мы знаем порядок аргументов, их тип и значение; единственное, что осталось, — перевести наши знания в реальный код, который мы хорошо написали и завернули в этот gist.

Заключение

Это шифрование было не самым сложным для реверс-инжиниринга, и наши усилия, безусловно, были замечены BattlEye; через 3 дня шифрование было изменено на TLS-подобную модель, где для безопасного обмена ключами AES используется RSA. Это делает MITM без чтения памяти процесса неосуществимым во всех смыслах и целях.

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

Узнайте, как прокачаться и в других специальностях или освоить их с нуля:

  • Профессия Data Scientist

  • Профессия Data Analyst

  • Курс по Data Engineering

Другие профессии и курсы

ПРОФЕССИИ

  • Профессия Fullstack-разработчик на Python

  • Профессия Java-разработчик

  • Профессия QA-инженер на JAVA

  • Профессия Frontend-разработчик

  • Профессия Этичный хакер

  • Профессия C++ разработчик

  • Профессия Разработчик игр на Unity

  • Профессия Веб-разработчик

  • Профессия iOS-разработчик с нуля

  • Профессия Android-разработчик с нуля

КУРСЫ

  • Курс по Machine Learning

  • Курс "Machine Learning и Deep Learning"

  • Курс "Математика для Data Science"

  • Курс "Математика и Machine Learning для Data Science" 

  • Курс "Python для веб-разработки"

  • Курс "Алгоритмы и структуры данных"

  • Курс по аналитике данных

  • Курс по DevOps

Источник: https://habr.com/ru/company/skillfactory/blog/553210/


Интересные статьи

Интересные статьи

В данной пошаговой инструкции мы подробно опишем весь процесс получения доступа к WhatsApp Business API через официального партнера Facebook — сервис Gupshup и подключени...
Удобное шифрование с удостоверяющим центром.Привет всем читателям Хабра! В этой статье рассмотрим проблемы и способы использования шифрования в документообороте. Перед ко...
Статья о том, как упорядочить найм1. Информируем о вакансии2. Ведём до найма3. Автоматизируем скучное4. Оформляем и выводим на работу5. Отчитываемся по итогам6. Помогаем с адаптацией...
Устраивать конкурсы в инстаграме сейчас модно. И удобно. Инстаграм предоставляет достаточно обширный API, который позволяет делать практически всё, что может сделать обычный пользователь ручками.
Тема статьи навеяна результатами наблюдений за методикой создания шаблонов различными разработчиками, чьи проекты попадали мне на поддержку. Порой разобраться в, казалось бы, такой простой сущности ка...