今日头条a_bogus参数加密逆向

概述

今日头条作为国内主流新闻资讯平台,其API请求中使用了名为a_bogus的加密参数进行反爬防护。本笔记记录如何通过补全浏览器环境和逆向分析来生成该参数。

网页分析

1755505587369-0354b86a-2e23-473e-bec9-1987c63fd508.png

1755505650528-c9300f35-1fa9-41d4-909d-e9ffa8cef37a.png

1755505751077-b315a837-8ca0-4450-88e6-21cec24c19e1.png

1755505817485-76701a54-dda0-411b-babe-466f71a1cee3.png

1755505910620-11f2d102-6cd7-4b35-a2e4-6a03f8d26f8a.png

1755506519940-14caea96-962a-4359-b064-7260d1b2dcb4.png

1755506609132-3db97f81-50dc-4879-9a39-0c54ed7f7445.png

1755506712508-50d3d585-682c-4aa9-b921-ded0cc168cab.png

1755506867351-f4cbbb2d-261f-4f64-8c40-a92e24f01677.png

1755506912198-dfa6744c-3109-4927-905a-5438d71b5d92.png

1755507014859-895861b8-c031-4a56-a7b3-11aac21b854a.png

技术要点

2.1 核心难点

  • a_bogus 是动态生成的加密参数
  • 加密逻辑依赖浏览器环境指纹
  • 使用混淆的JavaScript代码(bdms.js)实现加密

2.2 解决方案

  1. 补全浏览器环境对象
  2. 逆向分析加密调用链
  3. 代理关键对象监控调用过程

环境补全实现

3.1 基础环境配置

// 全局对象初始化
window = global;
window.requestAnimationFrame = function(){}
window.HTMLSpanElement = function(){}

// 窗口属性模拟
window.innerWidth = 1920
window.innerHeight = 331
window.outerWidth = 1920
window.outerHeight = 1040
window.screenX = 0
window.screenY = 0
window.pageYOffset = 0

// SDK版本信息
window._sdkGlueVersionMap = {
    "sdkGlueVersion": "1.0.0.55",
    "bdmsVersion": "1.0.1.7",
    "captchaVersion": "4.0.2"
}

3.2 存储对象模拟

// localStorage模拟
localStorage = {
    "__tea_cache_first_2018": "1",
    "__tea_cache_tokens_2018": "{\"web_id\":\"7530833203905971739\"}",
    // 其他缓存数据...
    getItem: function(){},
    removeItem: function(){}
}

// sessionStorage模拟
sessionStorage = {
    "__tea_session_id_24": "{\"sessionId\":\"ea368e47-fcc8...\"}",
    getItem: function(){},
    removeItem: function(){}
}

3.3 DOM对象模拟

// document对象
document = {
    cookie: 'ttcid=7ddaeb4ae85c4ad3...',
    createElement: function(tag){
        if (tag === 'span') return span
    },
    referrer: 'https://www.toutiao.com/',
    // 其他DOM属性...
}

// navigator对象
navigator = {
    userAgent: 'Mozilla/5.0 (Windows NT 10.0...)'
}

代理监控系统

4.1 代理实现函数

function setProxy(proxyObjArr) {
    for (let i = 0; i < proxyObjArr.length; i++) {
        const handler = `{
            get: function(target, property, receiver) {
                console.log("方法:", "get  ", "对象:", "${proxyObjArr[i]}", 
                          "  属性:", property, "  属性类型:", typeof property, 
                          ", 属性值:", target[property], 
                          ", 属性值类型:", typeof target[property]);
                return target[property];
            },
            set: function(target, property, value, receiver) {
                console.log("方法:", "set  ", "对象:", "${proxyObjArr[i]}", 
                          "  属性:", property, "  属性类型:", typeof property, 
                          ", 属性值:", value, 
                          ", 属性值类型:", typeof target[property]);
                return Reflect.set(...arguments);
            }
        }`;
        
        eval(`try {
            ${proxyObjArr[i]};
            ${proxyObjArr[i]} = new Proxy(${proxyObjArr[i]}, ${handler});
        } catch (e) {
            ${proxyObjArr[i]} = {};
            ${proxyObjArr[i]} = new Proxy(${proxyObjArr[i]}, ${handler});
        }`);
    }
}

4.2 代理对象配置

proxy_array = [
    'window',
    'canvas'
    // 可根据需要添加其他对象
]
setProxy(proxy_array)

加密参数生成

5.1 加密函数调用

