Files
mechanic-pwa/recon/findings/01-static-analysis.md
T

20 KiB
Raw Blame History

Этап 1. Статический анализ — отчёт

  • Дата: 2026-05-16
  • Аналитик: vladtechno@gmail.com + Claude
  • Источники: локальные APK в recon/artifacts/, jadx 1.5.5 декомпиляция

1. Что разбирали

Два приложения одного вендора NaughtySoft:

Приложение APK Версия Назначение
«Водитель» com.naughtysoft.TtcDriver (XAPK split) 1.2.313 (build 323) Основное приложение для водителей: балансы, штрафы, платежи, обращения, осмотры
«Механик» com.naughtysoft.ttc 1.8.13 (build 165) Приложение для механиков парка: фото машин в 1С

Оба от одного вендора (com.naughtysoft.*), технологический стек разный (см. §3) — признак того, что вендор находится в процессе технологической миграции и не унифицирует разработку.


2. Метаданные APK

Водитель (com.naughtysoft.TtcDriver)

Параметр Значение
Version 1.2.313 (build 323)
Target SDK 36 (Android 16)
Min SDK 24 (Android 7)
Main Activity com.naughtysoft.TtcDriver.MainActivity
Permissions INTERNET, CAMERA, RECORD_AUDIO, STORAGE, WAKE_LOCK, POST_NOTIFICATIONS, FCM (c2dm.permission.RECEIVE), AD_ID, ACCESS_NETWORK_STATE
Permissions, которых НЕТ GPS (ACCESS_FINE/COARSE_LOCATION), SMS, READ_CONTACTS, READ_PHONE_STATE, BIND_NOTIFICATION_LISTENER
android:usesCleartextTraffic true (приложение умеет HTTP без TLS)
network_security_config.xml отсутствует (TLS pinning через XML не настроен)
Распространение XAPK с App Bundle (base + split: arm64_v8a, en, mdpi, zh)
Native ABI только arm64_v8a (нет x86_64, нет armeabi-v7a)

Отсутствие GPS-разрешений = приложение пассивное, не трекает позицию водителя. Отсутствие SMS-разрешений = OTP-перехвата автоматического нет, логин классический.

Механик (com.naughtysoft.ttc)

Параметр Значение
Version 1.8.13 (build 165)
Target SDK 33 (Android 13)
Min SDK 19 (Android 4.4)
Application Label «Механик»
Permissions INTERNET, CAMERA, RECORD_AUDIO, ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION, STORAGE, VIBRATE, NETWORK_STATE, WIFI_STATE
Native ABI только arm64_v8a

В Механике в отличие от Водителя есть GPS и используется Xamarin (см. §3).


3. Технологические стеки

Водитель — Flutter (Dart AOT)

Подтверждено наличием:

  • lib/arm64-v8a/libflutter.so — Flutter engine (11 МБ)
  • lib/arm64-v8a/libapp.soDart AOT snapshot с бизнес-логикой (9.6 МБ)
  • Java-пакеты: io/flutter/, dev/flutter/pigeon, _COROUTINE/, Kotlin runtime
  • Flutter-плагины: dev/fluttercommunity/plus, com/baseflow/permissionhandler, com/jrai/flutter_keyboard_visibility, io/scer/pdfx, studio/midoridesign/gal, io/scer/pdf_renderer, dev/fluttered/map_launcher

Что это значит для реверса:

  • Бизнес-логика приложения — в Dart, скомпилированном AOT в libapp.so. jadx-декомпиляция Java/Kotlin даёт только тонкие plugin wrappers, не бизнес-код.
  • Все строковые константы (URL, paths, ключи) хранятся в libapp.so как plain ASCII → извлекаемы через grep -a/strings (см. §4).
  • HTTP-запросы Flutter делает через стандартный Dart HttpClient → видимы в mitmproxy как обычный HTTPS (без custom низкоуровневых трюков).
  • Дальнейший реверс Dart-кода (поведение, проверки) — через reFlutter (требует пересборки snapshot) или динамический анализ через mitmproxy. На статическом этапе ограничиваемся извлечёнными строками.

