Ofensiva y defensiva: cómo evitar que los ganchos dinámicos pasen por alto la verificación de la firma jni

ataque

Sabemos que la firma de verificación de jni tampoco es confiable y puede pasarse por alto mediante ganchos dinámicos. el código se muestra a continuación:

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)){
            ...
        }
        return method.invoke(base, *args)
    }

}
复制代码

Simplemente obtenga la firma firmada y agréguela en la aplicación.

HookSignHandler.Companion.hook(this);
复制代码

En este momento, independientemente de la capa java o la capa jni, cuando se obtiene getPackageManager(), su mPM es un objeto que ha sido enviado por proxy, por lo que cuando se ejecuta la función getPackageInfo() (en realidad, se ejecuta la función correspondiente de mPM) , se devolverá el conjunto.firma, en lugar de la firma de la aplicación actual, por lo que se omite.

Defender

Entonces, ¿cómo prevenir este método? Eso es para verificar si se envía por proxy cada vez que usa getPackageManager(). el código se muestra a continuación:

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();
}
复制代码

El propio proxy proporciona una función isProxyClass para comprobar si la clase del objeto actual es una clase de proxy.

Obtenemos el objeto mPM y verificamos su clase con isProxyClass.

Entonces esto implica el principio de proxy proxy dinámico.

Proxy dinámico

En primer lugar, un proxy dinámico debe necesitar una interfaz, es decir, la clase del proxy debe implementar una interfaz, de lo contrario, la clase no puede ser proxy. Por ejemplo, el mPM anterior es para implementar la interfaz android.content.pm.IPackageManager

¿porqué es eso? Esto también está relacionado con el principio de proxy dinámico.

En pocas palabras, cuando configuramos un proxy dinámico, una clase se genera automáticamente

public final class $Proxy0 extends Proxy implements XXXXX
{
    public $Proxy0(InvocationHandler paramInvocationHandler) throws 
    {
        super(paramInvocationHandler);
    }
 ...
}
复制代码

Esto queda claro de inmediato, porque se implementa la misma interfaz, por lo que se puede configurar en el objeto original sin causar problemas.

Pero hereda la clase Proxy, y puede ver que el InvocationHandler creado anteriormente también se pasa en el constructor, de modo que se puede llamar a la función del objeto original.

Su código detallado no se mostrará ni se explicará uno por uno, en resumen, implementa la interfaz y luego ejecuta la invocación del InvocationHandler en cada función para implementar el proxy.

Esta es la razón por la cual los proxies dinámicos definitivamente necesitan una interfaz.

La función Proxy.newProxyInstance() es para crear un objeto de la clase $Proxy0 y luego establecerlo en el objeto original, y luego está en el proxy.

Entonces el principio de isProxyClass es claro, solo necesitamos saber si este objeto hereda Proxy. Código:

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,获取更多干货

Supongo que te gusta

Origin juejin.im/post/7082568580151115806
Recomendado
Clasificación