Дисклеймер
Данная статья впервые была опубликована мною 27 ноября 2014 года в песочнице habrahabr.ru, но удалена модераторами в тот же день. Спустя 4 года я нашёл исходники статьи и публикую её практически без изменений.
И я не стал убирать скриншоты под спойлеры, потому что на них изображён текст, который должен быть всё время на виду, так как материал очень сложен для понимания, особенно для новичков.
Недавно скачал приложение-клиент на Android для ресурса Хабрахабр. Официальный мне не понравился, так как от браузера он мало чем отличался, а второй, который был в списке поиска по Google Play, был HabraCitizen. На мою попытку авторизоваться, приложение ответило просьбой приобрести полную версию за $1.5, так же приобретение даёт возможность скачивать посты для оффлайн просмотра.
Так как нажатие на Нет отменяло авторизацию, нажал Да и увидел окошко Lucky Patcher'a с предложением получить эту покупку бесплатно. Посчитав такой подход к проблеме скучным и, оправдываясь перед совестью тем, что я небогатый студент, закрыл окошко и открыл Apktool. Разобрав HabraCitizen буквально по байтам принялся искать, где прячется вызов просьбы уменьшить счёт моего электронного кошелька.
1. Узнаём
2. Ввёл id через поиск по всем .smali файлам с помощью модифицированного мной приложения Search Thru (оригинальное пропускало «мимо ушей» файлы форматов кроме .txt, в том числе и .smali).
<скриншот утерян>
Итак, R$string и библиотека меня ничуть не интересуют, поэтому моя цель находится по адресу
Формирование и отображение окошка происходит в отдельном методе, который может быть вызван из любой точки программы, ищем дальше.
3. В Smali вызов метода выглядит так:
Где
Нам нужно найти, где вызывается метод, создающий диалоговое окно:
по запросу
4. В обоих случаях перед вызовом метода с окошком происходит какая-то проверка:
А именно
5. На скриншотах в п. 4 можно заметить ещё кое-что общее: то, откуда программа получает результат
Где
6. Идём в
И видим, что в методе
7. Ищем вызов метода
Здесь мы видим, что будет, если мы имеем лицензию — отправка значения «true». То, что в зелёной рамочке, нужно скопировать, оно нам понадобится.
Далее второй результат:
В нём происходит что-то, что явно не делает нам хорошо — заменяем то, что в красной рамочке тем, что скопировали ранее.
И наконец, первый результат:
Здесь я заметил «неприятное событие» — программа удаляет Coockie, а перед этим ряд похожих друг на друга действий, в том числе и присваивает к
После очередной компиляции и переустановки приложения, разумеется, всё заработало без намёка на донат или падение.
Благодарю за проявленное вами терпение и упорство, необходимые для того, чтобы прочитать эту статью, и хочу заметить, что все описанные действия были совершены на смартфоне.
Несколько полезных ссылок:
Данная статья впервые была опубликована мною 27 ноября 2014 года в песочнице habrahabr.ru, но удалена модераторами в тот же день. Спустя 4 года я нашёл исходники статьи и публикую её практически без изменений.
И я не стал убирать скриншоты под спойлеры, потому что на них изображён текст, который должен быть всё время на виду, так как материал очень сложен для понимания, особенно для новичков.
Недавно скачал приложение-клиент на Android для ресурса Хабрахабр. Официальный мне не понравился, так как от браузера он мало чем отличался, а второй, который был в списке поиска по Google Play, был HabraCitizen. На мою попытку авторизоваться, приложение ответило просьбой приобрести полную версию за $1.5, так же приобретение даёт возможность скачивать посты для оффлайн просмотра.
Так как нажатие на Нет отменяло авторизацию, нажал Да и увидел окошко Lucky Patcher'a с предложением получить эту покупку бесплатно. Посчитав такой подход к проблеме скучным и, оправдываясь перед совестью тем, что я небогатый студент, закрыл окошко и открыл Apktool. Разобрав HabraCitizen буквально по байтам принялся искать, где прячется вызов просьбы уменьшить счёт моего электронного кошелька.
1. Узнаём
id
текста с просьбой оплаты в файлах /res/values/strings.xml и /res/values/public.xml
— 0x7f0c00d8
2. Ввёл id через поиск по всем .smali файлам с помощью модифицированного мной приложения Search Thru (оригинальное пропускало «мимо ушей» файлы форматов кроме .txt, в том числе и .smali).
<скриншот утерян>
Итак, R$string и библиотека меня ничуть не интересуют, поэтому моя цель находится по адресу
/smali/com/allesad/HabraCitizen/utils/i.smali
. Открываем и видим:Формирование и отображение окошка происходит в отдельном методе, который может быть вызван из любой точки программы, ищем дальше.
3. В Smali вызов метода выглядит так:
invoke-static {p0, v0}, Lcom/allesad/HabraCitizen/utils/i;->a(Landroid/content/Context;Landroid/content/DialogInterface$OnClickListener;)V
Где
p0, v0
— это параметры (если есть), com/allesad/HabraCitizen/utils/i
— адрес класса, в котором вызываемый метод, a
— имя вызываемого метода, (Landroid/content/Context;Landroid/content/DialogInterface$OnClickListener;)V
— это что-то, связанное с каждым параметром (видно, что 2 параметра и два раза встречается ;
). Важно заметить, что имена методов могут быть одинаковыми, при условии, если разное количество параметров или разные типы параметров(а).Нам нужно найти, где вызывается метод, создающий диалоговое окно:
по запросу
utils/i;->a
находим вызовы метода а
с двумя параметрами, то есть с(Landroid/content/Context;Landroid/content/DialogInterface$OnClickListener;)V
DawnloadsActivity.smali
и LoginActivity.smali
— как раз то, за что надо платить $1.5.4. В обоих случаях перед вызовом метода с окошком происходит какая-то проверка:
А именно
if-nez v0, :cond_1
, что означает if not equals zero
, в переводе если не равно нулю, прыгнуть на :cond_1
. После замены if-nez v0, :cond_1
на if-eq v0, v0, :cond_1
, что значит если v0 = v0, то перепрыгнуть вывод окошка
(и, разумеется, после компиляции приложения и установки с удалением оригинала), приложение не просило пожертвовать. Иногда этого достаточно и приложения начинают радовать платными (уже бесплатными) функциями, но сразу после авторизации HabraCitizen падало с ошибкой, значит придётся ещё попотеть.5. На скриншотах в п. 4 можно заметить ещё кое-что общее: то, откуда программа получает результат
v0
для сравнения if-nez
: invoke-vertual {v0}, Ljava/lang/Boolean;->booleanValue()Z
, который в свою очередь берёт результат из методаinvoke-vertual {0}, Lcom/allesad/HabraCitizen/utils/e;->p()Ljava/lang/Boolean;
Где
com/allesad/HabraCitizen/utils/e
— класс, а p
— имя метода.6. Идём в
/com/allesad/HabraCitizen/utils/e.smali
и ищем метод p()
:И видим, что в методе
p()
в одном месте встречаются SharedPreferences
и premium_account_key
, в конце которого происходит возврат результата (считывания значения premium_account_key
, в данном случае там «false») действий метода туда, где он был вызван. А задав в поиск по файлу premium
находим другой метод, который ничего не возвращает, но редактирует значение premium_account_key
в SharedPreferences
. Но чтобы давать ему что-то новое, это что-то нужно получить, в данном случае это p0
. Кстати, переменные с префиксом p
— параметры, получаемые при вызове данного метода. Значит, где-то происходит проверка лицензии и её результат отправляется в SharedPreferences
, что нам как раз надо подделать.7. Ищем вызов метода
a(Ljava/lang/Boolean;)V
(который редактирует SharedPreferences
), и смотрим в удобном для понимания порядке — с третьего результата поиска по первый:Здесь мы видим, что будет, если мы имеем лицензию — отправка значения «true». То, что в зелёной рамочке, нужно скопировать, оно нам понадобится.
Далее второй результат:
В нём происходит что-то, что явно не делает нам хорошо — заменяем то, что в красной рамочке тем, что скопировали ранее.
И наконец, первый результат:
Здесь я заметил «неприятное событие» — программа удаляет Coockie, а перед этим ряд похожих друг на друга действий, в том числе и присваивает к
premium_account_key
неведомое значение v1
(в самом деле false), как во втором результате. Скажу сразу, что подмена if-eqz v3, :cond_2
и if-nez v0, :cond_7
, как в случае с выводом окошка, приводит к крашу приложения сразу после его запуска. Чтобы всё работало, кроме замены того, что в красной рамочке на то, что было скопировано из третьего результата, я удалил те «похожие действия», вызов метода removeAllCoockie
и всё аж вплоть до :goto_3
(в данном случае это можно расценивать как логическое завершение мысли в русском языке). Вот как теперь стало:После очередной компиляции и переустановки приложения, разумеется, всё заработало без намёка на донат или падение.
Благодарю за проявленное вами терпение и упорство, необходимые для того, чтобы прочитать эту статью, и хочу заметить, что все описанные действия были совершены на смартфоне.
Несколько полезных ссылок:
-
Ссылка скрыта от гостей— приложение для декомпиляции и компиляции приложений на Android устройстве
Search Thru— приложение для поиска текста среди текстовых файлов- Regex Finder - приложение для поиска по содержимому файлов, созданное мною как лучшая алтернатива Search Thru