hook某银行加固app

时间:2024-04-18 07:11:24

分析

要在未修复dex并打包情况下对其app内在类进行hook,单纯的hook会由于加固的问题而导致加载不到内在想hook的类。因此需要进行加载加固的classloader。
在此之前需要先了解Context

在Android中,只有Application、Activity以及Service有自己的Context。
Application的Context
我们知道Application的Context是伴随着Application的创建而创建的,AMS在需要启动应用进程的时候,通过本地socket通知Zygote来fork应用进程,应用进程在启动完成之后,会通过Binder机制向AMS报告“我已经启动好了”,AMS在接收到该Binder调用之后,会立即通过Binder调用通知应用进程创建Application。
应用在创建Application的时候,首先通过ClassLoader加载对应的class文件,并通过newInstance创建一个应用的Application对象,这样就经过了Application的构造函数,同时应用进程还会实例化一个Context对象,通过Application对象的attachBaseContext方法赋值给Application,这些都做完之后,应用进程才会调用Application对象的onCreate方法。这里需要注意,Application中的context其实只是一个空壳,真正起作用的是应用进程通过attachBaseContext赋值的mBase,Application的context其实是mBase的静态代理。
Application的总结
● 继承关系:Application <- ContextWrap <- Context
● 调用顺序:构造函数 -> attachBaseContext -> onCreate
ContextWrap中包含了一个Context(mBase变量,是应用进程通过attachBaseContext赋值的),Application中关于Context的调用都是委托给它

在android的android.app.Application的源码中可以发现

Application继承ContextWrapper

attachBaseContext方法被attach调用,而大部分的壳都是在attachBaseContext这个方法里面去完成代码的解密,使用加固之后的应用的classloader会被换成其加固应用本身的。
编写
因此可以利用java.use编写脚本加载样本app的classloader

if(Java.available) {
    Java.perform(function(){
        var application = Java.use("android.app.Application");
        application.attach.overload('android.content.Context').implementation = function(context) {
            var result = this.attach(context); // 先执行原来的attach方法
            var classloader = context.getClassLoader();

            return result;
        }

    });
}

利用加载到的classloader再次对样本app的类进行hook

//框架为
if(Java.available) {
    Java.perform(function(){
        var application = Java.use("android.app.Application");

        application.attach.overload('android.content.Context').implementation = function(context) {
            var result = this.attach(context); // 先执行原来的attach方法
            var classloader = context.getClassLoader(); // 获取classloader
            Java.classFactory.loader = classloader;
            var Hook_class = Java.classFactory.use("com.xxx.xxx");  // app的类
            console.log("Hook_class: " + Hook_class);
            // 下面代码和写正常的hook一样
            Hook_class.函数.implementation = function()  // 有参数填参数
            {

            }

            return result;
        }
    });
}

有一处root检测,只需要将其返回的值为false就可以了。

public class RootCheckUtil {
    private static final String TAG = RootCheckUtil.class.getSimpleName();

    public static boolean isRooted() {
        if (Build.MODEL.equals("Redmi Note 4")) {
            return false;
        }
        if (isExecuted("/system/xbin/su")) {
            return true;
        }
        if (isExecuted("/system/bin/su")) {
            return true;
        }
        if (isExecuted("/su/bin/su")) {
            return true;
        }
        if (isExecuted("/system/sbin/su")) {
            return true;
        }
        if (isExecuted("/sbin/su")) {
            return true;
        }
        if (isExecuted("/vendor/bin/su")) {
            return true;
        }
        return checkRootMethod2();
    }

    private static boolean isExecuted(String path) {
        boolean ret = false;
        try {
            if (new File(path).exists()) {
                Process exec = Runtime.getRuntime().exec("ls -l " + path + "\n");
                BufferedReader br = new BufferedReader(new InputStreamReader(exec.getInputStream()));
                while (true) {
                    String str = br.readLine();
                    if (str == null) {
                        break;
                    } else if (str.length() > 10) {
                        String key = (String) str.subSequence(9, 10);
                        if (key.equals("x") || key.equals(d.ap)) {
                            ret = true;
                        }
                    }
                }
                exec.waitFor();
                br.close();
            }
        } catch (Exception e) {
            LoggerFactory.getTraceLogger().debug(TAG, "Exception rootcheck e");
        }
        return ret;
    }

    private static boolean checkRootMethod2() {
        try {
            return new File("/system/app/Superuser.apk").exists();
        } catch (Exception e) {
            LoggerFactory.getTraceLogger().debug(TAG, "Exception checkRootMethod e");
            return false;
        }
    }
}

在hook signature的过程中发现存在Context字段内存在一个Request-Data,对其产生了兴趣,代码如下

