com.amossys.hooker.hookers.Hooker.java Source code

Java tutorial

Introduction

Here is the source code for com.amossys.hooker.hookers.Hooker.java

Source

/*----------------------------------------------------------------------------+
 *|                                                                           |
 *|                          Android's Hooker                                 |
 *|                                                                           |
 *+---------------------------------------------------------------------------+
 *| Copyright (C) 2011 Georges Bossert and Dimitri Kirchner                   |
 *| This program is free software: you can redistribute it and/or modify      |
 *| it under the terms of the GNU General Public License as published by      |
 *| the Free Software Foundation, either version 3 of the License, or         |
 *| (at your option) any later version.                                       |
 *|                                                                           |
 *| This program is distributed in the hope that it will be useful,           |
 *| but WITHOUT ANY WARRANTY; without even the implied warranty of            |
 *| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the              |
 *| GNU General Public License for more details.                              |
 *|                                                                           |
 *| You should have received a copy of the GNU General Public License         |
 *| along with this program. If not, see <http://www.gnu.org/licenses/>.      |
 *+---------------------------------------------------------------------------+
 *| @url      : http://www.amossys.fr                                         |
 *| @contact  : android-hooker@amossys.fr                                     |
 *| @sponsors : Amossys, http://www.amossys.fr                                |
 *+---------------------------------------------------------------------------+
 */

package com.amossys.hooker.hookers;

import java.lang.reflect.GenericDeclaration;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

import android.content.Context;

import com.amossys.hooker.ApplicationConfig;
import com.amossys.hooker.SubstrateMain;
import com.amossys.hooker.common.InterceptEvent;
import com.amossys.hooker.exceptions.HookerInitializationException;
import com.amossys.hooker.hookers.interfaces.HookerListener;
import com.amossys.hooker.service.InstrumentationServiceConnection;
import com.saurik.substrate.MS;
import com.saurik.substrate.MS.MethodPointer;

/**
 * Hooker, a class extended by all the hookers
 * 
 * @author Georges Bossert
 * 
 */
public abstract class Hooker {

    /**
     * Abstract methods (child must implement)
     */
    public abstract void attach();

    /**
     * Attributes
     */
    // name of the hooker, (also its category)
    private String name;
    // package name of the application we are in memory
    private String packageName;
    // start time of the hooker
    private final long startTimestamp = Calendar.getInstance().getTime().getTime();
    // Service connection instance to communicate with the collecting service
    private final InstrumentationServiceConnection serviceConnection = new InstrumentationServiceConnection();

    /**
     * Create a new hooker given a name and a place to store events
     * 
     * @param name: the name of the hooker
     */
    public Hooker(String name) {
        this.name = name;
    }

    /**
     * Send event to the service by means of the service connection and sets its IDXP
     * 
     * @param event: the event to send
     */
    public void sendEventToCollectService(InterceptEvent event, Context appContext) {

        if (appContext != null && !serviceConnection.isBoundToTheService()) {
            serviceConnection.doBindService(appContext);
        }

        this.serviceConnection.sendEvent(event);
    }

    protected void insertEvent(InterceptEvent event, Context appContext) {
        if (event == null) {
            return;
        }

        // Compute relative timestamp
        long relativeTimestamp = event.getTimestamp() - this.startTimestamp;
        event.setRelativeTimestamp(relativeTimestamp);

        if (SubstrateMain.NETWORK_MODE || SubstrateMain.FILE_MODE) {
            this.sendEventToCollectService(event, appContext);
        }

        if (SubstrateMain.DEBUG_MODE) {
            SubstrateMain.log(event.toString());
        }

    }

    protected String getStringRepresentationOfAttribute(Object arg) {
        String argValue = null;
        /**
         * If the class of the argument (or one of its father) redefined the toString method, we use it,
         * if not, we use ToStringBuilder.reflectionToString() to infer it
         */
        try {
            if (arg.getClass().getMethod("toString").getDeclaringClass().equals(Object.class)) {
                argValue = ToStringBuilder.reflectionToString(arg, ToStringStyle.SHORT_PREFIX_STYLE);
            } else {
                argValue = arg.toString();
            }
        } catch (NoSuchMethodException e) {
            SubstrateMain.log("The attribute of object " + arg + " has no toString method defined.", e);
        }
        return argValue;
    }

    /**
     * Hook the specified methods and create an event for each calls of it.
     * 
     * @param listener
     * @param className
     * @param methods
     * @throws HookerInitializationException
     */
    protected void hookMethods(final HookerListener listener, final String className,
            final Map<String, Integer> methods) throws HookerInitializationException {
        hookMethodsWithOutputs(listener, className, methods, null);
    }

