【JS 逆向百例】某音 X-Bogus 逆向分析,JSVMP 纯算法还原
字数 2490 2025-08-11 17:40:19

某音X-Bogus参数逆向分析与JSVMP算法还原

1. 逆向目标

目标:某音网页端用户信息接口X-Bogus参数
接口:aHR0cHM6Ly93d3cuZG91eWluLmNvbS9hd2VtZS92MS93ZWIvdXNlci9wcm9maWxlL290aGVyLw==

2. JSVMP基础概念

JSVMP全称Virtual Machine based code Protection for JavaScript,即JS代码虚拟化保护方案。

2.1 JSVMP核心思想

  • 在JavaScript代码保护过程中引入代码虚拟化思想
  • 将目标代码转换成自定义的字节码
  • 这些字节码只有特殊的解释器才能识别
  • 隐藏目标代码的关键逻辑

2.2 JSVMP保护流程

  1. 服务器端读取JavaScript代码
  2. 词法分析
  3. 语法分析
  4. 生成AST语法树
  5. 生成私有指令
  6. 生成对应私有解释器
  7. 将私有指令加密与私有解释器发送给浏览器
  8. 浏览器边解释边执行

3. JSVMP逆向方法

目前JSVMP的逆向方法主要有三种:

  1. RPC远程调用:通过远程调用的方式绕过保护
  2. 补环境:模拟完整的浏览器环境
  3. 日志断点还原算法(插桩):找到关键位置,输出关键参数的日志信息,从结果往上倒推生成逻辑

本文主要介绍插桩还原算法的方法。

4. 抓包分析

在博主主页抓包,可以发现一个返回JSON数据的接口,包含博主信息。请求参数中包含:

  • X-Bogus:28个字符组成的参数,每次请求会改变
  • sec_user_id:博主主页URL后面的一串
  • webid:请求主页返回内容中包含
  • msToken:与cookie有关

经测试,该接口不验证webidmsToken,可置空。

5. 逆向分析流程

5.1 定位关键JS文件

  1. 下XHR断点,当URL中包含X-Bogus参数时断下
  2. 跟栈找到webmssdk.js文件,这是生成参数的主要JS逻辑(JSVMP实现)
  3. 使用AST解混淆(可使用v_jstools插件)

5.2 关键代码分析

还原混淆后,发现以下关键点:

  • this.openArgs[1]是携带了X-Bogus的完整URL
  • 代码中有大量三元表达式
  • M的值为15时,会走到生成X-Bogus的逻辑
  • S数组参与整个生成过程,不断被增删改查
  • for循环中的I值决定后续if语句的走向

5.3 插桩实现

在关键位置添加日志断点:

// 位置1日志断点
"位置1", "索引I", I, "索引A", A, "值S: ", JSON.stringify(S, function(key, value) {
    if (value == window) {
        return undefined
    }
    return value
})

// 位置2日志断点
"位置2", "索引I", I, "索引A", A, "值S: ", JSON.stringify(S, function(key, value) {
    if (value == window) {
        return undefined
    }
    return value
})

JSON.stringify处理说明

  • 使用replacer函数处理循环引用问题
  • 当value为window时返回undefined,排除该成员
  • 确保能完整输出S数组内容

5.4 日志分析

导出日志后分析X-Bogus生成过程:

  1. X-Bogus由28个字符组成,分为7组,每组4个字符
  2. 每组字符的生成逻辑相同
  3. 每个字符通过以下步骤生成:
    • 从乱码字符串获取指定位置的Unicode编码
    • 进行位运算
    • 从固定字符串short_str中取出对应字符

5.5 字符生成算法

function getXBogus(originalString) {
    // 生成乱码字符串
    var garbledString = getGarbledString(originalString);
    var XBogus = "";
    
    // 依次生成七组字符串
    for (var i = 0; i <= 20; i += 3) {
        var charCodeAtNum0 = garbledString.charCodeAt(i);
        var charCodeAtNum1 = garbledString.charCodeAt(i + 1);
        var charCodeAtNum2 = garbledString.charCodeAt(i + 2);
        var baseNum = charCodeAtNum2 | charCodeAtNum1 << 8 | charCodeAtNum0 << 16;
        
        // 依次生成四个字符
        var str1 = short_str[(baseNum & 16515072) >> 18];
        var str2 = short_str[(baseNum & 258048) >> 12];
        var str3 = short_str[(baseNum & 4032) >> 6];
        var str4 = short_str[baseNum & 63];
        
        XBogus += str1 + str2 + str3 + str4;
    }
    return XBogus;
}

其中short_str为固定字符串:
Dkdpgh4ZKsQB80/Mfvw36XI1R25-WUAlEi7NLboqYTOPuzmFjJnryx9HVGcaStCe=

6. 乱码字符串生成逻辑

