微信hook教程(安卓微信hook全套教程)

微信hook教程(安卓微信hook全套教程)之前推送了很多大厂分享 很多同学看完就觉得 大厂输出的理论知识居多 缺乏实践 那这篇文章 我们将介绍一个大厂的库 这个库能够实打实的帮助大家解决一些问题 今天的主角 初学者小张 资深研发老羊 1 三方库中的 bug 这天 QA 上线前给小张反馈了一个 bug 应用启动就崩溃 小张一点不慌 插入 USB 触发 一看日志 原来是个空指针 想了想 空指针比较好修复 大不了判空防御一下

大家好,我是讯享网,很高兴认识大家。



之前推送了很多大厂分享,很多同学看完就觉得,大厂输出的理论知识居多,缺乏实践。


那这篇文章,我们将介绍一个大厂的库,这个库能够实打实的帮助大家解决一些问题。


今天的主角:初学者小张,资深研发老羊。


1
三方库中的 bug

这天 QA 上线前给小张反馈了一个 bug,应用启动就崩溃,小张一点不慌,插入 USB,触发,一看日志,原来是个空指针。


想了想,空指针比较好修复,大不了判空防御一下,于是回答:这个问题交给我,马上修复。

根据堆栈,找到了空指针的元凶。


忽然间,小张愣住了,这个空指针是个三方库在初始化的时候获取用户剪切板出错了。

这可怎么解决呢?


本来以为判个空防御一下完事,这会遇到硬茬了。


毕竟是自己装的逼,含着泪也要修复了,我们模拟下现场。


/*
 
 这是三方库中的调用
 */

public class Tools {

    public static String getClipBoardStr(Context context) {
        ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
        ClipData primaryClip = clipboardManager.getPrimaryClip();
        // NPE
        ClipData.Item itemAt = primaryClip.getItemAt(0);
        if (itemAt == null) {
            return ””;
        }
        CharSequence text = itemAt.getText();
        if (text == null) {
            return ””;
        }
        return text.toString();
    }
}

讯享网

我们写个按钮来触发一下:


讯享网

果然发生了崩溃,空指针发生在clipboardManager.getPrimaryClip(),当手机上没有过复制内容时,getPrimaryClip返回的就是 null。


马上就要上线了,但是这个问题,也不是修复不了,根据自己的经验,大多数系统服务都可以被 hook,hook 掉 ClipboradManager 的相关方法,保证返回的 getPrimaryClip 的不为 null 即可。


于是看了几个点:

