@SuppressWarnings("unchecked") private static void addMaxPrioForSMSReceiver(ProcessManifest manifest) { for(AXmlNode receiver : manifest.getReceivers()) { for(AXmlNode receiverChild : receiver.getChildren()) { if(receiverChild.getTag().equals("intent-filter")) { //search for SMS receiver for(AXmlNode childChild : receiverChild.getChildren()) { if(childChild.getTag().equals("action")) { if(childChild.hasAttribute("name") && ((String)childChild.getAttribute("name").getValue()).equalsIgnoreCase("android.provider.Telephony.SMS_RECEIVED")){ //prepare the priority filter if(receiverChild.hasAttribute("priority")) ((AXmlAttribute<Integer>)receiverChild.getAttribute("priority")).setValue(Integer.MAX_VALUE); else { AXmlAttribute<Integer> attr = new AXmlAttribute<Integer>("priority", Integer.MAX_VALUE, ANDROID_NAMESPACE); receiverChild.addAttribute(attr); } } } } } } } }
private boolean isComponentExported(AXmlNode node) { boolean accessible = false; AXmlAttribute<?> attrEnabled = node.getAttribute("enabled"); if (attrEnabled == null || !attrEnabled.getValue().equals(Boolean.FALSE)) { AXmlAttribute<?> attrExported = node.getAttribute("exported"); if (attrExported != null){ if(attrExported.getValue().equals(Boolean.TRUE)) { accessible = true; } else if(attrExported.getValue().equals(Boolean.FALSE)) { accessible = false; } } else { if(node.getChildrenWithTag("intent-filter").size() != 0) { accessible = true; } } } return accessible; }
private void checkAndAddComponent(Set<String> entryPoints, AXmlNode node) { AXmlAttribute<?> attrEnabled = node.getAttribute("enabled"); if (attrEnabled == null || !attrEnabled.getValue().equals(Boolean.FALSE)) { AXmlAttribute<?> attr = node.getAttribute("name"); if (attr != null) entryPoints.add(expandClassName((String) attr.getValue())); else { // This component does not have a name, so this might be obfuscated // malware. We apply a heuristic. for (Entry<String, AXmlAttribute<?>> a : node.getAttributes().entrySet()) if (a.getValue().getName().isEmpty() && a.getValue().getType() == AxmlVisitor.TYPE_STRING) { String name = (String) a.getValue().getValue(); if (isValidComponentName(name)) entryPoints.add(expandClassName(name)); } } } }
/** * Gets the permissions this application requests * @return The permissions requested by this application * @return */ public Set<String> getPermissions() { List<AXmlNode> usesPerms = this.manifest.getChildrenWithTag("uses-permission"); Set<String> permissions = new HashSet<String>(); 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().isEmpty()) permissions.add((String) a.getValue()); } } return permissions; }
/** * 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<AXmlNode> getLaunchableActivities() { Set<AXmlNode> allLaunchableActivities = new HashSet<AXmlNode>(); for(AXmlNode activity : activities) { for(AXmlNode activityChildren : activity.getChildren()) { if(activityChildren.getTag().equals("intent-filter")) { boolean actionFilter = false; boolean categoryFilter = false; for(AXmlNode intentFilter : activityChildren.getChildren()) { if(intentFilter.toString().equals("<action name=\"android.intent.action.MAIN\">")) actionFilter = true; else if(intentFilter.toString().equals("<category name=\"android.intent.category.LAUNCHER\">")) categoryFilter = true; } if(actionFilter && categoryFilter) allLaunchableActivities.add(activity); } } } return allLaunchableActivities; }
/** * Initialises the {@link ProcessManifest} 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 List<AXmlNode> manifests = this.axml.getNodesWithTag("manifest"); if(manifests.isEmpty()) throw new RuntimeException("Manifest contains no manifest node"); else if(manifests.size() > 1) throw new RuntimeException("Manifest contains more than one manifest node"); this.manifest = manifests.get(0); // get application node List<AXmlNode> 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.receivers = this.axml.getNodesWithTag("receiver"); }
private boolean isLaunchableAcitivity(SootClass sc, ProcessManifest manifest) { Set<AXmlNode> launchableActivities = manifest.getLaunchableActivities(); for(AXmlNode node : launchableActivities) { if(node.hasAttribute("name")) { String activityName = (String)node.getAttribute("name").getValue(); activityName = getFullyQualifiedName(manifest, activityName); if(activityName.equals(sc.getName())) return true; } } return false; }
private static void addComponentCallerService(ProcessManifest androidManifest) { AXmlNode componentCallerService = new AXmlNode("service", null, androidManifest.getApplication()); AXmlAttribute<String> nameAttribute = new AXmlAttribute<String>("name", UtilInstrumenter.COMPONENT_CALLER_SERVICE_HELPER, ANDROID_NAMESPACE); AXmlAttribute<String> exportedAttribute = new AXmlAttribute<String>("exported", "false", ANDROID_NAMESPACE); componentCallerService.addAttribute(nameAttribute); componentCallerService.addAttribute(exportedAttribute); androidManifest.addService(componentCallerService); }
private static void addHookinHelperAsApplicationIfNecessary(ProcessManifest androidManifest){ AXmlNode application = androidManifest.getApplication(); if(!application.hasAttribute("name")) { AXmlAttribute<String> nameAttribute = new AXmlAttribute<String>("name", UtilInstrumenter.HELPER_APPLICATION_FOR_HOOKING, ANDROID_NAMESPACE); application.addAttribute(nameAttribute); } }
private static void addTracingService(ProcessManifest androidManifest) { AXmlNode tracingService = new AXmlNode("service", null, androidManifest.getApplication()); AXmlAttribute<String> nameAttribute = new AXmlAttribute<String>("name", UtilInstrumenter.HELPER_SERVICE_FOR_PATH_TRACKING, ANDROID_NAMESPACE); AXmlAttribute<String> exportedAttribute = new AXmlAttribute<String>("exported", "false", ANDROID_NAMESPACE); tracingService.addAttribute(nameAttribute); tracingService.addAttribute(exportedAttribute); androidManifest.addService(tracingService); }
/** * Parses the attributes required for a layout file inclusion * @param layoutFile The full path and file name of the file being parsed * @param rootNode The AXml node containing the attributes */ private void parseIncludeAttributes(String layoutFile, AXmlNode rootNode) { for (Entry<String, AXmlAttribute<?>> entry : rootNode.getAttributes().entrySet()) { String attrName = entry.getKey().trim(); AXmlAttribute<?> attr = entry.getValue(); if (attrName.equals("layout")) { if ((attr.getType() == AxmlVisitor.TYPE_REFERENCE || attr.getType() == AxmlVisitor.TYPE_INT_HEX) && attr.getValue() instanceof Integer) { // We need to get the target XML file from the binary manifest AbstractResource targetRes = resParser.findResource((Integer) attr.getValue()); if (targetRes == null) { System.err.println("Target resource " + attr.getValue() + " for layout include not found"); return; } if (!(targetRes instanceof StringResource)) { System.err.println("Invalid target node for include tag in layout XML, was " + targetRes.getClass().getName()); return; } String targetFile = ((StringResource) targetRes).getValue(); // If we have already processed the target file, we can // simply copy the callbacks we have found there if (callbackMethods.containsKey(targetFile)) for (String callback : callbackMethods.get(targetFile)) addCallbackMethod(layoutFile, callback); else { // We need to record a dependency to resolve later addToMapSet(includeDependencies, targetFile, layoutFile); } } } } }
private void collectIntentFilters(AXmlNode node) { String componentName = (String) node.getAttribute("name").getValue(); if(node.getChildrenWithTag("intent-filter").size() != 0) { List<IntentFilter> intentFilters = new ArrayList<>(); List<AXmlNode> currentFilters = node.getChildrenWithTag("intent-filter"); for(AXmlNode filter: currentFilters) { IntentFilter intentFilter = new IntentFilter(); //collect action List<AXmlNode> actionNodes = filter.getChildrenWithTag("action"); //assume action size == 1 List<String> actions = new ArrayList<>(); for(AXmlNode actionNode: actionNodes) { actions.add((String) actionNode.getAttribute("name").getValue()); } List<AXmlNode> categoryNodes = filter.getChildrenWithTag("category"); //assume action size == 1 List<String> categories = new ArrayList<>(); for(AXmlNode categoryNode: categoryNodes) { categories.add((String) categoryNode.getAttribute("name").getValue()); } intentFilter.setActions(actions); intentFilter.setCategories(categories); intentFilters.add(intentFilter); } this.filters.put(expandClassName(componentName), intentFilters); } else{ this.filters.put(expandClassName(componentName), Collections.<IntentFilter>emptyList()); } }
private void checkPublicAccessibleCompoenent(Set<String> entryPoints, AXmlNode node) { if (isComponentExported(node)) { AXmlAttribute<?> attr = node.getAttribute("name"); if (attr != null) entryPoints.add(expandClassName((String) attr.getValue())); } }
/** * Iterates over <code>list</code> and checks which node has the given <code>name</code>. * * @param list contains nodes. * @param name the node's name. * @return node with <code>name</code>. */ protected AXmlNode getNodeWithName(List<AXmlNode> list, String name) { for (AXmlNode node : list) { Object attr = node.getAttributes().get("name"); if(attr != null && attr.equals(name)) return node; } return null; }
/** * 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<AXmlNode> usesSdk = this.manifest.getChildrenWithTag("uses-sdk"); if (usesSdk == null || usesSdk.isEmpty()) return -1; AXmlAttribute<?> attr = usesSdk.get(0).getAttribute("minSdkVersion"); if (attr == null) return -1; if (attr.getValue() instanceof Integer) return (Integer) attr.getValue(); return Integer.getInteger((String) attr.getValue()); }
/** * Gets the target SDK version for which this application was developed * @return The target SDK version for which this application was developed */ public int targetSdkVersion() { List<AXmlNode> usesSdk = this.manifest.getChildrenWithTag("uses-sdk"); if (usesSdk == null || usesSdk.isEmpty()) return -1; AXmlAttribute<?> attr = usesSdk.get(0).getAttribute("targetSdkVersion"); if (attr == null) return -1; if (attr.getValue() instanceof Integer) return (Integer) attr.getValue(); return Integer.getInteger((String) attr.getValue()); }
@Override public List<AXmlNode> getNodesWithTag(String tag) { if(this.nodesWithTag.containsKey(tag)) return new ArrayList<AXmlNode>(this.nodesWithTag.get(tag)); else return Collections.emptyList(); }
@Override public NodeVisitor child(String ns, String name) { AXmlNode childNode = new AXmlNode(name == null ? null : name.trim(), ns == null ? null : ns.trim(), node); if (name != null) addPointer(name, childNode); return new MyNodeVisitor(childNode); }
/** * 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<AXmlNode> usesSdk = this.manifest.getChildrenWithTag("uses-sdk"); if (usesSdk == null || usesSdk.isEmpty()) return -1; AXmlAttribute<?> attr = usesSdk.get(0).getAttribute("minSdkVersion"); if (attr == null) return -1; if (attr.getValue() instanceof Integer) return (Integer) attr.getValue(); return Integer.parseInt("" + attr.getValue()); }
/** * Gets the target SDK version for which this application was developed * @return The target SDK version for which this application was developed */ public int targetSdkVersion() { List<AXmlNode> usesSdk = this.manifest.getChildrenWithTag("uses-sdk"); if (usesSdk == null || usesSdk.isEmpty()) return -1; AXmlAttribute<?> attr = usesSdk.get(0).getAttribute("targetSdkVersion"); if (attr == null) return -1; if (attr.getValue() instanceof Integer) return (Integer) attr.getValue(); return Integer.parseInt("" + attr.getValue()); }
/** * Get the package name of the application * @param apkFileLocation * @return */ public static String getApplicationPackageName(String apkFileLocation) { String packageName = null; try { ProcessManifest pm = new ProcessManifest(apkFileLocation); AXmlHandler axmlh = pm.getAXml(); // Find main activity and remove main intent-filter List<AXmlNode> anodes = axmlh.getNodesWithTag("manifest"); for (AXmlNode an: anodes) { boolean hasMain = false; boolean hasLauncher = false; AXmlNode filter = null; AXmlAttribute aname = an.getAttribute("package"); String aval = (String)aname.getValue(); packageName = aval; System.out.println("package: "+ packageName); break; } } catch (IOException | XmlPullParserException ex) { System.err.println("Could not read Android manifest file: " + ex.getMessage()); throw new RuntimeException(ex); } return packageName; }
/** * Parses the layout file with the given root node * @param layoutFile The full path and file name of the file being parsed * @param rootNode The root node from where to start parsing */ private void parseLayoutNode(String layoutFile, AXmlNode rootNode) { if (rootNode.getTag() == null || rootNode.getTag().isEmpty()) { System.err.println("Encountered a null or empty node name " + "in file " + layoutFile + ", skipping node..."); return; } String tname = rootNode.getTag().trim(); if (tname.equals("dummy")) { // dummy root node, ignore it } // Check for inclusions else if (tname.equals("include")) { parseIncludeAttributes(layoutFile, rootNode); } // The "merge" tag merges the next hierarchy level into the current // one for flattening hierarchies. else if (tname.equals("merge")) { // do not consider any attributes of this elements, just // continue with the children } else if (tname.equals("fragment")) { final AXmlAttribute<?> attr = rootNode.getAttribute("name"); if (attr == null) System.err.println("Fragment without class name detected"); else { if (attr.getType() != AxmlVisitor.TYPE_STRING) System.err.println("Invalid targer resource "+attr.getValue()+"for fragment class value"); getLayoutClass(attr.getValue().toString()); } } else { final SootClass childClass = getLayoutClass(tname); if (childClass != null && (isLayoutClass(childClass) || isViewClass(childClass))) parseLayoutAttributes(layoutFile, childClass, rootNode); } // Parse the child nodes for (AXmlNode childNode : rootNode.getChildren()) parseLayoutNode(layoutFile, childNode); }
/** * Parses the layout attributes in the given AXml node * @param layoutFile The full path and file name of the file being parsed * @param layoutClass The class for the attributes are parsed * @param rootNode The AXml node containing the attributes */ private void parseLayoutAttributes(String layoutFile, SootClass layoutClass, AXmlNode rootNode) { boolean isSensitive = false; int id = -1; for (Entry<String, AXmlAttribute<?>> entry : rootNode.getAttributes().entrySet()) { String attrName = entry.getKey().trim(); AXmlAttribute<?> attr = entry.getValue(); // On obfuscated Android malware, the attribute name may be empty if (attrName.isEmpty()) continue; // Check that we're actually working on an android attribute if (!isAndroidNamespace(attr.getNamespace())) continue; // Read out the field data if (attrName.equals("id") && (attr.getType() == AxmlVisitor.TYPE_REFERENCE || attr.getType() == AxmlVisitor.TYPE_INT_HEX)) id = (Integer) attr.getValue(); else if (attrName.equals("password")) { if (attr.getType() == AxmlVisitor.TYPE_INT_HEX) isSensitive = ((Integer) attr.getValue()) != 0; // -1 for true, 0 for false else if (attr.getType() == AxmlVisitor.TYPE_INT_BOOLEAN) isSensitive = (Boolean) attr.getValue(); else throw new RuntimeException("Unknown representation of boolean data type"); } else if (!isSensitive && attrName.equals("inputType") && attr.getType() == AxmlVisitor.TYPE_INT_HEX) { int tp = (Integer) attr.getValue(); isSensitive = ((tp & TYPE_NUMBER_VARIATION_PASSWORD) == TYPE_NUMBER_VARIATION_PASSWORD) || ((tp & TYPE_TEXT_VARIATION_PASSWORD) == TYPE_TEXT_VARIATION_PASSWORD) || ((tp & TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) == TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) || ((tp & TYPE_TEXT_VARIATION_WEB_PASSWORD) == TYPE_TEXT_VARIATION_WEB_PASSWORD); } else if (isActionListener(attrName) && attr.getType() == AxmlVisitor.TYPE_STRING && attr.getValue() instanceof String) { String strData = ((String) attr.getValue()).trim(); addCallbackMethod(layoutFile, strData); } else if (attr.getType() == AxmlVisitor.TYPE_STRING && attrName.equals("text")) { // To avoid unrecognized attribute for "text" field } else if (DEBUG && attr.getType() == AxmlVisitor.TYPE_STRING) { System.out.println("Found unrecognized XML attribute: " + attrName); } } // Register the new user control addToMapSet(this.userControls, layoutFile, new LayoutControl(id, layoutClass, isSensitive)); }
/** * 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<String> permissionNameAttr = new AXmlAttribute<String>("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<AXmlNode>(); 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<AXmlNode>(); 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<AXmlNode>(); 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<AXmlNode>(); services.add(node); }
public MyNodeVisitor() { this.node = new AXmlNode("dummy", "", null); }
public MyNodeVisitor(AXmlNode node) { this.node = node; }
/** * Get the name of the main activity in the AndroidManifest.xml file * @param apkFileLocation * @return */ public static String getMainActivityName(String apkFileLocation) { String mainActivityName = null; try { ProcessManifest pm = new ProcessManifest(apkFileLocation); AXmlHandler axmlh = pm.getAXml(); // Find main activity and remove main intent-filter List<AXmlNode> anodes = axmlh.getNodesWithTag("activity"); for (AXmlNode an: anodes) { boolean hasMain = false; boolean hasLauncher = false; AXmlNode filter = null; AXmlAttribute aname = an.getAttribute("name"); String aval = (String)aname.getValue(); System.out.println("activity: "+ aval); for (AXmlNode ch : an.getChildren()) { System.out.println("children: "+ ch); } List<AXmlNode> fnodes = an.getChildrenWithTag("intent-filter"); for (AXmlNode fn: fnodes) { hasMain = false; hasLauncher = false; // check action List<AXmlNode> acnodes = fn.getChildrenWithTag("action"); for (AXmlNode acn: acnodes) { AXmlAttribute acname = acn.getAttribute("name"); String acval = (String)acname.getValue(); System.out.println("action: "+ acval); if (acval.equals("android.intent.action.MAIN")) { hasMain = true; } } // check category List<AXmlNode> catnodes = fn.getChildrenWithTag("category"); for (AXmlNode catn: catnodes) { AXmlAttribute catname = catn.getAttribute("name"); String catval = (String)catname.getValue(); System.out.println("category: "+ catval); if (catval.equals("android.intent.category.LAUNCHER")) { hasLauncher = true; filter = fn; } } if (hasLauncher && hasMain) { break; } } if (hasLauncher && hasMain) { // replace name with the activity waiting for the connection to the PDP System.out.println("main activity is: "+ aval); System.out.println("excluding filter: "+ filter); filter.exclude(); mainActivityName = aval; break; } } } catch (IOException | XmlPullParserException ex) { System.err.println("Could not read Android manifest file: " + ex.getMessage()); throw new RuntimeException(ex); } return mainActivityName; }