Get Source Code from an .apk file? [closed]
Questions about writing and publishing Android apps, writing custom ROMs, and other topics that are primarily of concern to developers are off-topic. Consider taking advantage of other resources that may be appropriate for your question.
Closed 8 years ago .
Having an .apk file, is there any way I can get its source code?
36.8k 16 16 gold badges 144 144 silver badges 175 175 bronze badges
asked Oct 3, 2012 at 19:12
user16112 user16112
Before that, have you checked if the package is open source?
Oct 3, 2012 at 20:20
An .apk is simply a .zip file. But unfortunatedly any app build for ‘release’ will probably be protected by ProGuard.
Oct 3, 2012 at 21:55
@MatthewRead The Op asks if he could get the code from an api, and I answered what this phenomenon is called and ways to at least prevent from making a similar app. He doesn’t say how to prevent from getting the code So yes it does answer the question.
May 6, 2015 at 7:50
Jan 31, 2016 at 6:12
3 Answers 3
Even if ProGuard was used you still can get some interesting insights. Here is a StackOverflow question with a detailed explanation: Android: Getting source code from an APK file. YouTube video with a detailed guide. And a blog entry about that: How to retrieve source code from an Android APK file.
answered Oct 4, 2012 at 21:49
Nikolai Samteladze Nikolai Samteladze
280 1 1 silver badge 6 6 bronze badges
If you get lucky and your .apk file wasn’t protected by Proguard or analog, you can use APK tool. It is a tool for reverse engineering 3rd party, closed, binary Android apps.
answered Oct 3, 2012 at 20:15
HitOdessit HitOdessit
325 4 4 silver badges 14 14 bronze badges
Apktool does only generate Smali-Code, not Java code — that is a big difference.
Oct 5, 2012 at 7:52
Procedure for decoding .apk files, step-by-step method:
Step 1:
- Make a new folder and copy over the .apk file that you want to decode.
- Now rename the extension of this .apk file to .zip (e.g. rename from filename.apk to filename.zip) and save it. Now you can access the classes.dex files, etc. At this stage you are able to see drawables but not xml and java files, so continue.
Step 2:
- Now extract this .zip file in the same folder (or NEW FOLDER).
- Download dex2jar and extract it to the same folder (or NEW FOLDER).
- Move the classes.dex file into the dex2jar folder.
- Now open command prompt and change directory to that folder (or NEW FOLDER). Then write d2j-dex2jar classes.dex and press enter. You now have the classes.dex.dex2jar file in the same folder.
- Download java decompiler, double click on jd-gui, click on open file, and open classes.dex.dex2jar file from that folder: now you get class files.
- Save all of these class files (In jd-gui, click File -> Save All Sources) by src name. At this stage you get the java source but the .xml files are still unreadable, so continue.
Step 3:
Now open another new folder
- Put in the .apk file which you want to decode
- Download the latest version of apktool AND apktool install window (both can be downloaded from the same link) and place them in the same folder
- Download framework-res.apk and put it in the same folder (Not all apk file need this file, but it doesn’t hurt)
- Open a command window
- Navigate to the root directory of APKtool and type the following command: apktool if framework-res.apk
- apktool d myApp.apk (where myApp.apk denotes the filename that you want to decode)
now you get a file folder in that folder and can easily read the apk’s xml files.
Step 4:
It’s not any step just copy contents of both folder(in this case both new folder)to the single one
and enjoy the source code.
Как получить исходный код из .apk-файла?
Какого плана ответ Вы хотите получить? Полный процесс декомпиляции, думаю, никто описывать не будет, но Вы можете найти множество информации в интернете по запросу, например, «декомпиляция apk», а потом задать какой-то конкретный вопрос. Но вообще дело это непростое и не всегда результативное.
2 сен 2016 в 19:07
apk не содержит исходного кода, следовательно никак.
2 сен 2016 в 22:16
2 ответа 2
Сортировка: Сброс на вариант по умолчанию
apk содержит только байт-код (файлы .smali, а не исходники), который можно получить утилитой apktool (или другими на ее основе). Байт-код можно преобразовать в java-код утилитами dex2jar и в «исходники» утилитой jd gui (или другими на их основе).
Однако назвать полноценным исходником полученный результат можно с большой натяжкой, чаще всего из таких «исходников» невозможно вновь собрать рабочий apk, не проделав титанический труд.
Читаемость таких «исходников» тоже, мягко говоря, под вопросом. Результат примерно аналогичен машинному переводу: русский — английский — французкий — русский, то есть никакой.
Отслеживать
ответ дан 3 сен 2016 в 0:11
36.8k 6 6 золотых знаков 48 48 серебряных знаков 125 125 бронзовых знаков
Хорошее сравнение с машинным переводом.
23 сен 2016 в 3:50
Только с языками при этом лучше.)
4 дек 2019 в 16:15
Вы можете воспользоваться одним из apk декомпиляторов, например этим, или этим. В интернете можно найти много информации по декомпиляторам, так и гайдов/доков по ним.
Отслеживать
user177221
ответ дан 2 сен 2016 в 20:53
Rostyk Shevtsiv Rostyk Shevtsiv
406 2 2 серебряных знака 4 4 бронзовых знака
- java
- android
- дизассемблирование
-
Важное на Мете
Похожие
Подписаться на ленту
Лента вопроса
Для подписки на ленту скопируйте и вставьте эту ссылку в вашу программу для чтения RSS.
Дизайн сайта / логотип © 2024 Stack Exchange Inc; пользовательские материалы лицензированы в соответствии с CC BY-SA . rev 2024.1.22.3688
Гайд по реверсу клиент-серверного apk на примере задания NeoQUEST-2020

