国庆前抖音罗盘a_bogus突然更到了1.0.1.15版本,拿去年的短ab跑间歇性给数据,当时由于短ab断断续续的还能跑,假期在即就没管它了,昨天突然就g了,只能被迫开干。

由于只做商家后台不做dy前台,所以中间的长ab版本都没跟过,从星球找了一份1.0.1.5的纯算,对照着开始改。

插桩简单看了下后,发现和1.0.1.5整体流程貌似区别不大,而且整个流程日志只有四万多行,没必要费劲反编译,直接在1.0.1.5的基础上开改。

初始化时间和加密数组

url参数处理

这里算法从短ab到1.5长ab再到1.15都没改,加盐sm3哈希,变的只有salt

请求body处理

同样的加盐哈希,get方法的话就是空字符串加盐哈希

ua处理

短ab和1.5都是标准RC4,但是1.15可以明显看到进行了魔改:

S数组变成了255 254 ... 0

往下看,密钥生成部分出现了乘法,明显进行了魔改,不过逻辑很清晰:

加密部分还是RC4的原生xor,实际上各种加密算法的魔改基本都是在密钥轮进行的,很少有魔改加密轮的,因为没能力/不安全。

RC4之后的魔改base64还是和之前一样,没改变:

后续又是一次sm3

版本号处理

直接split后数字化。

短数组生成

对着日志撸,逻辑很清晰

随机数生成算法

这一部分短ab没有,直接复制大佬的1.5算法。

两个位置用到,分别是:

n[86] = gener_random(Math.random() * 65535, [n[25][0], n[25][1]]).concat(gener_random(Math.random() * 65535, [n[25][2], n[25][3]]))

n[89] = gener_random(Math.random() * 65535, [3, 82])

传参其实并不都是Math.random() * 65535,但是能用就懒得改。

短数组转长数组

    n[87] = n[86][0] ^ n[86][1] ^ n[86][2] ^ n[86][3] ^ n[86][4] ^ n[86][5] ^ n[86][6] ^ n[86][7] ^ n[24] ^ n[26] ^ n[27]  ^ n[28] ^ n[29] ^ n[30] ^ n[31] ^ n[32] ^ n[33] ^ n[34] ^ n[35] ^ n[36] ^ n[38] ^ n[39] ^ n[40] ^ n[41] ^ n[42] ^ n[43] ^ n[44] ^ n[45] ^ n[46] ^ n[47] ^ n[48] ^ n[49] ^ n[51] ^ n[52] ^ n[53] ^ n[55] ^ n[56] ^ n[57] ^ n[59] ^ n[60] ^ n[61] ^ n[62] ^ n[63] ^ n[64] ^ n[65] ^ n[66] ^ n[67] ^ n[68] ^ n[69] ^ n[70] ^ n[71] ^ n[72] ^ n[73] ^ n[74] ^ n[79] ^ n[80] ^ n[84] ^ n[85]
    new_list = [n[34], n[44], n[56], n[61], n[73], n[29], n[70], n[45], n[35], n[49], n[38], n[66], n[51], n[68], n[28], n[48], n[64], n[47], n[30], n[71], n[26], n[55], n[31], n[69], n[59], n[40], n[62], n[63], n[27], n[72], n[41], n[74], n[57], n[52], n[42], n[39], n[33], n[67], n[53], n[43], n[65], n[46], n[36], n[24], n[60], n[32], n[79], n[80], n[84], n[85]]
    n[88] = new_list.concat(n[77], n[82], [n[87]])

先是xor生成校验位,然后拼接生成短数组n[88]。

接着一顿操作把94位数组变成了125位数组。

这里询问一个搞完的大佬几个数字是否是常量,结果大佬直接把这部分代码发我了= =

