本文最后更新于 1035 天前,其中的信息可能已经有所发展或是发生改变。
因为有些游戏为了限制你上硬核渠道,或者对你分包的投放进行限制,会有一些简单的签名校验逻辑,要求渠道想分包必须依赖 cp 的重新签名,所以需要技术做一下通用的签名校验逻辑 hook 脚本
1. 签名校验代码一般是怎么写的
封装一个函数,从 PackageManager 获取签名
public static int getSignature(Context context) {
PackageManager pm = context.getPackageManager();
PackageInfo pi;
StringBuilder sb = new StringBuilder();
// 获取签名信息
try {
pi = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
Signature[] signatures = pi.signatures;
for (Signature signature : signatures) {
sb.append(signature.toCharsString());
}
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return sb.toString().hashCode();
}
运行的时候校验签名是否还是本公司的签名
int signature = getSignature(getApplicationContext());
if(!MD5Util.getMD5(String.valueOf(signature)).equals("本公司的签名md5")){
// 被篡改了,执行退出,或者默默的封号
}
2. 代理 PMS
- 一般 java 层的校验最后都是调用到 PackageManager,本质上都是通过 binder 和 PMS 通信调用 PMS 的函数,所以我们只需要代理 PMS ,hook 和包相关的信息即可
- 首先 建立一个代理对象 PmsHookBinderInvocationHandler
public class PmsHookBinderInvocationHandler implements InvocationHandler {
private Object base;
//应用正确的签名信息
private String oldSign;
private String oldPackageName;
public PmsHookBinderInvocationHandler(Object base, String sign, String appPkgName, int hashCode) {
this.base = base;
}
public void setOldSign(String oldSign) {
this.oldSign = oldSign;
}
public void setOldPackageName(String oldPackageName) {
this.oldPackageName = oldPackageName;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.i("ZKHOOK", method.getName());
if ("getPackageInfo".equals(method.getName())) {
String pkgName = (String) args[0];
Integer flag = (Integer) args[1];
if (flag == PackageManager.GET_SIGNATURES && oldPackageName.equals(pkgName)) {
Signature sign = new Signature(oldSign);
PackageInfo info = (PackageInfo) method.invoke(base, args);
info.signatures[0] = sign;
return info;
}
}
return method.invoke(base, args);
}
}
- 用代理对象替换实际的对象,然后运行,看代码要替换2个地方的 pm,
ActivityThread 里面的 sPackageManager PackageManager 里面的 mPM
public static void hookPMS(Context context, String signed, int hashCode) {
try {
// 获取全局的ActivityThread对象
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod =
activityThreadClass.getDeclaredMethod("currentActivityThread");
Object currentActivityThread = currentActivityThreadMethod.invoke(null);
// 获取ActivityThread里面原始的sPackageManager
Field sPackageManagerField = activityThreadClass.getDeclaredField("sPackageManager");
sPackageManagerField.setAccessible(true);
Object sPackageManager = sPackageManagerField.get(currentActivityThread);
// 准备好代理对象, 用来替换原始的对象
PmsHookBinderInvocationHandler hookBinderInvocationHandler = new PmsHookBinderInvocationHandler(sPackageManager, 0);
hookBinderInvocationHandler.setOldPackageName(context.getPackageName());
hookBinderInvocationHandler.setOldSign(signed);
Class<?> iPackageManagerInterface = Class.forName("android.content.pm.IPackageManager");
Object proxy = Proxy.newProxyInstance(
iPackageManagerInterface.getClassLoader(),
new Class<?>[]{iPackageManagerInterface},
hookBinderInvocationHandler);
// 1. 替换掉ActivityThread里面的 sPackageManager 字段
sPackageManagerField.set(currentActivityThread, proxy);
// 2. 替换 ApplicationPackageManager里面的 mPM对象
PackageManager pm = context.getPackageManager();
Field mPmField = pm.getClass().getDeclaredField("mPM");
mPmField.setAccessible(true);
mPmField.set(pm, proxy);
} catch (Exception e) {
Log.d("ZKHOOK", "hook pms error:" + Log.getStackTraceString(e));
}
}
public static void hookPMS(Context context){
String oldSign =
hookPMS(context, oldSign, context.getPackageName(), 0);
}
3. 在 cp 的 application 逻辑中运行 hookPMS 函数(这一步可以脚本化)
反编译的基础知识就不说了,找到 cp 的 application,在 onCreate 生命周期添加 smali 逻辑,同时把上面的 java 代码转成 smali ,丢进 cp 的包里面
# virtual methods
.method public onCreate()V
.locals 0
.prologue
.line 12
.line 13
invoke-static {p0}, Lcom/hook/ServiceManagerWraper;->hookPMS(Landroid/content/Context;)V
.line 14
return-void
.end method
到这里整个 hook 逻辑就完成了,回编译重新签名。基于java 层的签名校验就解决了