function get_a_bogus(arg){
    args_1 = [
        0,  // 固定参数1
        1,  // 固定参数2
        14, // 固定参数3
        arg, // 请求参数
        "",  // 空字符串
        navigator.userAgent // 浏览器UA
    ]
    
    // 获取加密所需的基础参数
    var r = window._U._v;
    
    // 调用核心加密函数
    a_bogus = window._U._u(
        r[0],   // 参数1
        args_1, // 参数数组
        r[1],   // 参数2
        r[2],   // 参数3
        null    // 上下文
    )
    return a_bogus
}

5.2 参数说明

参数类型说明
r[0]Number基础参数1(通常为1897)
args_1Array包含请求参数的数组
r[1]Object基础参数2(通常为空对象)
r[2]Number基础参数3(通常为6)

完整调用流程

  1. 初始化浏览器环境
  2. 加载加密JS文件(bdms.js)
  3. 设置代理监控
  4. 构造请求参数
  5. 生成a_bogus参数
  6. 发送API请求
// 1. 加载加密逻辑
require("./bdms")

// 2. 构造请求参数
const params = {
    offset: "0",
    channel_id: "94349549395",
    max_behot_time: "0",
    category: "pc_profile_channel",
    aid: "24",
    app_name: "toutiao_web"
}

// 3. 生成a_bogus
const a_bogus = get_a_bogus(urlencode(params))

// 4. 添加到请求参数
params.a_bogus = a_bogus

// 5. 发送请求
// const response = requests.get(url, params=params)

完整代码

import requests
import execjs
from urllib.parse import urlencode

headers = {
    "accept": "application/json, text/plain, */*",
    "accept-language": "zh-CN,zh;q=0.9",
    "cache-control": "no-cache",
    "pragma": "no-cache",
    "priority": "u=1, i",
    "referer": "https://www.toutiao.com/?wid=1753408727185",
    "sec-ch-ua": "\"Not;A=Brand\";v=\"99\", \"Google Chrome\";v=\"139\", \"Chromium\";v=\"139\"",
    "sec-ch-ua-mobile": "?0",
    "sec-ch-ua-platform": "\"Windows\"",
    "sec-fetch-dest": "empty",
    "sec-fetch-mode": "cors",
    "sec-fetch-site": "same-origin",
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36"
}
cookies = {
    "tt_webid": "7530833195744642610",
    "ttcid": "7ddaeb4ae85c4ad3a97a1f5a20f3128a13",
    "local_city_cache": "%E9%95%BF%E6%B2%99",
    "_ga": "GA1.1.152252405.1753408751",
    "csrftoken": "d50380b8fd876691377e1784f520d987",
    "s_v_web_id": "verify_mdi6arfb_JhCmehzG_uZlV_4Uni_95YM_SHlmdFBh8evy",
    "gfkadpd": "24,6457",
    "_ga_QEHZPBE5HH": "GS2.1.s1755501167$o10$g1$t1755501179$j48$l0$h0",
    "tt_scid": "IZYk7N4czkWBONK2CXhKEaMTAU9mHQsId8eTpHoGU.x2iaxnzaAU7tS2yjtoKlS2932d",
    "ttwid": "1%7CBXcBsVkKaGJhR8z2otQMXZQ5KyemAE5rZCYmMW7X7Yo%7C1755501182%7C4f426bea4b4347b5a88ccf4f952936fe836cf82bc83a9357735980f36356d1ae"
}
url = "https://www.toutiao.com/api/pc/list/feed"
params = {
    "offset": "0",
    "channel_id": "94349549395",
    "max_behot_time": "0",
    "category": "pc_profile_channel",
    "disable_raw_data": "true",
    "aid": "24",
    "app_name": "toutiao_web",
    "msToken": "rbgPJm26nRNMyXMIjHJoEN2mBaX7RApOkC9YcHYHGSprXqztmiSBt7y7Du9SXXLIrPg3UDzjloBJzp8sSNWZbHPDXzz2qjyc3Dryr0WO07LhQf8qRHyCre0=",
    # "a_bogus": "E68ZMDzkDk2Nvf6d53xLfY3qVll3YM770t9bMDhqbn31rL39HMPu9exoQ77vkoWjis/mIeyjy4hbYpn2rQInM1wfH8iN/2CZs6s0el1Mso0j53iruyRkrzDF-vG-SaBBRk-lrOX0w7lHFbYgAnAJ-7C4bfebYrtswnuYt9/bKf=="
}
url_params = "" + urlencode(params)
ctx = execjs.compile(open('env.js', 'r', encoding='utf-8').read()).call("get_a_bogus", url_params)
params['a_bogus'] = ctx
print(params['a_bogus'])
response = requests.get(url, headers=headers, cookies=cookies, params=params)

print(response.text)
print(response)
window = global;
window.requestAnimationFrame = function (){}
window.HTMLSpanElement = function (){}

