Android Hook 剪切板相关方法

想起之前做过的项目有安全合规要求:主动弹窗获取用户同意了才能调用剪切板相关方法,否则属于违规调用,如果是自己项目的相关调用可以自己加一层if判断

但是一些第三方的jar包里面也有在调用的话,我们就无能为力了,而且整个项目的所有调用处都一个一个去加判断的话,就会显得很麻烦,这里用Hook方法完成拦截方法调用+判断

先要理清 clipboardManager.getPrimaryClip()方法内部的逻辑:

Android11的ClipboardManager源码:http://aospxref.com/android-11.0.0_r21/xref/frameworks/base/core/java/android/content/ClipboardManager.java

里面是这样的:

 

其实就是调用的是mService的相关方法,而mService其实是ServiceManager里面剪切板相关方法封装后的接口,所以到了这里自然就想到用反射拿到这个mService字段,替换成我们自定义的代理类就可以了

1. 在application的onCreate里面执行hook方法,确保后面的调用都能生效

2. inject方法

到这里为止,后面在其他地方调用 getPrimaryClip()方法都会走到我们设置的代理类方法里面进行拦截。

注意,在Android9+的手机上运行,会碰到拿不到反射字段的情况,不信你们自己试一试

关于android9+的反射限制,可以百度查看其他人的分析,我这里用网上大神的解决方案:

1. 在根目录的build.gradle里面加这个仓库地址:

2. 在app的build.gradle脚本里面加这个依赖

implementation 'com.github.ChickenHook:RestrictionBypass:2.2'

sync一下,就搞定了,虽然as还是会标红提示你,但是可以无视直接build

但是!!!注意!!!

我开始也以为到这里就结束了,但是运行后发现,根本没有起作用

activity用法:

我们在其他地方调用一般是这样子调用的对吧,但是经过我断点发现,这里拿到的clipboardManager和之前inject方法里面拿到的clipboardManager实例根本就不是同一个!!!

自然里面的mService实例也不是我们修改过的那个代理类,所以才会不起作用

所以我们就要搞清楚这个clipboardManager到底是怎么获得的

省略断点,跟踪流程发现,实际上调用的是SystemServiceRegistry类的getSystemService方法

主要就是拿到fetcher,调用fetcher.getService(ctx)方法返回给我们

fetcher是来自SYSTEM_SERVICE_FETCHERS,发现这个字段就是个map

那么这个map是在哪里进行put的呢,在当前文件全局搜索发现只有这里put了东西进去

而这个registerService方法我们可以在开头的静态代码块里面发现调用

包括我们想要的clipboardManager

也就是说 SystemServiceRegistry类初始化的时候,这里就put进去了值

因为SYSTEM_SERVICE_FETCHERS字段是个static字段,所以整个app进程只会有一个且只会执行一次初始化的操作,所以无论我们传入的context是Application的还是Activity,拿到的都是同一个fetcher实例,那么问题只能出在fetcher.getService(ctx)方法里面

继续跟踪fetcher.getService(ctx)方法,断点进去

下面是具体方法的实现,方法有点长,直接看下面分析结论