Сегодня у нас насыщенная программа (еще бы, столько областей кибербезопасности за раз!): рассмотрим декомпиляцию Android-приложения, перехватим трафик для получения URL-адресов, пересоберем apk без исходного кода, поработаем криптоаналитиками и многое другое:)
Согласно легенде NeoQUEST-2020, герой нашел старые детали робота, которые необходимо использовать для получения ключа. Let’s get it started!
1. Реверсим apk
Итак, перед нами то немногое, что удалось извлечь из полуразобранного робота – apk-приложение, которое каким-то образом должно помочь нам получить ключ. Сделаем самое очевидное: запустим apk и посмотрим на его функционал. Более чем минималистичный интерфейс приложения сомнений не оставляет – это кастомный файловый клиент FileDroid, позволяющий скачать файл с удаленного сервера. Окей, выглядит несложно. Подключаем телефон к Интернету, делаем пробную попытку скачивания (сразу key.txt – ну а вдруг?) – безуспешно, файл на сервере отсутствует.

Переходим к следующему по уровню сложности мероприятию – декомпилируем apk c помощью JADX и анализируем исходный код приложения, который, к счастью, совсем не обфусцирован. Наша текущая задача – понять, какие файлы предлагает удаленный сервер для скачивания, и выбрать из них тот самый, с ключом.
Начинаем с класса com.ctf.filedroid.MainActivity, содержащего пока самый интересный для нас метод onClick(), в котором обрабатывается нажатие на кнопку «Download». Внутри этого метода дважды происходит обращение к классу ConnectionHandler: cперва вызывается метод ConnectionHandler.getToken(), а только затем – ConnectionHandler.getEncryptedFile(), в который передается имя файла, запрошенного пользователем.

Ага, то есть сначала нам нужен токен! Разберемся чуть подробнее с процессом его получения.
Метод ConnectionHandler.getToken() принимает на вход две строки, а затем отправляет GET-запрос, передавая эти строки в качестве параметров «crc» и «sign». В ответ сервер присылает данные в JSON-формате, из которых наше приложение извлекает токен доступа и использует его для скачивания файла. Это всё, конечно, хорошо, но что за «crc» и «sign»?

Чтобы понять это, двигаемся дальше в сторону класса Checks, любезно предоставляющего методы badHash() и badSign(). Первый из них подсчитывает контрольную сумму от classes.dex и resources.arsc, конкатенирует эти два значения и оборачивает в Base64 (обратим внимание на флаг 10 = NO_WRAP | URL_SAFE, вдруг пригодится). А что же второй метод? А он делает тоже самое с SHA-256 fingerprint’ом подписи приложения. Эх, похоже, что FileDroid не очень-то жаждет быть пересобранным 🙁

Окей, допустим, что токен получили. Что дальше? Передаем его на вход метода ConnectionHandler.getEncryptedFile(), который присовокупляет к токену имя запрошенного файла и формирует еще один GET-запрос, на этот раз с параметрами «token» и «file». Сервер в ответ (судя по названию метода) отправляет зашифрованный файл, который сохраняется на /sdcard/.
Итак, подведем небольшой промежуточный итог: у нас есть две новости, и… обе плохие. Во-первых, FileDroid не очень поддерживает наше рвение к модификации apk (происходит проверка контрольной суммы и подписи), а во-вторых, полученный от сервера файл обещает быть зашифрованным.
Ладно, будем решать проблемы по мере их поступления, а сейчас наша основная проблема состоит в том, что мы всё еще не знаем, какой файл нам нужно скачать. Однако в процессе изучения класса ConnectionHandler мы не могли не заметить, что прямо между методами getToken() и getEncryptedFile() разработчики FileDroid забыли еще один очень соблазнительный метод под говорящим названием getListing(). Значит, сервер такой функционал поддерживает… Кажется, это то, что нужно!

