All Downloads are FREE. Search and download functionalities are using the official Maven repository.

soot.jimple.infoflow.android.manifest.BaseProcessManifest Maven / Gradle / Ivy

package soot.jimple.infoflow.android.manifest;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import org.xmlpull.v1.XmlPullParserException;

import pxb.android.axml.AxmlVisitor;
import soot.jimple.infoflow.android.axml.AXmlAttribute;
import soot.jimple.infoflow.android.axml.AXmlDocument;
import soot.jimple.infoflow.android.axml.AXmlHandler;
import soot.jimple.infoflow.android.axml.AXmlNode;
import soot.jimple.infoflow.android.axml.ApkHandler;
import soot.jimple.infoflow.android.manifest.binary.BinaryAndroidApplication;
import soot.jimple.infoflow.android.manifest.containers.EagerComponentContainer;
import soot.jimple.infoflow.android.manifest.containers.EmptyComponentContainer;
import soot.jimple.infoflow.android.resources.ARSCFileParser;

/**
 * This class provides easy access to all data of an AppManifest.
* Nodes and attributes of a parsed manifest can be changed. A new byte * compressed manifest considering the changes can be generated. * * @author Steven Arzt * @author Stefan Haas, Mario Schlipf * @see App * Manifest */ public abstract class BaseProcessManifest implements IManifestHandler { /** * Factory class for creating component implementations * * @author Steven Arzt * */ protected interface IComponentFactory { /** * Creates a new data object for an activity inside an Android app * * @param node The binary XML node that contains the activity definition * @return The new activity object */ public A createActivity(AXmlNode node); /** * Creates a new data object for a broadcast receiver inside an Android app * * @param node The binary XML node that contains the broadcast receiver * definition * @return The new broadcast receiver object */ public B createBroadcastReceiver(AXmlNode node); /** * Creates a new data object for a content provider inside an Android app * * @param node The binary XML node that contains the content provider definition * @return The new broadcast content provider object */ public C createContentProvider(AXmlNode node); /** * Creates a new data object for a service inside an Android app * * @param node The binary XML node that contains the service definition * @return The new broadcast service object */ public S createService(AXmlNode node); } /** * Handler for zip-like apk files */ protected ApkHandler apk = null; /** * Handler for android xml files */ protected AXmlHandler axml; protected ARSCFileParser arscParser; // android manifest data protected AXmlNode manifest; protected AXmlNode application; // Components in the manifest file protected List providers = null; protected List services = null; protected List activities = null; protected List aliasActivities = null; protected List receivers = null; protected IComponentFactory factory = createComponentFactory(); /** * Processes an AppManifest which is within the file identified by the given * path. * * @param apkPath file path to an APK. * @throws IOException if an I/O error occurs. * @throws XmlPullParserException can occur due to a malformed manifest. */ public BaseProcessManifest(String apkPath) throws IOException, XmlPullParserException { this(new File(apkPath)); } /** * Processes an AppManifest which is within the given {@link File}. * * @param apkFile the AppManifest within the given APK will be parsed. * @throws IOException if an I/O error occurs. * @throws XmlPullParserException can occur due to a malformed manifest. * @see {@link BaseProcessManifest#ProcessManifest(InputStream)} */ public BaseProcessManifest(File apkFile) throws IOException, XmlPullParserException { this(apkFile, ARSCFileParser.getInstance(apkFile)); } /** * Processes an AppManifest which is within the given {@link File}. * * @param apkFile the AppManifest within the given APK will be parsed. * @param arscParser The parser for the Android resource database * @throws IOException if an I/O error occurs. * @throws XmlPullParserException can occur due to a malformed manifest. * @see {@link BaseProcessManifest#ProcessManifest(InputStream)} */ public BaseProcessManifest(File apkFile, ARSCFileParser arscParser) throws IOException, XmlPullParserException { if (!apkFile.exists()) throw new RuntimeException( String.format("The given APK file %s does not exist", apkFile.getCanonicalPath())); this.apk = new ApkHandler(apkFile); this.arscParser = arscParser; try (InputStream is = this.apk.getInputStream("AndroidManifest.xml")) { if (is == null) throw new FileNotFoundException( String.format("The file %s does not contain an Android Manifest", apkFile.getAbsolutePath())); this.handle(is); } } /** * Processes an AppManifest which is provided by the given {@link InputStream}. * * @param manifestIS InputStream for an AppManifest. * @param arscParser The Android resource file parser * @throws IOException if an I/O error occurs. * @throws XmlPullParserException can occur due to a malformed manifest. */ public BaseProcessManifest(InputStream manifestIS, ARSCFileParser arscParser) throws IOException, XmlPullParserException { this.arscParser = arscParser; this.handle(manifestIS); } /** * Initialises the {@link BaseProcessManifest} by parsing the manifest provided * by the given {@link InputStream}. * * @param manifestIS InputStream for an AppManifest. * @throws IOException if an I/O error occurs. * @throws XmlPullParserException can occur due to a malformed manifest. */ protected void handle(InputStream manifestIS) throws IOException, XmlPullParserException { this.axml = new AXmlHandler(manifestIS); // get manifest node AXmlDocument document = this.axml.getDocument(); this.manifest = document.getRootNode(); if (!this.manifest.getTag().equals("manifest")) throw new RuntimeException("Root node is not a manifest node"); // get application node List applications = this.manifest.getChildrenWithTag("application"); if (applications.isEmpty()) throw new RuntimeException("Manifest contains no application node"); else if (applications.size() > 1) throw new RuntimeException("Manifest contains more than one application node"); this.application = applications.get(0); // Get components this.providers = this.axml.getNodesWithTag("provider"); this.services = this.axml.getNodesWithTag("service"); this.activities = this.axml.getNodesWithTag("activity"); this.aliasActivities = this.axml.getNodesWithTag("activity-alias"); this.receivers = this.axml.getNodesWithTag("receiver"); } /** * Returns the handler which parsed and holds the manifest's data. * * @return Android XML handler */ public AXmlHandler getAXml() { return this.axml; } /** * Returns the handler which opened the APK file. If {@link BaseProcessManifest} * was instanciated directly with an {@link InputStream} this will return * null. * * @return APK Handler */ public ApkHandler getApk() { return this.apk; } /** * The unique manifest node of the AppManifest. * * @return manifest node */ public AXmlNode getManifest() { return this.manifest; } @Override public IAndroidApplication getApplication() { return new BinaryAndroidApplication(this.application, this); } @Override public IComponentContainer getContentProviders() { if (this.providers == null) return EmptyComponentContainer.get(); return new EagerComponentContainer<>( this.providers.stream().map(p -> factory.createContentProvider(p)).collect(Collectors.toList())); } @Override public IComponentContainer getServices() { if (this.services == null) return EmptyComponentContainer.get(); return new EagerComponentContainer<>( services.stream().map(s -> factory.createService(s)).collect(Collectors.toList())); } /** * Gets the type of the component identified by the given class name * * @param className The class name for which to get the component type * @return The component type of the given class if this class has been * registered as a component in the manifest file, otherwise null */ public ComponentType getComponentType(String className) { for (AXmlNode node : this.activities) if (node.getAttribute("name").getValue().equals(className)) return ComponentType.Activity; for (AXmlNode node : this.services) if (node.getAttribute("name").getValue().equals(className)) return ComponentType.Service; for (AXmlNode node : this.receivers) if (node.getAttribute("name").getValue().equals(className)) return ComponentType.BroadcastReceiver; for (AXmlNode node : this.providers) if (node.getAttribute("name").getValue().equals(className)) return ComponentType.ContentProvider; return null; } @Override public IComponentContainer getActivities() { if (this.activities == null) return EmptyComponentContainer.get(); return new EagerComponentContainer<>( this.activities.stream().map(a -> factory.createActivity(a)).collect(Collectors.toList())); } /** * Returns a list containing all nodes with tag activity-alias * * @return list with all alias activities */ public List getAliasActivities() { return new ArrayList(this.aliasActivities); } @Override public IComponentContainer getBroadcastReceivers() { if (this.receivers == null) return EmptyComponentContainer.get(); return new EagerComponentContainer<>( receivers.stream().map(r -> factory.createBroadcastReceiver(r)).collect(Collectors.toList())); } /** * Returns the provider which has the given name. * * @param name the provider's name * * @param name the provider's name * @return provider with name */ public AXmlNode getProvider(String name) { return this.getNodeWithName(this.providers, name); } /** * Returns the service which has the given name. * * @param name the service's name * @return service with name */ public AXmlNode getService(String name) { return this.getNodeWithName(this.services, name); } /** * Returns the activity which has the given name. * * @param name the activitie's name * @return activitiy with name */ public AXmlNode getActivity(String name) { return this.getNodeWithName(this.activities, name); } /** * Returns the alias analysis which has the given name * * @param name the alias activity's name * @return alias activity with name */ public AXmlNode getAliasActivity(String name) { return this.getNodeWithName(this.aliasActivities, name); } /** * Returns the receiver which has the given name. * * @param name the receiver's name * @return receiver with name */ public AXmlNode getReceiver(String name) { return this.getNodeWithName(this.receivers, name); } /** * Iterates over list and checks which node has the given * name. * * @param list contains nodes. * @param name the node's name. * @return node with name. */ protected AXmlNode getNodeWithName(List list, String name) { for (AXmlNode node : list) { Object attr = node.getAttributes().get("name"); if (attr != null && ((AXmlAttribute) attr).getValue().equals(name)) return node; } return null; } /** * Returns the target activity specified in the targetActivity * attribute of the alias activity * * @param aliasActivity * @return activity */ public AXmlNode getAliasActivityTarget(AXmlNode aliasActivity) { if (BaseProcessManifest.isAliasActivity(aliasActivity)) { AXmlAttribute targetActivityAttribute = aliasActivity.getAttribute("targetActivity"); if (targetActivityAttribute != null) { return this.getActivity((String) targetActivityAttribute.getValue()); } } return null; } /** * Returns whether the given activity is an alias activity or not * * @param activity * @return True if the activity is an alias activity, False otherwise */ public static boolean isAliasActivity(AXmlNode activity) { return activity.getTag().equals("activity-alias"); } public ArrayList getAllActivities() { ArrayList allActivities = new ArrayList<>(this.activities); allActivities.addAll(this.aliasActivities); return allActivities; } /** * Returns the Manifest as a compressed android xml byte array. This will * consider all changes made to the manifest and application nodes respectively * to their child nodes. * * @return byte compressed AppManifest * @see AXmlHandler#toByteArray() */ public byte[] getOutput() { return this.axml.toByteArray(); } /** * Gets the application's package name * * @return The package name of the application */ private String cache_PackageName = null; public String getPackageName() { if (cache_PackageName == null) { AXmlAttribute attr = this.manifest.getAttribute("package"); if (attr != null && attr.getValue() != null) cache_PackageName = (String) attr.getValue(); } return cache_PackageName; } /** * Gets the version code of the application. This code is used to compare * versions for updates. * * @return The version code of the application */ public int getVersionCode() { AXmlAttribute attr = this.manifest.getAttribute("versionCode"); return attr == null || attr.getValue() == null ? -1 : Integer.parseInt(attr.getValue().toString()); } /** * Gets the application's version name as it is displayed to the user * * @return The application#s version name as in pretty print */ public String getVersionName() { AXmlAttribute attr = this.manifest.getAttribute("versionName"); return attr == null || attr.getValue() == null ? null : attr.getValue().toString(); } /** * Gets the minimum SDK version on which this application is supposed to run * * @return The minimum SDK version on which this application is supposed to run */ public int getMinSdkVersion() { List usesSdk = this.manifest.getChildrenWithTag("uses-sdk"); if (usesSdk == null || usesSdk.isEmpty()) return -1; AXmlAttribute attr = usesSdk.get(0).getAttribute("minSdkVersion"); if (attr == null || attr.getValue() == null) return -1; if (attr.getValue() instanceof Integer) return (Integer) attr.getValue(); return Integer.parseInt(attr.getValue().toString()); } /** * Gets the target SDK version for which this application was developed * * @return The target SDK version for which this application was developed */ public int getTargetSdkVersion() { List usesSdk = this.manifest.getChildrenWithTag("uses-sdk"); if (usesSdk == null || usesSdk.isEmpty()) return -1; AXmlAttribute attr = usesSdk.get(0).getAttribute("targetSdkVersion"); if (attr == null || attr.getValue() == null) return -1; if (attr.getValue() instanceof Integer) return (Integer) attr.getValue(); return Integer.parseInt(attr.getValue().toString()); } /** * Gets the permissions this application requests * * @return The permissions requested by this application * @return */ public Set getPermissions() { List usesPerms = this.manifest.getChildrenWithTag("uses-permission"); Set permissions = new HashSet(); for (AXmlNode perm : usesPerms) { AXmlAttribute attr = perm.getAttribute("name"); if (attr != null) permissions.add((String) attr.getValue()); else { // The required "name" attribute is missing, so we collect all // empty // attributes as a best-effort solution for broken malware apps for (AXmlAttribute a : perm.getAttributes().values()) if (a.getType() == AxmlVisitor.TYPE_STRING && (a.getName() == null || a.getName().isEmpty())) permissions.add((String) a.getValue()); } } return permissions; } /** * Adds a new permission to the manifest. * * @param complete permission name e.g. "android.permission.INTERNET" */ public void addPermission(String permissionName) { AXmlNode permission = new AXmlNode("uses-permission", null, manifest, ""); AXmlAttribute permissionNameAttr = new AXmlAttribute("name", permissionName, AXmlHandler.ANDROID_NAMESPACE); permission.addAttribute(permissionNameAttr); } /** * Adds a new provider to the manifest * * @param node provider represented as an AXmlNode */ public void addProvider(AXmlNode node) { if (providers.isEmpty()) providers = new ArrayList(); providers.add(node); } /** * Adds a new receiver to the manifest * * @param node receiver represented as an AXmlNode */ public void addReceiver(AXmlNode node) { if (receivers.isEmpty()) receivers = new ArrayList(); receivers.add(node); } /** * Adds a new activity to the manifest * * @param node activity represented as an AXmlNode */ public void addActivity(AXmlNode node) { if (activities.isEmpty()) activities = new ArrayList(); activities.add(node); } /** * Adds a new service to the manifest * * @param node service represented as an AXmlNode */ public void addService(AXmlNode node) { if (services.isEmpty()) services = new ArrayList(); services.add(node); } /** * Closes this apk file and all resources associated with it */ @Override public void close() { if (this.apk != null) this.apk.close(); } /** * Returns all activity nodes that are "launchable", i.e. that are called when * the user clicks on the button in the launcher. * * @return */ public Set getLaunchableActivityNodes() { Set allLaunchableActivities = new LinkedHashSet(); for (AXmlNode activity : this.getAllActivities()) { for (AXmlNode activityChildren : activity.getChildren()) { if (activityChildren.getTag().equals("intent-filter")) { boolean actionFilter = false; boolean categoryFilter = false; for (AXmlNode intentFilter : activityChildren.getChildren()) { if (intentFilter.getTag().equals("action") && intentFilter.getAttribute("name").getValue() .toString().equals("android.intent.action.MAIN")) actionFilter = true; else if (intentFilter.getTag().equals("category") && intentFilter.getAttribute("name") .getValue().toString().equals("android.intent.category.LAUNCHER")) categoryFilter = true; } if (actionFilter && categoryFilter) allLaunchableActivities.add(activity); } } } return allLaunchableActivities; } /** * Generates a full class name from a short class name by appending the * globally-defined package when necessary * * @param className The class name to expand * @return The expanded class name for the given short name */ public String expandClassName(String className) { String packageName = getPackageName(); if (className.startsWith(".")) return packageName + className; else if (!className.contains(".")) return packageName + "." + className; else return className; } /** * Gets the Android resource parser * * @return The Android resource parser */ public ARSCFileParser getArscParser() { return arscParser; } /** * Creates a factory that can instantiate data objects for Android components * * @return The new factory */ protected abstract IComponentFactory createComponentFactory(); }