Встала передо мной задача: слить контент одного огромного международного сайта с двадцатилетней историей и соответствующим количеством пользователей. Но вот досада — на сайте стоит достаточно умная защита, контент генерируется JavaScript, а за хоть сколько-нибудь значимое количество запросов можно угодить в бан по IP

Позаморачивавшись немного с прокси и эмуляцией JavaScript, я вдруг вспомнил, что у многих сервисов сейчас, помимо сайта, есть и мобильные приложения. Полез в Google Play — и действительно, есть! Теперь, когда мне это стало известно, задача зазвучала чуть по-другому — отреверсить приложение, получить доступ к закрытому API и с его помощью выкачать контент.

Декомпиляция

Первое, что нужно сделать, — естественно, скачать приложение на девайс. После установки APK копируется в директорию /data/app, и выкачать его оттуда можно такой вот нехитрой командой:

adb pull /data/app/some.package.name-1.apk .

После успешного выполнения команды получаем искомый APK в текущей директории.

Как ты, наверное, знаешь, приложения для Android пишутся на Java, однако собранный пакет от «обычной» джавы отличается. А для декомпиляции нам нужен JAR-файл. Не беда, на помощь приходит мегаполезная утилита dex2jar:

./dex2jar.sh some.package.name-1.apk

Все, у нас есть JAR. Теперь нам поможет программа JD — Java Decompiler. Советую использовать standalone-версию под названием JD-GUI. Запускаем ее.

 
Рис. 1. JD-GUI

Нажимаем <Cmd + O> (или <Ctrl + O>) и выбираем наш JAR, после чего видим следующую картину (на рис. 1 не то приложение, которое рассматривается в статье, но суть должна быть ясна).

 
Рис. 2. JD-GUI: Декомпилированный код

JD декомпилирует приложения достаточно качественно, единственный недостаток — имена локальных переменных он не восстанавливает, поэтому со сложными алгоритмами разобраться тяжело.

Хорошо, оставим пока декомпилятор и попробуем посмотреть, какие запросы выполняет приложение.

Сниффим Android

В принципе, подойдет и Wireshark, но мне больше по душе пришлась софтинка с названием Burp Suite, написанная на Java. Нам вполне хватит Free Edition.

Запускаем Burp Suite, открываем вкладку Proxy, нажимаем на «Intercept is…». Что она делает? Поднимает локальный прокси-сервер с портом 8080. Теперь запускаем эмулятор Android, но не из AVD, а из терминала вот такой вот командой:

emulator -avd avd_name -http-proxy http://127.0.0.1:8080

Здесь avd_name — имя виртуального девайса, созданного ранее.

Теперь можно убедиться в том, что прокси работает, — открываем на эмуляторе браузер, в нем любую страницу и смотрим, что пишет Burp Suite. Небольшая особенность его работы — по умолчанию он «холдит» пакет, предоставляя пользователю возможность пропустить его далее либо дропнуть. Особенность важная, поскольку, если вовремя его не успеть пропустить, софт на эмуляторе может решить, что произошел тайм-аут операции. Хорошо, теперь ставим нашу софтину:

adb -e install some.package.name-1.apk

и запускаем ее на эмуляторе. Выявляем наиболее важные для нас запросы (ура, видим хост api.somesite.com!), пытаемся выполнить запрос в браузере компьютера — все ОK, в ответ приходит JSON!

Хорошо, предположим, что в параметрах фигурирует некий ID=1, а нам нужен ID=2. Изменяем в скопированном запросе 1 на 2, выполняем его в браузере — а хрен там, получаем ошибку! Вглядываемся в запрос и видим некий параметр, назовем его signature, и содержимое его подозрительно похоже на хеш :). Смотрим на эмуляторе, как меняется signature в зависимости от ID, — да, он все же меняется при смене любого параметра.

Хорошо, значит, придется реверсить приложение.

Реверсинг

Вернемся к Java Decompiler. Подзадача сейчас стоит следующая — понять, каким образом генерируется параметр signature. Можно поискать встроенным поиском, но мне такой метод не нравится, поэтому я делаю следующее:

  1. Сохраняем все исходники в отдельную директорию (File -> Save all sources).
  2. Переходим в терминале в эту директорию и грепаем:

    find . -iname “*.java” | xargs grep “signature=” -sl

На выходе получаем список путей до всех файлов, в которых содержится текст “signature=”

В моем случае их всего один :).

Хорошо, теперь:

vim some/package/name/http/SomeClass.java 

и изучаем исходники.

Алгоритм генерирования подписи я нашел за пару минут, еще за столько же переписал на бумажку. Да, это действительно хеш (из семейства SHA-*).

За десять минут алгоритм был переписан на Python (нравится мне на нем парсеры писать, и все тут). Я даже хотел было подключить прокси, но потом подумал — а нужно ли оно?

И действительно — оказалось не нужно, весь контент для API отдавался без каких-либо лимитов :).

Итог

В итоге за несколько дней было выкачано много сотен тысяч страниц, причем прелесть такого подхода не только в отсутствии лимитов, но и в небольшом размере передаваемого контента — данные в JSON весят в разы меньше, нежели HTML-страница с версткой.

Какой из этого можно сделать вывод? Вывод простой: ты не сможешь полностью защитить данные. Единственное, что могло бы усложнить задачу, — это наличие лимитов, но и эта проблема легко решается с помощью прокси. Использование HTTPS никак не спасает — я просто пошел легким путем, решив сниффать трафик, и даже если бы он был защищен, можно было бы вытащить необходимые параметры для формирования запроса все тем же реверсингом.

Еще немного усложнило бы задачу использование нативной библиотеки для подписывания запроса. Это решение отбросило бы «начинающих», но я, например, тоже знаю про JNI. В общем, мы с коллегами потратили достаточно большое количество времени на поиск решения проблемы, но, увы, так и не нашли. Если у тебя есть какие-то мысли по этому поводу — пиши на почту

By Ruslan Novikov

Интернет-предприниматель. Фулстек разработчик. Маркетолог. Наставник.