Для получения листинга нам потребуются уже известные «crc» и «sign» – не проблема, мы уже знаем, откуда они берутся. Считаем значения, отправляем GET-запрос и … Так, стоп. А куда мы GET-запрос собираемся отправлять? Неплохо было бы сначала получить URL-адрес удаленного сервера. Эх, возвращаемся в MainActivity.onClick() и смотрим, как формируются аргументы netPath для вызова методов getToken() и getEncryptedFile():
Method getSecureMethod = wat.class.getDeclaredMethod("getSecure", new Class[]); // . . . // netPath --> ConnectionHandler.getToken() (String) getSecureMethod.invoke((Object) null, new Object[]) // netPath --> ConnectionHandler. getEncryptedFile() (String) getSecureMethod.invoke((Object) null, new Object[])
Странные буквосочетания «fnks» и «qdmk» вынуждают нас обратиться к результату декомпиляции метода wat.getSecure(). Спойлер: этот результат у JADX так себе.

При более пристальном рассмотрении становится понятно, что всё это не слишком приятное содержимое метода можно заменить на привычный switch-case такого вида:
// . . . switch(CODE) < case «qdkm»: r.2 = com.ctf.filedroid.x37AtsW8g.rlieh786d(2); break; case «tkog»: r2 = com.ctf.filedroid.x37AtsW8g.rlieh786d(1); break; case «fnks»: String r2 = com.ctf.filedroid.x37AtsW8g.rlieh786d(0); break; >java.lang.StringBuilder r1 = new java.lang.StringBuilder r1.(r2) java.lang.String r0 = r1.toString() java.lang.String r1 = radon(r0) return r1
Так как «fnks» и «qdmk» уже используются для получения токена и скачивания файла, то «tkog» должен давать URL, необходимый для запроса листинга доступных файлов на сервере. Кажется, появляется надежда дешево получить требуемый путь… В первую очередь посмотрим, как хранятся URL’ы в приложении. Открываем функцию com.ctf.filedroid.x37AtsW8g.rlieh786d() и видим, что каждый URL сохранен в виде закодированного массива байтов, а сама функция формирует из этих байтов строку и возвращает её.

Хорошо. Но далее строка передается в функцию com.ctf.filedroid.wat.radon(), реализация которой вынесена в нативную библиотеку libae3d8oe1.so. Реверсить arm64? Хорошая попытка, FileDroid, но давай в другой раз?
2. Получаем URL-адреса сервера
Попробуем подойти с другой стороны: перехватить трафик, получить URL-адреса в открытом виде (а в качестве бонуса – еще и значения контрольной суммы и подписи!), сопоставить их байтовым массивам из com.ctf.filedroid.x37AtsW8g.rlieh786d() – может быть шифрование окажется обычным шифром Цезаря или XOR. Тогда не составит труда восстановить третий URL-адрес и выполнить листинг.
Для перенаправления трафика можно использовать любой удобный прокси (Charles, fiddler, BURP и т.п.). Выполняем настройку переадресации на мобильном устройстве, устанавливаем соответствующий сертификат, проверяем, что перехват осуществляется успешно, и запускаем FileFroid. Пытаемся скачать произвольный файл и … видим «NetworkError». Вызвана эта ошибка наличием certificate-pinning (см. метод com.ctf.filedroid.ConnectionHandler.sendRequest): файловый клиент проверяет, что «зашитый» в приложение сертификат соответствует серверу, с которым осуществляется взаимодействие. Теперь понятно, почему контролируется целостность ресурсов приложения!

Однако в перехваченном трафике мы можем увидеть хотя бы доменное имя сервера, к которому обращается файловый клиент, а значит, надежда расшифровать URL-адреса остается!

Вернемся к функции com.ctf.filedroid.x37AtsW8g.rlieh786d() и отметим, что во всех массивах совпадают первые несколько десятков байт:
cArr[0] = new char[]; cArr[1] = new char[]; cArr[2] = new char[];
Кроме того, последний байт третьего массива намекает, что без base64 дело не обошлось. Попробуем декодировать и поксорить получившиеся байты с известной частью URL:

Кажется, никто никогда еще так не радовался ARMag3dd0n’у! Дело за малым: последовательно декодируем из base64 URL-адреса и ксорим с найденным ключом. Но… а если бы это был не XOR, а самопальный перестановочный шифр, который не подберешь и со ста попыток?
3. Пересобираем apk с помощью Frida
В рамках этого write-up’а рассмотрим более безболезненный (и, на наш взгляд, более красивый) способ решения – с помощью фреймфорка Frida, который позволит в run-time исполнить произвольные методы apk-приложения с нужными нам аргументами. Для этого потребуется телефон с root-правами или эмулятор. Предполагаем следующий план действий:
- Установка компонентов Frida на ПК и подопытный телефон.
- Восстановление URL-адресов, соответствующих запросам на получение токена или листинга, и скачивание файла (с помощью Frida).
- Извлечение значений контрольной суммы и подписи оригинального приложения.
- Получение листинга файлов, хранящихся на сервере, и выявление нужного файла.
- Скачивание и расшифрование файла.

Вновь обращаемся к классу MainActivity и обнаруживаем, что в onCreate() вызывается метод doChecks(), который и вывел в лог приведенные ошибки:

Кроме того, в onResume() также проверяется, открыт ли типичный для Frida порт:

