DroidPlugin之Hook机制

在DroidPlugin源码中,作者提供了一份关于其实现原理的PPT讲义,这里我把其中介绍基本实现原理的章节贴出来。

以下用DP代指DroidPlugin

DP基本原理.png

本文主要介绍Hook机制,看下作者是如何做到“偷天换日”。

Hook机制

我认为,DP所指的“Hook”和传统意义上的Hook是有点区别的,例如在API Hook中提到这样一句话“通过hook‘接触’到需要修改的api函数入口点,改变它的地址指向新的自定义的函数”。Window上的API Hook可能会导致被hook的函数不被执行(不知道我这样理解对不对)。而DP的Hook机制,被hook的函数是会被执行的。阅读DP源码过程中我就有这样的疑问,那DP的Hook到底做了啥!

Java动态代理

再回答上面的疑问之前,我们需要了解“Java动态代理”这个玩意。关于动态代理的资料有很多,这里我就简单介绍下。
Java动态代理是Java反射提供的一个强大功能,使用动态代理,我们可以在被hook方法执行前后,做些额外操作。也可以修改被hook方法的入参,修改方法返回值。下面举个最简单的代理例子,来说明代理能做什么。
首先定义一个接口IShopping,声明一个购物的方法shopping。

package com.kisson.proxy;

public interface IShopping {
    void shopping(String money);
}

接着定义一个类GoShopping,实现IShopping的shopping方法。

package com.kisson.proxy;

public class GoShopping implements IShopping{

    @Override
    public void shopping() {
        // TODO Auto-generated method stub
        System.out.println("我去逛街咯!"+" 我有"+money+"元");
    }
}

接着我们需要利用动态代理去hook接口IShopping的shopping方法。然后定义一个IShoppingHook类并实现InvocationHandler接口

package com.kisson.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class IShoppingHook implements InvocationHandler {

    private Object mHookedObject;

    public IShoppingHook(Object hookedObject) {
        this.mHookedObject = hookedObject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        // 方法执行前,做预处理
        beforeHook();
        //钱越多越好!!!
        args[0] = "2000000";
       //在这里就是执行shopping方法的过程,通过反射来完成
        Object result = method.invoke(mHookedObject, args);
        // 方法执行后,做收尾处理
        afterHook();
        return result;
    }

    public void beforeHook() {
        // todo:something
        System.out.println("工作都已经做完了,可以安心购物咯!");
    }

    public void afterHook() {
        // todo:something
        System.out.println("买的真开心!");
    }
}

上面的准备工作就绪,接着看如何实现。

package com.kisson.proxy;

import java.lang.reflect.Proxy;

public class ProxyTest {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        ProxyTest test = new ProxyTest();
        IShopping shopping = new GoShopping();
        test.newProxyInstance(shopping).shopping("100");;
        
    }

    private IShopping newProxyInstance(IShopping shopping) {
        return (IShopping) Proxy.newProxyInstance(shopping.getClass()
                .getClassLoader(), shopping.getClass().getInterfaces(),
                new IShoppingHook(shopping));
    }

}

执行结果如下:

工作都已经做完了,可以安心购物咯!
我去逛街咯! 我有2000000元
买的真开心!

以上动态代理的过程我们做了两件事:
1.在shopping方法执行前后,分别执行了beforeHook和afterHook方法。
2.改变了shopping方法的入参,本来只有100元,最终结果却得到了2000000元!
DP使用Hook机制完成了三个工作:

  1. 动态代理实现函数hook。
  2. Binder代理绕过部分系统服务限制。
  3. IO重定向

以后针对这三个点分别介绍下。

Binder代理绕过部分系统服务限制

原理

        LocationManager lm = (LocationManager)getSystemService(Context.LOCATION_SERVICE);

上述代码,是获取系统位置服务。如果我们需要获取类似的系统服务,都是通过Context的getSystemService方法来获取。

getSystemService类图

