js css html

动态代理、反射Hook PackageManager

2022-07-27  本文已影响0人  h2coder

前言

最近更新App到应用商店,被检测出频繁获取应用列表11次,并且给出调用堆栈,发现是穿山甲广告sdk获取的,但sdk的初始化时已经配置了不允许获取应用列表了。

应用商店要求:APP在前台或后台运⾏时,APP或SDK收集⽤户个⼈信息的频率超过合规范围,请尽可能保证全局只收集1次(最多不超过3次),收集频次不要超过1次/秒。

思考

既然获取太频繁,那么Hook PackageManager,把重复调用的PackageInfo缓存起来,下次查询的参数如果一致,那么直接返回缓存,不调用到底层的PackageManager,并且设置缓存1分钟内有效。

使用

public class AppContext extends Application {
  @Override
  public void onCreate() {
    super.onCreate();
    HookPackageManager.get().replacePackageManager();
  }
}

源码

/**
 * 动态代理 + 反射,控制 PackageManager,返回PackageInfo的以及缓存
 */
public class HookPackageManager {
    /**
     * 过期时间,1分钟
     */
    private static final long DEFAULT_EXPIRED_TIME = 60 * 1000;

    /**
     * 需要Hook的方法名
     */
    private static final List<String> sHookMethodNames = Arrays.asList(
            "getPackageInfo",
            "getPackageInfoAsUser"
    );

    /**
     * 缓存PackageInfo
     */
    private final Map<String, CachePackageInfo> mCachePackageInfoMap = new ConcurrentHashMap<>();

    /**
     * 包含过期时间的PackageInfo
     */
    private static class CachePackageInfo {
        /**
         * 过期时间
         */
        long expiredTime;
        /**
         * 被缓存的PackageInfo
         */
        PackageInfo packageInfo;

        public CachePackageInfo(long expiredTime, PackageInfo packageInfo) {
            this.expiredTime = expiredTime;
            this.packageInfo = packageInfo;
        }
    }

    private static final class SingleHolder {
        private static final HookPackageManager INSTANCE = new HookPackageManager();
    }

    public static HookPackageManager get() {
        return SingleHolder.INSTANCE;
    }

    /**
     * 替换PackageManager
     */
    public void replacePackageManager() {
        try {
            //获取ActivityThread对象
            Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
            Field field = activityThreadClass.getDeclaredField("sCurrentActivityThread");
            field.setAccessible(true);
            Object activityThread = field.get(null);

            //获取了PackageManager对象
            Method getPackageManager = activityThread.getClass().getDeclaredMethod("getPackageManager");
            getPackageManager.setAccessible(true);
            final Object iPackageManager = getPackageManager.invoke(activityThread);

            //获取IPackageManager接口的Class对象
            Class<?> iPackageManagerClass = Class.forName("android.content.pm.IPackageManager");

            //生成动态代理类
            Object pmProxy = Proxy.newProxyInstance(
                    //加载代理类的类加载器
                    Thread.currentThread().getContextClassLoader(),
                    //代理类需要实现的接口(需要实现被代理类的接口)
                    new Class[]{iPackageManagerClass},
                    //当被代理类的接口实现方法被调用时,会回调此实现类的invoke()方法,该方法中应该保证被代理类的原逻辑的执行(即return时的调用)
                    /**
                     * 被代理类之前的执行逻辑,总是会由代理类通过回调调用到此处
                     *  proxy: 代理对象(在代理类中通过 this 传递过来)
                     *  method:通过iPackageManagerClass接口反射拿到的接口方法
                     *  args: 调用接口方法时,传入方法中的参数数组
                     *  我们可以通过在此处添加自己的逻辑,也可以通过改变参数和返回值来改变原有的逻辑
                     */
                    new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            //此处的iPackageManager为被代理对象,此方法保证了原逻辑的执行
                            if (sHookMethodNames.contains(method.getName())) {
                                long currentTimeMillis = System.currentTimeMillis();
                                //生成缓存Key
                                String cacheKey = generateCacheKey(method, args);
                                //从缓存中获取,如果有则直接返回,然后把返回的信息缓存
                                CachePackageInfo cachePackageInfo = mCachePackageInfoMap.get(cacheKey);
                                if (cachePackageInfo != null) {
                                    //没有过期,则返回
                                    if (currentTimeMillis < cachePackageInfo.expiredTime) {
                                        CoLogger.d("HookPackageManager => 命中缓存,直接返回PackageInfo");
                                        return cachePackageInfo.packageInfo;
                                    } else {
                                        //过期了,删掉缓存
                                        mCachePackageInfoMap.remove(cacheKey);
                                    }
                                }
                                //没有获取到缓存,则放行,再缓存起来,最后返回
                                Object result = method.invoke(iPackageManager, args);
                                CoLogger.d("HookPackageManager => 未命中缓存!");
                                if (result instanceof PackageInfo) {
                                    PackageInfo packageInfo = (PackageInfo) result;
                                    mCachePackageInfoMap.put(cacheKey, new CachePackageInfo(
                                            createExpiredTime(currentTimeMillis),
                                            packageInfo
                                    ));
                                    return packageInfo;
                                } else {
                                    return result;
                                }
                            } else {
                                return method.invoke(iPackageManager, args);
                            }
                        }
                    }
            );
            //获取ActivityThread下PackageManager的引用,并将代理对象赋值给此引用
            //此后在程序的运行中,该ActivityThread的sPackageManager属性总是此代理类,
            //方法被调用时,通过InvocationHandler.invoke方法来实现逻辑
            Field sPackageManagerField = activityThreadClass.getDeclaredField("sPackageManager");
            sPackageManagerField.setAccessible(true);
            sPackageManagerField.set(activityThread, pmProxy);
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    /**
     * 生成唯一缓存Key
     */
    private String generateCacheKey(Method method, Object[] args) {
        StringBuilder builder = new StringBuilder();
        String name = method.getName();
        String argsString = "";
        if (args != null) {
            argsString = Arrays.toString(args);
        }
        return builder.append(name).append("-").append(argsString).toString();
    }

    /**
     * 生成过期时间
     */
    private long createExpiredTime(long startTimeMillis) {
        return startTimeMillis + DEFAULT_EXPIRED_TIME;
    }
}
上一篇下一篇

猜你喜欢

热点阅读