HOME FORUMS MEMBERS RECENT POSTS LOG IN  
× Авторизация
Имя пользователя:
Пароль:
Нет аккаунта? Регистрация
Баннер 1   Баннер 2
НОВЫЕ ТОРГОВАЯ НОВОСТИ ЧАТ
loading...
Скрыть
Вернуться   ANTICHAT > БЕЗОПАСНОСТЬ И УЯЗВИМОСТИ > Уязвимости > Уязвимости CMS / форумов
   
Ответ
 
Опции темы Поиск в этой теме Опции просмотра

  #1  
Старый 03.05.2020, 01:27
testbot
Новичок
Регистрация: 02.05.2020
Сообщений: 1
С нами: 3176218

Репутация: 0
По умолчанию

Часть 1- логин сервер
Как-то раз в далеком 2017 году понадобилось мне разобрать одну популярную мобильную игру того времени- Зитву Бамков. Я уверен, многим из вас приходилось играть в нее, когда она была на пике популярности. На данный момент игра стала умирать. Разработчики отдают внутриигровые ресурсы за копеечный донат. По просьбе знакомого, и из-за обесценивания этой информации, публикую обзор полного взлома игры: от реверса, до создания альтернативного сервера и накрутки валюты гильдии, на которую даже сделали обзор некоторые ютуберы (да-да, Князь, привет). За это время у меня сохранились не все материалы, которые стоило бы использовать в статье. Поэтому в части скриншотов будет показана старая версия игры, а в части- новая. Однако мой сервер все еще работает с новой версией игры. Значит, можно надеяться, что обновления обратно совместимы

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

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

Легко заметить, что приложение написано на cocos2dx.
Например, если посмотреть smali код CastleClashActivity, можно заметить

Код:


Код:
.method public constructor ()V
    .locals 1

    .line 50
    invoke-direct {p0}, Lorg/cocos2dx/lib/Cocos2dxActivity;->()V

    const/4 v0, 0x0

    .line 67
    iput-boolean v0, p0, Lcom/igg/castleclash/CastleClashActivity;->isOfflineBack:Z

    .line 306
    iput-boolean v0, p0, Lcom/igg/castleclash/CastleClashActivity;->isChangeAccount:Z

    return-void
.end method
что оно наследует Cocos2dxActivity.

Cocos2dx это c++ форк игрового движка cocos2d, написанного на питоне
Если еще немного посмотреть smali код, то можно заметить, что он исключительно определяет не очень интересные функции-хелперы, и через JNI передает управление библиотеке libgame.so. Из чего можно сделать вывод, что самое интересное происходит в нативе

1. Анализ структуры траффика
Самый очевидный ход перед погружением в реверс натива. Может дать представление об устройстве механизма работы с сервером. Для этой цели буду использовать андроид приложение packet capture. Оно удобно тем, что позволяет записывать трафик только нужного приложения.







Что мы видим:
  • Клиент взаимодействует с двумя серверами. Вначале стучится к логин серверу (порт 9300), у которого получает данные игрового сервера. Потом- собственно к игровому (порт 9339)
  • Приложение общается с сервером через tcp сокет. Причем почему-то без ssl.
  • Трафик клиента вначале идет в открытом виде, а потом начинает шифроваться. Трафик сервера всегда не зашифрован (видно по кускам строк).
  • Используется little endian
Если внимательно посмотреть на данные, то можно угадать структуру пакетов. Вначале идут два short'a: размер пакета, и непонятная константа. Эта константа- идентификатор типа пакета, в зависимости от которого выбирается обработчик.

2. Реверс
Нужен для анализа игровой логики

Первым делом определим, как устроено взаимодействие с сервером на самом деле, и каким образом шифруются данные на клиенте

За взаимодействие с сервером тут отвечает класс NetMessage. Вот его методы слева направо:


За шифрование пакетов, очевидно, отвечают методы EncryptMsg, и DecryptMsg.
Посмотрим на EncryptMessage



Он использует метод ELangh класса Langh- набора криптографических утилит, который используется как синглтон. Всё приложение работает с одним его экземпляром
Внутри творится криптографическая магия. Однако прогуглив интересные методы Langh, я нашел библиотеку, которая, похоже, была перепилина в этот класс- http://read.pudn.com/downloads190/sourcecode/crypt/891222/DES Tool/yxyDES2.cpp__.htm .
Вот метод EncryptData из этой библиотеки, и кусок ELangh из игры. Почти одно и то же