/*** Override this class when the system service constructor needs a* ContextImpl and should be cached and retained by that context.*/static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {private final int mCacheIndex;CachedServiceFetcher() {// Note this class must be instantiated only by the static initializer of the// outer class (SystemServiceRegistry), which already does the synchronization,// so bare access to sServiceCacheSize is okay here.mCacheIndex = sServiceCacheSize++;}@Override@SuppressWarnings("unchecked")public final T getService(ContextImpl ctx) {final Object[] cache = ctx.mServiceCache;final int[] gates = ctx.mServiceInitializationStateArray;boolean interrupted = false;T ret = null;for (;;) {boolean doInitialize = false;synchronized (cache) {// Return it if we already have a cached instance.T service = (T) cache[mCacheIndex];if (service != null || gates[mCacheIndex] == ContextImpl.STATE_NOT_FOUND) {ret = service;break; // exit the for (;;)}// If we get here, there's no cached instance.// Grr... if gate is STATE_READY, then this means we initialized the service// once but someone cleared it.// We start over from STATE_UNINITIALIZED.if (gates[mCacheIndex] == ContextImpl.STATE_READY) {gates[mCacheIndex] = ContextImpl.STATE_UNINITIALIZED;}// It's possible for multiple threads to get here at the same time, so// use the "gate" to make sure only the first thread will call createService().// At this point, the gate must be either UNINITIALIZED or INITIALIZING.if (gates[mCacheIndex] == ContextImpl.STATE_UNINITIALIZED) {doInitialize = true;gates[mCacheIndex] = ContextImpl.STATE_INITIALIZING;}}if (doInitialize) {// Only the first thread gets here.T service = null;@ServiceInitializationState int newState = ContextImpl.STATE_NOT_FOUND;try {// This thread is the first one to get here. Instantiate the service// *without* the cache lock held.service = createService(ctx);newState = ContextImpl.STATE_READY;} catch (ServiceNotFoundException e) {onServiceNotFound(e);} finally {synchronized (cache) {cache[mCacheIndex] = service;gates[mCacheIndex] = newState;cache.notifyAll();}}ret = service;break; // exit the for (;;)}// The other threads will wait for the first thread to call notifyAll(),// and go back to the top and retry.synchronized (cache) {// Repeat until the state becomes STATE_READY or STATE_NOT_FOUND.// We can't respond to interrupts here; just like we can't in the "doInitialize"// path, so we remember the interrupt state here and re-interrupt later.while (gates[mCacheIndex] < ContextImpl.STATE_READY) {try {// Clear the interrupt state.interrupted |= Thread.interrupted();cache.wait();} catch (InterruptedException e) {// This shouldn't normally happen, but if someone interrupts the// thread, it will.Slog.w(TAG, "getService() interrupted");interrupted = true;}}}}if (interrupted) {Thread.currentThread().interrupt();}return ret;}

 我们可以发现会先从ctx.mServiceCache这个缓存数组里面找,找不到就去执行createService方法:

而createService就是最开始static代码块里面传入的实例方法:

ctx.getOuterContext拿到的实例其实就是Application的context

到这里我们可以发现,因为我们传入的context不同,导致拿到的缓存数组也不同,就会走到createService方法去创建实例。

但是实例化方法传入的参数是一样的,都是传入Application的context和主线程的handler。导致我们拿到的clipboardManager实例是经过相同的构造方法和构造参数构造出来的不同实例。

分析了获得clipboardManager实例的获得过程,我们就可以找地方下手了

既然实例是从fetcher.getService方法中返回的,那我们只要拦截这个方法,让它返回同一个实例,就可以解决问题了

先用反射拿到这个map

 @SuppressLint("PrivateApi") Class<?> clazz = Class.forName("android.app.SystemServiceRegistry");Field[] fields = clazz.getDeclaredFields();System.out.println(fields[1].getName());@SuppressLint("BlockedPrivateApi") Field field = clazz.getDeclaredField("SYSTEM_SERVICE_FETCHERS");field.setAccessible(true);ArrayMap objs = (ArrayMap) field.get(null);

不知道为什么这个SystemServiceRegistry类没有办法import,只能通过全路径的方式来反射加载了

拿到clipboard对应的fetcher,然后塞入我们修改过的代理类进去

注意这里要在try catch下完成,最开始的context传入的也是application的context

这里是完整实现代码,主要先执行替换fetcher的代理类,再进行clipboardManager相关方法的代理替换

    public void inject(Context context){ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);try {@SuppressLint({"BlockedPrivateApi", "PrivateApi"}) Field field = Class.forName("android.app.SystemServiceRegistry").getDeclaredField("SYSTEM_SERVICE_FETCHERS");field.setAccessible(true);ArrayMap objs = (ArrayMap) field.get(null);Object fetcher = objs.get("clipboard");@SuppressLint("PrivateApi") Class<?> clazz = Class.forName("android.app.SystemServiceRegistry$ServiceFetcher");objs.put("clipboard", Proxy.newProxyInstance(context.getClassLoader(), new Class[]{clazz}, (proxy, method, args) -> {if (method.getName().equals("getService")){return clipboardManager;}else {return method.invoke(fetcher, args);}}));} catch (Exception e) {throw new RuntimeException(e);}try{boolean isAgreed = true;//通过反射拿到mService字段@SuppressLint("SoonBlockedPrivateApi") Field mServiceField = ClipboardManager.class.getDeclaredField("mService");mServiceField.setAccessible(true);Object mService = mServiceField.get(clipboardManager);Class clazz = Class.forName("android.content.IClipboard");//生成代理类Object proxyInstance = Proxy.newProxyInstance(context.getClassLoader(), new Class[]{clazz}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//生成的代理类,判断如果是调用getPrimaryClip方法的话,加上是否用户同意过的逻辑,这里我用true代替了if (method.getName().equals("getPrimaryClip") && isAgreed){System.out.println("hhh, 不准调,没授权!!!");return null;}return method.invoke(mService,args);}});//将该代理类塞回去@SuppressLint("SoonBlockedPrivateApi") Field sServiceField = ClipboardManager.class.getDeclaredField("mService");sServiceField.setAccessible(true);sServiceField.set(clipboardManager, proxyInstance);}catch (Exception e){e.printStackTrace();}}

结束!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/13422.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JPA连接达梦数据库导致auto-ddl失效问题解决

现象&#xff1a; 项目使用了JPA&#xff0c;并且auto-ddl设置的为update&#xff0c;在连接达梦数据库的时候&#xff0c;第一次启动没有问题&#xff0c;但是后面重启就会报错&#xff0c;发现错误为重复建表&#xff0c;也就是说已经建好的表没有检测到&#xff0c;…

JVM类加载

一、类记载过程 1、通过类的全限定名获取存储该类的class文件 2、解析成运行时数据&#xff0c;即instanceKlass实例&#xff0c;存放到方法区 3、在堆区生成该类的class对象,即instanceMirrorKlass实例 二、将.class文件解析成什么&#xff1f;类的元信息在JVM中如何…

ceph集群中RBD的性能测试、性能调优

文章目录 rados benchrbd bench-write测试工具Fio测试ceph rbd块设备的iops性能测试ceph rbd块设备的带宽测试ceph rbd块设备的延迟 性能调优 rados bench 参考&#xff1a;https://blog.csdn.net/Micha_Lu/article/details/126490260 rados bench为ceph自带的基准测试工具&am…

全加器(多位)的实现

一&#xff0c;半加器 定义 半加器&#xff08;Half Adder&#xff09;是一种用于执行二进制数相加的简单逻辑电路。它可以将两个输入位的和&#xff08;Sum&#xff09;和进位&#xff08;Carry&#xff09;计算出来。 半加器有两个输入&#xff1a;A 和 B&#xff0c;分别代表…

MySQL基础扎实——MySQL中有那些不同的表格

表格类型 在MySQL中&#xff0c;常见的表格类型有以下几种&#xff1a; MyISAM&#xff1a;是MySQL默认的表格类型&#xff0c;具有较高的性能和较小的存储空间占用。但是&#xff0c;MyISAM不支持事务、崩溃恢复和数据行级锁定。 InnoDB&#xff1a;是MySQL提供的一个更强大…

Redis实现分布式锁

文章目录 4、分布式锁4.1 、基本原理和实现方式对比4.2 、Redis分布式锁的实现核心思路4.3 实现分布式锁版本一4.4 Redis分布式锁误删情况说明4.5 解决Redis分布式锁误删问题4.6 分布式锁的原子性问题4.7 Lua脚本解决多条命令原子性问题4.8 利用Java代码调用Lua脚本改造分布式锁…

MySQL | 常用命令示例

MySQL | 常用命令示例 一、启停MySQL数据库服务二、连接MySQL数据库三、创建和管理数据库四、创建和管理数据表五、数据备份和恢复六、查询与优化 MySQL是一款常用的关系型数据库管理系统&#xff0c;广泛应用于各个领域。在使用MySQL时&#xff0c;我们经常需要编写一些常用脚…

【数据结构】【王道408】——PPT截图与思维导图

自用视频PPT截图 视频网址王道B站链接 23考研 408新增考点&#xff1a; 并查集&#xff0c;红黑树 2023年408真题数据结构篇 408考纲解读 考纲变化 目录 第一章 绪论第二章 线性表顺序表单链表双链表循环链表静态链表差别 第三章 栈 队列 数组栈队列栈的应用数组 第四章 串第五…

软考A计划-系统集成项目管理工程师-项目质量管理-中

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff…

小程序UV:衡量用户规模与活跃度的重要指标

什么是UV UV是Unique Visitor&#xff08;独立访客&#xff09;的缩写&#xff0c;指的是在特定时间段内访问某个网站、应用或平台的独立用户数量。UV是根据设备、IP地址、Cookie等来识别不同的用户&#xff0c;对于相同的用户多次访问&#xff0c;只计算为一个UV。UV是衡量网…

流数据湖平台Apache Paimon(一)概述

文章目录 第1章 概述1.1 简介1.2 核心特性1.3 基本概念1.3.1 Snapshot1.3.2 Partition1.3.3 Bucket1.3.4 Consistency Guarantees一致性保证 1.4 文件布局1.4.1 Snapshot Files1.4.2 Manifest Files1.4.3 Data Files1.4.4 LSM Trees 第1章 概述 1.1 简介 Flink 社区希望能够将…

RocketMQ重复消费的解决方案::分布式锁直击面试!

文章目录 场景分析方法的幂等分布式锁Redis实现分布式锁抢锁的设计思路 分布式锁案例 直击面试rocketmq什么时候重复消费消息丢失的问题消息在哪里丢失发送端确保发送成功并且配合失败的业务处理消费端确保消息不丢失rocketmq 主从同步刷盘 场景分析 分布式系统架构中,队列是分…

css实现有缺口的border

css实现有缺口的border 1.问题回溯2.css实现有缺口的border 1.问题回溯 通常会有那种两个div都有border重叠在一起就会有种加粗的效果。 div1,div2,div3都有个1px的border&#xff0c;箭头标记的地方是没有处理解决的&#xff0c;很明显看着是有加粗效果的。其实这种感觉把di…

【Java从入门到大牛】集合进阶上篇

&#x1f525; 本文由 程序喵正在路上 原创&#xff0c;CSDN首发&#xff01; &#x1f496; 系列专栏&#xff1a;Java从入门到大牛 &#x1f320; 首发时间&#xff1a;2023年7月29日 &#x1f98b; 欢迎关注&#x1f5b1;点赞&#x1f44d;收藏&#x1f31f;留言&#x1f43…

IntelliJ IDEA流行的构建工具——Gradle

IntelliJ IDEA&#xff0c;是java编程语言开发的集成环境。IntelliJ在业界被公认为最好的java开发工具&#xff0c;尤其在智能代码助手、代码自动提示、重构、JavaEE支持、各类版本工具(git、svn等)、JUnit、CVS整合、代码分析、 创新的GUI设计等方面的功能可以说是超常的。 如…

基于java SpringBoot和HTML的博客系统

随着网络技术渗透到社会生活的各个方面&#xff0c;传统的交流方式也面临着变化。互联网是一个非常重要的方向。基于Web技术的网络考试系统可以在全球范围内使用互联网&#xff0c;可以在本地或异地进行通信&#xff0c;大大提高了通信和交换的灵活性。在当今高速发展的互联网时…

【达哥讲网络】第3集:数据交换的垫基石——二层交换原理

专业的网络工程师在进行网络设计时&#xff0c;会事先规划好不同业务数据的转发路径&#xff0c;一方面是为了满足用户应用需求&#xff0c;另一方面是为了提高数据转发效率、充分利用各设备/各链路的硬件或带宽资源。在进行网络故障排除时&#xff0c;理顺各路数据的转发路径也…

AI For Engineers 线上参会指南

AI For Engineers 线上参会指南 欢迎您报名参加 AI For Engineers&#xff1a;工程师 AI 全球会议&#xff0c;为了让各位参会者参会体验更佳&#xff0c;更好地利用本次会议收获更多。Altair 特别为各位准备了线上参会指南&#xff0c;一起来看看吧~ 会议时间&#xff1a;20…

掌握Python的X篇_12_如何使用VS Code调试Python程序

本篇将会介绍如何使用VS Code调试Python程序。 文章目录 1. 什么是调试2. 断点3. 如何启动调试4. 监视窗口5. 单步 1. 什么是调试 我们可以利用VS Code对Python代码进行调试。所谓调试&#xff0c;大家可以理解成有能力将程序进行 “慢动作播放”让我们有机会看到程序一步一步…

flutter minio

背景 前端 经常需要上传文件 图片 视频等等 到后端服务器&#xff0c; 如果到自己服务器 一般会有安全隐患。也不方便管理这些文件。如果要想使用一些骚操作 比如 按照前端请求生成不同分辨率的图片&#xff0c;那就有点不太方便了。 这里介绍以下 minio&#xff0c;&#xff0…