ПРОФИЛЬ niko13teen

Аватар niko13teen
niko13teen
12

⚫ Был недавно

Пентестер • Старший (Senior) • от 0 ₽
“BlackHat”
Не ищу работу
Связаться
Кубок Kraken Academy
#19
В рейтинге
12/50
Завершено модулей
10
Отзывов
1
Любимых модулей
0
Статей
  • @niko13teen

    Разработчик доверял клиенту. Я украл у него всю экономику. Вот как: Реверс-инжиниринг MMO RPG на Android (Часть 2)

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

    По задумке разработчика это выглядит так:

    Шаг 1: Клиент -> Сервер: «Хочу открыть магазин»
    Шаг 2: Сервер -> Клиент: «Проверяю условия: Уровень >=5? Золото >=500?» → Если нет → Ошибка.
    Шаг 3: Если да → Сервер списывает 500 золота.
    Шаг 4: Сервер -> Клиент: «Магазин открыт. Твое новое золото: [старый_баланс - 500]».

    Скомпрометированный сценарий:

    Шаг 1: Мы перехватываем ЛЮБОЙ запрос на проверку условий.
    Шаг 2: Подменяем ответ сервера с {"level":1,"gold":50} на {"level":5,"gold":500}. # Сервер отправляет клиенту информацию о уровне игрока и количестве его золота.
    Шаг 3: Приложение, получив ложное подтверждение, переходит к следующему шагу — отправке запроса на финальное создание магазина. Этот запрос содержит параметр gold: X, где X — это, по идее, оставшийся у клиента баланс ПОСЛЕ списания. # Катастрофическая ошибка, на этом моменте сервер доверяет клиенту
    Шаг 4: Сервер ДОВЕРЯЕТ клиенту в этом значении и УСТАНАВЛИВАЕТ баланс игрока равным X, а не ПРОВЕРЯЕТ корректность списания.

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

    А теперь посмотрим, как это происходит на уровне запросов:

    1. GET /rest/v1/players?select=level,gold&id=eq.8416f5d0-cf22-45a3-8a6a-cb53bc677e88 # запрашиваем данные игрока
    В ответе приходит {"level":1,"gold":50}, нам необходимо перехватить данный ответ и модифицировать его {"level":5,"gold":500} # так мы выполняем условия игры.
    2. PATCH /rest/v1/players?id=eq.8416f5d0-cf22-45a3-8a6a-cb53bc677e88 # после того, как мы нажали на кнопку "Open Shop", был отправлен PATCH запрос, который содержал остаток голды игрока
    {"gold":0}
    3. POST /rest/v1/player_shops?select=* # а после, POST запрос на создание магазина
    {"owner_id":"8416f5d0-cf22-45a3-8a6a-cb53bc677e88","shop_name":"EasyShop","is_open":true}

    Основным вектором атаки оказался этот запрос "PATCH /rest/v1/players?id=eq.8416f5d0-cf22-45a3-8a6a-cb53bc677e88", модифицируя параметр "gold": X, где X - это количество золота игрока после открытия магазина. Модифицируя этот параметр каждый раз (его можно отправлять множество раз), всегда будет установлено X-gold персонажа на стороне сервера.

    Что делать дальше?

    1. Как скрипт-кидди: крутануть себе триллион золота и получить пермач и исправление логической уязвимости за 10 минут.
    2. Как профессионал: не палить контору, подкручивать себе постепенно, но в пределах разумного, наша задача быть обнаруженным в последний момент.

    Переходи в мой ТГ, если нужно больше информации: https://t.me/HackDroid_Lab

    Самые внимательные, после прочтения и анализа данного текста, найдут тут уязвимость гораздо более серьезную чем экономическая, эта уязвимость открывает безграничные возможности, но пока без подсказки, тут все очевидно, удалось найти?

    Ответов: 0 Репостов: 1 Лайков: 5
  • @niko13teen

    Практика реверс-инжиниринга Android-приложений: Разбираем и нейтрализуем PairIP на реальном примере или как взломать MMO RPG. Часть 1.

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

    Так о чем я? Ах да, добро пожаловать в мир реального хакинга и реальных задач на интересных примерах. Кому нужно приложение пиццы, когда есть многопользовательская онлайн рпг? Но тут не все так просто, познакомьтесь с:

    PairIP — это коммерческая система защиты (DRM и анти-пиратства) для мобильных приложений, особенно популярная среди разработчиков на Flutter и React Native. Её главная задача — усложнить или сделать невозможным запуск приложения на неподдерживаемых или пиратских устройствах. А главное - усложнить жизнь хакерам. Как часто она встречается? Очень часто вы будете иметь с ней дело в низком и средних сегментах мобильных игр.

    Какую защиту предоставляет данная система:
    - Лицензионная проверка Google Play: Валидация легальности установки через сервисы Google.
    - Проверка целостности приложения (Integrity Check): Анализ сигнатуры APK, checksum критичных файлов, детект модификаций.
    - Детект нестандартного окружения: Выявление root: прав, запуска на эмуляторе (по таким признакам, как Build.FINGERPRINT, ro.boot.qemu), наличия инструментов отладки (Frida, Xposed).
    - Активное противодействие анализу: При обнаружении угрозы PairIP не просто сообщает об ошибке, а целенаправленно крашит приложение, вызывая System.exit() или выбрасывая необрабатываемые исключения. Это попытка "сломать" процесс динамического анализа.

    Первая кровь: Анализ и обнаружение угрозы.

    Наша цель — клиент MMO RPG (назовем её «game»), написанный на React Native. Задача проста: запустить его в контролируемой среде эмулятора (в моем случае Android SDK) для последующего анализа сетевого трафика и механик игры.

    Первый запуск — мгновенный провал. Ритуал знаком любому реверсеру: $adb multiple-install, $adb shell am start, и… ничего. Приложение запускается на долю секунды и бесшумно закрывается, не показав даже экрана-заставки. Никаких диалогов об ошибке, только черный экран. Это классическое поведение современной защиты — не спорить, не объяснять, а просто устранять угрозу.

    Logcat — наш лучший друг и свидетель.

    Когда приложение молчит, на помощь приходит системный журнал. Команда $adb logcat --pid=$(adb shell pidof com.game.app) мгновенно прояснила ситуацию:

    E LicenseClient: Error while checking license: com.pairip.licensecheck.LicenseCheckException: Licensing service could not process request.

    Вот он, «автограф» PairIP в дикой природе! Строка говорит нам многое:
    - Враг идентифицирован: Класс LicenseClient из пакета com.pairip.licensecheck.
    - Тактика противника: Не просто возврат false, а выбрасывание специального исключения LicenseCheckException.
    - Причина провала: Сервис лицензирования «не смог обработать запрос». На языке эмулятора это означает «я тебя вижу, самозванец».

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

    От разведки к стратегии: План контратаки.

    Анализ исключения и понимание архитектуры PairIP позволяют сформулировать четкий план обхода. Нам нужно создать многоуровневую защиту от самой защиты:
    - Нейтрализовать механизм самоуничтожения. Заблокировать все вызовы System.exit() и Runtime.exit(), чтобы приложение не могло закрыться по команде PairIP.
    - Обезвредить сигнализацию. Перехватить конструктор LicenseCheckException и сделать его безвредным, подменяя фатальное сообщение.
    - Подкупить или обмануть «стражей». Найти все методы проверки внутри LicenseClient (особенно те, что возвращают boolean) и заставить их всегда докладывать об успехе.

    Для реализации этого плана нет инструмента лучше, чем Frida — фреймворк для динамической инструментации, позволяющий в реальном времени модифицировать работу Java-кода и нативных библиотек. Наша задача — написать скрипт, который станет «теневым телохранителем» приложения, перехватывающим все враждебные команды.

    Код: Пишем «антикраж» для системы «антикража».

    # bypass_license.js
    Java.perform(function() {
    console.log('[+] Стартуем обход защиты Pairip... Берем в работу.');

    // 1. Первым делом нейтрализуем их главное оружие - исключение при проверке лицензии
    // Это как подставить подушку, когда кто-то пытается кричать "Пираты!"
    try {
    var LicenseCheckException = Java.use('com.pairip.licensecheck.LicenseCheckException');
    LicenseCheckException.$init.overload('java.lang.String').implementation = function(msg) {
    console.log('[ХУК] Поймали на слове! Хотели кинуть: "' + msg + '" - не получится.');
    // Всегда возвращаем "успешную" ошибку вместо настоящей
    return this.$init('ВСЁ ЧИСТО, ПРОПУСКАЕМ');
    };
    console.log('[✓] Исключение проверки лицензии теперь беззубое');
    } catch(e) {
    console.log('[!] Не удалось обезвредить исключение: ' + e);
    }

    // 2. Приложение любит самоубиваться через System.exit() при обнаружении взлома
    // Мы просто вырываем предохранитель из этой гранаты
    try {
    var System = Java.use('java.lang.System');
    System.exit.overload('int').implementation = function(status) {
    console.log('[ХУК] Пытается нажать красную кнопку (код ' + status + ')! Отключаем...');
    // Вместо выхода кидаем ошибку, которая остановит вылет
    throw Java.use('java.lang.RuntimeException').$new('Кнопка не работает, сорян');
    };
    console.log('[✓] Аварийное отключение приложения заблокировано');
    } catch(e) {
    console.log('[!] Не удалось заблокировать System.exit: ' + e);
    }

    // 3. Теперь атакуем ядро защиты - класс LicenseClient
    // Ищем ВСЕ методы, которые хоть как-то проверяют лицензию
    try {
    var LicenseClient = Java.use('com.pairip.licensecheck.LicenseClient');

    // Вытаскиваем все методы из класса как есть
    var methods = LicenseClient.class.getDeclaredMethods();

    console.log('[~] Сканируем методы проверки... Найдено: ' + methods.length);

    // Бежим по всем методам и ставим крючки где пахнет проверкой
    methods.forEach(function(method) {
    var methodName = method.getName();
    var returnType = method.getReturnType().getName();

    // Методы, возвращающие boolean с "check" в названии - точно проверки
    // Их заставляем всегда говорить "ДА, лицензия есть!"
    if (returnType === 'boolean' && methodName.toLowerCase().includes('check')) {
    console.log('[+] Ловим за руку: ' + methodName + ' -> всегда true');
    try {
    LicenseClient[methodName].implementation = function() {
    console.log('[ХУК] ' + methodName + ' спрашивает "Есть лицензия?" - отвечаем "ДА!"');
    return true; // Всегда врём, что лицензия есть
    };
    } catch(e) {
    // Иногда метод нельзя перехватить - идём дальше
    }
    }

    // Остальные подозрительные методы просто мониторим
    if (methodName.toLowerCase().includes('check') ||
    methodName.toLowerCase().includes('validate') ||
    methodName.toLowerCase().includes('verify')) {
    console.log('[~] Заметили подозрительный метод: ' + methodName);
    }
    });

    // Дополнительная ловушка - ищем живые экземпляры LicenseClient в памяти
    try {
    Java.choose('com.pairip.licensecheck.LicenseClient', {
    onMatch: function(instance) {
    console.log('[+] В памяти нашли работающий LicenseClient: ' + instance);
    // Тут можно с ним что-то сделать, но пока просто наблюдаем
    },
    onComplete: function() {
    console.log('[✓] Поиск экземпляров завершен');
    }
    });
    } catch(e) {
    // Не критично, если не найдёт
    }

    console.log('[✓] Глубокие хуки на LicenseClient установлены');
    } catch(e) {
    console.log('[!] Проблема с анализом LicenseClient: ' + e);
    }

    // 4. Ещё один путь для выхода - Runtime.exit(), перекрываем и его
    // Это как второй пожарный выход, который тоже запираем
    try {
    var Runtime = Java.use('java.lang.Runtime');
    Runtime.exit.overload('int').implementation = function(status) {
    console.log('[ХУК] Пробуют выйти через Runtime! Затыкаем дыру.');
    return; // Просто игнорируем вызов
    };
    console.log('[✓] Запасной выход через Runtime тоже заблокирован');
    } catch(e) {
    // Не страшно, если не сработает
    }

    console.log('[✓] ВСЁ ГОТОВО. Защита Pairip обезврежена.');
    });

    БЕЗ ЛИШНИХ СЛОВ АТАКУЕМ!

    $frida -U -f com.game.app -l .\bypass_license.js

    [Android Emulator 5554::com.game.app ]-> [+] Стартуем обход защиты Pairip... Берем в работу.
    [✓] Исключение проверки лицензии теперь беззубое
    [✓] Аварийное отключение приложения заблокировано
    [~] Сканируем методы проверки... Найдено: 31
    [~] Заметили подозрительный метод: checkLicenseInternal
    [~] Заметили подозрительный метод: lambda$initializeLicenseCheck$0
    [~] Заметили подозрительный метод: lambda$initializeLicenseCheck$1
    [~] Заметили подозрительный метод: lambda$reportSuccessfulLicenseCheck$0
    [+] Ловим за руку: performLocalInstallerCheck -> всегда true
    [~] Заметили подозрительный метод: performLocalInstallerCheck
    [~] Заметили подозрительный метод: populateInputDataForLicenseCheckV2
    [~] Заметили подозрительный метод: initializeLicenseCheck
    [~] Заметили подозрительный метод: reportSuccessfulLicenseCheck
    [✓] Поиск экземпляров завершен
    [✓] Глубокие хуки на LicenseClient установлены
    [✓] Запасной выход через Runtime тоже заблокирован
    [✓] ВСЁ ГОТОВО. Защита Pairip обезврежена.
    [ХУК] performLocalInstallerCheck спрашивает "Есть лицензия?" - отвечаем "ДА!"

    Это победа. Приложение не просто запустилось — оно запустилось, обманув собственную защиту. Система лицензирования сообщила об успешной проверке (performLocalInstallerCheck -> TRUE), и механизм самоуничтожения был заблокирован.

    Теперь, когда клиент жив и работает в эмуляторе, открывается поле для реальной исследовательской работы. О которой я напишу во второй части. За лайки, я так пойму интересно ли Вам вообще :)))

    Ответов: 0 Репостов: 0 Лайков: 3
  • @niko13teen

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

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

    Атака началась с классики — web-приложения компании. SQL-инъекции, XSS, подбор параметров... всего этого не было, я хочу бургер с колой, а не сушеные сухари. Увы, кроме пары тривиальных дефектов уровня «QA пропустил», ничего стоящего. Глубокий анализ js подарил несколько API-ключей к внутренним сервисам мониторинга, но это было не то. Двери в систему с фронтенда оказались надежно заперты. Пора было менять тактику. К слову, это заняло пару дней, по этому мой голод усилился. Если фронт крепок — ищи лазейку с фланга. А самым сочным флангом сегодня почти всегда является мобильное приложение.

    Я быстро развернул эмулятор, настроил проксирование трафика через Burp и принялся за обход SSL Pinning — стандартный ритуал перед штурмом. К моему удивлению, live-трафик приложения тоже не блистал уязвимостями. Эндпоинты были прилично защищены, токены валидировались. Но у меня оставался козырь — статический анализ. И тогда я достал Jadx. Обфускация ProGuard была, но настолько «тривиальная», что логика приложения и строковые константы остались читаемыми. Далее немного grep и find, пара сиг и мы нашли то, что искали. В груде мусора и старых данных лежал ничем не примечательный /api/user?phone=<phone.number> (формат изменен для безопасности).

    Я тут же протестировал его. Подставил свой номер — получил в ответ JSON не только со своими данными, но и с:

    - Адресами доставки (всеми когда-либо использованными)
    - Балансом бонусного счета
    - Активными промокодами
    - Токеном корзины (cart_token) — это был динамический ключ, который позволял оформить заказ от лица этого пользователя.
    - И другая информация

    Но я не был авторизован в этот момент. Получается, данную инфу можно получить просто так, зная лишь номер телефона. Я это проверил и это было действительно так. Классический IDOR, но не на текущем API, а на каком-то легаси-эндпоинте, который, видимо, забыли выключить. Достаточно знать номер телефона — и перед тобой открывается профиль любого пользователя.

    «Но чтобы получить данные, нужен номер телефона», — скажете вы. И будете правы. Но это не барьер, а следующая задача. Мне же нужны были не случайные клиенты, а именно «золотые» промокоды, доступные сотрудникам.

    В дело вступил OSINT:

    - Вакансии. На сайтах по поиску работы HR компании оставляют свои контакты. Целый список корпоративных номеров — готов.
    - Соцсети. Анализ VK, Telegram сотрудников из техотделов и отдела тестирования. Кто-то упоминал проект, кто-то оставлял номер в публичном профиле.
    - Легкий социнжиниринг. В духе «Здравствуйте, я из такой то компании, как мне связаться с...».

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

    Скажу сразу, нет я не поел, пока что, но я думаю уже совсем скоро у меня будут мои бургер и кола.

    Ответов: 0 Репостов: 0 Лайков: 4
  • @niko13teen

    Часто вижу, что успешность тестирования на проникновение сводят к следованию правильной методологии (OSINT, сканирование, эксплуатация, пост-эксплуатация, отчет). Но на практике все сложнее и в ней преобладает хаотичность. Реальный взлом это не аудит по чек листу , а конкретные шаги "ЭТОГО".
    Что на ваш взгляд на самом деле является ключевым фактором успеха?

    Ответов: 0 Репостов: 1 Лайков: 3