网站推广报价,给别人做彩票网站违法吗,网站建设有什么意义,网站开发按工时收费想起之前做过的项目有安全合规要求#xff1a;主动弹窗获取用户同意了才能调用剪切板相关方法#xff0c;否则属于违规调用#xff0c;如果是自己项目的相关调用可以自己加一层if判断 但是一些第三方的jar包里面也有在调用的话#xff0c;我们就无能为力了#xff0c;而且…
想起之前做过的项目有安全合规要求主动弹窗获取用户同意了才能调用剪切板相关方法否则属于违规调用如果是自己项目的相关调用可以自己加一层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.getServicectx方法断点进去 下面是具体方法的实现方法有点长直接看下面分析结论 /*** Override this class when the system service constructor needs a* ContextImpl and should be cached and retained by that context.*/static abstract class CachedServiceFetcherT implements ServiceFetcherT {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;}OverrideSuppressWarnings(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, theres 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;}// Its 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 cant respond to interrupts here; just like we cant 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 shouldnt 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();}}
结束