平时经常用 drozer dump manifest,感觉稍微有点难用,直到某次批量 dump,发现很多 manifest 连 hash 都一样,发现 drozer 抽风了,本文记录一下。
一、触发方式 在小米手机上,运行 run app.package.manifest com.miui.notes ,发现返回的是 com.xiaomi.micloud.sdk 的 manifest。 当然在其他手机上也有类似的现象,我就不列举了,批量 dump 就会发现的。
二、代码溯源 app.package.manifest 功能本质上是一个 drozer module,它位于 drozer/modules/app/package.py
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 class Manifest (Module, common.Assets ): name = "Get AndroidManifest.xml of package" description = "Retrieves AndroidManifest.xml from an installed package." examples = """Getting the manifest for drozer dz> run app.package.manifest com.mwr.dz <manifest versionCode="2" versionName="1.1" package="com.mwr.dz"> <uses-sdk minSdkVersion="8" targetSdkVersion="4"> </uses-sdk> <uses-permission name="android.permission.INTERNET"> </uses-permission> ... </manifest>""" author = "MWR InfoSecurity (@mwrlabs)" date = "2012-11-06" license = "BSD (3 clause)" path = ["app" , "package" ] permissions = ["com.mwr.dz.permissions.GET_CONTEXT" ] def add_arguments (self, parser ): parser.add_argument("package" , help ="the identifier of the package" ) def execute (self, arguments ): if arguments.package == None or arguments.package == "" : self.stderr.write("No package provided.n" ) else : self.__write_manifest(self.getAndroidManifest(arguments.package))
这个 getAndroidManifest 在 drozer/modules/common/assets.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Assets (loader.ClassLoader ): """ Utility methods for interacting with the Android Asset Manager. """ def getAndroidManifest (self, package ): """ Extract the AndroidManifest.xml file from a package on the device, and recover it as an XML representation. """ XmlAssetReader = self.loadClass("common/XmlAssetReader.apk" , "XmlAssetReader" ) asset_manager = self.getAssetManager(package) xml = asset_manager.openXmlResourceParser("AndroidManifest.xml" ) xml_string = str (XmlAssetReader.read(xml)) self.reflector.delete(asset_manager) self.reflector.delete(xml) return xml_string
加载一个 java 文件,在手机上运行后获取返回的字符串。
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 46 47 48 49 50 51 52 53 54 55 import java.io.IOException;import android.content.res.XmlResourceParser;import org.xmlpull.v1.XmlPullParser;import org.xmlpull.v1.XmlPullParserException;public class XmlAssetReader { public static String read (XmlResourceParser xml) { StringBuilder output = new StringBuilder(); try { while (xml.next() != XmlPullParser.END_DOCUMENT) { switch (xml.getEventType()) { case XmlPullParser.START_TAG: output.append("<" ); output.append(xml.getName()); for (int i=0 ; i<xml.getAttributeCount(); i++) { output.append(" " ); output.append(xml.getAttributeName(i)); output.append("=" "); output.append(xml.getAttributeValue(i).replace(" "" ,""" )); output.append("" "); } output.append(" >n"); break; case XmlPullParser.END_TAG: output.append(" </"); output.append(xml.getName()); output.append(" >n"); break; case XmlPullParser.TEXT: output.append(xml.getText()); output.append(" n"); break; default: break; } } } catch(IOException e) { return null; } catch(XmlPullParserException e) { return null; } return output.toString(); } }
因此,我们将这个过程移植到手机上,即可进行测试。
三、代码测试 编写如下的 java 代码进行模拟:
1 2 XmlResourceParser io = createPackageContext("com.miui.notes" ,0 ).getAssets().openXmlResourceParser("AndroidManifest.xml" ); Log.e("tutu" , "" + XmlAssetReader.read(io));
打印输出仍然是错误的,说明是openXmlResourceParser 的问题。
跟进一步,发现openXmlResourceParser(String)会调用openXmlResourceParser(0, String),第一个参数零表示 cookie,最终调用到 native 方法。 在AssetManager 里搜cookie 的代码,大致猜测出这个 cookie 表示下标,为零时表示随意选择一个 ApkAsset,而 AssetManager 里有 ApkAssets[] 结构体,很可能是相关的,编写如下代码进行验证。
1 2 3 4 5 6 7 8 9 10 11 12 13 String pkgName = "com.miui.notes" ; pkgName = getPackageName(); AssetManager assets = createPackageContext(pkgName, 0 ).getAssets(); Method m = assets.getClass().getDeclaredMethod("findCookieForPath" , String.class); m.setAccessible(true ); Field f = assets.getClass().getDeclaredField("mApkAssets" ); f.setAccessible(true ); ApkAssets[] as = (ApkAssets[]) f.get(assets); for (ApkAssets a : as) { Log.e("tutu" , a.getAssetPath()); } int cookieIdx = (int ) m.invoke(assets, getPackageManager().getApplicationInfo(pkgName, 0 ).publicSourceDir);Log.e("tutu" , "cookieIdx " + cookieIdx);
当包名是 com.miui.notes 和 自己时,日志分别为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 E/tutu: /system/framework/framework-res.apk E/tutu: /system/framework/framework-ext-res/framework-ext-res.apk E/tutu: /system/app/miuisystem/miuisystem.apk E/tutu: /system/app/miui/miui.apk E/tutu: /vendor/overlay/FrameworksResCommon.apk E/tutu: /vendor/overlay/DevicesAndroidOverlay.apk E/tutu: /data/app/com.miui.notes-l07QybyO3roqn-okroQsSQ==/base.apk E/tutu: /system/priv-app/RtMiCloudSDK/RtMiCloudSDK.apk E/tutu: cookieIdx 7 E/tutu: /system/framework/framework-res.apk E/tutu: /system/framework/framework-ext-res/framework-ext-res.apk E/tutu: /system/app/miuisystem/miuisystem.apk E/tutu: /system/app/miui/miui.apk E/tutu: /vendor/overlay/FrameworksResCommon.apk E/tutu: /vendor/overlay/DevicesAndroidOverlay.apk E/tutu: /data/app/com.leadroyal.helloandroid-ml5tuR__VGsg6o3Far2g7Q==/base.apk E/tutu: cookieIdx 7
差别仅在于最后那一项,com.miui.notes 会依赖 RtMiCloudSDK.apk,也就是出错的那一项,而我们的 apk 是没有它的。 将正确的值传递给 openXmlResourceParser ,发现返回是正确的,至此,破案了,是 openXmlResourceParser 发生了非预期的现象。
四、解决方案 由于相关 API 都是 hide 的,写代码不方便,在此我还是建议别用这个工具了,还是用我写的 ShrinkApkAnalyzer 吧。