Android 插件化之Hook机制详解手机开发
Hook 英文翻译过来就是「钩子」的意思,就是在程序执行的过程中去截取其中的信息。Android 操作系统中系统维护着自己的一套事件分发机制,那么Hook就是在事件传送到终点前截获并监控事件的传输。其原理示意图如下:
众所周知,Android 系统中使用了沙箱机制,普通用户程序的进程空间都是独立的,程序的运行互不干扰,而进程之间要实现通信需要借助Android的Binder机制。
在Hook技术中,根据 Hook 对象与 Hook 后处理的事件方式不同,Hook 可以分为消息 Hook、API Hook 等。
在Android开发中,有以下常见的一些Hook框架:
1,Xposed通过替换 /system/bin/app_process 程序控制 Zygote 进程,使得 app_process 在启动过程中会加载 XposedBridge.jar 这个 Jar 包,从而完成对 Zygote 进程及其创建的 Dalvik 虚拟机的劫持。
Xposed 在开机的时候完成对所有的 Hook Function 的劫持,在原 Function 执行的前后加上自定义代码。
Cydia Substrate 框架为苹果用户提供了越狱相关的服务框架,当然也推出了 Android 版 。Cydia Substrate 是一个代码修改平台,它可以修改任何进程的代码。不管是用 Java 还是 C/C++(native代码)编写的,而 Xposed 只支持 Hook app_process 中的 Java 函数。
3,LegendLegend 是 Android 免 Root 环境下的一个 Apk Hook 框架,该框架代码设计简洁,通用性高,适合逆向工程时一些 Hook 场景。大部分的功能都放到了 Java 层,这样的兼容性就非常好。
原理是这样的,直接构造出新旧方法对应的虚拟机数据结构,然后替换信息写到内存中即可。
反射是运行于JVM中的程序检测和修改运行时的一种行为,通过反射可以在运行时获取对象的属性和方法,这种动态获取信息以及动态调用对象方法的功能特性被称为反射机制。
关于反射更详细的内容可以查看下面的链接:Java基础之反射
为了方便后面理解,这里先说下对于Hook的一些基本的常识:
Hook的选择点
Hook时应尽量选择静态变量和单例对象,因为一旦创建对象,它们不容易变化,非常容易定位。
Hook流程
寻找 Hook 点,原则是静态变量或者单例对象,尽量 Hook public 的对象和方法; 选择合适的代理方式,如果是接口可以用动态代理; 使用代理对象替换原始对象。 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);而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的启动过程的拦截5964.html
app程序应用开发手机开发无线开发移动端开发
相关文章
- Android carservice架构及启动流程
- android中的加密算法,Android中加密算法[通俗易懂]
- 加密狗android,Android系统加密狗的设计与实现
- android bindservice方法,Android bindservice方法返回false
- Android UI设计
- robotium android,Robotium 测试Android apk安装包
- Android resource linking failed_android sdk location should not
- android 安装包过大,如何给我们的应用“减肥”?
- Android 本地化翻译插件,一键将你的 strings.xml 翻译为最多 104 种语言,告别手动复制黏贴
- 【Android 安全】DEX 加密 ( Application 替换 | 分析 ContentProvider 组件中调用 getApplication() 获取的 Application )
- 【Android 插件化】Hook 插件化框架 ( Hook Activity 启动流程 | AMS 启动前使用动态代理替换掉插件 Activity 类 )
- 【错误记录】Android Studio 编译报错 ( Could not find com.android.tools.build:gradle:4.2.1. )
- 【Android Gradle 插件】Module 目录下 build.gradle 配置文件 ( android 闭包块配置 | AppExtension 扩展类型参考文档 )
- 【Android Gradle 插件】ProductFlavor 配置 ( ProductFlavor#vectorDrawables 配置 )
- 【Android Gradle 插件】BuildType 编译类型配置 ⑥ ( BuildType#signingConfig 配置 )
- 【Android Gradle 插件】AndroidSourceSets 配置 ③ ( aidl 配置 | assets 配置 | compileConfigurationName 配置 )
- 【Android Gradle 插件】CompileOptions 配置 ( BaseExtension#compileOptions 脚配块配置 | CompileOptions 属性配置 )
- 【Android Gradle 插件】Gradle 自定义 Plugin 插件 ⑦ ( 自定义 Gradle 插件导入方式 | buildSrc 插件导入 | 构建脚本中自定义插件 | 独立文件 )
- 【Android Gradle 插件】自定义 Gradle 插件优化图片 ① ( Android 中的 WebP 图片格式使用 | WebP 格式转换 | WebP 参考文档 )
- 【错误记录】Android Studio 集成 ARoute 编译报错 ( 兼容 support 库和 androidx 库 | add ‘tools:replace=“android:appCo )
- Android控制文字水平间距android:letterSpacing详解手机开发
- [android] 插入一条记录到系统短信应用里详解手机开发
- Android Studio最全插件整理详解手机开发
- Android Studio安装插件Genymotion详解手机开发
- android插件化之路详解手机开发
- Android Remote Views详解手机开发
- Android模块化、组件化、插件化区别详解手机开发
- 新品秀:Dell Cast,让Android平板变身台式PC
- Android的单位以及屏幕分辨率详解