Как получить надежный и действительный контент манифеста файла APK, даже используя InputStream? - PullRequest
9 голосов
/ 06 марта 2020

Фон

Я хотел получить информацию об APK-файлах (включая разделенные APK-файлы), даже если они находятся внутри сжатых zip-файлов (без их распаковки). В моем случае это включает в себя различные вещи, такие как имя пакета, код версии, имя версии, ярлык приложения, значок приложения и, если это разделенный файл APK или нет.

Обратите внимание, что я Я хочу сделать все это внутри приложения Android, не используя P C, поэтому некоторые инструменты могут быть недоступны для использования.

Проблема

Это означает, что я могу ' • не используйте функцию getPackageArchiveInfo , так как для этой функции требуется путь к файлу APK и она работает только с файлами не-split-apk.

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

Существуют различные решения онлайн в том числе за пределами Android, но я не знаю ни одного, который стабилен и работает для всех случаев. Многие из них могут быть полезны даже для Android (например, здесь ), но могут не выполнить синтаксический анализ и могут потребовать путь к файлу вместо Uri / InputStream.

Что я Я нашел и попробовал

Я нашел this в StackOverflow, но, к сожалению, в соответствии с моими тестами, он всегда генерирует контент, но в некоторых редких случаях это недопустимо XML content.

До сих пор я обнаружил имена пакетов этих приложений и коды их версий, которые синтаксический анализатор не может проанализировать, так как вывод XML content недопустим:

  1. com.farpro c .wifi.analyzer 139
  2. com.teslacoilsw.launcherclientproxy 2
  3. com.hotornot.app 3072
  4. android 29 (это само системное приложение "Android System")
  5. com.google. android .videos 41300042
  6. com.facebook.katana 201518851
  7. com.keramidas.TitaniumBackupPr o 10
  8. com.google. android .apps.tachyon 2985033
  9. com.google. android .apps.photos 3594753

Использование средства просмотра XML и XML validator , вот проблемы с этими приложениями:

  • Для # 1, # 2 я получил очень странный контент, начиная с <mnfs.
  • Для # 3, он не любит "&" в <activity theme="resourceID 0x7f13000b" label="Features & Tests" ...
  • Для # 4, он пропустил конечный тег "manifest" в конце.
  • Для # 5 он пропустил несколько конечных тегов, по крайней мере, «намеренный фильтр», «получатель» и «манифест». Может быть, больше.
  • Для # 6 он по какой-то причине дважды получил атрибут "allowBackup" в теге "application".
  • Для # 7 он получил значение без атрибута в теге manifest : <manifest versionCode="resourceID 0xa" ="1.3.2".
  • Для # 8, он пропустил много контента после получения некоторых тегов «особенность использования» и не имел конечного тега для «манифеста».
  • Для # 9, он пропустил много контента после получения некоторых тегов «использования-разрешения» и не имел конечного тега для «манифеста»

Удивительно, но я не нашел никаких проблем с разделенными файлами APK. Только с основными файлами APK.

Вот код (также доступен здесь ):

MainActivity .kt

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        thread {
            val problematicApkFiles = HashMap<ApplicationInfo, HashSet<String>>()
            val installedApplications = packageManager.getInstalledPackages(0)
            val startTime = System.currentTimeMillis()
            for ((index, packageInfo) in installedApplications.withIndex()) {
                val applicationInfo = packageInfo.applicationInfo
                val packageName = packageInfo.packageName
//                Log.d("AppLog", "$index/${installedApplications.size} parsing app $packageName ${packageInfo.versionCode}...")
                val mainApkFilePath = applicationInfo.publicSourceDir
                val parsedManifestOfMainApkFile =
                        try {
                            val parsedManifest = ManifestParser.parse(mainApkFilePath)
                            if (parsedManifest?.isSplitApk != false)
                                Log.e("AppLog", "$packageName - parsed normal APK, but failed to identify it as such")
                            parsedManifest?.manifestAttributes
                        } catch (e: Exception) {
                            Log.e("AppLog", e.toString())
                            null
                        }
                if (parsedManifestOfMainApkFile == null) {
                    problematicApkFiles.getOrPut(applicationInfo, { HashSet() }).add(mainApkFilePath)
                    Log.e("AppLog", "$packageName - failed to parse main APK file $mainApkFilePath")
                }
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
                    applicationInfo.splitPublicSourceDirs?.forEach {
                        val parsedManifestOfSplitApkFile =
                                try {
                                    val parsedManifest = ManifestParser.parse(it)
                                    if (parsedManifest?.isSplitApk != true)
                                        Log.e("AppLog", "$packageName - parsed split APK, but failed to identify it as such")
                                    parsedManifest?.manifestAttributes
                                } catch (e: Exception) {
                                    Log.e("AppLog", e.toString())
                                    null
                                }
                        if (parsedManifestOfSplitApkFile == null) {
                            Log.e("AppLog", "$packageName - failed to parse main APK file $it")
                            problematicApkFiles.getOrPut(applicationInfo, { HashSet() }).add(it)
                        }
                    }
            }
            val endTime = System.currentTimeMillis()
            Log.d("AppLog", "done parsing. number of files we failed to parse:${problematicApkFiles.size} time taken:${endTime - startTime} ms")
            if (problematicApkFiles.isNotEmpty()) {
                Log.d("AppLog", "list of files that we failed to get their manifest:")
                for (entry in problematicApkFiles) {
                    Log.d("AppLog", "packageName:${entry.key.packageName} , files:${entry.value}")
                }
            }
        }
    }
}

