其他
【APP加密算法和签名算法的还原】
本文为看雪论坛优秀文章
看雪论坛作者ID:taobluesky
一
准备环境
jeb-3.24
jadx-gui-1.3.3-1
xcube
android 7.0(真机)
二
初探
然后接下来就跟预想的一样,有检测frida的代码,一旦spwan或者attach上去,app的进程立马就自动中止了。
[FRD AL00::com.**.*****]-> Process terminated
[FRD AL00::com.**.*****]->
三
正式开始
// okhttp4
try {
var CertificatePinner = Java.use('okhttp3.CertificatePinner');
CertificatePinner.check.overload('java.lang.String', 'java.util.List').implementation = function(str) {
writeFile('! Intercepted okhttp4 in [check()]: ' + str);
return;
};
try {//.overload('java.lang.String', 'kotlin.jvm.functions.Function0')
CertificatePinner.check$okhttp.implementation = function(str, _) {
writeFile('! Intercepted okhttp4 in [check$okhttp]: ' + str);
return;
};
} catch (ex) {
writeFile("is this Okhttp3 ?!");
}
writeFile('* Setup okhttp4 pinning')
} catch (err) {
writeFile('* Unable to hook into okhttp4 pinner')
writeFile(err);
}
可以看到登录接口的密码是加密的, 还有sign字段。首先寻找密码加密的关键代码如下:
再跟入:
使用rsa对密码进行加密,这个unname方法就是一个绝妙的注入点,编写获取public key的脚本:
// 密码加密:输出RSA的pubkey
var this3 = Java.use("com.**.*****.encrypt.this3");
this3.unname.implementation = function(str, str2){
writeFile('unname is called');
writeFile("pubkey:" + str2);
var ret = this.unname(str, str2);
writeFile('unname ret value is ' + ret);
return ret;
};
try {
String password = "qed";
String publicKey = "MIGfMA0GCSqGSIb3D********qGWVMv5z6FwIDAQAB";
byte[] decoded = Base64.decode(publicKey, Base64.DEFAULT);
RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
Cipher instance = Cipher.getInstance("RSA/ECB/PKCS1Padding");
instance.init(ENCRYPT_MODE, pubKey);
String pwdenc = Base64.encodeToString(instance.doFinal(password.getBytes(StandardCharsets.UTF_8)), Base64.DEFAULT);
Log.e(TAG, pwdenc);
} catch (Exception e) {
e.printStackTrace();
}
再跟进SignUtil类里面:
可以看到关键的方法handle在native-lib里面, 通过hookget方法可以获取传入验签的参数和sign结果:
var SignUtil = Java.use("com.**.****.encrypt.SignUtil");
SignUtil.get.implementation = function(str, str2, str3, str4){
writeFile('get is called');
writeFile("str:" + str);
writeFile("str2:" + str2);
writeFile("str3:" + str3);
writeFile("str4:" + str4);
var ret = this.get(str, str2, str3, str4);
writeFile('get ret value is ' + ret);
return ret;
};
sub_45A40函数:
上图已经对关键的方法和变量做了注释,逻辑已经很清晰了,sign=sha256(_requestData+_requestDataList+_token+_clientTime+salt_key),为了验证拼接得对不对,可以把temp_str输出,寻找一出合适的hook点:
这处就可以进行hook来输出,对应的asm代码如下:
这边要注意一下这个temp_str的类型是std::string,要想办法转换成cstring才能打印输出,我们可以利用上面分析出来的string_to_cstr函数来转换:
分析完了,我们来写hook代码:
var libnative_addr = Module.findBaseAddress("libnative-lib.so")
writeFile("libnative_addr is: " + libnative_addr)
// 内部一个std::string转cstring方法
var str_to_c = new NativeFunction(libnative_addr.add(0x45A85), "pointer", ["pointer"]);
// 输出签名字符串
try{
var addr_45266 = libnative_addr.add(0x45267);
writeFile("addr_45266: " + addr_45266);
Interceptor.attach(addr_45266, {
onEnter: function (args) {
writeFile("ohwawawa");
var ret = str_to_c(this.context.r2);
writeFile("addr_45266 OnEnter sign string:" + Memory.readCString(ret));
},
onLeave: function (retval) {
//console.log("retval is :", retval)
}
});
} catch(err) {
writeFile("[!!!!!!!!!!!!] " + err);
}