Android 插件化之Hook机制详解手机开发

Android Hook简介

什么是Hook

Hook 英文翻译过来就是「钩子」的意思,就是在程序执行的过程中去截取其中的信息。Android 操作系统中系统维护着自己的一套事件分发机制,那么Hook就是在事件传送到终点前截获并监控事件的传输。其原理示意图如下:
这里写图片描述

众所周知,Android 系统中使用了沙箱机制,普通用户程序的进程空间都是独立的,程序的运行互不干扰,而进程之间要实现通信需要借助Android的Binder机制。
在Hook技术中,根据 Hook 对象与 Hook 后处理的事件方式不同,Hook 可以分为消息 Hook、API Hook 等。

Hook 框架

在Android开发中,有以下常见的一些Hook框架:

1,Xposed

通过替换 /system/bin/app_process 程序控制 Zygote 进程,使得 app_process 在启动过程中会加载 XposedBridge.jar 这个 Jar 包,从而完成对 Zygote 进程及其创建的 Dalvik 虚拟机的劫持。
Xposed 在开机的时候完成对所有的 Hook Function 的劫持,在原 Function 执行的前后加上自定义代码。

2,Cydia Substrate

Cydia Substrate 框架为苹果用户提供了越狱相关的服务框架,当然也推出了 Android 版 。Cydia Substrate 是一个代码修改平台,它可以修改任何进程的代码。不管是用 Java 还是 C/C++(native代码)编写的,而 Xposed 只支持 Hook app_process 中的 Java 函数。

3,Legend

Legend 是 Android 免 Root 环境下的一个 Apk Hook 框架,该框架代码设计简洁,通用性高,适合逆向工程时一些 Hook 场景。大部分的功能都放到了 Java 层,这样的兼容性就非常好。
原理是这样的,直接构造出新旧方法对应的虚拟机数据结构,然后替换信息写到内存中即可。

Hook基础之反射

反射是运行于JVM中的程序检测和修改运行时的一种行为,通过反射可以在运行时获取对象的属性和方法,这种动态获取信息以及动态调用对象方法的功能特性被称为反射机制。
关于反射更详细的内容可以查看下面的链接:Java基础之反射
为了方便后面理解,这里先说下对于Hook的一些基本的常识:

Hook的选择点
Hook时应尽量选择静态变量和单例对象,因为一旦创建对象,它们不容易变化,非常容易定位。

Hook流程

  1. 寻找 Hook 点,原则是静态变量或者单例对象,尽量 Hook public 的对象和方法;
  2. 选择合适的代理方式,如果是接口可以用动态代理;
  3. 使用代理对象替换原始对象。

1,使用Hook拦截点击事件

下面以如何HooK Android的OnClickListener来讲解如何Hook API 。首先,我们看一下setOnClickListener的源码。

public void setOnClickListener(@Nullable OnClickListener l) { 
        if (!isClickable()) { 
            setClickable(true); 
        } 
        getListenerInfo().mOnClickListener = l; 
    } 
 
    ListenerInfo getListenerInfo() { 
        if (mListenerInfo != null) { 
            return mListenerInfo; 
        } 
        mListenerInfo = new ListenerInfo(); 
        return mListenerInfo; 
    }

可以发现,OnClickListener 对象被保存在了一个叫做 ListenerInfo 的内部类里,其中 mListenerInfo 是 View 的成员变量。而ListeneInfo 里面保存了 View 的各种监听事件,比如 OnClickListener、OnLongClickListener、OnKeyListener 等等。

如果我们要Hook OnClickListener事件,可以给 View 设置监听事件后,然后注入自定义的操作。

private void hookOnClickListener(View view) { 
        try { 
            // 得到 View 的 ListenerInfo 对象 
            Method getListenerInfo = View.class.getDeclaredMethod("getListenerInfo"); 
            getListenerInfo.setAccessible(true); 
            Object listenerInfo = getListenerInfo.invoke(view); 
            // 得到 原始的 OnClickListener 对象 
            Class<?> listenerInfoClz = Class.forName("android.view.View$ListenerInfo"); 
            Field mOnClickListener = listenerInfoClz.getDeclaredField("mOnClickListener"); 
            mOnClickListener.setAccessible(true); 
            View.OnClickListener originOnClickListener = (View.OnClickListener) mOnClickListener.get(listenerInfo); 
            // 用自定义的 OnClickListener 替换原始的 OnClickListener 
            View.OnClickListener hookedOnClickListener = new HookedOnClickListener(originOnClickListener); 
            mOnClickListener.set(listenerInfo, hookedOnClickListener); 
        } catch (Exception e) { 
            log.warn("hook clickListener failed!", e); 
        } 
    } 
 
    class HookedOnClickListener implements View.OnClickListener { 
        private View.OnClickListener origin; 
 
        HookedOnClickListener(View.OnClickListener origin) { 
            this.origin = origin; 
        } 
 
        @Override 
        public void onClick(View v) { 
            Toast.makeText(MainActivity.this, "hook click", Toast.LENGTH_SHORT).show(); 
            log.info("Before click, do what you want to to."); 
            if (origin != null) { 
                origin.onClick(v); 
            } 
            log.info("After click, do what you want to to."); 
        } 
    } 

