android parses android Manifest. XML in the unassembled apk and system source code analysis

Preface:

Scenario: Without installing the apk, get the package name, LAUNCHER Activity, etc. in the apk. This requires parsing our Android Manifest. XML file, and our APK is a zip compressed file, which is opened by compressing the file - good pressure, rar and so on.

You can see the above directory, and our Android Manifest. xml is in it. That's all we need to do. We just need to get the Android Manifest. xml file in the zip package and parse it. But before that, it's worth mentioning that when we pack the project into an apk, the Android Manifest. xml file has been encrypted, so it's not possible to parse the three APIs of xml directly.
There are two ways to get xml files:
1. Obtain by Decompilation
2. Get it through ZipFile. This parses the encrypted xml file through the api APKParser.jar provided by google.
APKParser Download: http://code.google.com/p/xml-apk-parser There is also a demo for reference.

Before analyzing how to parse androidManifest.xml in apk, we first understand how the system parses the androidManifest.xml file in our APK from the perspective of source code.

Source Angle Analysis - How to Parse Android Manifest. XML

The parsing apk is executed by the PackageManagerService of the system, and the parsing work of the PackageManagerService is in its initialization. Initialization of PackageManagerService:

In our System Server initAndLoop method, it is initialized by PackageManagerService.main(). Some versions of the source code may be the main() method, and System Server initializes many services at the framework layer, which only analyses PackageManager.
Deep into the PackageManagerService.main() method

You can see here that the PackageManagerService is initialized. Keep track of what the new PackageManagerService() does.


In its initialization method, there is such a piece of code, which is to obtain the app folder in the system directory, and then scan the apk file through scanDirLI(). Then we trace back to our scanDirLI() method.

As you can see, this method traverses the entire folder and then scans the apk files in it through the scanPackageLI () method.
In the scanPackageLI() method, a parsePackage() method is used to scan specific apk files.




The package object it returns contains a lot of information in our apk. So let's continue to track the whole parsePackage() method and how it parses our apk.

The code of this method is a bit long, separate screenshots, here are a few key points:
1. The first red box is to determine whether it is an apk file or not, and return null if it is not.
2. The openXmlResourceParser method returns an Xml resource parsing object, which parses our androidManifest.xml file.
3. Call his parsePackage () method with the same name for specific parsing operations. The following figure.



Parsing operation of parsePackage with the same name function:


From the above code, it is clear that the application and permission nodes in xml file are parsed separately, and the four components of our activity, service and some other node information are parsed in the parseApplication() method. At the same time, the parsed information to the four components and other information in xml file are encapsulated into the returned package object. Medium. The above is how the source code parses the specific process of Android Manifest. xml file in apk. Of course, the initialization of packageManagerService is not only to parse the APK file, but also to optimize the dex file and some other operations. You can have a deeper look at the source code if you want to understand.

After knowing how the source code parses the androidManifest.xml file, it's even less important to parse the xml file in the uninstalled apk.

Resolve the Android Manifest. XML file in the unassembled apk file

Before pasting the parsing code, a parsing result graph is presented.

Because of the excessive number, only a small part is intercepted here, and the above results output three fields.
tag-name represents the node information, name represents the key value of the current node attribute, and value represents the value value value of the current node attribute.
However, parsing this is not enough, and we need to get specific information, such as the package name / entry activity / channel information of this apk, which demo only demonstrates the access of entry activity.

package com.ljx.test;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;

import android.content.res.AXmlResourceParser;
import android.util.TypedValue;

public class AnalysisApk {

