WALA/com.ibm.wala.dalvik/src/com/ibm/wala/dalvik/util/AndroidEntryPointLocator.java

514 lines
22 KiB
Java

/*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html.
*
* This file is a derivative of code released under the terms listed below.
*
*/
/*
* Copyright (c) 2013,
* Tobias Blaschke <code@tobiasblaschke.de>
* All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. The names of the contributors may not be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package com.ibm.wala.dalvik.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import com.ibm.wala.classLoader.IClass;
import com.ibm.wala.classLoader.IClassLoader;
import com.ibm.wala.classLoader.IMethod;
import com.ibm.wala.dalvik.ipa.callgraph.impl.AndroidEntryPoint;
import com.ibm.wala.dalvik.ipa.callgraph.impl.AndroidEntryPoint.ExecutionOrder;
import com.ibm.wala.dalvik.util.androidEntryPoints.ActivityEP;
import com.ibm.wala.dalvik.util.androidEntryPoints.ApplicationEP;
import com.ibm.wala.dalvik.util.androidEntryPoints.LoaderCB;
import com.ibm.wala.dalvik.util.androidEntryPoints.LocationEP;
import com.ibm.wala.dalvik.util.androidEntryPoints.ProviderEP;
import com.ibm.wala.dalvik.util.androidEntryPoints.ServiceEP;
import com.ibm.wala.ipa.callgraph.AnalysisScope;
import com.ibm.wala.ipa.cha.IClassHierarchy;
import com.ibm.wala.types.ClassLoaderReference;
import com.ibm.wala.types.TypeReference;
import com.ibm.wala.util.MonitorUtil.IProgressMonitor;
import com.ibm.wala.util.config.SetOfClasses;
/**
* Searches an Android application for its EntryPoints.
*
* Iterates the ClassHierarchy matching its elements to a set of hardcoded entrypoint-specifications.
* Then optionally uses heuristics to select further entrypoints.
*
* @author Tobias Blaschke <code@tobiasblaschke.de>
*/
public final class AndroidEntryPointLocator {
private final IProgressMonitor mon;
/**
* Used to control the search mechanisms of AndroidEntryPointLocator.
*/
public static enum LocatorFlags {
/**
* If this flag is not set only functions of actual Android-Components are returned.
*
* If it's enabled additional call-backs are handled as an entrypoint.
*/
INCLUDE_CALLBACKS,
/**
* Additionally select all functions that override a function of an AndroidComponent.
*/
EP_HEURISTIC,
/**
* Additionally select all functions that override a function of the Android stubs.
*/
CB_HEURISTIC,
/**
* Add the constructor of detected components to the entrypoints.
*/
WITH_CTOR,
/**
* If a class does not override a method of a component, add a call to the method in the
* super-class to the entriepoints.
*/
WITH_SUPER,
/**
* This will pull in components defined in the Android-API.
* In most cases this is not wanted.
*/
WITH_ANDROID
}
private final static List<AndroidPossibleEntryPoint> possibleEntryPoints = new ArrayList<AndroidPossibleEntryPoint>();
protected final Set<LocatorFlags> flags;
public AndroidEntryPointLocator(final Set<LocatorFlags> flags) {
if (flags == null) {
this.flags = EnumSet.noneOf(LocatorFlags.class);
} else {
this.flags = flags;
}
this.mon = AndroidEntryPointManager.MANAGER.getProgressMonitor();
populatePossibleEntryPoints();
}
/**
* Searches a ClassHierarchy for EntryPoints by their method-signature (optionally with heuristics).
*
* Matches the hardcoded signatures against the methods in cha.
* Uses heuristics depending on the LocatorFlags given to the constructor .
*
* @param cha The ClassHierarchy to be searched
* @return partially sorted list of applicable EntryPoints
*/
public List<AndroidEntryPoint> getEntryPoints(final IClassHierarchy cha) {
if (cha == null) {
throw new IllegalArgumentException("I need a ClassHierarchy to search");
}
Set<AndroidEntryPoint> entryPoints = new HashSet<AndroidEntryPoint>();
mon.beginTask("Locating Entrypoints", IProgressMonitor.UNKNOWN);
int dummy = 0; // for the progress monitor
for (IClass cls : cha) {
mon.worked(dummy++);
if (isExcluded(cls)) continue;
if (!cls.isInterface() && !cls.isAbstract() && cls.getClassLoader().getName().equals(AnalysisScope.APPLICATION)) {
nextMethod:
for (final IMethod m : cls.getDeclaredMethods()) {
// If there is a Method signature in the possible entry points use thatone
for (AndroidPossibleEntryPoint e: possibleEntryPoints) {
if (e.name.equals(m.getName().toString()) ) {
if (this.flags.contains(LocatorFlags.WITH_ANDROID)) {
entryPoints.add(new AndroidEntryPoint(e, m, cha));
} else if (! isAPIComponent(m)) {
entryPoints.add(new AndroidEntryPoint(e, m, cha));
}
continue nextMethod;
}
}
} // for IMethod m
}
} // for IClass : cha
if (this.flags.contains(LocatorFlags.EP_HEURISTIC) || this.flags.contains(LocatorFlags.CB_HEURISTIC)) {
final Set<TypeReference> bases = new HashSet<TypeReference>();
if (this.flags.contains(LocatorFlags.EP_HEURISTIC)) {
// Add bases for EP-Heuristic
if (this.flags.contains(LocatorFlags.INCLUDE_CALLBACKS)) {
for (final AndroidComponent compo : AndroidComponent.values()) {
if (compo == AndroidComponent.UNKNOWN) continue;
if (compo.toReference() != null) {
bases.add(compo.toReference());
}
}
} else {
// Restrict the set
bases.add(AndroidTypes.Handler);
bases.add(AndroidTypes.Application);
bases.add(AndroidTypes.Activity);
/** @todo TODO: add Fragments in getEntryPoints */
//bases.add(AndroidTypes.Fragment);
bases.add(AndroidTypes.Service);
bases.add(AndroidTypes.ContentProvider);
bases.add(AndroidTypes.BroadcastReceiver);
}
heuristicScan(bases, entryPoints, cha);
}
if (this.flags.contains(LocatorFlags.CB_HEURISTIC)) {
heuristicAnyAndroid(entryPoints, cha);
}
}
List<AndroidEntryPoint> ret = new ArrayList<AndroidEntryPoint>(entryPoints);
Collections.sort(ret, new AndroidEntryPoint.ExecutionOrderComperator());
mon.done();
return ret;
}
/**
* Select all methods that override a method in base.
*
* If the heuristic selects a method it is added to the eps-argument.
*
* @param bases classes to search
* @param eps The set of detected entrypoints to add to
* @param cha The ClassHierarchy to search
*/
private void heuristicScan(Collection<? extends TypeReference> bases, Set<? super AndroidEntryPoint> eps, IClassHierarchy cha) {
for (final TypeReference base : bases) {
final IClass baseClass = cha.lookupClass(base);
this.mon.subTask("Heuristic scan in " + base);
final Collection<IClass> candids;
try {
candids = cha.computeSubClasses(base);
} catch (IllegalArgumentException e) { // Pretty agan :(
continue;
}
for (final IClass candid : candids) {
if (isExcluded(candid)) continue;
if ((! this.flags.contains(LocatorFlags.WITH_ANDROID) ) && (isAPIComponent(candid))) {
// Don't consider internal overrides
continue;
}
final Collection<IMethod> methods = candid.getDeclaredMethods();
for (final IMethod method : methods) {
if ((method.isInit() || method.isClinit()) && (! this.flags.contains(LocatorFlags.WITH_CTOR))) {
continue;
}
if (baseClass.getMethod(method.getSelector()) != null) {
final AndroidEntryPoint ep = makeEntryPointForHeuristic(method, cha);
if (! eps.contains(ep)) { // Just to be sure that a previous element stays as-is
eps.add(ep);
}
}
}
}
}
}
// private boolean isInnerClass(final TypeReference test) {
// return test.getName().toString().contains("$"); // PRETTY!
// }
private AndroidEntryPoint makeEntryPointForHeuristic(final IMethod method, final IClassHierarchy cha) {
AndroidComponent compo;
{ // Guess component
compo = AndroidComponent.from(method, cha);
if (compo == AndroidComponent.UNKNOWN) {
}
}
final AndroidEntryPoint ep = new AndroidEntryPoint(selectPositionForHeuristic(method), method, cha, compo);
return ep;
}
/**
* Select all methods that override or implement any method from the andoidPackage.
*
* Like heuristicScan but with an other restriction: instead of methods overriding methods in base
* select methods whose super-class starts with "Landroid".
*
* @param eps The set of detected entrypoints to add to
*/
private void heuristicAnyAndroid(Set<AndroidEntryPoint> eps, IClassHierarchy cha) {
final IClassLoader appLoader = cha.getLoader(ClassLoaderReference.Application);
final Iterator<IClass> appIt = appLoader.iterateAllClasses();
for (IClass appClass = ((appIt.hasNext())?appIt.next():null); appIt.hasNext(); appClass = appIt.next()) {
IClass androidClass = appClass; //.getSuperclass(); Override on new
{ // Only for android-classes
boolean isAndroidClass = false;
while (androidClass != null) {
if (isAPIComponent(androidClass)) {
isAndroidClass = true;
break;
}
for (IClass iface : appClass.getAllImplementedInterfaces ()) {
if (isAPIComponent(iface)) {
isAndroidClass = true;
break;
}
}
if (isAndroidClass) break;
androidClass = androidClass.getSuperclass();
}
if (! isAndroidClass) {
continue; // continue appClass;
}
}
{ // Overridden methods
if (isAPIComponent(appClass)) continue;
if (isExcluded(appClass)) continue;
final Collection<IMethod> methods = appClass.getDeclaredMethods();
for (final IMethod method : methods) {
if ((method.isInit() || method.isClinit()) && (! this.flags.contains(LocatorFlags.WITH_CTOR))) {
continue;
}
assert (method.getSelector() != null): "Method has no selector: " + method;
assert (androidClass != null): "androidClass is null";
if (androidClass.getMethod(method.getSelector()) != null) {
final AndroidEntryPoint ep = makeEntryPointForHeuristic(method, cha);
if (! eps.contains(ep)) { // Just to be sure that a previous element stays as-is
eps.add(ep);
}
}
}
}
{ // Implemented interfaces
final Collection<IClass> iFaces = appClass.getAllImplementedInterfaces();
for (final IClass iFace : iFaces) {
if (isAPIComponent(iFace)) {
continue;
}
if (isExcluded(iFace)) continue;
final Collection<IMethod> ifMethods = iFace.getDeclaredMethods();
for (final IMethod ifMethod : ifMethods) {
final IMethod method = appClass.getMethod(ifMethod.getSelector());
if (method == null || method.isAbstract()) {
continue;
}
if (method.getDeclaringClass().getClassLoader().getReference().equals(ClassLoaderReference.Application)) {
// The function is overridden
final AndroidEntryPoint ep = new AndroidEntryPoint(selectPositionForHeuristic(method), method, cha);
if (! eps.contains(ep)) { // Just to be sure that a previous element stays as-is
eps.add(ep);
}
} else {
// The function is taken from the super-class
if (this.flags.contains(LocatorFlags.WITH_SUPER)) {
final AndroidEntryPoint ep = makeEntryPointForHeuristic(method, cha);
if ((eps.contains(ep)) && (! method.isStatic())) {
// eps.get(ep) ... suuuuuper!
for (final AndroidEntryPoint eps_ep : eps) {
if (eps_ep.equals(ep)) {
final TypeReference[] oldTypes = eps_ep.getParameterTypes(0);
final TypeReference[] newTypes = new TypeReference[oldTypes.length + 1];
System.arraycopy(oldTypes, 0, newTypes, 0, oldTypes.length);
newTypes[oldTypes.length] = appClass.getReference();
eps_ep.setParameterTypes(0, newTypes);
}
}
} else {
if (! method.isStatic()) {
ep.setParameterTypes(0, new TypeReference[]{appClass.getReference()});
}
eps.add(ep);
}
}
}
}
}
} // Of 'Implemented interfaces'
}
}
private boolean isAPIComponent(final IMethod method) {
return isAPIComponent(method.getDeclaringClass());
}
private boolean isAPIComponent(final IClass cls) {
if (cls.getClassLoader().getReference().equals(ClassLoaderReference.Application)) {
if (cls.getName().toString().startsWith("Landroid/")) {
return true;
}
return false;
} else {
return true;
}
}
private boolean isExcluded(final IClass cls) {
final SetOfClasses set = cls.getClassHierarchy().getScope().getExclusions();
final String clsName = cls.getReference().getName().toString().substring(1);
return set.contains(clsName);
}
/**
* The position to place entrypoints selected by a heuristic at.
*
* Currently all methods are placed at ExecutionOrder.MULTIPLE_TIMES_IN_LOOP.
*/
private ExecutionOrder selectPositionForHeuristic(IMethod method) {
return ExecutionOrder.MULTIPLE_TIMES_IN_LOOP;
}
/**
* A definition of an Entrypoint functions o the App are matched against.
*
* To locate the Entrypoints of the analyzed Application all their Methods are matched
* against a set of hardcoded definitions. This set consists of AndroidPossibleEntryPoints.
* An AndroidPossibleEntryPoint is rather useless as you can build an actual AndroidEntryPoint
* without having a AndroidPossibleEntryPoint.
*
* To extend the set of known definitions have a look at the classes ActivityEP, ServiceEP, ...
*/
public static class AndroidPossibleEntryPoint implements AndroidEntryPoint.IExecutionOrder {
// private final AndroidComponent cls;
private final String name;
public final AndroidEntryPoint.ExecutionOrder order;
public AndroidPossibleEntryPoint(AndroidComponent c, String n, ExecutionOrder o) {
// cls = c;
name = n;
order = o;
}
public AndroidPossibleEntryPoint(AndroidComponent c, String n, AndroidPossibleEntryPoint o) {
// cls = c;
name = n;
order = o.order;
}
public int getOrderValue() { return order.getOrderValue(); }
public int compareTo(AndroidEntryPoint.IExecutionOrder o) { return order.compareTo(o); }
public AndroidEntryPoint.ExecutionOrder getSection() { return order.getSection(); }
public static class ExecutionOrderComperator implements Comparator<AndroidPossibleEntryPoint> {
@Override public int compare(AndroidPossibleEntryPoint a, AndroidPossibleEntryPoint b) {
return a.order.compareTo(b.order);
}
}
}
/**
* Read in the hardcoded entrypoint-specifications.
*
* Builds a list of possible EntryPoints in a typical Android application and produces information on
* the order in which they should be modeled.
*/
private void populatePossibleEntryPoints() {
// Populate the list of possible EntryPoints
ApplicationEP.populate(possibleEntryPoints);
ActivityEP.populate(possibleEntryPoints);
ServiceEP.populate(possibleEntryPoints);
ProviderEP.populate(possibleEntryPoints);
if (this.flags.contains(LocatorFlags.INCLUDE_CALLBACKS)) {
LocationEP.populate(possibleEntryPoints);
LoaderCB.populate(possibleEntryPoints);
}
Collections.sort(possibleEntryPoints, new AndroidPossibleEntryPoint.ExecutionOrderComperator());
}
public static void debugDumpEntryPoints(List<AndroidPossibleEntryPoint> eps) {
int dfa = 0;
int indent = 0;
for (AndroidPossibleEntryPoint ep: eps) {
if (dfa == 0) {
System.out.println("AT_FIRST:");
indent = 1;
dfa++;
}
if ((dfa == 1) && (ep.getOrderValue() >= ExecutionOrder.BEFORE_LOOP.getOrderValue())) {
System.out.println("BEFORE_LOOP:");
dfa++;
}
if ((dfa == 2) && (ep.getOrderValue() >= ExecutionOrder.START_OF_LOOP.getOrderValue())) {
System.out.println("START_OF_LOOP:");
indent = 2;
dfa++;
}
if ((dfa == 3) && (ep.getOrderValue() >= ExecutionOrder.MIDDLE_OF_LOOP.getOrderValue())) {
System.out.println("MIDDLE_OF_LOOP:");
dfa++;
}
if ((dfa == 4) && (ep.getOrderValue() >= ExecutionOrder.MULTIPLE_TIMES_IN_LOOP.getOrderValue())) {
System.out.println("MULTIPLE_TIMES_IN_LOOP:");
indent = 3;
dfa++;
}
if ((dfa == 5) && (ep.getOrderValue() >= ExecutionOrder.END_OF_LOOP.getOrderValue())) {
System.out.println("END_OF_LOOP:");
indent = 2;
dfa++;
}
if ((dfa == 6) && (ep.getOrderValue() >= ExecutionOrder.AFTER_LOOP.getOrderValue())) {
System.out.println("AFTER_LOOP:");
indent = 1;
dfa++;
}
if ((dfa == 7) && (ep.getOrderValue() >= ExecutionOrder.AT_LAST.getOrderValue())) {
System.out.println("AT_LAST:");
indent = 1;
dfa++;
}
for (int i=0; i<indent; i++) System.out.print("\t");
System.out.println(ep.name + " metric: " + ep.getOrderValue());
}
}
}