安卓对抗之签名校验(入门)

常见签名校验

一般来说,签名校验的方式有以下几种

  • 通过安卓提供的api,利用PackageManager获取签名数据,和已存的正确值进行对比.

  • 在native中,反射Java的api,进行对比,本质上和1相同

  • 自己读取apk文件,解压,校验META-INF里的RSA文件(主流加固实现在native)

重定向过签名校验小记

在安卓中,签名校验是一个很重要的环节,一般来说,签名校验是通过PackageManager获取签名数据,和已存的正确值进行对比。

但是,这个方法很容易被逆向者过掉,甚至有工具可以一键过掉(重定向)。

其中重定向呢,市面上主流的一键重定向过签名工具目前都是值只去hook主进程,如果有需要的话,我可以在一个小节说明如果利用子进程去对抗重定向以及其它的一些会修改注入app的操作

基于安卓原生API签名校验

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);
}

基于安卓原生API的底层的签名校验方式

众所周知,安卓系统启动的时候就会创建一堆系统服务,PackageManager就是利用的其中一个叫package的系统服务去获取的安装的各种应用的信息。

再众所周知,应用与APP进行交互的唯一途径就是安卓的匿名内存共享 也就是+IBinder。

通常情况下,一键反签名校验工具都会去hook那些getPackageInfo的方法,然后返回一个假的签名数据。

但是呢,我不走寻常路,我直接去获取package这个系统服务,然后调用getPackageInfo方法,这样就不会被hook到了。

val smc = Class.forName("android.os.ServiceManager")
val sCache = (smc.getDeclaredField("sCache").also {
    if (!it.isAccessible) it.isAccessible = true
}.get(smc) as ArrayMap<String, IBinder>).also { it.clear() }
val gsm = smc.getDeclaredMethod("getService", String::class.java)

fun checkSignV2(context: Context, realSign: String): Boolean {
    kotlin.runCatching {
        if (sCache["package"] == null) { 
            val userId = Process.myUid() / 100000
            val binder = HiddenApi.getServiceByMethod("package")
            val bz = Class.forName("android.content.pm.IPackageManager\$Stub")
            val am = bz.getDeclaredMethod("asInterface", IBinder::class.java)
            val mmg = am.invoke(bz, binder)
            val ic = Class.forName("android.content.pm.IPackageManager")
            val gmi = ic.getDeclaredMethod("getPackageInfo", String::class.java, Int::class.java, Int::class.java)
            val  signatures: Array<Signature> = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
                (gmi.invoke(mmg, context.packageName, PackageManager.GET_SIGNING_CERTIFICATES, userId) as PackageInfo).signingInfo.apkContentsSigners
            } else {
                (gmi.invoke(mmg, context.packageName, PackageManager.GET_SIGNATURES, userId) as PackageInfo).signatures
            }
            val builder = StringBuilder()
            signatures.forEach {
                builder.append(it.toCharsString())
            }
            return builder.toString() == realSign
        }
        return false
    }.onFailure {
        it.printStackTrace()
    }
    return true
}

是不是很抽象呢?其实这个狠小儿科啦,只是利用了一些隐藏的api而已。

对于那些对于Android API进行背书的卷王们,他们可能不是很了解这个操作,嘻嘻。

基于native的签名校验

在native中,最建议的方式就是去读取apk文件,解压,校验META-INF里的RSA文件。 这个操作很朴实无华,在子进程反反签名校验的时候,也可以调用这个检测。 进阶一点就是去内存里面读取特征咯...只要是注入总会有马脚的...