讯享网
public @Nullable ClipData getPrimaryClip() {

    try {
        return mService.getPrimaryClip(mContext.getOpPackageName(), mContext.getUserId());
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}


这个 mService 的初始化为:


讯享网mService = IClipboard.Stub.asInterface(
                ServiceManager.getServiceOrThrow(Context.CLIPBOARD_SERVICE));

这么看,已经八成可以 hook了,再看下我们自己能构造 ClipData 吗?


public ClipData(CharSequence label, String[] mimeTypes, Item item) {}

恩,hook 的思路基本可行。


小张内心暗喜,多亏是遇到了我呀,还好我实力扎实。


这时候,资深研发老羊过来问了句,马上就要上线了,你这干啥呢?


小张滔滔不绝的描述了一下当前遇到了问题,和自己的解决思路,本以为老羊这次会拍拍自己的肩膀「还好是你遇到了呀」来表示对自己的认可。


老羊开口说道:


getPrimaryClip返回 null 造成的空指针,那你在之前调用一个setPrimaryClip不就行了?


恩?卧槽…看一眼源码:

讯享网
#ClipboardManager
public void setPrimaryClip(@NonNull ClipData clip) {
    try {
        Preconditions.checkNotNull(clip);
        clip.prepareToLeaveProcess(true);
        mService.setPrimaryClip(clip, mContext.getOpPackageName(), mContext.getUserId());
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}

还真有这个方法…


那试试吧。


添加了一行:


讯享网ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
clipboardManager.setPrimaryClip(new ClipData(“bugfix”, new String[]{“text/plain”}, new ClipData.Item(””)));

果然不再崩溃了。


这时候老羊说了句:


你也想想,假设三方库里面真有个致命的 bug,然后你没找到合适的 hook 点你怎么处理?想好了过来告诉我。


致命 bug,没找到合适的 hook 点?


模拟下代码:


public class Tools {

    public static void evilCode() {
        int a = 1 / 0;
    }

    public static String getClipBoardStr(Context context) {
        evilCode();
        ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
        ClipData primaryClip = clipboardManager.getPrimaryClip();
        ClipData.Item itemAt = primaryClip.getItemAt(0);
        if (itemAt == null) {
            return ””;
        }
        CharSequence text = itemAt.getText();
        if (text == null) {
            return ””;
        }
        return text.toString();
    }

}
讯享网假设 getClipBoardStr 内部调用了一行 evilCode,执行到就crash。

一眼望去这个 evilCode 方法,简单是简单,但是在三方库里面怎么解决呢?


小张百思不得其解,忽然灵光一闪:


是不是老羊想考察我的推动能力,让我没事别瞎 hook 人家代码,这种问题当然找三方库那边修复,然后给个新版本咯。


于是跑过去,告诉老羊,我想到了,这种问题,我们应该及时推动三方库那边解决,然后我们升级版本即可。


老羊听了后,恩,确实要找他们,但是如果是上线前遇到,推动肯定是来不及了,就是人家立马给你个新版本,直接升级风险也是比较大的。


然后老羊说道:


我看你对于反射找 hook 点已经比较熟悉了,其实还有一类 hook 更加好用,也更加稳定。


叫做字节码 hook。

怎么说?


我们的代码在打包过程中,会经过如下步骤:


.java -> .class -> dex -> apk


上面那个类的 evil 方法,从 class 文件的角度来看,其实都是字节码。


假设我们在编译过程中,这么做:


.java -> .class -> 拿到 Tools.class,修正里面的方法 evil 方法 -> dex -> apk


这个时机,其实构建过程中也给我们提供了,也就是传说的 Transform 阶段(这里不讨论 AGP 7 之后的变化,还是有对应时机的)。


小张又问,这个时机我知道,Tools.class 文件怎么修改呢?


老羊说,这个你去看看我的博客:


Android 进阶之路:ASM 修改字节码,这样学就对了!

不过话说回来,既然你会遇到这样的痛点,那么别的开发者肯定也会遇到。


这个时候应该怎么想?


小张:肯定有人造了好用的轮子。


老羊:恩,99%的情况,轮子肯定都造好了,剩下 1%,那就是你的机会了。


2
轻量级 aop 框架 lancet 出现

饿了么,很早的时候就开源了一个框架,叫 lancet。

https://github.com/eleme/lancet

这个框架可以支持你,在不懂字节码的情况下,也能够完成对对应方法字节码的修改。

代入到我们刚才的思路:


.java -> .class -> lancet 拿到 Tools.class,修正里面的方法 evilCode 方法 -> dex -> apk


小张:怎么使用 lancet 来修改我们的 evilCode 方法呢?



在项目的根目录添加:


classpath ‘me.ele:lancet-plugin:1.0.6’
讯享网

在 module 的build.gradle 添加依赖和 apply plugin:


apply plugin: ‘me.ele.lancet’

dependencies {
    implementation ‘me.ele:lancet-base:1.0.6’ // 最好查一下,用最新版本
}
讯享网


然后,我们做一件事情,把Tools 里面的 evilCode方法:


public static void evilCode() {
    int a = 1 / 0;
}
讯享网

里面的这个代码给去掉,让它变成空方法。


我们编写代码:


package com.imooc.blogdemo.blog04;

import me.ele.lancet.base.annotations.Insert;
import me.ele.lancet.base.annotations.TargetClass;

public class ToolsLancet {

    @TargetClass(“com.imooc.blogdemo.blog04.Tools”)
    @Insert(“evilCode”)
    public static void evilCode() {

    }

}

我们编写一个新的方法,保证其是个空方法,这样就完成让原有的 evilCode 中调用没有了。

其中:


  • TargetClass 注解:标识你要修改的类名;
  • Insert注解:表示你要往 evilCode 这个方法里面注入下面的代码
  • 下面的方法声明需要和原方法保持一致,如果有参数,参数也要保持一致(方法名、参数名不需要一致)

然后我们打包,看看背后发生了什么神奇的事情。


在打包完成后,我们反编译,看看 Tools.class


讯享网public class Tools {    
   //… 
    public static void evilCode() {
        Tools._lancet.com_imooc_blogdemo_blog04_ToolsLancet_evilCode();
    }

&nbsp;&nbsp;&nbsp;&nbsp;private&nbsp;static&nbsp;void&nbsp;evilCode\(___twin___()&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">int</span>&nbsp;a&nbsp;=&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(174, 135, 250);overflow-wrap: inherit !important;word-break: inherit !important;">1</span>&nbsp;/&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(174, 135, 250);overflow-wrap: inherit !important;word-break: inherit !important;">0</span>;<br />&nbsp;&nbsp;&nbsp;&nbsp;}<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">private</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">static</span>&nbsp;<span style="font-size: inherit;color: inherit;line-height: inherit;overflow-wrap: inherit !important;word-break: inherit !important;"><span style="line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">class</span>&nbsp;<span style="line-height: inherit;color: rgb(165, 218, 45);overflow-wrap: inherit !important;word-break: inherit !important;">_lancet</span>&nbsp;</span>{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;"><span style="line-height: inherit;overflow-wrap: inherit !important;word-break: inherit !important;">private</span>&nbsp;<span style="line-height: inherit;color: rgb(165, 218, 45);overflow-wrap: inherit !important;word-break: inherit !important;">_lancet</span><span style="line-height: inherit;color: rgb(255, 152, 35);overflow-wrap: inherit !important;word-break: inherit !important;">()</span>&nbsp;</span>{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(91, 218, 237);overflow-wrap: inherit !important;word-break: inherit !important;">@TargetClass</span>(<span style="font-size: inherit;line-height: inherit;color: rgb(238, 220, 112);overflow-wrap: inherit !important;word-break: inherit !important;">"com.imooc.blogdemo.blog04.Tools"</span>)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(91, 218, 237);overflow-wrap: inherit !important;word-break: inherit !important;">@Insert</span>(<span style="font-size: inherit;line-height: inherit;color: rgb(238, 220, 112);overflow-wrap: inherit !important;word-break: inherit !important;">"evilCode"</span>)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;"><span style="line-height: inherit;overflow-wrap: inherit !important;word-break: inherit !important;">static</span>&nbsp;<span style="line-height: inherit;overflow-wrap: inherit !important;word-break: inherit !important;">void</span>&nbsp;<span style="line-height: inherit;color: rgb(165, 218, 45);overflow-wrap: inherit !important;word-break: inherit !important;">com_imooc_blogdemo_blog04_ToolsLancet_evilCode</span><span style="line-height: inherit;color: rgb(255, 152, 35);overflow-wrap: inherit !important;word-break: inherit !important;">()</span>&nbsp;</span>{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />&nbsp;&nbsp;&nbsp;&nbsp;}<br />}<br /></code></pre></section><p style="margin-bottom: 0px;"><br /></p><p style="margin-bottom: 0px;"><span style="font-size: 14px;">可以看到,原本的evilCode方法中的校验,被换成了一个生成的方法调用,而这个生成的方法和我们编写的非常类似,并且其为空方法。</span><br /></p><p style="margin-bottom: 0px;"><span style="font-size: 14px;"><br /></span></p><p style="margin-bottom: 0px;"><span style="font-size: 14px;">而原来的 evilCode 逻辑,放在一个evilCode\)twin()方法中,可惜这个方法没地方调用。
这样原有的 evilCode 逻辑就变成了一个空方法了。
我们可以大致梳理下原理:
lancet 会将我们注明需要修改的方法调用中转到一个临时方法中,这个临时方法你可以理解为和我们编写的方法逻辑基本保持一致。
然后将该方法的原逻辑也提取到一个新方法中,以备使用。
小张:确实很神奇,那这个原方法我们什么时候会使用呢?
老羊:很多时候,可能原有逻辑只是个概率很低的问题,比如发送请求,只有在超时等情况才发生错误,你不能粗暴的把人家逻辑移除了,你可能更想加个 try-catch 然后给个提示什么的。
这个时候你可以这么改:
package&nbsp;com.imooc.blogdemo.blog04;

import&nbsp;me.ele.lancet.base.Origin;
import&nbsp;me.ele.lancet.base.annotations.Insert;
import&nbsp;me.ele.lancet.base.annotations.TargetClass;

public&nbsp;class&nbsp;ToolsLancet&nbsp;{

&nbsp;&nbsp;&nbsp;&nbsp;@TargetClass(“com.imooc.blogdemo.blog04.Tools”)
&nbsp;&nbsp;&nbsp;&nbsp;@Insert(“evilCode”)
&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;static&nbsp;void&nbsp;evilCode()&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Origin.callVoid();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;catch&nbsp;(Exception&nbsp;e)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e.printStackTrace();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;}

}
讯享网
我们再来看下反编译代码:
public&nbsp;class&nbsp;Tools&nbsp;{

&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;static&nbsp;void&nbsp;evilCode()&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Tools._lancet.com_imooc_blogdemo_blog04_ToolsLancet_evilCode();
&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp;&nbsp;private&nbsp;static&nbsp;void&nbsp;evilCode\(___twin___()&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">int</span>&nbsp;a&nbsp;=&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(174, 135, 250);overflow-wrap: inherit !important;word-break: inherit !important;">1</span>&nbsp;/&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(174, 135, 250);overflow-wrap: inherit !important;word-break: inherit !important;">0</span>;<br />&nbsp;&nbsp;&nbsp;&nbsp;}<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">private</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">static</span>&nbsp;<span style="font-size: inherit;color: inherit;line-height: inherit;overflow-wrap: inherit !important;word-break: inherit !important;"><span style="line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">class</span>&nbsp;<span style="line-height: inherit;color: rgb(165, 218, 45);overflow-wrap: inherit !important;word-break: inherit !important;">_lancet</span>&nbsp;</span>{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(91, 218, 237);overflow-wrap: inherit !important;word-break: inherit !important;">@TargetClass</span>(<span style="font-size: inherit;line-height: inherit;color: rgb(238, 220, 112);overflow-wrap: inherit !important;word-break: inherit !important;">"com.imooc.blogdemo.blog04.Tools"</span>)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(91, 218, 237);overflow-wrap: inherit !important;word-break: inherit !important;">@Insert</span>(<span style="font-size: inherit;line-height: inherit;color: rgb(238, 220, 112);overflow-wrap: inherit !important;word-break: inherit !important;">"evilCode"</span>)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;"><span style="line-height: inherit;overflow-wrap: inherit !important;word-break: inherit !important;">static</span>&nbsp;<span style="line-height: inherit;overflow-wrap: inherit !important;word-break: inherit !important;">void</span>&nbsp;<span style="line-height: inherit;color: rgb(165, 218, 45);overflow-wrap: inherit !important;word-break: inherit !important;">com_imooc_blogdemo_blog04_ToolsLancet_evilCode</span><span style="line-height: inherit;color: rgb(255, 152, 35);overflow-wrap: inherit !important;word-break: inherit !important;">()</span>&nbsp;</span>{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">try</span>&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Tools.evilCode\)twin();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;catch&nbsp;(Exception&nbsp;var1)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var1.printStackTrace();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;}
}
讯享网
看到没,不出所料中转方法内部调用了原有方法,然后外层包了个 try-catch。
是不是很强大,而且相对于运行时反射相关的 hook 更加稳定,其实他就像你写的代码,只不过是直接改的 class。
小张:所以我早上遇到的剪切板崩溃问题,其实也可以利用 lancet 加一个 try-catch。
老羊:是的,挺会举一反三的,当然也从侧面反映出来字节码 hook 的强大之处,几乎不需要找什么 hook 点,只要你有方法,就能干涉。
另外,我给你介绍的都是最基础的 api,你下去好好看看 lancet 的其他用法。
小张:好嘞,又学到了。
3
新的问题又来了

过了几日,忽然项目又遇到一个问题:
用户未授权读取剪切板之前,不允许有读取剪切板的行为,否则认定为不合规。
小张听到这个任务,大脑快速运转:
这个读取剪切板行为的 API 是:
clipboardManager.getPrimaryClip();
讯享网
搜索下项目中的调用,然后逐一修改。
先不说能不能搜索完整,这三方库里面肯定有,此外后续新增的代码如何控制呢?
另外之前学习 lancet,可以修改三方库代码,但是我也不能把包含clipboardManager.getPrimaryClip的方法全部列出来,一个个字节码修改?还是解决不了后续新增,已经能保证全部搜出来呀。
最终心里嘀咕:别让我干,别让我干,八成是个坑。
这时候老羊来了句:这个简单,小张熟悉,他搞就行了。
小张:我…
重新思考一下,反正搜索出来,一一修改是不可能了。
那就从源头上解决:
系统肯定是通过framework,system 进程那边去判断是否读取剪切板的。
那么我们只要把:
clipboardManager.getPrimaryClip
&nbsp;&nbsp;&nbsp;&nbsp;IClipboard.getPrimaryClip(mContext.getOpPackageName(),&nbsp;mContext.getUserId());

内部的逻辑hook 掉,换掉IClipBoard 的实现,然后切到我们自己的逻辑即可。
懂了,这就是我之前想的系统服务的 hook 而已,难怪老羊安排给我,我给他说过这个。于是乎…我开启了一顿写模式…
此处代码略。(确实可以,不过非本文主要内容)
正完成了 Android 10.0的测试,准备翻翻各个版本有没有源码修改,好适配适配,老羊走了过来。
说了句:这都两个小时过去了,你还没搞完?
小张:两个小时搞完?你来。
老羊:我让你自己看看 lancet 其他 api你没看?
这个用 lancet 就是送分题你知道吗?看好:
讯享网public&nbsp;class&nbsp;ToolsLancet&nbsp;{

&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;模拟用户同意后的状态
&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;static&nbsp;boolean&nbsp;isAuth&nbsp;=&nbsp;true;

&nbsp;&nbsp;&nbsp;&nbsp;@TargetClass(“android.content.ClipboardManager”)
&nbsp;&nbsp;&nbsp;&nbsp;@Proxy(“getPrimaryClip”)
&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;ClipData&nbsp;getPrimaryClip()&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(isAuth)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;(ClipData)&nbsp;Origin.call();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;这里也可以&nbsp;return&nbsp;null,毕竟系统也&nbsp;return&nbsp;null
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;new&nbsp;ClipData(“未授权呢”,&nbsp;new&nbsp;String[]{“text/plain”},&nbsp;new&nbsp;ClipData.Item(””));
&nbsp;&nbsp;&nbsp;&nbsp;}
}

小张:这个不行呀,android.content.ClipboardManager类是系统的,不是我们写的,在打包阶段没有这个 class。
老羊:我当然知道,你仔细看,这次用的注解和上次有什么不同。
这次用的是:
  • @Proxy:意思就是代理,会代理ClipboardManager. getPrimaryClip到我们这个方法中来。

我们反编译看看:
原来的调用:
讯享网public&nbsp;static&nbsp;String&nbsp;getClipBoardStr(Context&nbsp;context)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;ClipboardManager&nbsp;clipboardManager&nbsp;=&nbsp;(ClipboardManager)&nbsp;context.getSystemService(Context.CLIPBOARD_SERVICE);
&nbsp;&nbsp;&nbsp;&nbsp;ClipData&nbsp;primaryClip&nbsp;=&nbsp;clipboardManager.getPrimaryClip();
&nbsp;&nbsp;&nbsp;&nbsp;ClipData.Item&nbsp;itemAt&nbsp;=&nbsp;primaryClip.getItemAt(0);
&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(itemAt&nbsp;==&nbsp;null)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;””;
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;CharSequence&nbsp;text&nbsp;=&nbsp;itemAt.getText();
&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(text&nbsp;==&nbsp;null)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;””;
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;text.toString();
}

反编译的调用:
讯享网public&nbsp;class&nbsp;Tools&nbsp;{

&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;static&nbsp;String&nbsp;getClipBoardStr(Context&nbsp;context)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ClipboardManager&nbsp;clipboardManager&nbsp;=&nbsp;(ClipboardManager)context.getSystemService(“clipboard”);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ClipData&nbsp;primaryClip&nbsp;=&nbsp;Tools._lancet.com_imooc_blogdemo_blog04_ToolsLancet_getPrimaryClip(clipboardManager);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Item&nbsp;itemAt&nbsp;=&nbsp;primaryClip.getItemAt(0);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(itemAt&nbsp;==&nbsp;null)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;””;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;else&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CharSequence&nbsp;text&nbsp;=&nbsp;itemAt.getText();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;text&nbsp;==&nbsp;null&nbsp;?&nbsp;””&nbsp;:&nbsp;text.toString();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp;&nbsp;private&nbsp;static&nbsp;class&nbsp;_lancet&nbsp;{

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@TargetClass(“android.content.ClipboardManager”)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@Proxy(“getPrimaryClip”)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;static&nbsp;ClipData&nbsp;com_imooc_blogdemo_blog04_ToolsLancet_getPrimaryClip(ClipboardManager&nbsp;var0)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;ToolsLancet.isAuth&nbsp;?&nbsp;var0.getPrimaryClip()&nbsp;:&nbsp;new&nbsp;ClipData(“未授权呢”,&nbsp;new&nbsp;String[]{“text/plain”},&nbsp;new&nbsp;Item(””));
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;}
}

看到没有,clipboardManager.getPrimaryClip()方法变成了Tools._lancet.com_imooc_blogdemo_blog04_ToolsLancet_getPrimaryClip,中转到了我们的hook 实现。
这次明白了吧:
  1. lancet 对于我们自己的类中方法,可以使用@Insert 指令;
  2. 遇到系统的调用,我们可以针对调用函数使用@Proxy 指令将其中转到中转函数;

好了,lancet 还有一些 api,你再下去好好看看。
4
完结

终于结束了,大家退出小张和老羊的对话场景。
其实字节码 hook 在 Android 开发过程中更为强大,比我们传统的找 Hook 点(单例,静态变量),然后反射的方式方便太多了,还有个最大的优势就是稳定。
当然lancet hook 有个前提就是要明确知道方法调用,如果你想 hook 一个类的所有调用,那么写起来就有点费劲了,可能并不如动态代理那么方便。




点击 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
小讯
上一篇 2025-04-27 11:28
下一篇 2025-04-24 14:50

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/186249.html