分析
要在未修复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的效果