6.1 初始处理

  1. 对URL参数进行两次MD5、两次转Uint8Array处理:
var md5 = require("md5");

// 字符串转换为Uint8Array对象
_0x5960a2 = function(a) {
    for (var c = a.length >> 1, e = c << 1, b = new Uint8Array(c), d = 0, f = 0; f < e;) {
        b[d++] = _0x511f86[a.charCodeAt(f++)] << 4 | _0x511f86[a.charCodeAt(f++)];
    }
    return b;
}

// originalString: URL后面的原始参数
var uint8Array = _0x5960a2(md5(_0x5960a2(md5(originalString))));

6.2 生成两个大数

  1. fixedString1:时间戳(1663385262240 / 1000 = 1663385262.24)
  2. fixedString2:通过特定方法生成(536919696)
function _0x2996f8() {
    try {
        return _0x4b3b53 || (_0xb55f3e.perf ? -1 : (_0x4b3b53 = _0x229792(3735928559), _0x4b3b53));
    } catch (a) {
        return -1;
    }
}

6.3 生成两个数组

array1(19个元素):

  • [0]至[3]:固定值
  • [4]:uint8Array[14]
  • [5]:uint8Array[15]
  • [6]至[7]:固定值
  • [8]至[9]:与UA有关
  • [10]至[13]:来自fixedString1的位运算
  • [14]至[17]:来自fixedString2的位运算
  • [18]:array1所有元素的异或结果

完整值示例:
[64,1.00390625,1,8,9,185,69,63,74,125,99,73,3,241,32,0,190,144,100]

array2:由array1元素交换位置得来
[array1[0], array1[2], array1[4], array1[6], array1[8], array1[10], array1[12], array1[14], array1[16], array1[18], array1[1], array1[3], array1[5], array1[7], array1[9], array1[11], array1[13], array1[15], array1[17]]

完整值示例:
[64,1,9,69,74,99,3,32,190,100,1.00390625,8,185,63,125,73,241,0,144]

6.4 生成乱码字符串

通过三个关键函数处理:

function _0x2f2740(a, c, e, b, d, f, t, n, o, i, r, _, x, u, s, l, v, h, g) {
    let w = new Uint8Array(19);
    return w[0] = a, w[1] = r, w[2] = c, w[3] = _, w[4] = e, w[5] = x, w[6] = b, w[7] = u, w[8] = d, w[9] = s, w[10] = f, w[11] = l, w[12] = t, w[13] = v, w[14] = n, w[15] = h, w[16] = o, w[17] = g, w[18] = i, String.fromCharCode.apply(null, w);
}

function _0x46fa4c(a, c) {
    let e, b = [], d = 0, f = "";
    for (let a = 0; a < 256; a++) {
        b[a] = a;
    }
    for (let c = 0; c < 256; c++) {
        d = (d + b[c] + a.charCodeAt(c % a.length)) % 256, e = b[c], b[c] = b[d], b[d] = e;
    }
    let t = 0;
    d = 0;
    for (let a = 0; a < c.length; a++) {
        t = (t + 1) % 256, d = (d + b[t]) % 256, e = b[t], b[t] = b[d], b[d] = e, f += String.fromCharCode(c.charCodeAt(a) ^ b[(b[t] + b[d]) % 256]);
    }
    return f;
}

function _0x583250(a) {
    return String.fromCharCode(a);
}

function _0x2b6720(a, c, e) {
    return _0x583250(a) + _0x583250(c) + e;
}

处理步骤:

  1. _0x2f2740.apply(null, array2) → 生成中间字符串
  2. _0x46fa4c.apply(null, [key, 中间字符串]) → 进一步处理
  3. _0x2b6720.apply(null, [2, 255, 上一步结果]) → 最终乱码字符串

7. 完整算法还原步骤

  1. 获取URL参数并进行两次MD5和Uint8Array转换
  2. 生成时间戳和特定数值
  3. 构建array1和array2
  4. 通过三个关键函数处理生成乱码字符串
  5. 从乱码字符串中获取字符编码
  6. 通过位运算和固定字符串映射生成X-Bogus的28个字符

8. 注意事项

  1. 日志中的行号、变量值可能会变化,但逻辑一致
  2. JSON.stringify处理后的日志可能与实际值有差异
  3. 需要仔细定位关键断点位置
  4. 算法中的q数组可以写死固定值
  5. 每组字符的生成逻辑相同,可以抽象为通用方法