到此,我们成功Hook 了 OnClickListener事件,然后我们可以使用下面的代码来进行调用。

Button btnSend = (Button) findViewById(R.id.btn_send); 
        btnSend.setOnClickListener(new View.OnClickListener() { 
            @Override 
            public void onClick(View v) { 
                log.info("onClick"); 
            } 
        }); 
        hookOnClickListener(btnSend);

Hook技术除了上面的作用外,还可以用于无痕埋点等功能。

2,使用 Hook 拦截通知

当我们在项目中使用众多的SDK,而美国SDK内部可能会使用NotificationManager 发送通知,这就导致通知难以管理和控制。发送通知使用的是 NotificationManager 的notify方法,而NotificationManager 会返回一个INotificationManager 类型的对象,并调用其 enqueueNotificationWithTag 方法完成通知的发送。以下是notify方法的源码:

public void notify(String tag, int id, Notification notification) 
    { 
        INotificationManager service = getService(); 
        …… // 省略部分代码 
        try { 
            service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id, 
                    stripped, idOut, UserHandle.myUserId()); 
            if (id != idOut[0]) { 
                Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]); 
            } 
        } catch (RemoteException e) { 
        } 
    } 

而INotificationManager的源码如下:

private static INotificationManager sService; 
 
    /** @hide */ 
    static public INotificationManager getService() 
    { 
        if (sService != null) { 
            return sService; 
        } 
        IBinder b = ServiceManager.getService("notification"); 
        sService = INotificationManager.Stub.asInterface(b); 
        return sService; 
    }

INotificationManager 是跨进程通信的 Binder 类,sService 是 NMS(NotificationManagerService) 在客户端的代理,也就是说发送通知后首先委托给 sService,由它传递给 NMS。由上面的源码可以发现 sService 是一个静态成员变量,所以该对象只会初始化一次。所以,要想Hook NotificationManager,可以通过反射拿到sService。核心代码如下:

rivate void hookNotificationManager(Context context) { 
        try { 
            NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); 
            // 得到系统的 sService 
            Method getService = NotificationManager.class.getDeclaredMethod("getService"); 
            getService.setAccessible(true); 
            final Object sService = getService.invoke(notificationManager); 
 
            Class iNotiMngClz = Class.forName("android.app.INotificationManager"); 
            // 动态代理 INotificationManager 
            Object proxyNotiMng = Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{iNotiMngClz}, new InvocationHandler() { 
 
                @Override 
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
                    log.debug("invoke(). method:{}", method); 
                    if (args != null && args.length > 0) { 
                        for (Object arg : args) { 
                            log.debug("type:{}, arg:{}", arg != null ? arg.getClass() : null, arg); 
                        } 
                    } 
                    // 操作交由 sService 处理,不拦截通知 
                    // return method.invoke(sService, args); 
                    // 拦截通知,什么也不做 
                    return null; 
                    // 或者是根据通知的 Tag 和 ID 进行筛选 
                } 
            }); 
            // 替换 sService 
            Field sServiceField = NotificationManager.class.getDeclaredField("sService"); 
            sServiceField.setAccessible(true); 
            sServiceField.set(notificationManager, proxyNotiMng); 
        } catch (Exception e) { 
            log.warn("Hook NotificationManager failed!", e); 
        } 
    }

对于Hook,应当尽可能的早,例如可以在attachBaseContext进行Hook。

3,Hook Activity

熟悉Android的Activity启动流程的同学都知道,启动Activity是由Instrumentation类的execStartActivity来执行的,而execStartActivity函数有一个核心的对象ActivityManagerService(AMS)。

public ActivityResult execStartActivity( 
            Context who, IBinder contextThread, IBinder token, Activity target, 
            Intent intent, int requestCode, Bundle options) { 
        IApplicationThread whoThread = (IApplicationThread) contextThread; 
        .... 
        try { 
            intent.migrateExtraStreamToClipData(); 
            intent.prepareToLeaveProcess(who); 
 
        //通过ActivityManagerNative.getDefault()获取一个对象,开始启动新的Activity 
            int result = ActivityManagerNative.getDefault() 
                .startActivity(whoThread, who.getBasePackageName(), intent, 
                        intent.resolveTypeIfNeeded(who.getContentResolver()), 
                        token, target != null ? target.mEmbeddedID : null, 
                        requestCode, 0, null, options); 
 
 
            checkStartActivityResult(result, intent); 
        } catch (RemoteException e) { 
            throw new RuntimeException("Failure from system", e); 
        } 
        return null; 
    }