	public static void main(String[] args) {
		File file = null;
		file = new File("F:\\XXX.apk");
		ArrayList<String> mActivities = new ArrayList<String>();
		try {
			ZipFile zipFile = new ZipFile(file);
			Enumeration enumeration = zipFile.entries();
			// Get the AndroidManifest.xml file in the apk
			ZipEntry zipEntry = zipFile.getEntry(("AndroidManifest.xml"));
			AXmlResourceParser parser = new AXmlResourceParser();
			parser.open(zipFile.getInputStream(zipEntry));
			boolean flag = true;
			while (flag) {
				int event = parser.next();
				if (event == XmlPullParser.START_TAG) {

					int count = parser.getAttributeCount();

					//// Parse the entire Android Manifest. XML file and output it
					// for (int i = 0; i != parser.getAttributeCount(); ++i) {
					// System.out.printf("%s%s%s=\"%s\"",
					// new StringBuilder(10),
					// getNamespacePrefix(parser.getAttributePrefix(i)),
					// parser.getAttributeName(i),
					// getAttributeValue(parser, i));
					// System.out.println();
					// }

					for (int i = 0, size = parser.getAttributeCount(); i != size; ++i) {
						// Parse the entire Android Manifest. XML file and output it
						// System.out.println("tag-name " + parser.getName());
						// System.out.println("name " +
						// parser.getAttributeName(i));
						// System.out.println("value " +
						// getAttributeValue(parser,i));
						// System.out.println("");

						// Get application entry activity
						if (parser.getName().endsWith("activity") && parser.getAttributeName(i).equals("name")) {
							mActivities.add(getAttributeValue(parser, i));
						}
						if (parser.getAttributeValue(i).contains("MAIN")) {
							System.out.println(mActivities.get(mActivities.size() - 1));
							return;
						}

					}
				}

			}
		} catch (ZipException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (XmlPullParserException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	private static String getNamespacePrefix(String prefix) {
		if (prefix == null || prefix.length() == 0) {
			return "";
		}
		return prefix + ":";
	}

	private static String getAttributeValue(AXmlResourceParser parser, int index) {
		int type = parser.getAttributeValueType(index);
		int data = parser.getAttributeValueData(index);
		if (type == TypedValue.TYPE_STRING) {
			return parser.getAttributeValue(index);
		}
		if (type == TypedValue.TYPE_ATTRIBUTE) {
			return String.format("?%s%08X", getPackage(data), data);
		}
		if (type == TypedValue.TYPE_REFERENCE) {
			return String.format("@%s%08X", getPackage(data), data);
		}
		if (type == TypedValue.TYPE_FLOAT) {
			return String.valueOf(Float.intBitsToFloat(data));
		}
		if (type == TypedValue.TYPE_INT_HEX) {
			return String.format("0x%08X", data);
		}
		if (type == TypedValue.TYPE_INT_BOOLEAN) {
			return data != 0 ? "true" : "false";
		}
		if (type == TypedValue.TYPE_DIMENSION) {
			return Float.toString(complexToFloat(data)) + DIMENSION_UNITS[data & TypedValue.COMPLEX_UNIT_MASK];
		}
		if (type == TypedValue.TYPE_FRACTION) {
			return Float.toString(complexToFloat(data)) + FRACTION_UNITS[data & TypedValue.COMPLEX_UNIT_MASK];
		}
		if (type >= TypedValue.TYPE_FIRST_COLOR_INT && type <= TypedValue.TYPE_LAST_COLOR_INT) {
			return String.format("#%08X", data);
		}
		if (type >= TypedValue.TYPE_FIRST_INT && type <= TypedValue.TYPE_LAST_INT) {
			return String.valueOf(data);
		}
		return String.format("<0x%X, type 0x%02X>", data, type);
	}

	private static String getPackage(int id) {
		if (id >>> 24 == 1) {
			return "android:";
		}
		return "";
	}

	private static void log(StringBuilder xmlSb, String format, Object... arguments) {
		log(true, xmlSb, format, arguments);
	}

	private static void log(boolean newLine, StringBuilder xmlSb, String format, Object... arguments) {
		// System.out.printf(format,arguments);
		// if(newLine) System.out.println();
		xmlSb.append(String.format(format, arguments));
		if (newLine)
			xmlSb.append("\n");
	}

	/////////////////////////////////// ILLEGAL STUFF, DONT LOOK :)

	public static float complexToFloat(int complex) {
		return (float) (complex & 0xFFFFFF00) * RADIX_MULTS[(complex >> 4) & 3];
	}

	private static final float RADIX_MULTS[] = { 0.00390625F, 3.051758E-005F, 1.192093E-007F, 4.656613E-010F };
	private static final String DIMENSION_UNITS[] = { "px", "dip", "sp", "pt", "in", "mm", "", "" };
	private static final String FRACTION_UNITS[] = { "%", "%p", "", "", "", "", "", "" };

}

The two pieces of code annotated in the main method above are all parsing the Android Manifest. XML file, but the output style is different. Interested ones can try to write it down by themselves, and then see if the output result is the entry activity of our application. __________

The idea of the above code is to parse all the nodes in the Android Manifest. XML file, then find the active node, and then parse the attributes of the activity to see if it contains "MAIN", because only "MAIN" and "LAUNCHER" exist simultaneously, this activity is the entry activity that we apply, but the problem with the above code is that when a "MAIN" and "LAUNCHER" exist simultaneously, it is the entry activity. If there are multiple actions in Android Manifest. xml, the above judgment logic is incorrect. Strictly speaking, replace MAIN with LAUNCHER, but I know beforehand that only one action in my application is... MAIN, so I deal with it here. You can see that SplashActivity is found through the demo code above, and then you can open the Android Manifest. XML file of our project to see if it is the activity.



From the figure above, we can see that our analysis is completely correct.

How to parse the Android Manifest. XML file and source code analysis at the system level, basically that's it. Look at the code carefully and write it by yourself. If there are future needs in this regard, I believe it will not be you!





Keywords: xml Android Java Google

Added by spxdcz on Sat, 15 Jun 2019 01:20:00 +0300