唯品会登录edata字段分析
Java层
jadx搜索"edata"
对EDATA
查找用例
com.achievo.vipshop.commons.api.middleware.ApiRequest -> getNewEncodeBodyMap
com.achievo.vipshop.commons.config.GobalConfig -> encodeStr
com.vip.vcsp.security.sign.VCSPSecurityConfig -> encodeStr
com.vip.vcsp.security.sign.VCSPSecurityConfig -> es
找到这里,发现没办法继续跳转了,不过这个类有个初始化函数initInstance
从中可以看到clazz
就是com.vip.vcsp.KeyInfo
类
然后在com.vip.vcsp.KeyInfo
,我们也找到了es
函数,发现它是调用了esNav
函数,而这个是libkeyinfo.so
中的native函数。
frida hook
Java.perform(function() {
var KeyInfo = Java.use("com.vip.vcsp.KeyInfo");
KeyInfo.esNav.implementation = function(ctx, str1, str2, str3, i) {
console.log(str1);
console.log(str2);
console.log(str3);
console.log(i);
var ret = this.esNav(ctx, str1, str2, str3, i);
console.log(ret);
return ret;
}
}
由此可知str1
是urlencode之后的参数
so层
ida打开libkeyinfo.so
,函数窗口搜索Java
说明函数是静态注册的,打开Java_com_vip_vcsp_KeyInfo_esNav
跟进一下
从函数主体可以看出,so通过反射来调用Java层的AES加密方法。
先将Functions_es
的a1
参数类型设为JNIEnv*
,在a1
处按Y
,设置为JNIEnv *a1
这样就方便许多了,我们知道AES/CBC最重要的3个参数是data
,key
,IV
,然后我们找个Java的AES/CBC实现来和native反射调用对比一下
private static final String key = "aesEncryptionKey";
private static final String initVector = "encryptionIntVec";
public static String encrypt(String value) {
try {
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(value.getBytes());
return Base64.encodeBase64String(encrypted);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
key
通过对比,可以看出v16
就是key
,而v16
是由v15
赋值的,v15
是j_getMD516
的结果
IV
通过对比,v26
就是IV
,v26
由v25
赋值,v25
由v53
赋值,v53
看起来似乎是由全0构成的,但是在其下方有个函数j_rand16Str()
看起来孤零零的,点进去看看,
继续跟进
看起来rand16Str
的调用对不上,回到j_rand16Str
处,F5
一下
更新之后可以通过rand16Str
的代码看出,v53
就是一个长度16的16进制随机字符
data
要加密的数据,其实就是esNav传进来的参数
base64
接下来有个base64,可以看出v44是由v53
和v41
拼接而成,而v53
就是前面提到的IV
,v41
则是由v37
而来,也就是AES加密的结果。
接下来,我们通过frida hook来验证上面的分析,hook rand16Str
得到IV的值,hook getMD516
得到key的值,hook base64_encode
来得到AES加密后的结果,以及base64之后的结果
function dump(name, addr, legnth) {
console.log("======================== " + name + " ============");
console.log(hexdump(addr, {length:legnth||32}));
}
Java.perform(function() {
var KeyInfo = Java.use("com.vip.vcsp.KeyInfo");
KeyInfo.esNav.implementation = function(ctx, str1, str2, str3, i) {
console.log(str1);
console.log(str2);
console.log(str3);
console.log(i);
var ret = this.esNav(ctx, str1, str2, str3, i);
console.log(ret);
return ret;
}
var ptr_es = Module.findExportByName("libkeyinfo.so", "Functions_es");
Interceptor.attach(ptr_es, {
onEnter: function(args) {
logvar = true;
console.log("es-arg1", args[1]);
console.log("es-arg2", args[2]);
console.log("es-arg3", args[3]);
console.log("es-arg4", args[4]);
},
onLeave: function(retval) {
logvar = false;
}
})
var ptr_getMD516 = Module.findExportByName("libkeyinfo.so", "getMD516");
Interceptor.attach(ptr_getMD516, {
onEnter: function(args) {
console.log(args[1]);
dump("md5", args[1], parseInt(args[2]));
},
onLeave: function(retval) {
console.log("md5-ret", retval);
dump("md5-ret", retval, 16);
}
})
var ptr_rand = Module.findExportByName("libkeyinfo.so", "rand16Str");
Interceptor.attach(ptr_rand, {
onEnter: function(args){
this.arg0 = args[0];
},
onLeave: function(retval) {
console.log("\n\nrand-ret-arg0", this.arg0.readCString());
}
})
var ptr_b64 = Module.findExportByName("libkeyinfo.so", "base64_encode");
Interceptor.attach(ptr_b64, {
onEnter: function(args){
dump("b64", args[0], parseInt(args[1]));
},
onLeave: function(retval) {
console.log("b64-ret", retval.readCString());
}
})
});
接下来就是验证,打开CyberChef (gchq.github.io),配置如下
完全对上了
代码实现
import base64
from random import choice
from urllib.parse import urlencode
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
nhex = lambda n: ''.join(choice('0123456789abcdef') for _ in range(n))
def calc_edata(params, iv=None):
if isinstance(params, (list, tuple, dict)):
if hasattr(params, 'items'):
params = params.items()
params = urlencode(sorted(params))
if isinstance(params, str):
params = params.encode()
iv = iv or nhex(16)
if isinstance(iv, str):
iv = iv.encode()
key = bytes.fromhex('3feeaf10550a61596331f8e5c7f77a97')
data = pad(params, 16)
cryptor = AES.new(key, mode=AES.MODE_CBC, iv=iv)
content = cryptor.encrypt(data)
edata = base64.b64encode(iv + content).decode()
return edata
验证
代码仅供把玩。