平时经常用 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 comes from drozer.modules.base 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 吧。