ManifestParser.kt

class ManifestParser{
    var isSplitApk: Boolean? = null
    var manifestAttributes: HashMap<String, String>? = null

    companion object {
        fun parse(file: File) = parse(java.io.FileInputStream(file))
        fun parse(filePath: String) = parse(File(filePath))
        fun parse(inputStream: InputStream): ManifestParser? {
            val result = ManifestParser()
            val manifestXmlString = ApkManifestFetcher.getManifestXmlFromInputStream(inputStream)
                    ?: return null
            val factory: DocumentBuilderFactory = DocumentBuilderFactory.newInstance()
            val builder: DocumentBuilder = factory.newDocumentBuilder()
            val document: Document? = builder.parse(manifestXmlString.byteInputStream())
            if (document != null) {
                document.documentElement.normalize()
                val manifestNode: Node? = document.getElementsByTagName("manifest")?.item(0)
                if (manifestNode != null) {
                    val manifestAttributes = HashMap<String, String>()
                    for (i in 0 until manifestNode.attributes.length) {
                        val node = manifestNode.attributes.item(i)
                        manifestAttributes[node.nodeName] = node.nodeValue
                    }
                    result.manifestAttributes = manifestAttributes
                }
            }
            result.manifestAttributes?.let {
                result.isSplitApk = (it["android:isFeatureSplit"]?.toBoolean()
                        ?: false) || (it.containsKey("split"))
            }
            return result
        }

    }
}

ApkManifestFetcher.kt

Вопросы

  1. Почему я получаю недопустимое содержимое XML для некоторых файлов манифеста APK (следовательно, для них не удается выполнить синтаксический анализ XML)?
  2. Как я могу заставить его работать, всегда?
  3. Есть ли лучший способ разобрать файл манифеста в действительный XML? Может быть, лучшая альтернатива, которая могла бы работать со всеми видами файлов APK, в том числе внутри заархивированных файлов, без их распаковки?

Ответы [ 2 ]

0 голосов
/ 16 марта 2020

Кажется, что ApkManifestFetcher не обрабатывает все случаи, такие как текст (между тегами) и объявления пространства имен и, возможно, некоторые другие вещи. Ниже доработка ApkManifestFetcher , которая обрабатывает все 300+ APK на моем телефоне, за исключением Netflix APK, который имеет некоторые пустые атрибуты.

Я больше не верю, что файлы, которые запускаются с <mnfs имеет какое-либо отношение к запутыванию, но кодируется с использованием UTF-8, а не UTF-16, что предполагает приложение (16 бит против 8 бит). Переработанное приложение обрабатывает кодировку UTF-8 и может анализировать эти файлы.

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

Тем не менее, приведенный ниже код может быть достаточно хорошим для определенных приложений. Лучше, хотя и дольше, было бы использовать код из apktool , который, кажется, способен обрабатывать все APK.

ApkManifestFetcher

0 голосов
/ 15 марта 2020

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

Псевдонимы и шестнадцатеричные ссылки могут запутать это; эти проблемы должны быть решены.

Например, откат от manifest до mnfs мог бы решить хотя бы одну проблему:

fun getRootNode(document: Document): Node? {
    var node: Node? = document.getElementsByTagName("manifest")?.item(0)
    if (node == null) {
        node = document.getElementsByTagName("mnfs")?.item(0)
    }
    return node
}

«Функции и тесты» требуется TextUtils.htmlEncode() для &amp; или другой конфигурации синтаксического анализатора.

Упрощение анализа отдельных файлов AndroidManifest.xml упростит тестирование, поскольку для каждого другого пакета возможен более неожиданный ввод - пока он не приблизится парсеру манифеста, который использует ОС (может помочь исходный код ). Как видите, он может установить куки для чтения. Возьмите этот список имен пакетов и создайте тестовый пример для каждого из них, тогда проблемы будут довольно изолированными. Но главная проблема в том, что эти файлы cookie , скорее всего , недоступны сторонним приложениям.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...