某音X-Bogus参数逆向分析与JSVMP算法还原 1. 逆向目标 目标:某音网页端用户信息接口X-Bogus参数 接口: aHR0cHM6Ly93d3cuZG91eWluLmNvbS9hd2VtZS92MS93ZWIvdXNlci9wcm9maWxlL290aGVyLw== 2. JSVMP基础概念 JSVMP全称Virtual Machine based code Protection for JavaScript,即JS代码虚拟化保护方案。 2.1 JSVMP核心思想 在JavaScript代码保护过程中引入代码虚拟化思想 将目标代码转换成自定义的字节码 这些字节码只有特殊的解释器才能识别 隐藏目标代码的关键逻辑 2.2 JSVMP保护流程 服务器端读取JavaScript代码 词法分析 语法分析 生成AST语法树 生成私有指令 生成对应私有解释器 将私有指令加密与私有解释器发送给浏览器 浏览器边解释边执行 3. JSVMP逆向方法 目前JSVMP的逆向方法主要有三种: RPC远程调用 :通过远程调用的方式绕过保护 补环境 :模拟完整的浏览器环境 日志断点还原算法(插桩) :找到关键位置,输出关键参数的日志信息,从结果往上倒推生成逻辑 本文主要介绍 插桩还原算法 的方法。 4. 抓包分析 在博主主页抓包,可以发现一个返回JSON数据的接口,包含博主信息。请求参数中包含: X-Bogus :28个字符组成的参数,每次请求会改变 sec_user_id :博主主页URL后面的一串 webid :请求主页返回内容中包含 msToken :与cookie有关 经测试,该接口不验证 webid 和 msToken ,可置空。 5. 逆向分析流程 5.1 定位关键JS文件 下XHR断点,当URL中包含X-Bogus参数时断下 跟栈找到 webmssdk.js 文件,这是生成参数的主要JS逻辑(JSVMP实现) 使用AST解混淆(可使用v_ jstools插件) 5.2 关键代码分析 还原混淆后,发现以下关键点: this.openArgs[1] 是携带了X-Bogus的完整URL 代码中有大量三元表达式 当 M 的值为15时,会走到生成X-Bogus的逻辑 S 数组参与整个生成过程,不断被增删改查 for 循环中的 I 值决定后续 if 语句的走向 5.3 插桩实现 在关键位置添加日志断点: JSON.stringify处理说明 : 使用replacer函数处理循环引用问题 当value为window时返回undefined,排除该成员 确保能完整输出S数组内容 5.4 日志分析 导出日志后分析X-Bogus生成过程: X-Bogus由28个字符组成,分为7组,每组4个字符 每组字符的生成逻辑相同 每个字符通过以下步骤生成: 从乱码字符串获取指定位置的Unicode编码 进行位运算 从固定字符串 short_str 中取出对应字符 5.5 字符生成算法 其中 short_str 为固定字符串: Dkdpgh4ZKsQB80/Mfvw36XI1R25-WUAlEi7NLboqYTOPuzmFjJnryx9HVGcaStCe= 6. 乱码字符串生成逻辑 6.1 初始处理 对URL参数进行两次MD5、两次转Uint8Array处理: 6.2 生成两个大数 fixedString1 :时间戳(1663385262240 / 1000 = 1663385262.24) fixedString2 :通过特定方法生成(536919696) 6.3 生成两个数组 array1 (19个元素): [ 0]至[ 3 ]:固定值 [ 4]:uint8Array[ 14 ] [ 5]:uint8Array[ 15 ] [ 6]至[ 7 ]:固定值 [ 8]至[ 9 ]:与UA有关 [ 10]至[ 13 ]:来自fixedString1的位运算 [ 14]至[ 17 ]:来自fixedString2的位运算 [ 18 ]:array1所有元素的异或结果 完整值示例: [64,1.00390625,1,8,9,185,69,63,74,125,99,73,3,241,32,0,190,144,100] array2 :由array1元素交换位置得来 [array1[0], array1[2], array1[4], array1[6], array1[8], array1[10], array1[12], array1[14], array1[16], array1[18], array1[1], array1[3], array1[5], array1[7], array1[9], array1[11], array1[13], array1[15], array1[17]] 完整值示例: [64,1,9,69,74,99,3,32,190,100,1.00390625,8,185,63,125,73,241,0,144] 6.4 生成乱码字符串 通过三个关键函数处理: 处理步骤: _0x2f2740.apply(null, array2) → 生成中间字符串 _0x46fa4c.apply(null, [key, 中间字符串]) → 进一步处理 _0x2b6720.apply(null, [2, 255, 上一步结果]) → 最终乱码字符串 7. 完整算法还原步骤 获取URL参数并进行两次MD5和Uint8Array转换 生成时间戳和特定数值 构建array1和array2 通过三个关键函数处理生成乱码字符串 从乱码字符串中获取字符编码 通过位运算和固定字符串映射生成X-Bogus的28个字符 8. 注意事项 日志中的行号、变量值可能会变化,但逻辑一致 JSON.stringify处理后的日志可能与实际值有差异 需要仔细定位关键断点位置 算法中的q数组可以写死固定值 每组字符的生成逻辑相同,可以抽象为通用方法