Наш файловый клиент оказывается немного нетолерантным к отладке, руту и самой Frida. Такое противодействие абсолютно не входит в наши планы, поэтому получаем smali-код приложения с помощью утилиты apktool, открываем в любом текстовом редакторе файл MainActivity.smali, находим метод onCreate() и превращаем вызов doChecks() в безобидный комментарий:

Затем лишаем метод suicide() возможности действительно завершить работу приложения:

Далее снова соберем наше слегка улучшенное приложение с помощью apktool и подпишем его, выполнив следующие команды (могут понадобиться права Администратора):
cd "C:\Program Files\Java\jdk-14\bin" .\keytool -genkey -v -keystore filedroid.keystore -alias filedroid_alias -keyalg RSA -keysize 2048 -validity 10000 .\jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore filedroid.keystore filedroid_patched.apk filedroid_alias .\jarsigner -verify -verbose -certs filedroid_patched.apk
Переустанавливаем приложение на телефоне, запускаем его – ура, загрузка проходит без происшествий, лог чист!

Переходим к установке фреймворка Frida на ПК и мобильное устройство:
$ sudo pip3 install frida-tools $ wget https://github.com/frida/frida/releases/download/$(frida --version)/frida-server-$(frida --version)-android-arm.xz $ unxz frida-server-$(frida --version)-android-arm.xz $ adb push frida-server-$(frida --version)-android-arm /data/local/tmp/frida-server
Запускаем сервер фреймворка Frida на мобильном устройстве:
$ adb shell su -с "chmod 755 /data/local/tmp/frida-server" $ adb shell su -с "/data/local/tmp/frida-server &"
Подготавливаем простой скрипт get-urls.js, который вызовет wat.getSecure() для всех поддерживаемых серверов запросов:
Java.perform(function () < const wat = Java.use('com.ctf.filedroid.wat'); console.log(wat.getSecure("fnks")); console.log(wat.getSecure("qdmk")); console.log(wat.getSecure("tkog")); >);
Запускаем FileDroid на мобильном устройстве и «цепляемся» нашим скриптом к соответствующему процессу:

4. Получаем листинг файлов на сервере
Наконец-то удаленный сервер стал для нас чуть ближе! Теперь нам известно, что сервер поддерживает запросы по следующим путям:
import hashlib import binascii import base64 from asn1crypto import cms, x509 from zipfile import ZipFile def get_info(apk): with ZipFile(apk, 'r') as zipObj: classes = zipObj.read("classes.dex") resources = zipObj.read("resources.arsc") cert = zipObj.read("META-INF/CERT.RSA") crc = "%s%s" % (get_crc(classes), get_crc(resources)) return get_full_crc(classes, resources).decode("utf-8"), get_sign(cert).decode("utf-8") def get_crc(file): crc = binascii.crc32(file) & 0xffffffff return crc def get_full_crc(classes, resources): crc = "%s%s" % (get_crc(classes), get_crc(resources)) return base64.urlsafe_b64encode(bytes(crc, "utf-8")) def get_sign(file): pkcs7 = cms.ContentInfo.load(file) data = pkcs7['content']['certificates'][0].chosen.dump() sha256 = hashlib.sha256() sha256.update(data) return base64.urlsafe_b64encode(sha256.digest()) get_info('filedroid.apk')
Вручную тоже можно. Любым удобным инструментом считаем CRC32 от classes.dex и resources.arsc (например, для Linux – стандартной утилитой crc32), получаем значения 1276945813 и 2814166583 соответственно, конкатенируем их (выйдет 12769458132814166583) и кодируем в base64, например, тут:

Для того, чтобы выполнить аналогичную процедуру для подписи приложения, в окне JADX переходим в раздел «APK Signature», копируем значение «SHA-256 Fingerprint» и кодируем его в base64 как байтовый массив:

Важно: в оригинальном apk base64-кодирование осуществляется с флагом URL_SAFE, т.е. вместо символов «+» и «/» используются «–» и «_» соответственно. Необходимо убедиться, что при самостоятельном кодировании это будет тоже соблюдаться. Для этого при кодировании онлайн можно заменить используемый алфавит с «ABCDEFGHIJKLMNOPQRSTUVWXYZabcde fghijklmnopqrstuvwxyz0123456789+/» на «ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijkl mnopqrstuvwxyz0123456789–_», а при использовании скрипта на python3 – просто вызвать функцию base64.urlsafe_b64encode().
Наконец-то у нас есть все составляющие успешного получения листинга файлов:
Далее запросим одноразовый токен доступа и скачаем файл:
import requests response = requests.get('https://filedroid.neoquest.ru/api/verifyme', params=< 'crc': 'MTI3Njk0NTgxMzI4MTQxNjY1ODM=', 'sign': HeiTSPWdCuhpbmVxqLxW-uhrozfG_QWpTv9ygn45eHY=>, verify=False) token = response.json()['token'] print(token) response = requests.get('https://filedroid.neoquest.ru/api/file', params=, verify=False) with open("0p3n1fuw4nt2esk4p3.jpg", 'wb') as fd: fd.write(response.content)
Открываем скачанный файл и вспоминаем об одном небольшом обстоятельстве, оставленном нами на потом:

