前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android 免Hook消息监控

Android 免Hook消息监控

作者头像
Rouse
发布2024-05-28 12:43:51
1610
发布2024-05-28 12:43:51
举报
文章被收录于专栏:Android补给站

前言

在一些情况下,app中经常要做Hook ActivityThread、Choreographer FrameHandler,ViewRootImpl,InputMethodManager中Handler的操作,然而我们往往不可避免的就去hook替换原有的Handler或者Callback,除此之外,还有什么办法呢?

我们本篇通过Looper实现另一种免hook的方式。

Android发展已经十多年了,回想起几年前做隐私走查的需求,当时我们使用了aspectj 和 hook android.os.ServiceManager 实现,具体原理是把ServiceManager中的IBinder对象给wrap一层Proxy(动态代理BinderProxy) ,然后再替换进ServiceManager,通过这种方式可以解决通过反射和binder.transact(...)直接调用的拦截,避免了通过hook方法名拦截不到的问题。当时以为hook 技术已经到了瓶颈,而现实是plt hook 和 native hook如强势来袭,使得hook技术更上一层楼。

不过,hook 本身存在不稳定和难以维护的风险,比方说一些Binder方法的code 会有一些调整,同一个方法在一些版本的code是不一样的。总的来,如果紧跟成熟方案,理论上不会有太大的问题。

扯的有点远,我们本篇的主题是免hook消息监控,本篇不会使用反射或者其他hook工具,就能实现对重要组件的监控。

以往的消息监控都是给Handler设置一个Callback,为什么这么做呢,主要原因是一些组件内部的Handler都被final修饰,更笨无法替换Handler,因此需要使用Callback,因为Callback优先获得执行机会,这就看Handler#dispatchMessaage实现。该的方法实现如下:

代码语言:javascript
复制
public void dispatchMessage(@NonNull Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {  //优先于handleMessage执行
                return;
            }
        }
        handleMessage(msg);
    }
}

免Hook原理

为什么Looper能实现消息监控呢 ?得益于Looper#setMessageLogging 来实现消息监控,看到这里和性能监控不是一回事么?还有没有继续看的必要呢?

性能监控和消息监控

本篇的主要内容是消息监控而不是性能监控

我们来看看性能监控的核心代码,实际上是匹配日志,显然,这段日志在Android 各个版本中几乎没有变过,因此被用来巧妙的实现性能监控。

代码语言:javascript
复制
Looper.getMainLooper().setMessageLogging(new Printer() {
    private static final String START = ">>>>> Dispatching";
    private static final String END = "<<<<< Finished";

    @Override
     public void println(String x) {
          if (x.startsWith(START)) {
                //从这里开启一个定时任务来打印方法的堆栈信息
           }
           if (x.startsWith(END)) {
                  //从这里取消定时任务
          }
       }
  });

然而,这就完了么 ?

显然不是的,我们知道,通过Looper实现消息监控,意味着我们能拿到Message中的一些信息。

深入分析日志

从上面的监控手段中,我们对println(String msg)的消息只拿到了 >>>>> Dispatching 和 <<<<< Finished,就实现Handler性能监控,如果我们拿整个msg会怎么样?我们来打印一下执行结果

代码语言:javascript
复制
>>>>> Dispatching to Handler (android.app.ActivityThread$H) {3eede03} null: 159
<<<<< Finished to Handler (android.app.ActivityThread$H) {3eede03} null
>>>>> Dispatching to Handler (android.view.ViewRootImpl$ViewRootHandler) {daba180} android.view.ViewRootImpl$7@648f3b9: 0
<<<<< Finished to Handler (android.view.ViewRootImpl$ViewRootHandler) {daba180} android.view.ViewRootImpl$7@648f3b9
>>>>> Dispatching to Handler (android.view.Choreographer$FrameHandler) {16853fe} android.view.Choreographer$FrameDisplayEventReceiver@777575f: 0
<<<<< Finished to Handler (android.view.Choreographer$FrameHandler) {16853fe} 
>>>>> Dispatching to Handler (android.view.ViewRootImpl$ViewRootHandler) {daba180} com.android.internal.policy.PhoneWindow$1@69ed357: 0
<<<<< Finished to Handler (android.view.ViewRootImpl$ViewRootHandler) {daba180} com.android.internal.policy.PhoneWindow$1@69ed357
>>>>> Dispatching to Handler (android.view.ViewRootImpl$ViewRootHandler) {daba180} null: 29
<<<<< Finished to Handler (android.view.ViewRootImpl$ViewRootHandler) {daba180} null
>>>>> Dispatching to Handler (android.view.ViewRootImpl$ViewRootHandler) {daba180} null: 6
<<<<< Finished to Handler (android.view.ViewRootImpl$ViewRootHandler) {daba180} null
>>>>> Dispatching to Handler (android.view.Choreographer$FrameHandler) {16853fe} android.view.Choreographer$FrameDisplayEventReceiver@777575f: 0
<<<<< Finished to Handler (android.view.Choreographer$FrameHandler) {16853fe} android.view.Choreographer$FrameDisplayEventReceiver@777575f
>>>>> Dispatching to Handler (android.view.ViewRootImpl$ViewRootHandler) {daba180} null: 13
<<<<< Finished to Handler (android.view.ViewRootImpl$ViewRootHandler) {daba180} null
>>>>> Dispatching to Handler (android.view.inputmethod.InputMethodManager$H) {9fe7862} null: 4
<<<<< Finished to Handler (android.view.inputmethod.InputMethodManager$H) {9fe7862} null
>>>>> Dispatching to Handler (android.os.Handler) {7d34df3} androidx.emoji2.text.EmojiCompatInitializer$LoadEmojiCompatRunnable@3aa6ab0: 0
<<<<< Finished to Handler (android.os.Handler) {7d34df3} androidx.emoji2.text.EmojiCompatInitializer$LoadEmojiCompatRunnable@3aa6ab0
>>>>> Dispatching to Handler (android.os.Handler) {3f782e5} androidx.emoji2.text.EmojiCompat$ListenerDispatcher@c62b1ba: 0
<<<<< Finished to Handler (android.os.Handler) {3f782e5} androidx.emoji2.text.EmojiCompat$ListenerDispatcher@c62b1ba
>>>>> Dispatching to Handler (android.app.ActivityThread$H) {3eede03} null: 131
<<<<< Finished to Handler (android.app.ActivityThread$H) {3eede03} null