window = global;
window.requestAnimationFrame = function (){}
window.onwheelx = {
    "_Ax": "0X21"
}
window.innerWidth = 1920
window.innerHeight = 331
window.outerWidth = 1920
window.outerHeight = 1040
window.screenX = 0
window.screenY = 0
window.pageYOffset = 0
window._sdkGlueVersionMap = {
    "sdkGlueVersion": "1.0.0.55",
    "bdmsVersion": "1.0.1.7",
    "captchaVersion": "4.0.2"
}
window.EventSource = function (){}

span = {
    classList: {}
}
localStorage = {
    "__tea_cache_first_2018": "1",
    "__PRE_CACHE__KEYS": "",
    "__pwa_push_show_count": "3",
    "__tea_cache_first_24": "1",
    "__tea_cache_tokens_2018": "{\"web_id\":\"7530833203905971739\",\"user_unique_id\":\"verify_mdi6arfb_JhCmehzG_uZlV_4Uni_95YM_SHlmdFBh8evy\",\"timestamp\":1755420203978,\"_type_\":\"default\"}",
    "xmst": "qdgaz2YbD0bYbuSAHX82t4nPpmXOLOJ-3WB0JYY2AKt3AvzhiX9YxwQtosoldnfPjbYlLihGE3W0z03xcvvjyBoh_TderM9TH3gHKZ2-oeC6u1wKhC5AzUG6XUP4Mrw=",
    "__tea_cache_tokens_1300": "{\"web_id\":\"7530833195744642610\",\"user_unique_id\":\"7530833195744642610\",\"timestamp\":1753672463960,\"_type_\":\"default\"}",
    "__tea_cache_tokens_24": "{\"web_id\":\"7530833195744642610\",\"user_unique_id\":\"7530833195744642610\",\"timestamp\":1755422052831,\"_type_\":\"default\"}",
    "__pwa_push_show_time": "1755417098928",
    "__tea_cache_refer_24": "{\"refer_key\":\"\",\"refer_title\":\"今日头条\",\"refer_manual_key\":\"\",\"routeChange\":false}",
    "__is_visited_home": "1",
    "loglevel": "SILENT",
    "__click_close_download_panel_ts__": "1753429771457",
    "tt_scid": "QqnVBu5PLTT5IYFV0LBt6D0i8IEcZ17Aebn86VJcNa3TruYKbChQ7VMnJCqf4e3-2205",
    "__tea_cache_first_1300": "1",
    "show_player_gesture_tips": "0",
    "_byted_param_sw": "KIcozisJHmcff1wheN4=",
    "web_runtime_security_uid": "fb35592c-8202-4de2-b6da-e0957145bacf",
    "SLARDARtoutiao_web_pc": "JTdCJTIydXNlcklkJTIyOiUyMjc1MzA4MzMxOTU3NDQ2NDI2MTAlMjIsJTIyZGV2aWNlSWQlMjI6JTIyNzUzMDgzMzE5NTc0NDY0MjYxMCUyMiwlMjJleHBpcmVzJTIyOjE3NjMxOTgwNTMwMDklN0Q=",
    "ttcid": "7ddaeb4ae85c4ad3a97a1f5a20f3128a13"
}
localStorage.getItem = function (){}
localStorage.removeItem = function (){}
sessionStorage = {
    "__tea_session_id_24": "{\"sessionId\":\"ea368e47-fcc8-4784-b10c-cc8bfe4b1072\",\"timestamp\":1755422548963}",
    "tt_scid": "QqnVBu5PLTT5IYFV0LBt6D0i8IEcZ17Aebn86VJcNa3TruYKbChQ7VMnJCqf4e3-2205",
    "_tea_cache_duration": "{\"duration\":90935,\"page_title\":\"今日头条\"}",
    "_byted_param_sw": "KIcozisJHmcff1wheN4=",
    "/": "1",
    "__tea_session_id_2018": "{\"sessionId\":\"2db0d03c-d1c2-49f8-a2b4-fda228db67d1\",\"timestamp\":1755420203981}"
}
sessionStorage.getItem = function (){}
sessionStorage.removeItem = function (){}

document = {
    cookie: 'ttcid=7ddaeb4ae85c4ad3a97a1f5a20f3128a13; local_city_cache=%E9%95%BF%E6%B2%99; _ga=GA1.1.152252405.1753408751; csrftoken=d50380b8fd876691377e1784f520d987; s_v_web_id=verify_mdi6arfb_JhCmehzG_uZlV_4Uni_95YM_SHlmdFBh8evy; gfkadpd=24,6457; _ga_QEHZPBE5HH=GS2.1.s1755420185$o6$g1$t1755422181$j60$l0$h0; tt_scid=QqnVBu5PLTT5IYFV0LBt6D0i8IEcZ17Aebn86VJcNa3TruYKbChQ7VMnJCqf4e3-2205',
    createElement: function (tag){
        if (tag === 'span'){
            return span
        }
    },
    documentElement: {},
    createEvent: function (){}
}
document.referrer = 'https://www.toutiao.com/?wid=1753408727185'
document.all = {}
location = {}
navigator = {}
navigator.userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36'
screen = {}
history = {}
XMLHttpRequest = function (){}

