抖音a_bogus参数加密逆向#
#概述
抖音Web端的核心API(如视频列表、用户主页)请求中,a_bogus是一个动态生成的校验码,负责“拦截”非浏览器发起的非法访问。本文将拆解环境模拟、反检测Hook、SDK加载、XHR模拟生成参数的完整流程,给出可复用的Python+JS方案。
#网页分析
(注:原图片保留路径,此处仅简要总结关键步骤)
- 打开Chrome开发者工具,筛选
XHR/Fetch找到含a_bogus的请求(如/aweme/v1/web/aweme/post/) - 在请求发起前下
XHR断点或搜索a_bogus关键词,定位加密入口在bdms(字节跳动安全SDK)模块 - 分析bdms的依赖项:大量DOM API、浏览器环境检测、函数Native代码验证
#核心技术路线
本次逆向采用「环境补全优先+动态代理兜底」的方案:
- 全局环境模拟:补全
window/document/navigator等浏览器全局对象 - 函数Native伪装:重写
Function.prototype.toString绕过函数源检测 - 动态代理监控:用
Proxy监听bdms未覆盖的属性访问,按需补全 - SDK加载与调用:注入bdms代码,初始化后模拟XHR请求触发加密
#核心代码实现
#1. 浏览器环境与反检测Hook(JS文件:env.js)
window = global;
console_log = console.log;
// -------------------------- 第一步:函数Native伪装 --------------------------
(() => {
'use strict';
const $toString = Function.toString;
// 随机生成Symbol,避免冲突
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的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 --------------------------
// 必须伪装的定时器/异步API
setInterval = function setInterval(){}; safefunction(setInterval);
setTimeout = function setTimeout(){}; safefunction(setTimeout);
window.requestAnimationFrame = function requestAnimationFrame(){}; safefunction(window.requestAnimationFrame);
window.addEventListener = function addEventListener(){}; safefunction(window.addEventListener);
// 窗口/屏幕尺寸(尽量匹配真实浏览器)
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');
// -------------------------- 第五步:动态代理兜底(按需补全bdms访问的属性) --------------------------
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代码保存为bdms.js
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() {
// 加密后a_bogus会挂载到window上
console_log('触发XHR.send,等待加密...');
};
safefunction(XMLHttpRequest.prototype.send);
function get_ab(fullUrlWithParams) {
const xhr = new XMLHttpRequest();
// bdms会读取这两个数组获取请求信息
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;
}#2. Python调用JS生成a_bogus
import requests
import execjs
from urllib.parse import urlencode
# 从抖音页面复制的请求头和Cookie(需替换为自己的有效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])#完整工作流程
- 准备bdms代码:从Chrome开发者工具
Sources面板搜索bdms.init,提取对应代码保存为bdms.js - 配置环境文件:替换
env.js和Python代码中的webid/sec_user_id/Cookie为自己的有效信息 - 运行Python脚本:调用JS生成a_bogus,发起API请求获取数据
#技术难点与解决方案
| 难点 | 解决方案 | 效果 |
|---|---|---|
函数toString检测 | 重写Function.prototype.toString,用随机Symbol存储伪造的native标签 | 完美绕过函数源检测 |
Object.prototype.toString.call环境检测 | 伪装全局对象的Symbol.toStringTag | 符合浏览器环境的类型判断 |
| 动态未覆盖的属性访问 | 用Proxy监听核心对象的属性访问,按需补全 | 减少环境补全的工作量 |
| 加密逻辑封装在SDK中 | 模拟XHR的bdmsInvokeList和invokeList触发加密 | 无需深入解析SDK的加密算法 |
#注意事项
- bdms代码更新:抖音会不定期更新bdms,需及时重新提取
- Cookie有效性:需使用登录后的Cookie,且不能频繁请求
- WebID和UserAgent匹配:WebID是根据UserAgent和设备信息生成的,需保持一致

