/** * Finds the given resource in the given package * * @param resName * The name of the resource to retrieve * @param resID * @param packageName * The name of the package in which to look for the resource * @return The specified resource if available, otherwise null */ private AbstractResource findResource(String resName, String resID, String packageName) { // Find the correct package for (ARSCFileParser.ResPackage pkg : this.resourcePackages) { // If we don't have any package specification, we pick the app's // default package boolean matches = (packageName == null || packageName.isEmpty()) && pkg.getPackageName().equals(this.appPackageName); matches |= pkg.getPackageName().equals(packageName); if (!matches) continue; // We have found a suitable package, now look for the resource for (ARSCFileParser.ResType type : pkg.getDeclaredTypes()) if (type.getTypeName().equals(resID)) { AbstractResource res = type.getFirstResource(resName); return res; } } return null; }
@Override public void attr(String ns, String name, int resourceId, int type, Object obj) { // Is this the target file attribute? String tname = name.trim(); if (tname.equals("layout")) { if (type == AxmlVisitor.TYPE_REFERENCE && obj instanceof Integer) { // We need to get the target XML file from the binary manifest AbstractResource targetRes = resParser.findResource((Integer) obj); if (targetRes == null) { logger.trace(Utils.INDENT + "Target resource " + obj + " for layout include not found"); return; } if (!(targetRes instanceof StringResource)) { logger.trace(Utils.INDENT + "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 MapUtils.addToSet(includeDependencies, targetFile, layoutFile); } } } super.attr(ns, name, resourceId, type, obj); }
/** * 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); } } } } }
/** * Finds the last assignment to the given local representing a resource ID * by searching upwards from the given statement * * @param stmt * The statement from which to look backwards * @param local * The variable for which to look for assignments * @return The last value assigned to the given variable */ private Integer findLastResIDAssignment(Stmt stmt, Local local, BiDiInterproceduralCFG<Unit, SootMethod> cfg, Set<Stmt> doneSet) { if (!doneSet.add(stmt)) return null; // If this is an assign statement, we need to check whether it changes // the variable we're looking for if (stmt instanceof AssignStmt) { AssignStmt assign = (AssignStmt) stmt; if (assign.getLeftOp() == local) { // ok, now find the new value from the right side if (assign.getRightOp() instanceof IntConstant) return ((IntConstant) assign.getRightOp()).value; else if (assign.getRightOp() instanceof FieldRef) { SootField field = ((FieldRef) assign.getRightOp()).getField(); for (Tag tag : field.getTags()) if (tag instanceof IntegerConstantValueTag) return ((IntegerConstantValueTag) tag).getIntValue(); else System.err.println("Constant " + field + " was of unexpected type"); } else if (assign.getRightOp() instanceof InvokeExpr) { InvokeExpr inv = (InvokeExpr) assign.getRightOp(); if (inv.getMethod().getName().equals("getIdentifier") && inv.getMethod().getDeclaringClass().getName().equals("android.content.res.Resources") && this.resourcePackages != null) { // The right side of the assignment is a call into the // well-known // Android API method for resource handling if (inv.getArgCount() != 3) { System.err.println("Invalid parameter count for call to getIdentifier"); return null; } // Find the parameter values String resName = ""; String resID = ""; String packageName = ""; // In the trivial case, these values are constants if (inv.getArg(0) instanceof StringConstant) resName = ((StringConstant) inv.getArg(0)).value; if (inv.getArg(1) instanceof StringConstant) resID = ((StringConstant) inv.getArg(1)).value; if (inv.getArg(2) instanceof StringConstant) packageName = ((StringConstant) inv.getArg(2)).value; else if (inv.getArg(2) instanceof Local) packageName = findLastStringAssignment(stmt, (Local) inv.getArg(2), cfg); else { System.err.println("Unknown parameter type in call to getIdentifier"); return null; } // Find the resource ARSCFileParser.AbstractResource res = findResource(resName, resID, packageName); if (res != null) return res.getResourceID(); } } } } // Continue the search upwards for (Unit pred : cfg.getPredsOf(stmt)) { if (!(pred instanceof Stmt)) continue; Integer lastAssignment = findLastResIDAssignment((Stmt) pred, local, cfg, doneSet); if (lastAssignment != null) return lastAssignment; } return null; }
/** * Collects the XML-based callback methods, e.g., Button.onClick() declared in layout XML files * * @param resParser The ARSC resource parser * @param lfp The layout file parser * @param jimpleClass The analysis class that gives us a mapping between layout IDs and components */ private void collectXmlBasedCallbackMethods(ARSCFileParser resParser, LayoutFileParser lfp, AbstractCallbackAnalyzer jimpleClass) { // Collect the XML-based callback methods for (Entry<String, Set<Integer>> lcentry : jimpleClass.getLayoutClasses().entrySet()) { final SootClass callbackClass = Scene.v().getSootClass(lcentry.getKey()); for (Integer classId : lcentry.getValue()) { AbstractResource resource = resParser.findResource(classId); if (resource instanceof StringResource) { final String layoutFileName = ((StringResource) resource).getValue(); // Add the callback methods for the given class Set<String> callbackMethods = lfp.getCallbackMethods().get(layoutFileName); if (callbackMethods != null) { for (String methodName : callbackMethods) { final String subSig = "void " + methodName + "(android.view.View)"; // The callback may be declared directly in the // class // or in one of the superclasses SootClass currentClass = callbackClass; while (true) { SootMethod callbackMethod = currentClass.getMethodUnsafe(subSig); if (callbackMethod != null) { addCallbackMethod(callbackClass.getName(), new AndroidMethod(callbackMethod)); break; } if (!currentClass.hasSuperclass()) { System.err.println("Callback method " + methodName + " not found in class " + callbackClass.getName()); break; } currentClass = currentClass.getSuperclass(); } } } // For user-defined views, we need to emulate their // callbacks Set<LayoutControl> controls = lfp.getUserControls().get(layoutFileName); if (controls != null) { for (LayoutControl lc : controls) { registerCallbackMethodsForView(callbackClass, lc); } } } else { System.err.println("Unexpected resource type for layout class"); } } } // Add the callback methods as sources and sinks { Set<SootMethodAndClass> callbacksPlain = new HashSet<SootMethodAndClass>(); for (Set<SootMethodAndClass> set : this.callbackMethods.values()) { callbacksPlain.addAll(set); } System.out.println("Found " + callbacksPlain.size() + " callback methods for " + this.callbackMethods.size() + " components"); } }