douyin-abogus-reverse

Overview

Douyin Web side generally uses a feature calleda_bogussignature parameters. It is essentially a dynamically generated check value used to block requests that are not initiated by the browser. Simply put, the server will use a set of rules hidden in the security SDK to verify this value. If the verification fails, the request will be rejected.

This article will start from a complete reverse practice perspective, dismantle key steps such as environment simulation, anti-detection Hook, SDK loading, XHR simulation trigger encryption, etc., and finally provide a reusable Python + JavaScript solution, allowing you to generate legal code locallya_bogusparameter.


Web analysis

Before starting the reverse engineering, we first confirm from the browser sidea_bogusThe generation location and dependencies. Here is the Douyin personal homepage video list interface as an example:

GET /aweme/v1/web/aweme/post/?...&a_bogus=XXXX
  1. Location encryption request Open Chrome Developer Tools and switch toNetworkpanel, filterXHR/FetchTypes of requests that can be easily found carrya_bogusParameter interface (such as/aweme/v1/web/aweme/post/)。

  2. Find the encryption entrance This type of encryption is usually not placed in the business code, but is assembled by the security module before the request is initiated. Set XHR breakpoints directly on the request, or search globallya_boguskeyword, you will find that the encryption entrance is located in a file calledbdms(ByteDance Security SDK) module.

  3. Analyze dependencies bdmsIt is not a piece of code that can run independently. When it loads, it will detect a lot of browser environment, including:

  • Whether the DOM API exists (e.g.document.createElement
  • Browser global object (windownavigatorscreenwait)
  • Whether various functions are native (viaFunction.prototype.toStringjudge)

This shows that simply cutting outbdmsThe code cannot be run directly in the Node.js or Python environment - we must first build a realistic enough browser sandbox.


Core technical route

This reverse engineering adopts the idea of ​​"Environment completion first + Dynamic agent to cover up":

  1. Global environment simulation CompletewindowdocumentnavigatorWait for the key properties and methods of the browser global object tobdmsIt will not report an error as soon as it is started.

  2. Function Native disguise bdmswill passfn.toString()Check if a function is still[native code]state. we need to rewriteFunction.prototype.toString, and mark the fake function withnativemark.

  3. Dynamic Agent Monitoring useProxymonitorbdmsProperty access to environment objects. When you encounter uncompleted attributes, you can see the logs in the console and supplement them as needed to avoid writing in a large number of unused attributes at the beginning.

  4. SDK loading and calling Inject organizedbdmscode, call its initialization method, and then simulateXMLHttpRequestThe encryption process is triggered bywindow.a_bogusGet the generated parameters.


Core code implementation

The following is a complete browser environment simulation scriptenv.js, which combines environment completion, Native function camouflage, dynamic proxy and SDK initialization logic.

Note: You need to extract from Douyin pagebdmsThe core code is saved asbdms.jsand place it in the same directory,env.jswill pass in the endrequire('./bdms')Load it.

window = global;
console_log = console.log;

// ==================== 第一步:函数 Native 伪装 ====================
(() => {
  'use strict';
  const $toString = Function.toString;
  // 使用随机 Symbol 作为 native 标签,避免被检测到固定属性名
  const fakeNativeTag = Symbol('('.concat('', ')_', (Math.random() + '').toString(36)));

  // 自定义 toString:如果函数已经打上标签,就返回伪造的 native 代码,否则沿用原始行为
  const myToString = function () {
    return (typeof this === 'function' && this[fakeNativeTag]) || $toString.call(this);
  };

  function set_native(obj, key, value) {
    Object.defineProperty(obj, key, {
      enumerable: false,
      configurable: true,
      writable: true,
      value: value
    });
  }

  // 替换 Function.prototype.toString
  delete Function.prototype['toString'];
  set_native(Function.prototype, 'toString', myToString);
  // 保护自定义 toString 本身,使其看起来像原生函数
  set_native(Function.prototype.toString, fakeNativeTag, 'function toString() { [native code] }');

  // 导出一个工具函数,用来给任意函数打上“原生”标记
  globalThis.safefunction = (func) => {
    set_native(func, fakeNativeTag, `function ${func.name || ''}() { [native code] }`);
  };
}).call(globalThis);

// ==================== 第二步:补全基础全局 API ====================
// 定时器相关(这些在 bdms 中经常被检测)
setInterval = function setInterval(){}; safefunction(setInterval);
setTimeout = function setTimeout(){}; safefunction(setTimeout);
window.requestAnimationFrame = function requestAnimationFrame(){}; safefunction(window.requestAnimationFrame);
window.addEventListener = function addEventListener(){}; safefunction(window.addEventListener);

// 窗口及屏幕尺寸(与你提取 bdms 时的浏览器保持一致)
window.outerWidth = 1920;
window.outerHeight = 1040;
window.innerWidth = 1920;
window.innerHeight = 937;

// 抖音安全 SDK 的版本标记(从页面环境中提取得到)
window._sdkGlueVersionMap = {
    "sdkGlueVersion": "1.0.0.64-fix.01",
    "bdmsVersion": "1.0.1.19-fix.01",
    "captchaVersion": "4.0.10"
};

// ==================== 第三步:补全 DOM 相关对象与第三方 SDK ====================
// 极简 DOM 对象实现
span = {classList: {}};
canvas = {getContext: function getContext(){}}; safefunction(canvas.getContext);
document = {
  createElement: function (tag) {
    if (tag === 'span') return span;
    if (tag === 'canvas') return canvas;
    console_log('未处理的 DOM 元素创建:', tag);
    return {};
  },
  documentElement: {},
  createEvent: function createEvent(){}, safefunction(document.createEvent),
  addEventListener: function addEventListener(){}, safefunction(document.addEventListener),
  all: {},
  location: {
    "ancestorOrigins": {},
    "href": "https://www.douyin.com/",
    "origin": "https://www.douyin.com",
    "protocol": "https:",
    "host": "www.douyin.com"
  }
};
safefunction(document.createElement);

// 第三方 SDK 存根(抖音可能会检测浏览器插件或开发环境)
window.CefSharp = { isMock: true, BindObjectAsync: () => Promise.resolve() };
window.eoapi = { mock: true, invoke: () => {} };

// ==================== 第四步:补全其他浏览器对象并伪装 toStringTag ====================
location = document.location;
navigator = {
  "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36",
  "platform": "Win32",
  "storage": {}
};
history = {};
screen = {availWidth: 1920, availHeight: 1040, width: 1920, height: 1080, colorDepth: 24};
localStorage = {getItem: () => {}, setItem: () => {}}; safefunction(localStorage.getItem);
sessionStorage = {};

// 伪装 Symbol.toStringTag,绕过 Object.prototype.toString.call 的环境检测
const fakeToStringTag = (obj, tag) => {
  Object.defineProperty(obj, Symbol.toStringTag, {
    value: tag,
    configurable: true,
    writable: true
  });
};
fakeToStringTag(document, 'HTMLDocument');
fakeToStringTag(location, 'Location');
fakeToStringTag(navigator, 'Navigator');
fakeToStringTag(history, 'History');
fakeToStringTag(screen, 'Screen');
fakeToStringTag(localStorage, 'Storage');
fakeToStringTag(sessionStorage, 'Storage');

// ==================== 第五步:动态代理兜底 ====================
function createProxy(objName) {
  return new Proxy(window[objName] || {}, {
    get(target, prop) {
      console_log(`[GET] 对象: ${objName}, 属性: ${String(prop)}`);
      return Reflect.get(target, prop);
    },
    set(target, prop, val) {
      console_log(`[SET] 对象: ${objName}, 属性: ${String(prop)}, 值: ${JSON.stringify(val)}`);
      return Reflect.set(target, prop, val);
    }
  });
}
// 只对核心对象挂代理,既能监控缺失属性,又不会严重影响性能
['window', 'document', 'location', 'navigator', 'screen'].forEach(name => {
  window[name] = createProxy(name);
});

// ==================== 第六步:注入 bdms 并初始化 ====================
require('./bdms');  // 将你从抖音提取的 bdms 代码放在同目录下
window.bdms.init({
  aid: 6383,                  // 抖音 Web 应用 ID
  pageId: 6241,               // 页面 ID
  paths: ['^/webcast/', '^/aweme/v1/'],  // 需要签名的接口路径(正则)
  boe: false,
  ddrt: 8.5,
  ic: 8.5
});

// ==================== 第七步:模拟 XHR 触发 a_bogus 生成 ====================
XMLHttpRequest = function XMLHttpRequest() {};
safefunction(XMLHttpRequest);
XMLHttpRequest.prototype.send = function send() {
  console_log('触发 XHR.send,等待加密...');
};
safefunction(XMLHttpRequest.prototype.send);

function get_ab(fullUrlWithParams) {
  const xhr = new XMLHttpRequest();
  // bdms 会通过这两个数组读取请求信息来构造 a_bogus
  xhr.bdmsInvokeList = [
    { args: ['GET', fullUrlWithParams, true] },
    { args: ['Accept', 'application/json, text/plain, */*'] }
  ];
  xhr.invokeList = [
    { name: 'addEventListener', args: ['load', null] },
    { name: 'addEventListener', args: ['error', null] }
  ];
  xhr.send(null);
  return window.a_bogus;
}

Python side call

aboveenv.jsRuns in Node.js environment and exposesget_abmethod. Python side can be usedPyExecJSto call it and construct the complete request.

import requests
import execjs
from urllib.parse import urlencode

# 从抖音页面中复制的请求头与 Cookie(需要替换为你自己的有效信息)
headers = {
    "accept": "application/json, text/plain, */*",
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36",
    "referer": "https://www.douyin.com/"
}
cookies = {
    # 替换成你登录后的 Cookie
    "s_v_web_id": "verify_xxx",
    "ttwid": "xxx"
}

# 抖音接口本身的业务参数
base_params = {
    "device_platform": "webapp",
    "aid": "6383",
    "channel": "channel_pc_web",
    "sec_user_id": "MS4wLjABAAAAPs96_XYppoAye-VK57HhjucNZfR_mTyR4KKqDBHdt5k",  # 替换为目标用户ID
    "count": "18",
    "webid": "7486005262718158363"  # 替换为自己当前的 webid
}
url = "https://www.douyin.com/aweme/v1/web/aweme/post/"

# 构建不含 a_bogus 的完整 URL
full_url = url + "?" + urlencode(base_params)

# 编译 JS 并调用生成 a_bogus
ctx = execjs.compile(open('env.js', 'r', encoding='utf-8').read())
a_bogus = ctx.call("get_ab", full_url)
base_params['a_bogus'] = a_bogus

# 发起最终请求
response = requests.get(url, headers=headers, cookies=cookies, params=base_params)
print("响应状态码:", response.status_code)
print("响应内容:", response.text[:500])

Complete workflow

  1. Extract bdms code In Chrome Developer ToolsSourcesSearch in panelbdms.init, copy the relevant code block and save it asbdms.js. Be careful not to omit any dependent modules (if the code is split into multiple files, they need to be merged in the same scope).

  2. Configure environment information Place the aboveenv.jsand in Python scriptwebidsec_user_idand replace cookies with your own valid data. These can be copied directly from the Network panel after logging into Douyin in the browser.

  3. Run the build Make sure that the Node.js and Python environments are correct. Execute the Python script to output valida_bogusinterface response.


Technical difficulties and solutions

DifficultiesSolutionsEffects
functiontoStringDetectedOverwrittenFunction.prototype.toString, use random Symbol to store fake native tagsPerfectly bypassing all[native code]Verification
Object.prototype.toString.callDetect environment typesCamouflage for all global objectsSymbol.toStringTagThe return type is consistent with the real browser
Security SDK accessed undefined propertyMounting core objectsProxy, printing uncovered attributes in real time on the console, and completing on-demandSignificantly reducing the workload of environment completion and improving stability
The encryption logic is encapsulated inside the SDK and is difficult to stripBy simulating XHRbdmsInvokeListandinvokeListTrigger the encryption processGet a valid signature without understanding the internal algorithm

Notes

  1. bdms version update Douyin will upgrade the security SDK from time to time. Once discovereda_bogusThe signature is invalid and the latest one needs to be extracted from the page again.bdmscode and possibly adjust environment scripts.

  2. Cookie Validity Signing cookies that rely on login status (e.g.s_v_web_idttwidwait). These cookies have a certain validity period and are associated with the server session, and need to be retrieved after expiration.

  3. WebID and User‑Agent consistency webidIt is generated based on the browser fingerprint (including User-Agent, screen resolution, etc.). When replacing, make sureenv.jsInformation such as User‑Agent in thewebidThe current browser is completely consistent, otherwise the signature may fail to be verified.

  4. Request frequency control Even with the correct signature, too fast a request frequency may still trigger risk control. It is recommended to add random delays between requests and try to avoid using it in high-concurrency scenarios.