IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android-APK:为何你的应用老是被破解,该如何有效地做签名校验 -> 正文阅读

[移动开发]Android-APK:为何你的应用老是被破解,该如何有效地做签名校验

/**

  • 做普通的签名校验
    */
    private boolean doNormalSignCheck() {
    String trueSignMD5 = “d0add9987c7c84aeb7198c3ff26ca152”;
    String nowSignMD5 = “”;
    try {
    // 得到签名的MD5
    PackageInfo packageInfo = getPackageManager().getPackageInfo(
    getPackageName(),
    PackageManager.GET_SIGNATURES);
    Signature[] signs = packageInfo.signatures;
    String signBase64 = Base64Util.encodeToString(signs[0].toByteArray());
    nowSignMD5 = MD5Utils.MD5(signBase64);
    } catch (PackageManager.NameNotFoundException e) {
    e.printStackTrace();
    }
    return trueSignMD5.equals(nowSignMD5);
    }

系统将应用的签名信息封装在 PackageInfo 中,调用 PackageManager 的 getPackageInfo(String packageName, int flags) 即可获取指定包名的签名信息。

这个并不用我多说了,如果没听过的话,用搜索引擎找一下「Android 签名校验」,花上几分钟就明白了,很容易的。

编译出 release 包并安装,可以看见运行效果很满意。但是事实真的如此么?下面我们让他作为受害者,被一键破解。

使用工具去除先前的校验

很多人可能不知道,去除简单的签名校验连小朋友都能做到!

请看具有「安全性测试」功能的「M* 管理器」上场,一键去除我们上文准备好的受害者的签名校验:

神奇的一幕发生了,居然还是通过,也就是我们刚才的操作形同虚设,我们把被破解后的安装包传回 PC,准备下一步分析

JADX 上场

为了知道他做了什么,我们需要逆向出目前受害者的代码。这里我们使用开源项目jadx来完成。

打开 jadx 之后会直接弹出「打开」对话框,选取被破解的 apk 即可:

简单对比下可以发现,多了一个「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.java
package android.app;
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);

函数原型为

//?ContextImpl.java
package?android.app;

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

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

//?ActivityThread.java
package?android.app;

@UnsupportedAppUsage
private?static?volatile?ActivityThread?sCurrentActivityThread;

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

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

//?ActivityThread.java
package?android.app;

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

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

//?ActivityThread.java
package?android.app;

@UnsupportedAppUsage
AppBindData?mBoundApplication;

@UnsupportedAppUsage
private?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,
?手动构建?Context
*/
@SuppressLint({“DiscouragedPrivateApi”,“PrivateApi”})
public?static?Context?getContext()?throws?ClassNotFoundException,
NoSuchMethodException,
InvocationTargetException,
IllegalAccessException,
NoSuchFieldException,

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-01-30 19:02:44  更:2022-01-30 19:04:58 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/28 5:47:00-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码