而ActivityManagerNative是一个抽象类,它实现了IActivityManager接口。

public abstract class ActivityManagerNative extends Binder implements IActivityManager

也就是说,ActivityManagerNative是为了远程服务通信做准备的”Stub”类,一个完整的AID L有两部分,一个是个跟服务端通信的Stub,一个是跟客户端通信的Proxy。

阅读源码以发现,ActivityManagerNative就是Stub,除此之外,ActivityManagerNative 文件中还有个ActivityManagerProxy,而更加详细的内容本文就不多讲解了。不过要实现Hook效果,需要注意下IActivityManager的getDefault函数。

gDefault()是一个单例,ServiceManager通过获取到AMS远端服务的Binder对象,然后使用asInterface方法转化成本地化对象。涉及的核心代码如下:

 private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() { 
        protected IActivityManager create() { 
            IBinder b = ServiceManager.getService("activity"); 
            if (false) { 
                Log.v("ActivityManager", "default service binder = " + b); 
            } 
            IActivityManager am = asInterface(b); 
            if (false) { 
                Log.v("ActivityManager", "default service = " + am); 
            } 
            return am; 
        }

不过Activity的启动过程可以使用下面的核心类来帮助理解:

ActivityManagerService、ActivityManagerNative、ActivityManagerProxy、ActivityThread、ViewRootImpl、PhoneWindow

下面继续看,那么我们究竟如何实现Hook Activity呢?很简单,只要通过反射拿到IActivityManager对象,然后拿到ActivityManager对象,然后通过动态代理,用代理对象替换掉真实的ActivityManager即可。这也是很多组件化框架的使用的原理。

下面是网上提供的一个实例,通过Hook Activity增加Log输出。

public class HookUtil { 
    private Class<?> proxyActivity; 
    private Context context; 
 
    public HookUtil(Class<?> proxyActivity, Context context) { 
        this.proxyActivity = proxyActivity; 
        this.context = context; 
    } 
 
    public void hookAms() { 
        try { 
            //通过完整的类名拿到class 
            Class<?> ActivityManagerNativeClss = Class.forName("android.app.ActivityManagerNative"); 
            //拿到这个类的某个field 
            Field gDefaultFiled = ActivityManagerNativeClss.getDeclaredField("gDefault"); 
            //field为private,设置为可访问的 
            gDefaultFiled.setAccessible(true); 
            //拿到ActivityManagerNative的gDefault的Field的实例 
            //gDefault为static类型,不需要传入具体的对象 
            Object gDefaultFiledValue = gDefaultFiled.get(null); 
 
            //拿到Singleton类 
            Class<?> SingletonClass = Class.forName("android.util.Singleton"); 
            //拿到类对应的field 
            Field mInstanceField = SingletonClass.getDeclaredField("mInstance"); 
            //field是private 
            mInstanceField.setAccessible(true); 
            //gDefaultFiledValue是Singleton的实例对象 
            //拿到IActivityManager 
            Object iActivityManagerObject = mInstanceField.get(gDefaultFiledValue); 
 
            AmsInvocationHandler handler = new AmsInvocationHandler(iActivityManagerObject); 
            Class<?> IActivityManagerIntercept = Class.forName("android.app.IActivityManager"); 
            Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), 
                    new Class<?>[]{IActivityManagerIntercept}, handler); 
            mInstanceField.set(gDefaultFiledValue, proxy); 
 
        } catch (ClassNotFoundException e) { 
            e.printStackTrace(); 
        } catch (NoSuchFieldException e) { 
            e.printStackTrace(); 
        } catch (IllegalAccessException e) { 
            e.printStackTrace(); 
        } 
    } 
 
    private class AmsInvocationHandler implements InvocationHandler { 
 
        private Object iActivityManagerObject; 
 
        private AmsInvocationHandler(Object iActivityManagerObject) { 
            this.iActivityManagerObject = iActivityManagerObject; 
        } 
 
        @Override 
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
 
            Log.i("HookUtil", method.getName()); 
            //添加日志 
            if ("startActivity".contains(method.getName())) { 
                Log.e("HookUtil","Activity Hook已经开始启动"); 
            } 
            return method.invoke(iActivityManagerObject, args); 
        } 
    } 
}

然后使用的时候在Application注册下即可:

public class MyApplication extends Application { 
 
    @Override 
    public void onCreate() { 
        super.onCreate(); 
        HookUtil hookUtil=new HookUtil(SecondActivity.class, this); 
        hookUtil.hookAms(); 
    } 
}

关于Hook Activity的内容,可以参考下面的文章:
Hook技术之Activity的启动过程的拦截

原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/5964.html

(0)
上一篇 2021年7月17日
下一篇 2021年7月17日

相关推荐

发表回复

登录后才能评论