攻防:如何防止动态hook绕过jni签名校验

Posted BennuCTech

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了攻防:如何防止动态hook绕过jni签名校验相关的知识,希望对你有一定的参考价值。

我们知道jni校验签名也不可靠,可以被动态hook绕过。代码如下:

class HookSignHandler(var base : Any) : InvocationHandler 

    companion object
        internal var signature = "xxx"
        fun hook(context: Context)
            try
                var activityThreadClass = Class.forName("android.app.ActivityThread")
                var currentActicityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread")
                var currentActivityThread = currentActicityThreadMethod.invoke(null)

                var sPackageManagerField = activityThreadClass.getDeclaredField("sPackageManager")
                sPackageManagerField.isAccessible = true;
                var sPackageManager = sPackageManagerField.get(currentActivityThread)

                var iPackageManagerInterface = Class.forName("android.content.pm.IPackageManager")
                var proxy = Proxy.newProxyInstance(iPackageManagerInterface.classLoader, arrayOf(iPackageManagerInterface), HookSignHandler(sPackageManager))

                sPackageManagerField.set(currentActivityThread, proxy)

                var pm = context.packageManager
                var mPMField = pm.javaClass.getDeclaredField("mPM")
                mPMField.isAccessible = true
                mPMField.set(pm, proxy)
            
            catch (e : Exception)
                Log.e("hook", "hook", e)
            
        
    

    override fun invoke(proxy: Any?, method: Method, args: Array<out Any>): Any 
        if("getPackageInfo".equals(method.name))
            var flag = args[1] as Int
            if(flag == PackageManager.GET_SIGNATURES)
                var sign = Signature(signature)
                var info = method.invoke(base, *args) as PackageInfo
                info.signatures[0] = sign
                return info
            
        
        return method.invoke(base, *args)
    


只要得到了签名的signature,并且在application中添加

HookSignHandler.Companion.hook(this);

这时无论java层还是jni层,当获取getPackageManager()时,它的mPM都是已经被代理的对象,这样当执行getPackageInfo()函数(实际上是执行mPM的对应函数)就会返回设置好的signature,而不是当前app的签名,这样就绕过了。

那么怎么防止这种手段?
那就是每次使用getPackageManager()时都检查一下它是否被代理。代码如下:

try 
    Field mPM = getPackageManager().getClass().getDeclaredField("mPM");
    mPM.setAccessible(true);
    if(Proxy.isProxyClass(mPM.get(getPackageManager()).getClass()))
        Toast.makeText(this, "hook!!", Toast.LENGTH_LONG).show();
    
 catch (Exception e) 
    e.printStackTrace();

Proxy本身提供了一个函数isProxyClass来检查当前对象的类是否是代理类。

我们将mPM对象获取到,用isProxyClass验证它的class即可。

那么这个这就涉及到了动态代理proxy的原理了。

动态代理

首先,动态代理一定需要一个接口,就是说代理的类必须实现某个接口,否则无法代理该类。比如上面的mPM就是实现接口android.content.pm.IPackageManager

这是为什么?这也与动态代理的原理有关。

简单来说,当我们设置动态代理后,实际上会自动生成一个类

public final class $Proxy0 extends Proxy implements XXXXX

    public $Proxy0(InvocationHandler paramInvocationHandler) throws 
    
        super(paramInvocationHandler);
    
 ...

这样就一下子清晰了,因为实现了同一个接口,所以可以设置给原对象而不产生问题。

但是它又继承了Proxy类,而且可以看到构造函数中将之前创建的InvocationHandler也传进来了,这样就可以调用到原对象的函数了。

它的详细代码就不展示和一一解释了,简单来说就是它实现了接口,在每个函数中再去执行InvocationHandler的invoke,就实现了代理。

这也是为什么动态代理一定需要一个接口的原因。

Proxy.newProxyInstance()函数就是创建一个$Proxy0这个类的对象,然后设置给原对象,就代理上了。

那么isProxyClass的原理就清晰了,我们只要知道这个对象是不是继承Proxy即可。代码:

public static boolean isProxyClass(Class<?> cl) 
    return Proxy.class.isAssignableFrom(cl) && proxyClassCache.containsValue(cl);

可以看到使用了isAssignableFrom,那么再来说一说这个函数。

isAssignableFrom和instanceof

这两个作用类似,从例子上看:B extends A

A.class.isAssignableFrom(B.class); 表示A是B的父类,注意两边都是Class

b instanceOf A 判断A是否是b对象的类。这里b是B类的对象。A是B的父类,所以也一样是b对象的类

关注公众号:BennuCTech,发送“HookSignHandler”获取完整源码

以上是关于攻防:如何防止动态hook绕过jni签名校验的主要内容,如果未能解决你的问题,请参考以下文章

GDB动态调试攻防世界Simple-Check-100

GDB动态调试攻防世界Simple-Check-100

一次简单的绕过apk签名校验

Android Studio NDK 入门教程--JNI签名验证防止恶意调用

安卓逆向 -- 绕过SO层签名验证

Android Studio NDK 入门教程--JNI签名验证防止恶意调用