getSystemService方法的是在ContextImpl类中实现的。很久没看源码,看了下源码发现该方法的实现被修改了。

    @Override
    public Object getSystemService(String name) {
        return SystemServiceRegistry.getSystemService(this, name);
    }

这里出现了一个SystemServiceRegistry类,其作用是用来管理所有通过getSystemService返回的系统服务。
在类中SystemServiceRegistry类中有很大一坨静态代码块,用于注册各种系统服务。

//位置服务的注册
registerService(Context.LOCATION_SERVICE, LocationManager.class,
                new CachedServiceFetcher<LocationManager>() {
            @Override
            public LocationManager createService(ContextImpl ctx) {
                IBinder b = ServiceManager.getService(Context.LOCATION_SERVICE);
                return new LocationManager(ctx, ILocationManager.Stub.asInterface(b));
            }});

在这里我们可以看到LocationManager的创建过程了,通过调用ServiceManager的getService方法来创建IBinder对象,作为LocationManager构造方法的参数。在ContexImpl类中有个mServiceCache对象,用于保存创建的各种XXManager对象,类似单例模式吧。

public final class ServiceManager {
    private static final String TAG = "ServiceManager";

    private static IServiceManager sServiceManager;
    private static HashMap<String, IBinder> sCache = new HashMap<String, IBinder>();

    private static IServiceManager getIServiceManager() {
        if (sServiceManager != null) {
            return sServiceManager;
        }

        // Find the service manager
        sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
        return sServiceManager;
    }

    /**
     * Returns a reference to a service with the given name.
     * 
     * @param name the name of the service to get
     * @return a reference to the service, or <code>null</code> if the service doesn't exist
     */
    public static IBinder getService(String name) {
        try {
            IBinder service = sCache.get(name);
            if (service != null) {
                return service;
            } else {
                return getIServiceManager().getService(name);
            }
        } catch (RemoteException e) {
            Log.e(TAG, "error in getService", e);
        }
        return null;
    }
//....省略部分代码
}

通过ServiceManager源码,可以知道:

  1. 使用sCache对象来保存系统中已经创建的IBinder。
  2. getService方法中,首先会在sCache中去查找是否有缓存,如果没有,则调用IServiceManager的getService方法创建IBinder。

DP hook系统服务的原理:

  1. 对ServiceManager进行操作,反射得到sCache对象,将系统已经原有的系统移除掉(Remove方法),接着再通过反射调用ServiceManager的getService方法获取新的服务(IBinder),并在此加入sCache。
  2. 将系统服务中(IBinder)某些有执行限制的方法,进行动态代理处理,在这些方法执行的前后进行“欺骗”操作。

总结起来就是偷天换日、欺上瞒下!

源码分析之偷天换日

首先来看看“偷天换日”的类图。


“偷天换日”类图

结合类图,我们来说明下各个类的作用。

1.Hook类

import android.content.Context;

/**
 * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/3/2.
 * ---------------------------------
 * Hook类是所有需要进行Hook操作类的基类,直接子类有BinderHook,
 * ServiceManagerCacheBinderHook,ProxyHook,SQLiteDatabaseHook等
 */
public abstract class Hook {
    //mEnable用于标识是否开启Hook操作,如果不开启,则走系统自由流程
    private boolean mEnable = false;
    //宿主的上下文环境
    protected Context mHostContext;

    protected BaseHookHandle mHookHandles;

    public void setEnable(boolean enable, boolean reInstallHook) {
        this.mEnable = enable;
    }

    public final void setEnable(boolean enable) {
        setEnable(enable, false);
    }

    public boolean isEnable() {
        return mEnable;
    }


    protected Hook(Context hostContext) {
        mHostContext = hostContext;
        mHookHandles = createHookHandle();
    }

    //创建BaseHookHandle对象
    protected abstract BaseHookHandle createHookHandle();

