写点什么

Android-APK 防止二次签名妙招:为何你的应用老是被破解,该如何有效地做签名校验

用户头像
Android架构
关注
发布于: 2021 年 11 月 05 日


简单对比下可以发现,多了一个「HookApplication」类



点击进去即可直接看见源代码:


public?class?HookApplication?extends?Application?implements?InvocationHandler?{private?static?final?int?GET_SIGNATURES?=?64;private?String?appPkgName?=?BuildConfig.FLAVOR;private?Object?base;private?byte[][]?sign;


private?void?hook(Context?context)?{try?{DataInputStream?dataInputStream?=?new?DataInputStream(new?ByteArrayInputStream(Base64.decode("省略很长的签名?base64",?0)));byte[][]?bArr?=?new?byte[(dataInputStream.read()?&?255)][];for?(int?i?=?0;?i?<?bArr.length;?i++)?{bArr[i]?=?new?byte[dataInputStream.readInt()];dataInputStream.readFully(bArr[i]);}Class?cls?=?Class.forName("android.app.ActivityThread");Object?invoke?=?cls.getDeclaredMethod("currentActivityThread",?new?Class[0]).invoke(null,?new?Object[0]);Field?declaredField?=?cls.getDeclaredField("sPackageManager");declaredField.setAccessible(true);Object?obj?=?declaredField.get(invoke);Class?cls2?=?Class.forName("android.content.pm.IPackageManager");this.base?=?obj;this.sign?=?bArr;this.appPkgName?=?context.getPackageName();Object?newProxyInstance?=?Proxy.newProxyInstance(cls2.getClassLoader(),?new?Class[]{cls2},?this);declaredField.set(invoke,?newProxyInstance);PackageManager?packageManager?=?context.getPackageManager();Field?declaredField2?=?packageManager.getClass().getDeclaredField("mPM");declaredField2.setAccessible(true);declaredField2.set(packageManager,?newProxyInstance);System.out.println("PmsHook?success.");}?catch?(Exception?e)?{System.err.println("PmsHook?failed.");e.printStackTrace();}}


/?access?modifiers?changed?from:?protected?/public?void?attachBaseContext(Context?context)?{hook(context);super.attachBaseContext(context);}


public?Object?invoke(Object?obj,?Method?method,?Object[]?objArr)?throws?Throwable?{if?("getPackageInfo".equals(method.getName()))?{String?str?=?objArr[0];if?((objArr[1].intValue()?&?64)?!=?0?&&?this.appPkgName.equals(str))?{PackageInfo?packageInfo?=?(PackageInfo)?method.invoke(this.base,?objArr);packageInfo.signatures?=?new?Signature[this.sign.length];for?(int?i?=?0;?i?<?packageInfo.signatures.length;?i++)?{packageInfo.signatures[i]?=?new?Signature(this.sign[i]);}return?packageInfo;}}return?method.invoke(this.base,?objArr);}}


有点长,但是也不是很费解。


他继承自 Application,重写了 attachBaseContext 来调用 hook(context) ,在里面做了 IPackageManager 的动态代理,实现在调用 getPackageInfo 方法的时候,修改 signatures[] 为在破解之前计算好的数值。这就是为什么我们的检测手段无效了。


所谓的知己知彼,百战不殆,我们先来分析下他做了什么:


  1. 替换掉原来的 Application

  2. 在 attachBaseContext 里初始化 hook

  3. 动态代理 IPackageManager

  4. hook 替换掉 signatures 的值


所以应对方案也就水到渠成:


  1. 检查 Application

  2. 在调用 attachBaseContext 之前检测签名

  3. 检查 IPackageManager 有没有被动态代理

  4. 使用别的 API 去获取

检查 Application

他替换掉了 Application 为他自己的,那么变化的太多了,Application 的类名 / 方法数 / 字段数 / AndroidManifast 中 Application 节点的 name,都会变。我们这里以检查 Application 的类名为例:


/**


  • 校验 application*/private boolean checkApplication(){Application nowApplication = getApplication();String trueApplicationName = "MyApp";String nowApplicationName = nowApplication.getClass().getSimpleName();return trueApplicationName.equals(nowApplicationName);}

  • 先定义我们自己的 Application ——「MyApp」

  • 然后通过 getApplication() 获取到 Application 实例

  • 然后通过 getClass() 获取到类信息

  • 然后通过 getSimpleName() 获取到类名

  • 与正确的值比对然后返回



可以看到可以检测出被二次打包

在?attachBaseContext 之前检测

只要我们检测的够早,他就追不上我们。不,他会 hook 到我们的几率就越小


A: 要有多早?B: emm,就在 Application 的构造方法里检测吧 A: 那,,,没 context 呀 B: 那就自己造一个 context!A: 你放屁!B: 走你