5. Добавим щепоточку криптографии.
Эх, рановато мы отложили FileDroid. Снова вернемся в JADX и посмотрим, не оставили ли разработчики файлового клиента чего-нибудь полезного для нас. Да, это тот случай, когда code cleanup явно не популярен: неиспользуемый метод decryptFile() спокойно ждет нашего внимания в классе ConnectionHandler. Что мы имеем?
Шифрование AES в режиме CBC, синхропосылка занимает первые 16 байт… Лень – двигатель прогресса, лучше снова воспользуемся Frida и расшифруем наш 0p3n1fuw4nt2esk4p3.jpg без лишних усилий. Вот только что передать в качестве ключ шифрования? Вариантов не так много, а с учетом наличия еще одного «забытого» метода savePlainFile(String file, String token) выбор очевиден.
Подготовим следующий скрипт decrypt.js (в качестве token укажем актуальное значение, например, ‘HoHknc572mVpZESSQN1Xa7S9zOidxX1PMbykdoM1EXI=’):
Java.perform(function () < const JavaString = Java.use('java.lang.String'); const file_name = JavaString.$new('0p3n1fuw4nt2esk4p3.jpg'); const ConnectionHandler = Java.use('com.ctf.filedroid.ConnectionHandler'); const result = ConnectionHandler.savePlainFile(file_name, ); console.log(result); >);
Помещаем зашифрованный файл 0p3n1fuw4nt2esk4p3.jpg на /sdcard/, запускаем FileDroid и инжектим скрипт decrypt.js с помощью Frida. После того, как скрипт отработает, на /sdcard/ появится файл plainfile.jpg. Открываем его и … just solved!

Это непростое задание требовало от участников знаний и навыков сразу в нескольких сферах инфобеза, и мы рады тому, что большинство соревнующихся успешно с ним справилось!
Надеемся, что те, кому чуть-чуть не хватило времени или знаний до получения ключа, теперь тоже успешно пройдут аналогичные таски в любом CTF 🙂
- Блог компании НеоБИТ
- Информационная безопасность
- Криптография
- Занимательные задачки
- Реверс-инжиниринг
Вскрываем, модифицируем и запаковываем Android-приложения
Иногда некоторые приложения на Android чем-то не устраивают пользователя. В качестве примера можно привести назойливую рекламу. А то бывает и так — всем хороша программа, да только перевод в ней или кривой, или вовсе отсутствует. Или, например, программа триальная, а получить полную версию возможности нет. Как же изменить ситуацию?
Введение
В этой статье мы поговорим о том, как разобрать пакет APK с приложением, рассмотрим его внутреннюю структуру, дизассемблируем и декомпилируем байт-код, а также попробуем внести в приложения несколько изменений, которые могут принести нам ту или иную выгоду.
Чтобы сделать все это самостоятельно, потребуются хотя бы начальные знания языка Java, на котором пишутся приложения для Android, и языка XML, который используется в Android повсеместно — от описания самого приложения и его прав доступа до хранения строк, которые будут выведены на экран. Также понадобится умение обращаться со специализированным консольным софтом.
Итак, что же представляет собой пакет APK, в котором распространяется абсолютно весь софт для Android?
Декомпиляция приложений
В статье мы работали только с дизассемблированным кодом приложения, однако если в большие приложения вносить более серьезные изменения, разобраться в коде smali будет гораздо сложнее. К счастью, мы можем декомпилировать код dex в Java-код, который будет хоть и не оригинальным и не компилируемым обратно, но гораздо более легким для чтения и понимания логики работы приложения. Чтобы сделать это, нам понадобятся два инструмента:
- dex2jar — транслятор байт-кода Dalvik в байт-код JVM, на основе которого мы сможем получить код на языке Java;
- jd-gui — сам декомпилятор, позволяющий получить из байт-кода JVM читаемый код Java. В качестве альтернативы можно использовать Jad (www.varaneckas.com/jad); хоть он и довольно старый, но в некоторых случаях генерирует более читаемый код, нежели Jd-gui.
Использовать их следует так. Сначала запускаем dex2jar, указывая в качестве аргумента путь до apk-пакета:
% dex2jar.sh mail.apk
В результате в текущем каталоге появится Java-пакет mail.jar, который уже можно открыть в jd-gui для просмотра Java-кода.
Устройство APK-пакетов и их получение
Пакет приложения Android, по сути, является обычным ZIP-файлом, для просмотра содержимого и распаковки которого никаких специальных инструментов не требуется. Достаточно иметь архиватор — 7zip для Windows или консольный unzip в Linux. Но это что касается обертки. А что внутри? Внутри же у нас в общем случае такая структура:
- META-INF/ — содержит цифровой сертификат приложения, удостоверяющий его создателя, и контрольные суммы файлов пакета;
- res/ — различные ресурсы, которые приложение использует в своей работе, например изображения, декларативное описание интерфейса, а также другие данные;
- AndroidManifest.xml — описание приложения. Сюда входит, например, список требуемых разрешений, требуемая версия Android и необходимое разрешение экрана;
- classes.dex — компилированный байт-код приложения для виртуальной машины Dalvik;
- resources.arsc — тоже ресурсы, но другого рода — в частности, строки (да-да, этот файл можно использовать для русификации!).
Перечисленные файлы и каталоги есть если не во всех, то, пожалуй, в абсолютном большинстве APK. Однако стоит упомянуть еще несколько не столь распространенных файлов/каталогов:
- assets — аналог ресурсов. Основное отличие — для доступа к ресурсу необходимо знать его идентификатор, список asset’ов же можно получать динамически, используя метод AssetManager.list() в коде приложения;
- lib — нативные Linux-библиотеки, написанные с помощью NDK (Native Development Kit).
Этот каталог используют производители игр, помещая туда движок игры, написанный на C/C++, а также создатели высокопроизводительных приложений (например, Google Chrome). С устройством разобрались. Но как же получить сам файл пакета интересующего приложения? Поскольку без рута с устройства забрать файлы APK не представляется возможным (они лежат в каталоге /data/app), а рутить не всегда целесообразно, имеется как минимум три способа получить файл приложения на компьютер:
- расширение APK Downloader для Chrome;
- приложение Real APK Leecher;
- различные файлообменники и варезники.
Какой из них использовать — дело вкуса; мы предпочитаем использовать отдельные приложения, поэтому опишем использование Real APK Leecher, тем более что написан он на Java и, соответственно, работать будет хоть в винде, хоть в никсах.
После запуска программы необходимо заполнить три поля: Email, Password и Device ID — и выбрать язык. Первые два — e-mail и пароль твоего гуглоаккаунта, который ты используешь на устройстве. Третий же является идентификатором устройства, и его можно получить, набрав на номеронабирателе код ##8255## и затем найдя строку Device ID. При заполнении надо ввести только ID без префикса android-.