    /**
     * 子类实现onInstall方法,主要用于替换ServiceManager中sCache已经存在的系统服务。
     * 首先移除已经存在的系统服务IBinder对象,然后自己生成这些系统服务的IBinder对象,并放入sCache对象中。
     * onInstall方法可以理解为“安装Fake服务的过程”
     */
    protected abstract void onInstall(ClassLoader classLoader) throws Throwable;

    protected void onUninstall(ClassLoader classLoader) throws Throwable {

    }
}

2.BaseHookHandle

import android.content.Context;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/2/28.
 * 所有IXXXBinderHook的基类,该类的作用主要是负责HookedMethodHandler的“载入”、“读取”
 */
public abstract class BaseHookHandle {
    //宿主的上下文
    protected Context mHostContext;
    //HookedMethodHandler的对象缓存集合
    protected Map<String, HookedMethodHandler> sHookedMethodHandlers = new HashMap<String, HookedMethodHandler>(5);

    public BaseHookHandle(Context hostContext) {
        mHostContext = hostContext;
        init();
    }
    //将所有HookedMethodHandler对象存入到sHookedMethodHandlers中
    protected abstract void init();

    //获取sHookedMethodHandlers中所有key值
    public Set<String> getHookedMethodNames(){
        return sHookedMethodHandlers.keySet();
    }

    //根据sHookedMethodHandlers的key值获取对应的HookedMethodHandler对象
    public HookedMethodHandler getHookedMethodHandler(Method method) {
        if (method != null) {
            return sHookedMethodHandlers.get(method.getName());
        } else {
            return null;
        }
    }
}

3.HookedMethodHandler

import android.content.Context;
import com.morgoo.helper.Log;
import java.lang.reflect.Method;

/**
 * 该类可以说整个Hook机制的核心,前面在介绍动态代理的时候,我们声明了IShoppingHook类并实现InvocationHandler接口,
 * 在其invoke方法中,通过发射机制调用被Hook的方法并在其调用前后分别调用了beforeHook和afterHook方法。
 * 那么本类的作用也是如此:在InvocationHandler的invoke方法需要调用的被Hook方法的过程,挪到doHookInner中实现。
 * 在真正调用被Hook的方法前后,做一些处理,以绕过系统限制!
 * **/
public class HookedMethodHandler {

    private static final String TAG = HookedMethodHandler.class.getSimpleName();

    protected final Context mHostContext;
    //Hook后的fake服务(IBinder对象)
    private Object mFakedResult = null;
    //是否使用fake服务
    private boolean mUseFakedResult = false;

    public HookedMethodHandler(Context hostContext) {
        this.mHostContext = hostContext;
    }


    public synchronized Object doHookInner(Object receiver, Method method, Object[] args) throws Throwable {
        long b = System.currentTimeMillis();
        try {
            mUseFakedResult = false;
            mFakedResult = null;
            boolean suc = beforeInvoke(receiver, method, args);
            Object invokeResult = null;
            if (!suc) {
                invokeResult = method.invoke(receiver, args);
            }
            afterInvoke(receiver, method, args, invokeResult);
            if (mUseFakedResult) {
                return mFakedResult;
            } else {
                return invokeResult;
            }
        } finally {
            long time = System.currentTimeMillis() - b;
            if (time > 5) {
                Log.i(TAG, "doHookInner method(%s.%s) cost %s ms", method.getDeclaringClass().getName(), method.getName(), time);
            }
        }
    }

    public void setFakedResult(Object fakedResult) {
        this.mFakedResult = fakedResult;
        mUseFakedResult = true;
    }

    /**
     * 在某个方法被调用之前执行,如果返回true,则不执行原始的方法,否则执行原始方法
     */
    protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable {
        return false;
    }

    protected void afterInvoke(Object receiver, Method method, Object[] args, Object invokeResult) throws Throwable {
    }

    public boolean isFakedResult() {
        return mUseFakedResult;
    }

    public Object getFakedResult() {
        return mFakedResult;
    }
}

4.ServiceManagerCacheBinderHook

