其他分享
首页 > 其他分享> > 两个简单实战Demo,带你轻松理解Android-Hook机制,安卓高级开发工程师面试题

两个简单实战Demo,带你轻松理解Android-Hook机制,安卓高级开发工程师面试题

作者:互联网

简单案例一: 使用 Hook 修改 View.OnClickListener 事件

首先,我们先分析 View.setOnClickListener 源码,找出合适的 Hook 点。可以看到 OnClickListener 对象被保存在了一个叫做 ListenerInfo 的内部类里,其中 mListenerInfo 是 View 的成员变量。ListeneInfo 里面保存了 View 的各种监听事件。因此,我们可以想办法 hook ListenerInfo 的 mOnClickListener 。

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

static class ListenerInfo {


ListenerInfo getListenerInfo() {
if (mListenerInfo != null) {
return mListenerInfo;
}
mListenerInfo = new ListenerInfo();
return mListenerInfo;
}


}

接下来,让我们一起来看一下怎样 Hook View.OnClickListener 事件?

大概分为三步:

从 View 的源代码,我们可以知道我们可以通过 getListenerInfo 方法获取,于是,我们利用反射得到 ListenerInfo 对象

从上面的分析,我们知道 OnClickListener 事件被保存在 ListenerInfo 里面,同理我们利用反射获取

public static void hookOnClickListener(View view) throws Exception {
// 第一步:反射得到 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 = (Vie

《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》

【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享

w.OnClickListener) mOnClickListener.get(listenerInfo);
// 第三步:用 Hook代理类 替换原始的 OnClickListener
View.OnClickListener hookedOnClickListener = new HookedClickListenerProxy(originOnClickListener);
mOnClickListener.set(listenerInfo, hookedOnClickListener);
}

public class HookedClickListenerProxy implements View.OnClickListener {

private View.OnClickListener origin;

public HookedClickListenerProxy(View.OnClickListener origin) {
this.origin = origin;
}

@Override
public void onClick(View v) {
Toast.makeText(v.getContext(), “Hook Click Listener”, Toast.LENGTH_SHORT).show();
if (origin != null) {
origin.onClick(v);
}
}

}

执行以下代码,将会看到当我们点击该按钮的时候,会弹出 toast “Hook Click Listener”

mBtn1 = (Button) findViewById(R.id.btn_1);
mBtn1.setOnClickListener(this);
try {
HookHelper.hookOnClickListener(mBtn1);
} catch (Exception e) {
e.printStackTrace();
}

简单案例二: HooK Notification

发送消息到通知栏的核心代码如下:

NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(id, builder.build());

跟踪 notify 方法发现最终会调用到 notifyAsUser 方法

public void notify(String tag, int id, Notification notification)
{
notifyAsUser(tag, id, notification, new UserHandle(UserHandle.myUserId()));
}

而在 notifyAsUser 方法中,我们惊喜地发现 service 是一个单例,因此,我们可以想方法 hook 住这个 service,而 notifyAsUser 最终会调用到 service 的 enqueueNotificationWithTag 方法。因此 hook 住 service 的 enqueueNotificationWithTag 方法即可