private RpcSignUtil.SignData a(String operationType, byte[] body, String ts, InnerRpcInvokeContext invokeContext, int[] signCostPtr) {
        StringBuffer signPlain = new StringBuffer();
        signPlain.append("Operation-Type=").append(operationType).append("&");
        signPlain.append("Request-Data=").append(Base64.encodeToString(body, 2)).append("&");
        signPlain.append("Ts=").append(ts);
        String content = signPlain.toString();
        if (MiscUtils.isDebugger(getRpcFactory().getContext())) {
            LogCatUtil.debug("RpcInvoker", "[getSignData] sign content: " + content);
        }
        boolean useSignAtlas = MiscUtils.isAlipayGW(invokeContext.gwUrl);
        long startTime = SystemClock.elapsedRealtime();
        MonitorInfoUtil.startLinkRecordPhase(operationType, WbCloudFaceContant.SIGN, null);
        try {
            return RpcSignUtil.signature(this.d.getContext(), invokeContext.appKey, isReq2Online(invokeContext), content, useSignAtlas);
        } finally {
            long signCost = SystemClock.elapsedRealtime() - startTime;
            signCostPtr[0] = (int) signCost;
            LogCatUtil.debug("RpcInvoker", "[getSignData] sign time = " + signCost + "ms. ");
            MonitorInfoUtil.endLinkRecordPhase(operationType, WbCloudFaceContant.SIGN, null);
        }
    }

在写hook的过程,发现其加密的发现是调用了android的原生包中的类android.util.Base64。而真正想要hook的类为

public static SignData signature(Context context, String externalAppKey, boolean isReq2Online, String content, boolean pUseSignAtlas) {
        try {
            SignRequest signRequest = new SignRequest();
            signRequest.appkey = MpaasPropertiesUtil.getAppkey(externalAppKey, isReq2Online, context);
            signRequest.content = content;
            if (a(context, pUseSignAtlas)) {
                signRequest.signType = SignRequest.SIGN_TYPE_ATLAS;
            }
            return SignData.createSignDataBySignResult(SecurityUtil.signature(signRequest));
        } catch (Throwable e) {
            LoggerFactory.getTraceLogger().warn("RpcSignUtil", e);
            return SignData.newEmptySignData();
        }
    }

综合上述,最终的hook脚本为

if(Java.available) {
    Java.perform(function(){
        var application = Java.use("android.app.Application");
        var dedata1 = Java.use("android.util.Base64");

        application.attach.overload('android.content.Context').implementation = function(context) {
            var result = this.attach(context);
            var classloader = context.getClassLoader();
            Java.classFactory.loader = classloader;
            var Hook_class = Java.classFactory.use("com.alipay.mobile.common.transport.utils.RpcSignUtil");
            var passroot = Java.classFactory.use("com.1111.common.api.util.RootCheckUtil");
            var requestdata = Java.classFactory.use("com.alipay.mobile.common.rpc.RpcInvoker");
            console.log("Hook_class: " + Hook_class);

            Hook_class.signature.implementation = function(context,externalAppKey,isReq2Online,dcontent,pUseSignAtlas)
            {
                console.log("context:"+ context);
                console.log("externalAppKey:"+ externalAppKey);
                console.log("isReq2Online:"+ isReq2Online);
                console.log("content:"+ dcontent);
                console.log("pUseSignAtlas:"+ pUseSignAtlas);
            }

            requestdata.a.overload('java.lang.String', '[B', 'java.lang.String', 'com.alipay.mobile.common.rpc.transport.InnerRpcInvokeContext', '[I').implementation = function (operationType,body,ts,invokeContext,signCostPtr) {
                console.log("-------------------");
                console.log("-------------------");

                var ret = dedata1.encodeToString(body, 10);
                console.log("Request-Data"+ret);
                console.log("=========decode========");
                console.log(base64decode(ret));
            }

            passroot.isRooted.implementation = function (){
                return false;
            }

            passroot.isExecuted.implementation = function (path){
                return false;
            }

            passroot.checkRootMethod2.implementation = function (){
                return false;
            }
            return result;
        }

        function base64decode(str){
            var base64DecodeChars = new Array(
                -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
                52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
                -1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
                15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
                -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
                41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1);
            var c1, c2, c3, c4;
            var i, len, out;
            len = str.length;
            i = 0;
            out = "";
            while(i < len) {
                do {
                    c1 = base64DecodeChars[str.charCodeAt(i++) & 0xff];
                } while(i < len && c1 == -1);
                if(c1 == -1)
                    break;
                do {
                    c2 = base64DecodeChars[str.charCodeAt(i++) & 0xff];
                } while(i < len && c2 == -1);
                if(c2 == -1)
                    break;
                out += String.fromCharCode((c1 << 2) | ((c2 & 0x30) >> 4));
                do {
                    c3 = str.charCodeAt(i++) & 0xff;
                    if(c3 == 61)
                        return out;
                    c3 = base64DecodeChars[c3];
                } while(i < len && c3 == -1);
                if(c3 == -1)
                    break;
                out += String.fromCharCode(((c2 & 0XF) << 4) | ((c3 & 0x3C) >> 2));
                do {
                    c4 = str.charCodeAt(i++) & 0xff;
                    if(c4 == 61)
                        return out;
                    c4 = base64DecodeChars[c4];
                } while(i < len && c4 == -1);
                if(c4 == -1)
                    break;
                out += String.fromCharCode(((c3 & 0x03) << 6) | c4);
            }
            return out;
        }
    });
 }

最后进行hook的效果