**
 * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/3/2.
 * 本类主要完成对系统ServiceManager类中的对象和方法进行hook操作。
 * 包括:1.从sCache对象中remove掉已经存在的系统服务Binder对象
 * 2.生成fake系统服务Binder对象,并加入到sCache对象中
 */
public class ServiceManagerCacheBinderHook extends Hook implements InvocationHandler {

    //系统服务名字,如ActivityManager的“activity”
    private String mServiceName;

    public ServiceManagerCacheBinderHook(Context hostContext, String servicename) {
        super(hostContext);
        mServiceName = servicename;
        setEnable(true);
    }


    @Override
    protected void onInstall(ClassLoader classLoader) throws Throwable {
        //通过反射获取sCache对象
        Object sCacheObj = FieldUtils.readStaticField(ServiceManagerCompat.Class(), "sCache");
        //判断sCacheObj类型是否是Map
        if (sCacheObj instanceof Map) {
            Map sCache = (Map) sCacheObj;
            //通过Service Name获取对应系统服务的IBinder对象
            Object Obj = sCache.get(mServiceName);
            if (Obj != null && false) {
                //FIXME 已经有了怎么处理?这里我们只是把原来的给remove掉,再添加自己的。程序下次取用的时候就变成我们hook过的了。
                //但是这样有缺陷。
                //add by me
                // 作者这个疑问是需要解决。因为系统原有的已经被替换了,等于后续其他app使用系统服务的时候都是被替换的IBinder对象
                //虽然说没有啥大影响,但是还是要做到善始善终。how to do?
                throw new RuntimeException("Can not install binder hook for " + mServiceName);
            } else {
                //移除掉mServiceName对应的系统服务IBinder对象
                sCache.remove(mServiceName);
                //这里又重新创建了对应的系统服务,注意这里不是被Hook过的,是通过反射调用ServiceManager的getService方法获取
                //到的。这里多这一步的目的,是获取由系统创建的服务,因为上一步被我们remove调用的系统服务也有可能是被Hook过的。
                IBinder mServiceIBinder = ServiceManagerCompat.getService(mServiceName);
                if (mServiceIBinder != null) {
                    /**
                     *这里出现一个MyServiceManager类,该类可以理解为类似ServiceManger的存在。
                     * 将原始的mServiceIBinder对象通过addOriginService加入到缓存中
                     * */
                    MyServiceManager.addOriginService(mServiceName, mServiceIBinder);
                    //以下使用Java动态代理来完成生成fake IBinder的过程
                    Class clazz = mServiceIBinder.getClass();
                    List<Class<?>> interfaces = Utils.getAllInterfaces(clazz);
                    Class[] ifs = interfaces != null && interfaces.size() > 0 ? interfaces.toArray(new Class[interfaces.size()]) : new Class[0];
                    IBinder mProxyServiceIBinder = (IBinder) MyProxy.newProxyInstance(clazz.getClassLoader(), ifs, this);
                    sCache.put(mServiceName, mProxyServiceIBinder);
                    MyServiceManager.addProxiedServiceCache(mServiceName, mProxyServiceIBinder);
                }
            }
        }
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            //这里比较简单,就不说明了!
            IBinder originService = MyServiceManager.getOriginService(mServiceName);
            if (!isEnable()) {
                return method.invoke(originService, args);
            }
            HookedMethodHandler hookedMethodHandler = mHookHandles.getHookedMethodHandler(method);
            if (hookedMethodHandler != null) {
                return hookedMethodHandler.doHookInner(originService, method, args);
            } else {
                return method.invoke(originService, args);
            }
        }
        //打印各种异常!
        catch (InvocationTargetException e) {
            Throwable cause = e.getTargetException();
            if (cause != null && MyProxy.isMethodDeclaredThrowable(method, cause)) {
                throw cause;
            } else if (cause != null) {
                RuntimeException runtimeException = !TextUtils.isEmpty(cause.getMessage()) ? new RuntimeException(cause.getMessage()) : new RuntimeException();
                runtimeException.initCause(cause);
                throw runtimeException;
            } else {
                RuntimeException runtimeException = !TextUtils.isEmpty(e.getMessage()) ? new RuntimeException(e.getMessage()) : new RuntimeException();
                runtimeException.initCause(e);
                throw runtimeException;
            }
        } catch (IllegalArgumentException e) {
            try {
                StringBuilder sb = new StringBuilder();
                sb.append(" DROIDPLUGIN{");
                if (method != null) {
                    sb.append("method[").append(method.toString()).append("]");
                } else {
                    sb.append("method[").append("NULL").append("]");
                }
                if (args != null) {
                    sb.append("args[").append(Arrays.toString(args)).append("]");
                } else {
                    sb.append("args[").append("NULL").append("]");
                }
                sb.append("}");

                String message = e.getMessage() + sb.toString();
                throw new IllegalArgumentException(message, e);
            } catch (Throwable e1) {
                throw e;
            }
        } catch (Throwable e) {
            if (MyProxy.isMethodDeclaredThrowable(method, e)) {
                throw e;
            } else {
                RuntimeException runtimeException = !TextUtils.isEmpty(e.getMessage()) ? new RuntimeException(e.getMessage()) : new RuntimeException();
                runtimeException.initCause(e);
                throw runtimeException;
            }
        }
    }

    /**
     * 前面有提到过BaseHookHandle主要是用来管理HookedMethodHandler对象的。
     * 那么这里的ServiceManagerHookHandle类是handle IBinder中“queryLocalInterface”方法的hook
     * 该类中有一个queryLocalInterface内部类,继承自HookedMethodHandler,前面有提到过,HookedMethodHandler类作用
     * 主要是在调用被hook方法前后进行某些处理。例如这里就是在IBinder调用queryLocalInterface方法后进行处理:
     * 设置被hook后的系统服务。
     * queryLocalInterface返回的是IInterface对象(一定要注意IInterface和IBinder的区别,
     * 在MyServiceManager中针对这两个对象进行了不同缓存),那么在这里就是IServiceManager对象(IServiceManager继承自IInterface);
     * 花絮:DP中,作者写了一个ServiceManagerBinderHook类,其实这个类原本是用来Hook这个IServiceManager对象然后fake。
     * 估计后来发现通过IBinder的queryLocalInterface方法可以生成IServiceManager,就弃用了ServiceManagerBinderHook类,所以在DP
     * 中,这个类没有被使用过!!!
     */
    private class ServiceManagerHookHandle extends BaseHookHandle {

        private ServiceManagerHookHandle(Context context) {
            super(context);
        }

        @Override
        protected void init() {
            //创建IBinder的方法
            sHookedMethodHandlers.put("queryLocalInterface", new queryLocalInterface(mHostContext));
        }


        class queryLocalInterface extends HookedMethodHandler {
            public queryLocalInterface(Context context) {
                super(context);
            }

            //在queryLocalInterface方法调用后,调用setFakedResult方法,设置fake过的系统服务
            @Override
            protected void afterInvoke(Object receiver, Method method, Object[] args, Object invokeResult) throws Throwable {
                Object localInterface = invokeResult;
                Object proxiedObj = MyServiceManager.getProxiedObj(mServiceName);
                if (localInterface == null && proxiedObj != null) {
                    setFakedResult(proxiedObj);
                }
            }
        }
    }

    @Override
    protected BaseHookHandle createHookHandle() {
        return new ServiceManagerHookHandle(mHostContext);
    }
}

Hook、HookedMethodHandler、BinderHook这个三基类共同协作完成了偷天换日的操作。其实只要理解这个三个类的功能是什么,就很好理解DP中Hook的精髓!

小结

越深入阅读DP源码,就越佩服作者思路。虽然原理看起来很简单,但是其中所付出时间可见一斑。
这节笔记就暂时做到这里,因为版面已经很长了。希望各位不吝啬你的喜欢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容