Другие статьи в выпуске:
Хакер #170. Малварь для OS X
- Содержание выпуска
- Подписка на «Хакер» -60%
После заполнения и сохранения нередко выскакивает сообщение «Error while connecting to server». Оно не имеет отношения к Google Play, поэтому смело его игнорируй и ищи интересующие тебя пакеты.
Просмотр и модификация
Допустим, ты нашел интересующий тебя пакет, скачал, распаковал… и при попытке просмотра какого-нибудь XML-файла с удивлением обнаружил, что файл не текстовый. Чем же его декомпилировать и как вообще работать с пакетами? Неужели необходимо ставить SDK? Нет, SDK ставить вовсе не обязательно. На самом деле для всех шагов по распаковке, модификации и упаковке пакетов APK нужны следующие инструменты:
- архиватор ZIP для распаковки и запаковки;
- smali — ассемблер/дизассемблер байт-кода виртуальной машины Dalvik (code.google.com/p/smali);
- aapt — инструмент для запаковки ресурсов (по умолчанию ресурсы хранятся в бинарном виде для оптимизации производительности приложения). Входит в состав Android SDK, но может быть получен и отдельно;
- signer — инструмент для цифровой подписи модифицированного пакета (bit.ly/Rmrv4M).
Использовать все эти инструменты можно и по отдельности, но это неудобно, поэтому лучше воспользоваться более высокоуровневым софтом, построенным на их основе. Если ты работаешь в Linux или Mac OS X, то тут есть инструмент под названием apktool. Он позволяет распаковывать ресурсы в оригинальный вид (в том числе бинарные XML- и arsc-файлы), пересобирать пакет с измененными ресурсами, но не умеет подписывать пакеты, так что запускать утилиту signer придется вручную. Несмотря на то что утилита написана на Java, ее установка достаточно нестандартна. Сначала следует получить сам jar-файл:
$ cd /tmp $ wget http://bit.ly/WC3OCz $ tar -xjf apktool1.5.1.tar.bz2
Далее нам понадобится скрипт-обвязка для запуска apktool (он, кстати, доступен и для Windows), включающий в себя еще и утилиту aapt, которая понадобится для запаковки пакета:
$ wget http://bit.ly/WRjEc7 $ tar -xjf apktool-install-linux-r05-ibot.tar.bz2
Далее просто сваливаем содержимое обоих архивов в каталог ~/bin и добавляем его в $PATH:
$ mv apktool.jar ~/bin $ mv apktool-install-linux-r05-ibot/* ~/bin $ export PATH=~/bin:$PATH
Если же ты работаешь в Windows, то для нее есть превосходный инструмент под названиемVirtuous Ten Studio, который также аккумулирует в себе все эти инструменты (включая сам apktool), но вместо CLI-интерфейса предоставляет пользователю интуитивно понятный графический интерфейс, с помощью которого можно выполнять операции по распаковке, дизассемблированию и декомпиляции в несколько кликов. Инструмент этот Donation-ware, то есть иногда появляются окошки с предложением получить лицензию, но это, в конце концов, можно и потерпеть. Описывать его не имеет никакого смысла, потому что разобраться в интерфейсе можно за несколько минут. А вот apktool, вследствие его консольной природы, следует обсудить подробнее.

Рассмотрим опции apktool. Если вкратце, то имеются три основные команды: d (decode), b (build) и if (install framework). Если с первыми двумя командами все понятно, то что делает третья, условный оператор? Она распаковывает указанный UI-фреймворк, который необходим в тех случаях, когда ты препарируешь какой-либо системный пакет.
Рассмотрим наиболее интересные опции первой команды:
- -s — не дизассемблировать файлы dex;
- -r — не распаковывать ресурсы;
- -b — не вставлять отладочную информацию в результаты дизассемблирования файла dex;
- —frame-path — использовать указанный UI-фреймворк вместо встроенного в apktool. Теперь рассмотрим пару опций для команды b:
- -f — форсированная сборка без проверки изменений;
- -a — указываем путь к aapt (средство для сборки APK-архива), если ты по какой-то причине хочешь использовать его из другого источника.
Пользоваться apktool очень просто, для этого достаточно указать одну из команд и путь до APK, например:
$ apktool d mail.apk
После этого в каталоге mail появятся все извлеченные и дизассемблированные файлы пакета.
Препарирование. Отключаем рекламу
Теория — это, конечно, хорошо, но зачем она нужна, если мы не знаем, что делать с распакованным пакетом? Попробуем применить теорию с пользой для себя, а именно модифицируем какую-нибудь софтину так, чтобы она не показывала нам рекламу. Для примера пусть это будет Virtual Torch — виртуальный факел. Для нас эта софтина подойдет идеально, потому что она под завязку набита раздражающей рекламой и к тому же достаточно проста, чтобы не потеряться в дебрях кода.

Итак, с помощью одного из приведенных способов скачай приложение из маркета. Если ты решил использовать Virtuous Ten Studio, просто открой APK-файл в приложении и распакуй его, для чего создай проект (File -> New project), затем в контекстном меню проекта выбери Import File. Если же твой выбор пал на apktool, то достаточно выполнить одну команду:
$ apktool d com.kauf.particle.virtualtorch.apk
После этого в каталоге com.kauf.particle.virtualtorch появится файловое дерево, похожее на описанное в предыдущем разделе, но с дополнительным каталогом smali вместо dex-файлов и файлом apktool.yml. Первый содержит дизассемблированный код исполняемого dex-файла приложения, второй — служебную информацию, необходимую apktool для сборки пакета обратно.
Первое место, куда мы должны заглянуть, — это, конечно же, AndroidManifest.xml. И здесь мы сразу встречаем следующую строку:
Нетрудно догадаться, что она отвечает за предоставление приложению полномочий на использование интернет-соединения. По сути, если мы хотим просто избавиться от рекламы, нам, скорее всего, достаточно будет запретить приложению интернет. Попытаемся это сделать. Удаляем указанную строку и пробуем собрать софтину с помощью apktool:
$ apktool b com.kauf.particle.virtualtorch
В каталоге com.kauf.particle.virtualtorch/build/ появится результирующий APK-файл. Однако установить его не получится, так как он не имеет цифровой подписи и контрольных сумм файлов (в нем просто нет каталога META-INF/). Мы должны подписать пакет с помощью утилиты apk-signer. Запустили. Интерфейс состоит из двух вкладок — на первой (Key Generator) создаем ключи, на второй (APK Signer) подписываем. Чтобы создать наш приватный ключ, заполняем следующие поля:
- Target File — выходной файл хранилища ключей; в нем обычно хранится одна пара ключей;
- Password и Confirm — пароль для хранилища;
- Alias — имя ключа в хранилище;
- Alias password и Confirm — пароль секретного ключа;
- Validity — срок действия (в годах). Значение по умолчанию оптимально.
Остальные поля, в общем-то, необязательны — но необходимо заполнить хотя бы одно.

WARNING
Чтобы подписать приложение с помощью apk-signer, ты должен установить Android SDK и указать полный путь до него в настройках приложения.
Вся информация предоставлена исключительно в ознакомительных целях. Ни редакция, ни автор не несут ответственности за любой возможный вред, причиненный материалами данной статьи.
Теперь этим ключом можно подписать APK. На вкладке APK Signer выбираем только что сгенерированный файл, вводим пароль, алиас ключа и пароль к нему, затем находим файл APK и смело жмем кнопку «Sign». Если все пройдет нормально, пакет будет подписан.
INFO
Так как мы подписали пакет нашим собственным ключом, он будет конфликтовать с оригинальным приложением, а это значит, что при попытке обновить софтину через маркет мы получим ошибку.
Цифровая подпись необходима только стороннему софту, поэтому если ты занимаешься модификацией системных приложений, которые устанавливаются копированием в каталог /system/app/, то подписывать их не нужно.
После этого скидываем пакет на смартфон, устанавливаем и запускаем. Вуаля, реклама пропала! Вместо нее, однако, появилось сообщение, что у нас нет интернета или отсутствуют соответствующие разрешения. По идее, этого могло бы и хватить, но сообщение выглядит раздражающе, да и, если честно, нам просто повезло с тупым приложением. Нормально написанная софтина, скорее всего, уточнит свои полномочия или проверит наличие интернет-соединения и в противном случае просто откажется запускаться. Как быть в этом случае? Конечно, править код.
Обычно авторы приложений создают специальные классы для вывода рекламы и вызывают методы этих классов во время запуска приложения или одной из его «активностей» (упрощенно говоря, экранов приложения). Попробуем найти эти классы. Идем в каталог smali, далее com (в org лежит только открытая графическая библиотека cocos2d), далее kauf (именно туда, потому что это имя разработчика и там лежит весь его код) — и вот он, каталог marketing. Внутри находим кучу файлов с расширением smali. Это классы, и наиболее примечателен из них класс Ad.smali, по названию которого нетрудно догадаться, что именно он выводит рекламу.
Мы могли бы изменить логику его работы, но гораздо проще будет тупо убрать вызовы любых его методов из самого приложения. Поэтому выходим из каталога marketing и идем в соседний каталог particle, а затем в virtualtorch. Особого внимания здесь заслуживает файл MainActivity.smali. Это стандартный для Android класс, который создается Android SDK и устанавливается в качестве точки входа в приложение (аналог функции main в Си). Открываем файл на редактирование.
Внутри находится код smali (местный ассемблер). Он довольно запутанный и трудный для чтения в силу своей низкоуровневой природы, поэтому мы не будем его изучать, а просто найдем все упоминания класса Ad в коде и закомментируем их. Вбиваем строку «Ad» в поиске и попадаем на строку 25:
.field private ad:Lcom/kauf/marketing/Ad;
Здесь создается поле ad для хранения объекта класса Ad. Комментируем с помощью установки знака ### перед строкой. Продолжаем поиск. Строка 423:
new-instance v3, Lcom/kauf/marketing/Ad;
Здесь происходит создание объекта. Комментируем. Продолжаем поиск и находим в строках 433, 435, 466, 468, 738, 740, 800 и 802 обращения к методам класса Ad. Комментируем. Вроде все. Сохраняем. Теперь пакет необходимо собрать обратно и проверить его работоспособность и наличие рекламы. Для чистоты эксперимента возвращаем удаленную из AndroidManifest.xml строку, собираем пакет, подписываем и устанавливаем.

Оп-па! Реклама пропала только во время работы приложения, но осталась в главном меню, которое мы видим, когда запускаем софтину. Так, подождите, но ведь точка входа — это класс MainActivity, а реклама пропала во время работы приложения, но осталась в главном меню, значит, точка входа другая? Чтобы выявить истинную точку входа, вновь открываем файл AndroidManifest.xml. И да, в нем есть следующие строки:
Они говорят нам (и, что важнее, андроиду) о том, что активность с именем Start должна быть запущена в ответ на генерацию интента (события) android.intent.action.MAIN из категории android.intent.category.LAUNCHER. Это событие генерируется при тапе на иконку приложения в ланчере, поэтому оно и определяет точку входа, а именно класс Start. Скорее всего, программист сначала написал приложение без главного меню, точкой входа в которое был стандартный класс MainActivity, а затем добавил новое окно (активность), содержащее меню и описанное в классе Start, и вручную сделал его точкой входа.
Открываем файл Start.smali и вновь ищем строку «Ad», находим в строках 153 и 155 упоминание класса FirstAd. Он тоже есть в исходниках и, судя по названию, как раз и отвечает за показ объявлений на главном экране. Смотрим дальше, идет создание экземпляра класса FirstAd и интента, по контексту имеющего отношение к этому экземпляру, а дальше метка cond_10, условный переход на которую осуществляется аккурат перед созданием экземпляра класса:
if-ne p1, v0, :cond_10 .line 74 new-instance v0, Landroid/content/Intent; . :cond_10
Скорее всего, программа каким-то случайном образом вычисляет, нужно ли показывать рекламу на главном экране, и, если нет, перескакивает сразу на cond_10. Ок, упростим ей задачу и заменим условный переход на безусловный:
#if-ne p1, v0, :cond_10 goto :cond_10
Больше упоминаний FirstAd в коде нет, поэтому закрываем файл и вновь собираем наш виртуальный факел с помощью apktool. Копируем на смартфон, устанавливаем, запускаем. Вуаля, вся реклама исчезла, с чем нас всех и поздравляем.
WWW
- Перевод приложений Android;
- пример снятия триала с приложения.
Итоги
Эта статья лишь краткое введение в методы вскрытия и модификации Android-приложений. За кадром остались многие вопросы, такие как снятие защиты, разбор обфусцированного кода, перевод и замена ресурсов приложения, а также модификация приложений, написанных с использованием Android NDK. Однако, имея базовые знания, разобраться во всем этом — лишь вопрос времени.