public void notifyAsUser(String tag, int id, Notification notification, UserHandle user)
{
//
INotificationManager service = getService();
String pkg = mContext.getPackageName();
// Fix the notification as best we can.
Notification.addFieldsFromContext(mContext, notification);
if (notification.sound != null) {
notification.sound = notification.sound.getCanonicalUri();
if (StrictMode.vmFileUriExposureEnabled()) {
notification.sound.checkFileUriExposed(“Notification.sound”);
}
}
fixLegacySmallIcon(notification, pkg);
if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
if (notification.getSmallIcon() == null) {
throw new IllegalArgumentException("Invalid notification (no valid small icon): "

private static INotificationManager sService;

static public INotificationManager getService()
{
if (sService != null) {
return sService;
}
IBinder b = ServiceManager.getService(“notification”);
sService = INotificationManager.Stub.asInterface(b);
return sService;
}

综上,要 Hook Notification,大概需要三步:

于是,我们可以写出如下的代码

public static void hookNotificationManager(final Context context) throws Exception {
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

Method getService = NotificationManager.class.getDeclaredMethod(“getService”);
getService.setAccessible(true);
// 第一步:得到系统的 sService
final Object sOriginService = getService.invoke(notificationManager);

Class iNotiMngClz = Class.forName(“android.app.INotificationManager”);
// 第二步:得到我们的动态代理对象
Object proxyNotiMng = Proxy.newProxyInstance(context.getClass().getClassLoader(), new
Class[]{iNotiMngClz}, new InvocationHandler() {

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.d(TAG, “invoke(). method:” + method);
String name = method.getName();
Log.d(TAG, “invoke: name=” + name);
if (args != null && args.length > 0) {
for (Object arg : args) {
Log.d(TAG, “invoke: arg=” + arg);
}
}
Toast.makeText(context.getApplicationContext(), “检测到有人发通知了”, Toast.LENGTH_SHORT).show();
// 操作交由 sOriginService 处理,不拦截通知
return method.invoke(sOriginService, args);
// 拦截通知,什么也不做
// return null;
// 或者是根据通知的 Tag 和 ID 进行筛选
}
});
// 第三步:偷梁换柱,使用 proxyNotiMng 替换系统的 sService
Field sServiceField = NotificationManager.class.getDeclaredField(“sService”);
sServiceField.setAccessible(true);
sServiceField.set(notificationManager, proxyNotiMng);

}


Hook 使用进阶

Hook ClipboardManager

第一种方法

从上面的 hook NotificationManager 例子中,我们可以得知 NotificationManager 中有一个静态变量 sService,这个变量是远端的 service。因此,我们尝试查找 ClipboardManager 中是不是也存在相同的类似静态变量。

查看它的源码发现它存在 mService 变量,该变量是在 ClipboardManager 构造函数中初始化的,而 ClipboardManager 的构造方法用 @hide 标记,表明该方法对调用者不可见。

而我们知道 ClipboardManager,NotificationManager 其实这些都是单例的,即系统只会创建一次。因此我们也可以认为
ClipboardManager 的 mService 是单例的。因此 mService 应该是可以考虑 hook 的一个点。

public class ClipboardManager extends android.text.ClipboardManager {
private final Context mContext;
private final IClipboard mService;

/** {@hide} */
public ClipboardManager(Context context, Handler handler) throws ServiceNotFoundException {
mContext = context;
mService = IClipboard.Stub.asInterface(
ServiceManager.getServiceOrThrow(Context.CLIPBOARD_SERVICE));
}
}

接下来,我们再来一个看一下 ClipboardManager 的相关方法 setPrimaryClip , getPrimaryClip

public void setPrimaryClip(ClipData clip) {
try {
if (clip != null) {
clip.prepareToLeaveProcess(true);
}
mService.setPrimaryClip(clip, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}

/**

可以发现这些方法最终都会调用到 mService 的相关方法。因此,ClipboardManager 的 mService 确实是一个可以 hook 的一个点。

hook ClipboardManager.mService 的实现

大概需要三个步骤

public static void hookClipboardService(final Context context) throws Exception {
ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
Field mServiceFiled = ClipboardManager.class.getDeclaredField(“mService”);
mServiceFiled.setAccessible(true);
// 第一步:得到系统的 mService
final Object mService = mServiceFiled.get(clipboardManager);

// 第二步:初始化动态代理对象
Class aClass = Class.forName(“android.content.IClipboard”);
Object proxyInstance = Proxy.newProxyInstance(context.getClass().getClassLoader(), new
Class[]{aClass}, new InvocationHandler() {

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.d(TAG, “invoke(). method:” + method);
String name = method.getName();

标签:面试题,OnClickListener,mService,Demo,安卓,ClipboardManager,notification,public,View
来源: https://blog.csdn.net/m0_65637712/article/details/122158506