function get_environment(proxy_array) {
  if (!Array.isArray(proxy_array)) {
    console.error('Expected an array of property names')
    return
  }

  const createProxyHandler = (objName) => ({
    get(target, property, receiver) {
      const value = Reflect.get(target, property, receiver)
      console.log(
        `[GET] 对象: ${objName}, 属性: ${String(property)}, ` +
          `类型: ${typeof property}, 值类型: ${typeof value}, ` +
          `值: ${typeof value === 'function' ? 'function' : JSON.stringify(value)}`
      )
      return value
    },
    set(target, property, value, receiver) {
      console.log(
        `[SET] 对象: ${objName}, 属性: ${String(property)}, ` +
          `类型: ${typeof property}, 值类型: ${typeof value}, ` +
          `值: ${typeof value === 'function' ? 'function' : JSON.stringify(value)}`
      )
      return Reflect.set(target, property, value, receiver)
    },
    apply(target, thisArg, argumentsList) {
      console.log(
        `[CALL] 函数: ${objName}, ` + `参数: ${JSON.stringify(argumentsList)}`
      )
      return Reflect.apply(target, thisArg, argumentsList)
    }
  })

  proxy_array.forEach((objName) => {
    try {
      // 尝试获取全局对象
      let obj
      if (objName in window) {
        obj = window[objName]
      } else {
        obj = {}
        // 如果是特殊对象需要初始化
        if (objName === 'localStorage' || objName === 'sessionStorage') {
          obj = {
            getItem: () => {},
            setItem: () => {},
            removeItem: () => {},
            clear: () => {},
            length: 0,
            key: () => null
          }
        }
        window[objName] = obj
      }

      // 创建代理
      window[objName] = new Proxy(obj, createProxyHandler(objName))
    } catch (e) {
      console.error(`处理 ${objName} 时出错:`, e)
      window[objName] = new Proxy({}, createProxyHandler(objName))
    }
  })
}
function setProxy(proxyObjArr) {
    for (let i = 0; i < proxyObjArr.length; i++) {
        const handler = `{
      get: function(target, property, receiver) {
        console.log("方法:", "get  ", "对象:", "${proxyObjArr[i]}", "  属性:", property, "  属性类型:", typeof property, ", 属性值:", target[property], ", 属性值类型:", typeof target[property]);
        return target[property];
      },
      set: function(target, property, value, receiver) {
        console.log("方法:", "set  ", "对象:", "${proxyObjArr[i]}", "  属性:", property, "  属性类型:", typeof property, ", 属性值:", value, ", 属性值类型:", typeof target[property]);
        return Reflect.set(...arguments);
      }
    }`;
        eval(`try {
            ${proxyObjArr[i]};
            ${proxyObjArr[i]} = new Proxy(${proxyObjArr[i]}, ${handler});
        } catch (e) {
            ${proxyObjArr[i]} = {};
            ${proxyObjArr[i]} = new Proxy(${proxyObjArr[i]}, ${handler});
        }`);
    }
}
proxy_array = [
  'window',
  // 'document',
  // 'location',
  // 'navigator',
  // 'history',
  // 'screen',
  // 'localStorage',
  // 'sessionStorage',
  //   'span',
    'canvas'
]
setProxy(proxy_array)

require("./bdms")

function get_a_bogus(arg){
    args_1 = [
        0,
        1,
        14,
        arg,
        "",
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36"
    ]
    var r = window._U._v;
    a_bogus = window._U._u(r[0], args_1, r[1], r[2], null)
    return a_bogus
}
// console.log(get_a_bogus())

补环境的另一种实现思路

插件安装包

1755507308603-e06939f2-3c19-477d-828f-1e22311cd077.png

1755507287490-bf7263ac-6fb5-4280-bd6f-053eff0075cf.png

常见问题解决

7.1 环境补全不完整

现象:报错提示某些属性未定义\ 解决

  1. 检查报错信息中缺失的对象/属性
  2. 在环境初始化部分添加对应的模拟代码
  3. 使用代理监控确认属性访问情况

7.2 加密结果不一致

现象:生成的a_bogus与服务端验证不匹配\ 解决

  1. 检查所有环境参数是否与真实浏览器一致
  2. 确认请求参数的顺序和格式
  3. 检查时间戳等动态参数的影响