Как мы получили карту API без официальной документации.
- Рутованное устройство Redmi Note 7 (lavender), Android 13, arm64-v8a, root через Magisk.
- adb-serial:
276bcca9. - Пакет приложения:
ru.kwork.app(App Bundle:base.apk+split_config.arm64_v8a+split_config.xxhdpi).
| Инструмент | Назначение |
|---|---|
adb (android-tools) |
связь с устройством, извлечение APK |
jadx 1.5.5 |
декомпиляция APK → Java-исходники |
mitmproxy |
перехват HTTPS-трафика (этап валидации) |
frida 17.12.0 + frida-server (на устройстве) |
обход SSL pinning (этап валидации) |
adb -s 276bcca9 shell pm path ru.kwork.app
adb -s 276bcca9 pull <path>/base.apk research/captures/kwork-base.apk
.tooling/jadx/bin/jadx -d research/captures/kwork-src research/captures/kwork-base.apkРезультат: ~16 000 Java-файлов. Код частично обфусцирован (короткие имена пакетов
cy, nr, mc…), но слой данных ru.kwork.app.data сохранил осмысленные имена.
ru/kwork/app/data/remote/*Service.java — 14 интерфейсов Retrofit. Это и есть
описание API: каждый метод аннотирован HTTP-глаголом и путём, параметры —
аннотациями полей. Пример из CatalogService:
@e // @FormUrlEncoded
@o("/searchKworks") // @POST("/searchKworks")
Object searchKworks(@c("query") String query, @c("categoryId") Integer cat,
@c("page") Integer page, @c("token") String token,
@i("Authorization") String auth, @i("Cookie") Set<String> cookie, ...);Соответствие обфусцированных аннотаций Retrofit:
| В коде | Retrofit | Значение |
|---|---|---|
@o("…") |
@POST |
путь, POST |
@f("…") |
@GET |
путь, GET |
@e |
@FormUrlEncoded |
форма |
@c("name") |
@Field |
поле формы |
@d |
@FieldMap |
карта полей |
@i("Header") |
@Header |
заголовок |
@a |
@Body |
тело (JSON) |
@q |
@Part |
multipart |
Каталог всех методов сгенерирован парсером research/extract_endpoints.py →
03-endpoints.md.
// host: "ru" → "kwork.ru", "en" → "kwork.com", иначе "<lang>.kwork.com"
// baseUrl:
return "https://api." + host + "/"; // => https://api.kwork.ru/Заголовок Authorization берётся из preferencesHelper.a() и подставляется в
каждый вызов сервиса (включая /signIn). Значение строится из двух констант:
getString("serverlogin", "mobile_api") // c()
getString("serverpassword", "qFvfRl7w") // d()
// a() = "Basic " + base64("mobile_api:qFvfRl7w")
// = Basic bW9iaWxlX2FwaTpxRnZmUmw3dw==Это не пользовательский токен, а статичный идентификатор клиента-приложения.
SHA-1( "35" + Σ(len(BuildProp_i) % 10 для 14 build-свойств)
+ Settings.Secure.ANDROID_ID
+ System.currentTimeMillis() )Считается один раз и кэшируется в SharedPreferences (uad). Для нашей библиотеки
достаточно сгенерировать один стабильный псевдослучайный hex и переиспользовать.
/signInвозвращаетSignInResponseс пользовательскимtoken.tokenпередаётся полем формы во всех авторизованных запросах.- Дополнительно отправляется
Cookie: slrememberme=<value>. - На устройстве
token/slremembermeхранятся зашифрованными (Crypto + Base64), но в сеть уходят в открытом виде — нам шифрование хранилища не нужно.
Статический анализ даёт пути и поля; формы ответов уточняются перехватом:
- CA mitmproxy в системные сертификаты (Android 13 + Magisk-модуль/bind-mount cacerts).
frida-server(root) + universal SSL unpinning, если приложение использует pinning.- Прогон сценариев в приложении → экспорт flow в
research/captures/(gitignore).
См. research/README.md для пошаговых команд.