Механик — Xamarin (.NET/Mono)

Подтверждено наличием:

  • Java-пакеты: xamarin/, mono/, crc64xxxxxxxxxxxxxxxx/ (CRC64-обфускация имён классов — характерный паттерн Xamarin)
  • assets/assemblies/assemblies.blob (4 МБ) — упакованный контейнер всех .NET DLL
  • assets/assemblies/assemblies.manifest (1.6 КБ) — манифест содержимого blob

Что это значит для реверса:

  • jadx даёт ноль информации о логике Механика (всё в C# DLL).
  • Для распаковки нужен pyxamstore (распаковать blob → отдельные DLL) + dnSpy/ILSpy для C# декомпиляции.
  • Это отдельный поток работы, отложен. Приоритет — водитель.

Вывод о стеке вендора: Xamarin (Mechanic) сейчас deprecated Microsoft'ом в пользу .NET MAUI. Driver переписан на Flutter — современный стек. Вендор находится в процессе технологической миграции, что согласуется с фактом «прекратил доработки» — ресурсы ушли на перенос/переход, а не на обновление наших фич.


4. Сетевая инфраструктура

Все извлечены из libapp.so водительского приложения через grep -aoE 'https?://...'.

Бэкенды

Endpoint Назначение
https://api.ttcontrol.naughtysoft.ru/api Главный API вендора NaughtySoft. Основной канал общения приложения с сервером.
https://taksi.0nalog.com:1703/ Нестандартный порт 1703 — классический паттерн HTTP-сервиса 1С Предприятие. С большой вероятностью это сам сервер 1С PremiumPark, на котором работает конфигурация вендора. Имя 0nalog (вместо nalog) — возможный obscurity-приём.
https://ttcdriver.firebaseio.com Firebase Realtime Database. Помимо FCM-пушей, часть данных может синхронизироваться через Realtime DB. Project ID: ttcdriver.

Архитектурная гипотеза

Driver App (Flutter)
    │
    ├──► api.ttcontrol.naughtysoft.ru (HTTPS, основное взаимодействие)
    │         │
    │         └──► (на бэке вендора, вероятно) ──► taksi.0nalog.com:1703 (1С)
    │
    ├──► taksi.0nalog.com:1703 (возможно, прямые запросы — нужно подтвердить на этапе 2)
    │
    ├──► ttcdriver.firebaseio.com (Realtime DB + FCM регистрация)
    │
    └──► FCM (Firebase Cloud Messaging — push-уведомления о штрафах и пр.)

Подтвердить факт прямых обращений на 0nalog:1703 (или это идёт только через прокси api.ttcontrol) — задача этапа 2 (mitmproxy).

Карта API endpoints

Извлечены пути /v1/... и /v3/... из libapp.so. Префикс домена — api.ttcontrol.naughtysoft.ru/api.

Endpoint Назначение (гипотеза)
/v1/GetBalance Остаток баланса водителя
/v1/GetFines Список штрафов
/v1/GetChecks Чеки 54-ФЗ
/v1/GetPayroll Зарплата/начисления v1
/v3/GetPayroll Зарплата/начисления v3 (новее)
/v1/CreatePayment Инициация платежа
/v1/GetPaymentDetails/ Детали платежа
/v1/BanksSBP Список банков СБП — платежи через Систему Быстрых Платежей
/v1/ChangeQIWIWalletCardNumber QIWI Wallet (legacy — QIWI закрылся в 2024, мёртвый код)
/v1/CreateAppeal Создать обращение/заявку
/v1/GetAppealHistory История обращений водителя
/v1/AddMessageAppeal Добавить сообщение к обращению
/v1/AddInspection Добавить осмотр (фото повреждений)
/v1/FOTO/ Endpoint для фотографий
/v1/AttractedAdd Добавить привлечённого (реферал?)
/v1/AttractedList Список привлечённых
/v1/OnLine Heartbeat / online status
/v1/GetStatus/ Статус (требует расшифровки на этапе 2)
/v1/Sell Продать что-то (требует расшифровки)
/v1/GetListOfQuestions Список вопросов (FAQ / скрининг)

Аутентификация

Найдены характерные строки в libapp.so:

  • Authorization — заголовок HTTP
  • Bearer — Bearer-токен
  • apiKey — упоминание (вероятно Firebase API key)
  • authorization (lowercase) — тоже встречается

Гипотеза: стандартная Bearer-token аутентификация. Логин → получение access_token → дальше во всех запросах Authorization: Bearer <token>. Подтвердить и понять lifecycle (refresh? expiry?) — задача этапа 3.


5. Защитные механизмы

TLS pinning

  • На уровне Android system (network_security_config.xml): отсутствует — конфиг не найден в декомпиле.
  • Cleartext traffic разрешён — приложение умеет HTTP без TLS (вероятно для taksi.0nalog.com:1703).
  • Программный TLS pinning в Dart-коде: возможен, но в libapp.so явных маркеров pinning'а (типа setTrustedRoots, custom SecurityContext с pinned certs) поверхностный grep не выявил. Проверится на этапе 2: если mitmproxy сразу читает трафик — pinning'а нет; если нет — нужен reFlutter/Frida.

Прогноз: на этапе 2 mitmproxy будет работать без обхода TLS pinning. Это сильно упрощает разведку.

Root/Emulator detection

Не сканировалось специально (требует анализа Dart-кода в libapp.so). На этапе 2 проверим эмпирически — запустится ли приложение на эмуляторе. Учитывая что:

  • Приложение Flutter (без agressive anti-debug по умолчанию)
  • Нет premium-фич, ориентированных на security (банковский класс, антифрод)

— вероятность блокирующего detection'а низкая.

Request signing / HMAC

Маркеров явного HMAC (типа Mac.getInstance("HmacSHA256")) в Kotlin-стороне не найдено (большая часть Java-кода — Flutter engine + plugins, без бизнес-логики). В Dart возможна реализация, но это нестандартный паттерн для Flutter приложений — обычно полагаются на Bearer + TLS.

Прочее

  • Play Integrity API — не используется (нет соответствующих библиотек в декомпиле).
  • App attestation / SafetyNet — отсутствует.

6. Сторонние SDK и зависимости

Из Kotlin/Java декомпиляции водителя

  • com.google.firebase.* — Firebase (FCM, аналитика)
  • com.google.android.gms.* — Google Play Services
  • dev.flutter.pigeon — Flutter Pigeon (Dart ↔ Native bridges)
  • io.flutter.* — Flutter engine
  • com.baseflow.permissionhandler — permission_handler plugin
  • com.jrai.flutter_keyboard_visibility — keyboard visibility plugin
  • io.scer.pdfx, io.scer.pdf_renderer — PDF rendering plugins
  • studio.midoridesign.gal — gal plugin (сохранение в галерею)
  • dev.fluttercommunity.plus — Flutter community plugins
  • dev.fluttered.map_launcher — map_launcher plugin (открытие карт)
  • com.github.rmtmckenzie.native_device_orientation — ориентация устройства
  • com.softmaestri.notification — кастомные нотификации (другой вендор?)
  • com.getkeepsafe.relinker — динамическая загрузка native libraries
  • org.apache.commons — Apache Commons
  • org.chromium.support_lib_boundary — WebView boundary library

НЕ найдено (важно):

  • ru.yoomoney.* — нет ЮКассы SDK
  • ru.tinkoff.* — нет Tinkoff Acquiring SDK
  • com.yandex.payments.* — нет Yandex Pay
  • com.sberbank.* — нет Сбер SDK
  • com.squareup.okhttp3 / retrofit2 — нет (HTTP идёт через Dart)
  • dagger, hilt — нет DI-фреймворков (Flutter сам по себе их не требует)

Вывод по платежам: на Kotlin-стороне платёжных SDK провайдеров нет. Платёж через /v1/CreatePayment скорее всего возвращает deep link (типа https://qr.nspk.ru/<id> для СБП или URL веб-страницы провайдера), и Flutter открывает его через стандартный intent. Это отличный сценарий для миграции — точно повторяемо в нашем приложении без необходимости лицензировать чьи-то SDK.


7. Промежуточные гипотезы

  1. Архитектура двухслойная: NaughtySoft API (api.ttcontrol) — фасад, под капотом стоит 1С (taksi.0nalog:1703). Подтвердить на этапе 2: смотреть, идут ли с приложения прямые запросы на 0nalog:1703 (тогда оба слоя независимы), или только на api.ttcontrol (тогда 0nalog — публично доступный backend 1С только в декомпиле, а реально приложение его не дёргает).
  2. Платежи через СБП + deep links, без нативных SDK провайдеров → легко повторимо в нашем будущем приложении.
  3. Фото-функция (/v1/AddInspection, /v1/FOTO/) уже есть на бэке — она просто не выдана водителям в UI текущего приложения. Если на этапе 2 удастся снять трафик этих endpoints (через Механика или через намеренный вызов в Driver если кнопка где-то есть), мы получим готовый протокол для нашего блока B без дизайна с нуля.
  4. QIWI код мёртв — индикатор того, что подрядчик не зачищает мёртвый функционал. Подтверждает «прекратили доработки».
  5. TLS pinning слабый или отсутствует → mitmproxy будет работать в этап 2 «из коробки».

8. Open questions для следующих этапов

  • Какой формат запроса/ответа на /v1/CreatePayment? (этап 2)
  • Использует ли приложение /api/... префикс или /v1/... идут напрямую от корня? (этап 2)
  • Использует ли водитель taksi.0nalog.com:1703 напрямую или только через прокси api.ttcontrol? (этап 2)
  • Программный TLS pinning в Dart — есть? (этап 2, эмпирически)
  • Refresh-токен и его lifecycle — какой? (этап 3)
  • Что приходит в FCM-сообщении при штрафе? Полный объект или только триггер «обнови данные»? (этап 5)
  • Что лежит в assemblies.blob Механика? Те же endpoints или другие? (отдельный поток)

9. Блокеры и риски

# Риск Митигация
R1 ARM-only ABI в обоих APK — на x86_64 эмуляторе adb install падает с INSTALL_FAILED_NO_MATCHING_ABIS Для этапа 2 либо: (a) создать AVD x86_64 API 33+ с включённой ARM-translation, (b) использовать физическое Android-устройство, (c) использовать ARM-эмулятор (медленный)
R2 Программный TLS pinning в Dart-коде возможен, но не подтверждён на статике Проверить эмпирически на этапе 2; если есть — обойти через reFlutter
R3 Механик на Xamarin требует отдельного тулинга (pyxamstore + dnSpy) для разбора C# логики Отдельный поток работы; приоритет — водитель
R4 Часть Dart-логики (валидация, формат запросов) невидима без динамики Перенесено на этап 2 (mitmproxy)

10. Артефакты

Файл Что
recon/artifacts/original-driver.xapk Исходный XAPK водителя из APKPure
recon/artifacts/driver-xapk/ Распакованный XAPK (base APK + splits + manifest.json)
recon/artifacts/original-mechanic.apk APK Механика
recon/artifacts/decompiled/driver/ jadx-декомпиляция водителя (4331 классов, 25 errors ≈ 0.6%)
recon/artifacts/decompiled/mechanic/ jadx-декомпиляция Механика (даёт только Xamarin glue, не C# логику)
recon/artifacts/native/driver/lib/arm64-v8a/libapp.so Извлечённый Dart AOT snapshot водителя — главный источник URL и endpoints

11. Следующий шаг (этап 2)

Все необходимые данные для разворачивания этапа 2 готовы:

  • Список endpoints для отслеживания — есть
  • Базовые URL'ы — есть
  • Подтверждено отсутствие XML-pinning'а — mitmproxy будет работать
  • Топология FCM известна — этап 5 пойдёт от Firebase project ttcdriver

Перед этапом 2 нужно решить ARM-блокер (R1): выбрать ARM-эмулятор / x86_64 с ARM translation / физическое устройство. Это открытый пункт для пользователя.

После этого по плану: установка приложения на стенд → mitmproxy + системный CA → проигрыш сценариев S1-S6 (см. §5 design-doc'а) → каталог реальных запросов/ответов.