chore: initial commit — recon artifacts + design spec + Phase 1 plan

This commit is contained in:
2026-05-16 21:58:10 +10:00
commit d6eeb0cc50
20 changed files with 338613 additions and 0 deletions
+283
View File
@@ -0,0 +1,283 @@
# Этап 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'а) → каталог реальных запросов/ответов.