Значит, игра использует des для шифрования. Но, как мы знаем, des- блочный шифр. Какой паддинг используется для данных, размер которых не кратен 8 байтам?
Как оказалось, никакого паддинга там и нет. Китайцы используют гениальное решение- шифруют packet_data[acket_data_size // 8] (в терминах питона), и дописывают в конце packet_data[packet_data_size // 8:] в исходном виде.
По-моему это очень странно. Оставим это дело на их совести

Для создания декодера пакетов осталась всего одна деталь- ключ des. Поискав по xref'ам метода Langh::InitializeK, я быстро нашел установку ключа



Им оказалась строка "L*#)@!&8".

У нас есть все для создания прокси-сервера, который будет декодировать, и логировать пакеты

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



Описываем интерфейс пакета



И три сущности: ClientPacket, EncryptedClientPacket, ServerPacket, Packet(для унификации механизма чтения и отправки пакетов. Кастится к EnctyptedClientPacket, и ServerPacket)
Делаем чтение, и отправку



Прокси логин-сервер будет состоять из двух корутин
Первая получает пакет у клиента, дешифрует (превращает EncryptedClientPacket в ClientPacket), печатает нам его содержимое и отправляет на сервер
Вторая получает пакет у сервера, печатает содержимое, и отправляет клиенту

Итоговый код прокси логин-сервера у меня выглядит так:

Код:


Код:
class LoginServer(serverIp: String = "0.0.0.0", val loginServerIp: String) :
    TcpServer(serverIp, LOGIN_SERVER_PORT) {

    var serverProcessors = listOf(LoginServerServerPacketProcessor())
    var clientProcessors = listOf(LoginServerClientPacketProcessor())

    override suspend fun processClient(iggChannel: IGGChannel) {
        val loginSocket = aSocket(ActorSelectorManager(Dispatchers.IO)).tcp()
            .connect(InetSocketAddress(loginServerIp, LOGIN_SERVER_PORT))
        val loginChannel = IGGChannel(loginSocket)

        var stop = false

        val gameSession = GameSession()

        coroutineScope {
            launch {
                while (!stop) {
                    try {
                        var serverPacket: ServerPacket? = ServerPacket(loginChannel.readPacket())
                        println("Login server: $serverPacket")

                        val packetType = serverPacket!!.type

                        val processor = serverProcessors.firstOrNull {
                            it.packetType == serverPacket!!.type
                        }

                        if (processor != null) {
                            val newPacket = processor.process(serverPacket, gameSession)
                            if (serverPacket.smartBuffer.unpackedItemsInfo.isNotEmpty()) {
                                println("Login server: ${serverPacket.toSplitHexString()}")
                                println("Login server: ${serverPacket.toPacketContentString()}")
                            }
                            serverPacket = newPacket
                        }

                        if (serverPacket == null)  // Processor has drop the packet
                            println("Login server: Packet was dropped- $packetType")
                        else
                            iggChannel.sendPacket(serverPacket.asPacket())

                    } catch (e: Exception) {
                        stop = true
                        iggChannel.close()
                    }
                }

            }

            launch {
                while (!stop) {
                    try {
                        val encryptedClientPacket = EncryptedClientPacket(iggChannel.readPacket(), gameSession.isEncryptionEnabled)

                        var clientPacket: ClientPacket? = ClientPacket(encryptedClientPacket)
                        println("Login server: $clientPacket")
                        val packetType = clientPacket!!.type

                        val processor = clientProcessors.firstOrNull {
                            it.packetType == clientPacket!!.type
                        }

                        if (processor != null) {
                            val newPacket = processor.process(clientPacket, gameSession)
                            if (clientPacket.smartBuffer.unpackedItemsInfo.isNotEmpty()) {
                                println("Login server: ${clientPacket.toSplitHexString()}")
                                println("Login server: ${clientPacket.toPacketContentString()}")
                            }
                            clientPacket = newPacket
                        }

                        if (clientPacket == null) {
                            println("Packet was dropped: $packetType")
                        } else {
                            loginChannel.sendPacket(EncryptedClientPacket(clientPacket, gameSession.clientPacketSerialNum?.inc()).asPacket())
                        }
                    } catch (e: Exception) {
                        stop = true
                        loginChannel.close()
                    }
                }
            }
        }

        println("Disconnected")
    }
Адрес логин сервера клиент получает из конфига (http://config.igg.com/appconf/1030059902/server_config), адрес которого лежит в assets/config.xml
Деплоим свой конфиг, скопировав содержимое оригинального, c нужным адресом в секции LoginServer. Меняем адрес конфига в assets/config.xml на наш. Пересобираем приложение
В расшифрованных телах пакетов клиента в первых четырех байтах идет инкрементирующееся чисто- порядковый id отправленного пакета. К сожалению, архитектура у меня не позволяет выводить полное содержимое пакета, ибо при инжекте пакета старый порядковый id будет все ломать. Поэтому я не смог показать полные данные пакета с ним. Вот вывод моего реверс прокси. Как видите, у меня организована авторазметка пакетов



При подключении клиент сервер отправляет igg id, токен, и версию игры
В ответ логин сервер отправляет данные игрового сервера, и игровой токен
 
Ответить с цитированием

  #2  
Старый 03.05.2020, 07:32
Qx_ Bit
Новичок
Регистрация: 02.05.2020
Сообщений: 0
С нами: 3175853

Репутация: 0
По умолчанию

Прикольно, можно попробовать)
 
Ответить с цитированием

  #3  
Старый 05.05.2020, 01:04
wooolff
Новичок
Регистрация: 19.02.2017
Сообщений: 0
С нами: 4856945

Репутация: 0
По умолчанию

я немного потерялся, ты пишешь про exe, java, более конкретней можешь.
- исследуем такое то приложение, столько то файлов, такие и такие я исследую тем, а такие темто и т.д....
прикольно, но получилось как фильм от первого лица когда главный герой снимает камерой, а все остальное додумуй сам)))))))))))
а так от меня лайк)))
 
Ответить с цитированием

  #4  
Старый 10.05.2020, 21:19
mrtyrel
Новичок
Регистрация: 28.04.2020
Сообщений: 0
С нами: 3180724

Репутация: 0
По умолчанию

Я 0 в реверсе но статью прочел полностью очень интересно!
 
Ответить с цитированием
Ответ





Здесь присутствуют: 1 (пользователей: 0 , гостей: 1)
 


Быстрый переход




ANTICHAT ™ © 2001- Antichat Kft.

×

Создать сделку

Продавец: ник или ID

Название сделки:

Сумма USDT:

Срок сделки, дней:

Кто платит комиссию:

Условия сделки:

После создания сделки средства будут зарезервированы в холде до завершения сделки.

×

Мои сделки

Загрузка...
×

Сделка


Загрузка чата...
×

ESCROW ADMIN PANEL

Загрузка...
Загрузка...