通过学习 Application 的创建流程可知,Context 是通过 LoadedApk 调用 createAppContext 方法实现的


// LoadedApk.javapackage android.app;ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);


函数原型为


//?ContextImpl.javapackage?android.app;


@UnsupportedAppUsagestatic?ContextImpl?createAppContext(ActivityThread?mainThread,?LoadedApk?packageInfo)?{return?createAppContext(mainThread,?packageInfo,?null);}


第一个参数好说,因为这是个单例类,调用 currentActivityThread 即可获取 ActivityThread 对象


//?ActivityThread.javapackage?android.app;


@UnsupportedAppUsageprivate?static?volatile?ActivityThread?sCurrentActivityThread;


@UnsupportedAppUsagepublic?static?ActivityThread?currentActivityThread()?{return?sCurrentActivityThread;}


但是需要注意的是有 「@UnsupportedAppUsage」修饰,需要反射调用。在学习 Application 的创建流程的时候可知(其实是我不会上网找的流程),另一个 LoadedApk 对象是通过 getPackageInfoNoCheck 方法创建的。


//?ActivityThread.javapackage?android.app;


@Override@UnsupportedAppUsagepublic?final?LoadedApk?getPackageInfoNoCheck(ApplicationInfo?ai,CompatibilityInfo?compatInfo)?{return?getPackageInfo(ai,?compatInfo,?null,?false,?true,?false);}


这个值保存在 ActivityThread 实例的 mBoundApplication.info 变量里。


//?ActivityThread.javapackage?android.app;


@UnsupportedAppUsageAppBindData?mBoundApplication;


@UnsupportedAppUsageprivate?void?handleBindApplication(AppBindData?data)?{//?省略无关代码 mBoundApplication?=?data;//?省略无关代码 data.info?=?getPackageInfoNoCheck(data.appInfo,?data.compatInfo);//?省略无关代码}


mBoundApplication 虽然不是静态变量,但是因为我们之前已经获取到了 ActivityThread 实例,所以不耽误我们反射获取。现在我们调用 ContextImpl.createAppContext 的条件已经满足了,反射调用即可。


ContextUtils 最终实现代码如下:


public?class?ContextUtils?{


/***?手动构建?Context*/@SuppressLint({"DiscouragedPrivateApi","PrivateApi"})public?static?Context?getContext()?throws?ClassNotFoundException,NoSuchMethodException,InvocationTargetException,IllegalAccessException,NoSuchFieldException,NullPointerException{


//?反射获取?ActivityThread?的?currentActivityThread?获取?mainThreadClass?activityThreadClass?=?Class.forName("android.app.ActivityThread");Method?currentActivityThreadMethod?=activityThreadClass.getDeclaredMethod("currentActivityThread");currentActivityThreadMet


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


hod.setAccessible(true);Object?mainThreadObj?=?currentActivityThreadMethod.invoke(null);


//?反射获取?mainThread?实例中的?mBoundApplication?字段 Field?mBoundApplicationField?=?activityThreadClass.getDeclaredField("mBoundApplication");mBoundApplicationField.setAccessible(true);Object?mBoundApplicationObj?=?mBoundApplicationField.get(mainThreadObj);


//?获取?mBoundApplication?的?packageInfo?变量 if?(mBoundApplicationObj?==?null)?throw?new?NullPointerException("mBoundApplicationObj 反射值空");Class?mBoundApplicationClass?=?mBoundApplicationObj.getClass();Field?infoField?=?mBoundApplicationClass.getDeclaredField("info");infoField.setAccessible(true);Object?packageInfoObj?=?infoField.get(mBoundApplicationObj);


//?反射调用?ContextImpl.createAppContext(ActivityThread?mainThread,?LoadedApk?packageInfo)if?(mainThreadObj?==?null)?throw?new?NullPointerException("mainThreadObj 反射值空");if?(packageInfoObj?==?null)?throw?new?NullPointerException("packageInfoObj 反射值空");Method?createAppContextMethod?=?Class.forName("android.app.ContextImpl").getDeclaredMethod("createAppContext",?mainThreadObj.getClass(),?packageInfoObj.getClass());createAppContextMethod.setAccessible(true);return?(Context)?createAppContextMethod.invoke(null,?mainThreadObj,?packageInfoObj);


}}


后面的事就好办多了,就是在 Application 的构造函数里用我们手动构造的 context 去获取签名(这个时候还没有 context)


public?class?MyApp?extends?Application?{


private?static?boolean?sEarlyCheckSignResult?=?false;public?static?boolean?getEarlyCheckSignResult(){?return?sEarlyCheckSignResult;}


public?MyApp()?{//?在构造函数里提早检测 sEarlyCheckSignResult?=?earlyCheckSign();}


boolean?earlyCheckSign(){

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
Android-APK防止二次签名妙招:为何你的应用老是被破解,该如何有效地做签名校验