    protected void hookMethodsWithOutputs(final HookerListener listener, final String className,
            final Map<String, Integer> methods, final Map<String, Object> outputs)
            throws HookerInitializationException {

        final String hookerName = this.getHookerName();

        MS.hookClassLoad(className, new MS.ClassLoadHook() {

            @SuppressWarnings({ "unchecked", "rawtypes" })
            public void classLoaded(Class<?> resources) {

                /**
                 * Based on the name of the method, we retrieve all the possibilities
                 */
                Map<GenericDeclaration, String> methodsToHook = new HashMap<GenericDeclaration, String>();
                boolean found;
                for (String methodName : methods.keySet()) {
                    found = false;

                    /**
                     * Checks if the requested method is a constructor or not
                     */
                    if (className.substring(className.lastIndexOf('.') + 1).equals(methodName)) {
                        found = true;
                        for (int iConstructor = 0; iConstructor < resources
                                .getConstructors().length; iConstructor++) {
                            methodsToHook.put(resources.getConstructors()[iConstructor], methodName);
                        }
                    } else {
                        for (Method m : resources.getMethods()) {
                            if (m.getName().equals(methodName)) {
                                found = true;
                                methodsToHook.put(m, methodName);
                            }
                        }
                    }
                    if (!found) {
                        SubstrateMain.log(new StringBuilder("No method found with name ").append(className)
                                .append(":").append(methodName).toString());
                    }
                }

                for (final GenericDeclaration pMethod : methodsToHook.keySet()) {
                    final String methodName = methodsToHook.get(pMethod);
                    if (SubstrateMain.DEBUG_MODE) {
                        SubstrateMain.log(new StringBuilder("Hooking method ").append(className).append(":")
                                .append(methodName).toString());
                    }

                    // To debug Substrate if you have a stacktrace
                    // for (Class param : ((Method) pMethod).getParameterTypes()) {
                    // SubstrateMain.log("   Param: " + param.getSimpleName());
                    // }

                    final int intrusiveLevelFinal = methods.get(methodName);

                    final MS.MethodPointer<Object, Object> old = new MethodPointer<Object, Object>();
                    MS.hookMethod_(resources, (Member) pMethod, new MS.MethodHook() {
                        public Object invoked(final Object resources, final Object... args) throws Throwable {

                            if (ApplicationConfig.isFiltered() || ApplicationConfig.getPackageName() == null) {
                                return old.invoke(resources, args);
                            }

                            if (isSelfHooking((Member) pMethod)) {
                                SubstrateMain.log(
                                        "Self hooking detected on method '" + ((Member) pMethod).getName() + "'.");
                                return old.invoke(resources, args);
                            }

                            final String packName = ApplicationConfig.getPackageName();
                            final Context appContext = ApplicationConfig.getContext();

                            InterceptEvent event = null;

                            if (packName != null && appContext != null) {

                                // Open the connection to the service if not yet bound
                                if (!serviceConnection.isBoundToTheService()) {
                                    serviceConnection.doBindService(appContext);
                                }

                                // Create the intercept event for this hook
                                event = new InterceptEvent(hookerName, intrusiveLevelFinal,
                                        System.identityHashCode(resources), packName, className, methodName);

                                //TODO: We should also save the parameters value before the call.
                                //      it requires to clone the parameters
                                //      and save them in the event if their value is different after the call

                                /**
                                 * If specified, we execute the before method of the provided listener
                                 */
                                if (listener != null) {
                                    listener.before(className, pMethod, resources, event);
                                }

                            }

                            /**
                             * We invoke the original method and capture the result
                             */
                            Object result = old.invoke(resources, args);

                            // if requested we modify the output value of the invocation
                            if (outputs != null && outputs.containsKey(methodName)) {

                                if (result == null || outputs.get(methodName) == null
                                        || result.getClass().isAssignableFrom(outputs.get(methodName).getClass())) {
                                    result = outputs.get(methodName);
                                } else {
                                    SubstrateMain.log("Cannot replace method " + methodName + " output with "
                                            + outputs.get(methodName) + ": types are incompatible.", false);
                                }
                            }

                            // Store the result in the event (if available)
                            if (event != null && appContext != null) {

                                // Register the parameters of the method call in the event
                                if (args != null) {
                                    for (Object arg : args) {
                                        if (arg != null) {
                                            String argValue = getStringRepresentationOfAttribute(arg);
                                            event.addParameter(arg.getClass().getName(), argValue);
                                        } else {
                                            event.addParameter(null, null);
                                        }
                                    }
                                }

                                // if the invocation returned something we store it in the event
                                if (result != null) {
                                    String strResult = getStringRepresentationOfAttribute(result);
                                    event.setReturns(result.getClass().getName(), strResult);
                                } else {
                                    event.setReturns(null, null);
                                }

                                /**
                                 * if specified, we execute the after method of the provided listener
                                 */
                                if (listener != null) {
                                    listener.after(className, pMethod, resources, event);
                                }

                                insertEvent(event, appContext);
                            }

                            return result;
                        }

                        /**
                         * Computes if we are self hooking ourselves. To do so, we generate a stack trace to retrieve
                         * the caller list of the current invocation and check no Hooker appears after the second entry of the stack trace.
                         * @param pMethod 
                         * @param pMethod 
                         * @return true if we are self-hooking
                         */
                        private boolean isSelfHooking(Member pMethod) {
                            boolean selfHooking = false;
                            StackTraceElement[] stackTrace = new Throwable().getStackTrace();
                            if (stackTrace.length > 2) {
                                for (int i = 2; i < stackTrace.length; i++) {
                                    if (stackTrace[i].getClassName().startsWith(Hooker.class.getName())) {
                                        selfHooking = true;
                                        break;
                                    }
                                }
                            }
                            return selfHooking;
                        }

                    }, old);
                }

            }
        });

    }

    /**
     * @return the name
     */
    public String getHookerName() {
        return name;
    }

    public String getPackageName() {
        return this.packageName;
    }

}