分析
要在未修复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(); // 获取classloaderJava.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的效果