动态代理、反射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;
}
}