공격 및 방어: 동적 후크가 jni 서명 확인을 우회하는 것을 방지하는 방법

공격

우리는 jni 검증 서명도 신뢰할 수 없으며 동적 후크에 의해 우회될 수 있다는 것을 알고 있습니다. 코드 쇼는 아래와 같습니다.

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

}
复制代码

서명된 서명을 받아 애플리케이션에 추가하기만 하면 됩니다.

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

이때, java layer나 jni layer에 관계없이 getPackageManager()를 획득했을 때 그 mPM은 프록시된 객체이므로 getPackageInfo() 함수(실제로는 mPM의 해당 함수가 실행될 때) , 집합이 반환됩니다. 현재 앱의 서명이 아닌 서명이므로 무시됩니다.

지키다

이 방법을 방지하는 방법은 무엇입니까? 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로 해당 클래스를 확인합니다.

그런 다음 이것은 동적 프록시 프록시의 원칙을 포함합니다.

동적 프록시

우선, 동적 프록시는 인터페이스가 필요합니다. 즉, 프록시의 클래스는 인터페이스를 구현해야 합니다. 그렇지 않으면 클래스를 프록시할 수 없습니다. 예를 들어 위의 mPM은 android.content.pm.IPackageManager 인터페이스를 구현하기 위한 것입니다.

왜 그런 겁니까? 이는 동적 프록시의 원리와도 관련이 있습니다.

간단히 말해서 동적 프록시를 설정하면 실제로 클래스가 자동으로 생성됩니다.

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

이는 동일한 인터페이스가 구현되어 문제를 일으키지 않고 원래 객체로 설정할 수 있기 때문에 즉시 명확합니다.

하지만 Proxy 클래스를 상속받아 생성자에도 이전에 생성한 InvocationHandler가 전달되어 원래 객체의 함수를 호출할 수 있음을 알 수 있습니다.

자세한 코드는 보여주지 않고 하나씩 설명하며, 간단히 말해 인터페이스를 구현한 후 각 함수에서 InvocationHandler를 호출하여 프록시를 구현한다.

이것이 동적 프록시에 인터페이스가 필요한 이유입니다.

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

추천

출처juejin.im/post/7082568580151115806