Files

284 lines
20 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Этап 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.so`**Dart 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'а) → каталог реальных запросов/ответов.