很明显,只要是当前线程的Looper中的消息,一旦执行都能被拦截,而且整条消息的中的Handler、callback、what也会暴露出来。我们平时想hook的目标也能被跟踪到:

  • android.app.ActivityThread$H
  • android.view.ViewRootImpl$ViewRootHandler
  • android.view.inputmethod.InputMethodManager
  • android.view.Choreographer$FrameHandler
  • android.media.AudioManager.ServiceEventHandlerDelegate$
  • android.content.AsyncQueryHandler.WorkerHandler

当然,还有一些普通的Handler,如果是业务中的还是相当好定位的,但是如果是第三方的,难度还是稍微有些高。

为什么能实现呢,我们还是从消息文本来看。

代码语言:javascript
复制
logging.println(">>>>> Dispatching to " + msg.target + " "
        + msg.callback + ": " + msg.what);
      //省略一些代码    
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);

实际上,msg 日志中,一些重要的信息被我们遗忘了,就是msg.target、msg.callback以及msg.what,拿到这些消息其实我们已经完全可以避免去Hook这些目标组件的Handler了。

我们只需要解析字符串就能完全避免hook,告别反射获取Handler的行为。

实现代码

我们只需要解析出msg.callback的className,msg.target的className,以及msg.what即可,这里我们实现一个类来记录数据。

代码语言:javascript
复制
class MessageInfo{
  String target; //className
  String callback;  //className
  int what;
}

然后,我们再实现两个方法parseBefore和parseAfter,原理就是从字符串中提取上面的信息,当然你还可以使用正则或者其他算法,这里就省掉了,自行实现即可。

注意:因为Handler是顺序执行的,为了避免内存问题,这里我们要复用对象 MessageInfo holder = new MessageInfo();

接着我们实现免hook消息监听

代码语言:javascript
复制
Looper.getMainLooper().setMessageLogging(new Printer() {
    private static final String START = ">>>>> Dispatching";
    private static final String END = "<<<<< Finished";
    
    MessageInfo holder = new MessageInfo();

    @Override
     public void println(String x) {
          if (x.startsWith(START)) {
          
               MessageInfo info = parseBefore(x,holder);
               
               if(isActivityThreadH(info)){
                   ActivityThreadH_before(info);
               }else if(isViewRootHandler(info)){
                   ViewRootHandler_before(info);
               }else if(isFrameHandler(info)){
                   FrameHandler_before(info);
               }
           }
           if (x.startsWith(END)) {
                MessageInfo info = parseAfter(x,holder);
               if(isActivityThreadH(info)){
                   ActivityThreadH_after(info);
               }else if(isViewRootHandler(info)){
                   ViewRootHandler_after(info);
               }else if(isFrameHandler(info)){
                   FrameHandler_after(info);
               }
          }
       }
  });

问题和总结

如何获取消息

实际上到这里我们已经可以实现大部分需求了,主要要拿Message,目前来说除了代理looper 循环或者扫描Messagener之外,兼容全版本的方法是没有的。

在Android 10新增了 Looper Observer,通过Looper Observer 可以拿到后置消息,不过,这里我们还是按实际情况来说,获取Message的意义并不大,往往是获取Handler的意义更大一些。

那么如何拿到Handler呢,这里有两种方法:

  • 通过反射
  • Looper Observer 拿到msg.target获取。

这种方式的可靠性

使用字符串识别可靠么 ?

首先,性能监控app也是这么做的。另外,日志都避免了你获取消息本体,显然没有其他风险,android 官方改动的机率应该不大。

总结

本篇比较简短,但如果是监控ActivityThread、Choreographer、ViewRoot、InputMethodManager 的相关Handler消息的需求,不仅省掉了hook等方式,还能更加细致的追踪每个消息的执行。

优缺点

优点
  • 细化性能监控,由此我们的性能监控可以做的更加细化
  • 避免hook,我们对ActivityThread、choreographer等的监控,完全避免了hook
缺点

缺点也比较明显,因为拦截非常依赖msg.target和mgs.callback类名特征,因此,可能不能满足一些情况,但对ViewRootImpl、Choreographer、ActivityThread、InputMethodManager、AudioPortEventHandler、AudioManager等系统组件的Handler仍然是可以的,因为大部分都有特定的类名特征。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-05-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Android补给站 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 免Hook原理
    • 性能监控和消息监控
      • 深入分析日志
        • 实现代码
        • 问题和总结
          • 如何获取消息
            • 这种方式的可靠性
            • 总结
              • 优缺点
                • 优点
                • 缺点
            相关产品与服务
            应用性能监控
            应用性能监控(Application Performance Management,APM)是一款应用性能管理平台,基于实时多语言应用探针全量采集技术,为您提供分布式性能分析和故障自检能力。APM 协助您在复杂的业务系统里快速定位性能问题,降低 MTTR(平均故障恢复时间),实时了解并追踪应用性能,提升用户体验。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档