В моей предыдущей истории я успешно перепроектировал мобильную игру Tower Defense на Android, в которой используется Unity.

Изменить: Часть 3 завершена, обфускация обходится

У меня было несколько разговоров с компанией, стоящей за игрой, которая знала об исходной статье. С тех пор они обновили свой API, чтобы изменить свою методологию: они обновили хэш-соль и добавили шифрование. О нет, мы обречены! Или мы?

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

Положение вещей

Перво-наперво, я снова запустил свой mitmproxy и проверил, изменились ли вызовы API. Они обновили конечную точку, и теперь данные выглядят зашифрованными как в запросе, так и в ответе:

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

Затем, как и раньше, нам нужно выгрузить новые файлы libil2cpp.so и global-metadata.dat и запустить на них инструменты, а затем загрузить их в IDA. Поскольку мы уже знаем, где был загружен хеш (конструктор класса Crypto) и где были собраны пакеты (HttpClient), мы можем немного запустить эти части.

Копаемся в коде

Дав IDA время для загрузки нескольких тысяч функций, давайте взглянем на новый и обновленный метод HttpPost. С тех пор, как я начал чистый проект разборки, я изменил метку нескольких полей на основе выходных данных сборки Il2CppDumper DLL, перевернутых с помощью .NET Reflector, чтобы получить более чистый псевдокод:

Мы снова можем найти наш дружелюбный Crypto__ComputeHash, который вычисляет значение Hash на основе незашифрованных байтов, а затем те же байты передаются в метод Crypto.Encrypt. Итак, они действительно шифруют тело перед его отправкой. Проверка этого метода обнаруживает шифрование на основе Rijndael (AES):

Самое интересное в этом то, что они используют стандартные методы шифрования .NET, а это означает, что у нас будет все описание полей и детали прямо в документации .NET Framework, а также пример кода для правильной репликации их процесса шифрования и дешифрования. из MSDN.

Наконец, ключи и соли переместились из класса Crypto в отдельный класс CryptoConstants, но это не сильно изменит нашу работу. Наши милые разработчики игр даже помогли нам с размером в полях C #:

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

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

Отладка живого процесса на устройстве

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

Для этого мы устанавливаем сервер отладки IDA на наше устройство, запускаем его и используем ADB для перенаправления порта на нашу хост-машину через USB. Как только это будет сделано, мы можем подключить наш отладчик IDA к любому процессу на устройстве:

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

Во-первых, поскольку мы хотим сбросить байты, добавленные к телу JSON, прежде чем оно будет хешировано MD5, нам просто нужно установить точку останова, в которую добавляется окончательный массив «соли»:

Затем мы хотим сломать, когда используются ключи шифрования, чтобы мы могли выгрузить ключи AES и блоки памяти IV:

Обратите внимание: если вы хотите прервать часть кода, которая выполняется очень рано только один раз, вам необходимо подключить отладчик до того, как эти строки будут выполнены. Я не нашел никакого трюка, чтобы заставить IDA запускать приложение с нуля с немедленно подключенным отладчиком, но, к счастью, сборке Unity требуется некоторое время для загрузки, поэтому мы можем коснуться значка приложения, а затем сразу нажать Home, чтобы приостановить выполнение игры, давая нам время подключить отладчик IDA, а затем возобновить игру. Здесь мы прерываемся при любом вызове метода Decrypt, поэтому последующее присоединение - нормально.

Небольшое примечание о Dalvik и IDA: отладчик улавливает несколько сигналов, отправленных виртуальной машиной Dalvik, вызванных GC. Затем ожидаются такие сигналы, как SIGPWR и SIGXCPU, поэтому нам нужно установить их как «игнорировать и передавать приложению» в настройке отладчика, иначе IDA будет каждый раз прерывать выполнение.

Затем, после присоединения к запущенному игровому процессу, как только мы нажимаем кнопку, для которой требуются Decrypt и CalculateHash (любая кнопка, которая выполняет что-то онлайн, например, кнопка Tournament), отладчик приостанавливает работу в точках останова, которые мы установили ранее:

Если я дважды щелкну значение, помеченное мной как «vSalt», оно покажет значение памяти. Мы пропускаем первые 16 байтов (0x10), так как это заголовки и метаданные Mono для байтовых массивов, а затем мы можем увидеть значение соли прямо на глазах:

Однако AES IV и Key помещаются в необработанный адрес памяти, который IDA не обернула в качестве переменной, поэтому нам понадобится ручная математическая обработка мозга из псевдокода:

** (_ DWORD **) (dwCryptoClass + 80)
* (_ DWORD *) (* (_ DWORD *) (dwCryptoClass + 80) + 4)

Из предыдущей статьи мы знаем, что значение dwCryptoClass + 80 является указателем на значение первого поля класса, поскольку они начинаются со смещения 80 (0x50). Наше второе поле (IV) - это следующий указатель (+ 4) на значение, указанное dwCryptoClass + 80.

Итак, чтобы получить фактические значения для этих двух полей, нам сначала нужно взять указатель dwCryptoClass (в моем случае 0xCCA71300), а затем добавить 0x50. Это дает нам наш первый адрес указателя (указатели имеют размер 4 байта, так как это библиотека ARMv7, поэтому 32-битная адресация памяти):

Набор инструкций ARM также является прямым порядком байтов, поэтому наш адрес следует читать снизу вверх: 0xE6BD6D60. Затем мы должны перейти туда, чтобы получить фактические указатели на каждый массив байтов поля:

Здесь у нас есть два указателя рядом друг с другом. Если вы получите приоритет оператора прямо из фрагмента кода выше, наши ответы будут скрыты за этими двумя адресами: 0xE699F4D0 для первого поля (Crypto._aesKey) и 0xCB2EBDE8 для второго поля (Crypto._ivKey). Опять же, по каждому из этих адресов мы добавляем 0x10, чтобы получить фактические байты массива:

Мы знаем, что длина IV составляет 16 байтов, а длина ключа - 32 байта, что довольно стандартно, а также отмечено в документации Rfc2898DeriveBytes .NET (поскольку мы отметили из псевдокода, что они использовали этот класс), поэтому мы просто сбрасываем достаточно байтов из этих двух адресов памяти. Мы также можем вывести это из псевдокода.

Оттуда мы могли бы написать небольшое шифрование / дешифрование с использованием великолепного Golang, но, чтобы не тратить время на возню с настройками алгоритма, давайте повторно воспользуемся C # и расшифруем сообщение, которое мы сбросили ранее в mitmproxy:

Затем мы можем использовать ту же методологию для сброса ключа ComputeHash. Поскольку у нас есть прямая переменная vSaltBytes, которую мы смогли сопоставить в IDA, двойной щелчок показывает значение. Обновление нашего предыдущего вычисления хэша и передача ему нашего расшифрованного запроса приводит нас к тому же хешу, что и в запросе mitm’d.

Это снова мы! Мы успешно получили ключ AES и IV, которые позволяют нам зашифровать и расшифровать сетевые сообщения игры, и сбросили новую 16-байтовую (против 6 байтов в предыдущей статье) соль MD5. Обладая этой информацией, мы могли бы создать новое игровое чит-приложение и отправлять поддельные запросы на сервер.

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

Если вы с нетерпением ждете возможности проделать все это вокруг запутанного кода, ознакомьтесь со следующей частью!

Ваше здоровье!

📝 Прочтите этот рассказ позже в Журнале.

🗞 Просыпайтесь каждое воскресное утро и слышите самые интересные истории, мнения и новости недели, ожидающие в вашем почтовом ящике: Получите примечательный информационный бюллетень›