ПРОФИЛЬ 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
Самые внимательные, после прочтения и анализа данного текста, найдут тут уязвимость гораздо более серьезную чем экономическая, эта уязвимость открывает безграничные возможности, но пока без подсказки, тут все очевидно, удалось найти?
Комментировать могут только авторизованные пользователи Войти Зарегистрироваться -
Практика реверс-инжиниринга 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), и механизм самоуничтожения был заблокирован.
Теперь, когда клиент жив и работает в эмуляторе, открывается поле для реальной исследовательской работы. О которой я напишу во второй части. За лайки, я так пойму интересно ли Вам вообще :)))
-
Охота за 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 сотрудников из техотделов и отдела тестирования. Кто-то упоминал проект, кто-то оставлял номер в публичном профиле.
- Легкий социнжиниринг. В духе «Здравствуйте, я из такой то компании, как мне связаться с...».Буквально через пару часов у меня был список номеров, которые с высокой вероятностью принадлежали сотрудникам. Дальше дело техники и креатива.
Скажу сразу, нет я не поел, пока что, но я думаю уже совсем скоро у меня будут мои бургер и кола.
-
Часто вижу, что успешность тестирования на проникновение сводят к следованию правильной методологии (OSINT, сканирование, эксплуатация, пост-эксплуатация, отчет). Но на практике все сложнее и в ней преобладает хаотичность. Реальный взлом это не аудит по чек листу , а конкретные шаги "ЭТОГО".
Что на ваш взгляд на самом деле является ключевым фактором успеха?