唯品会登录edata字段分析

唯品会登录edata字段分析

Java层

image-20211130180456283

jadx搜索"edata"

image-20211130180651678

EDATA查找用例

image-20211130180736943

com.achievo.vipshop.commons.api.middleware.ApiRequest -> getNewEncodeBodyMap

image-20211130180830005

com.achievo.vipshop.commons.config.GobalConfig -> encodeStr

image-20211130180912901

com.vip.vcsp.security.sign.VCSPSecurityConfig -> encodeStr

image-20211130181000812

com.vip.vcsp.security.sign.VCSPSecurityConfig -> es

image-20211130181047965

找到这里,发现没办法继续跳转了,不过这个类有个初始化函数initInstance

image-20211130144003950

从中可以看到clazz就是com.vip.vcsp.KeyInfo

image-20211130190122519

然后在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;
    }
}
image-20211130190458104

由此可知str1是urlencode之后的参数

so层

ida打开libkeyinfo.so,函数窗口搜索Java

image-20211130145030206

说明函数是静态注册的,打开Java_com_vip_vcsp_KeyInfo_esNav

image-20211130190900182

跟进一下

image-20211130190923390
image-20211130191106743

从函数主体可以看出,so通过反射来调用Java层的AES加密方法。

先将Functions_esa1参数类型设为JNIEnv*,在a1处按Y,设置为JNIEnv *a1

image-20211130191501749
image-20211130191735911

这样就方便许多了,我们知道AES/CBC最重要的3个参数是datakeyIV,然后我们找个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
image-20211130192613358

通过对比,可以看出v16就是key,而v16是由v15赋值的,v15j_getMD516的结果

IV
image-20211130192925782

通过对比,v26就是IVv26v25赋值,v25v53赋值,v53看起来似乎是由全0构成的,但是在其下方有个函数j_rand16Str()看起来孤零零的,点进去看看,

image-20211130193135099

继续跟进

image-20211130193201181

看起来rand16Str的调用对不上,回到j_rand16Str处,F5一下

image-20211130193352635
image-20211130193437571

更新之后可以通过rand16Str的代码看出,v53就是一个长度16的16进制随机字符

data
image-20211130193752036

要加密的数据,其实就是esNav传进来的参数

base64
image-20211130194159021

接下来有个base64,可以看出v44是由v53v41拼接而成,而v53就是前面提到的IVv41则是由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());
        }
    })
});
image-20211130200026249
image-20211130200331154

接下来就是验证,打开CyberChef (gchq.github.io),配置如下

image-20211130201113326

完全对上了

代码实现

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

验证

image-20211130202323249

代码仅供把玩。

你可能感兴趣的:(唯品会登录edata字段分析)