function arr94_to_arr125(arr94) {
    let result = [];
    let number_of_loops = 0;
    const xor_array = [145, 110, 66, 189, 44, 211];
    number_of_loops = Math.floor(arr94.length / 3);
    for (let i = 0, j = 0; i < number_of_loops; i++) {
        j = i * 3;
        let y = (Math.random() * 1000) & 255;
        result.push(y & xor_array[0] | (arr94[j] & xor_array[1]));
        result.push(y & xor_array[2] | (arr94[j + 1] & xor_array[3]));
        result.push(y & xor_array[4] | (arr94[j + 2] & xor_array[5]));
        result.push((arr94[j] & xor_array[0]) | (arr94[j + 1] & xor_array[2]) | arr94[j + 2] & xor_array[4]);
    }
    result.push(arr94[93]);
    return result;
}

不过大佬发的是1.0.1.17版本抖音短视频的sdk,我改了下后发现死活过不去,还以为是1.17在1.15基础上又变了,最后调试后发现原来是弱智GPT提取代码时搞错了几个运算符,GPT给我拉了一坨大的......

生成ab

版本号数组拼接上面的125数组后RC4,然后另外两个常量生成的字符串再拼接,最后同样的魔改B64,得到的a[93]就是最终ab。

经测试1.0.1.15可以通过dy 1.0.1.17的签名认证。

扩展

上面的整个流程除了arr94_to_arr125外,其它的流程和1.5流程变化不大,硬说变化就是数组位置、加密方法的改变。

这里看到arr94_to_arr125时候就很好奇,上面的其它加密都是魔改rc4 sm3 b64的,而这个通过随机数算法加密生成的新数组,在抖音服务器那边如何解密还原呢?问了GPT,回答又是答非所问的一坨,于是问了下密码学大佬,发现这里的常量数组[145, 110, 66, 189, 44, 211]选的很有深意,因为它们的二进制为:

10010001
01101110

01000010
10111101

00101100
11010011

每一组的两个都是互补的,通过位运算将加密后,完全可以通过结果的四个数字逆推得到原始数组,消除中间随机数带来的影响。

function arr125_to_arr94(arr125) {
    let result = [];
    const xor_array = [145, 110, 66, 189, 44, 211];
    const number_of_loops = Math.floor((arr125.length - 1) / 4);
    for (let i = 0; i < number_of_loops; i++) {
        const r0 = arr125[i * 4];
        const r1 = arr125[i * 4 + 1];
        const r2 = arr125[i * 4 + 2];
        const r3 = arr125[i * 4 + 3];
        let x0 = 0, x1 = 0, x2 = 0;
        // 逐位恢复 x0, x1, x2
        for (let b = 0; b < 8; b++) {
            const s0_b = (xor_array[0] >> b) & 1;
            const s1_b = (xor_array[1] >> b) & 1;
            const s2_b = (xor_array[2] >> b) & 1;
            const s3_b = (xor_array[3] >> b) & 1;
            const s4_b = (xor_array[4] >> b) & 1;
            const s5_b = (xor_array[5] >> b) & 1;
            const r0_b = (r0 >> b) & 1;
            const r1_b = (r1 >> b) & 1;
            const r2_b = (r2 >> b) & 1;
            const r3_b = (r3 >> b) & 1;
            let x0_b = 0, x1_b = 0, x2_b = 0;
            // 根据按位操作的特性,推导 x0_b, x1_b, x2_b
            if (s1_b) {
                x0_b = r0_b;
            } else if (s0_b) {
                // 当 s1_b 为 0,s0_b 为 1 时
                x0_b = r3_b;
            }
            if (s3_b) {
                x1_b = r1_b;
            } else if (s2_b) {
                // 当 s3_b 为 0,s2_b 为 1 时
                x1_b = r3_b;
            }
            if (s5_b) {
                x2_b = r2_b;
            } else if (s4_b) {
                // 当 s5_b 为 0,s4_b 为 1 时
                x2_b = r3_b;
            }
            // 组合比特位
            x0 |= x0_b << b;
            x1 |= x1_b << b;
            x2 |= x2_b << b;
        }
        result.push(x0);
        result.push(x1);
        result.push(x2);
    }
    result.push(arr125[124]);
    return result;
}

神奇的密码学~