Web逆向实战:Python复现抖音bd-ticket-guard-client-data加密参数

发布时间:2026/7/24 11:08:29
Web逆向实战:Python复现抖音bd-ticket-guard-client-data加密参数 1. 项目概述从点赞到逆向一个典型的Web安全分析实战最近在分析一些短视频平台的交互逻辑时我发现了一个挺有意思的参数bd-ticket-guard-client-data。这个参数通常出现在点赞、评论、关注等核心用户交互请求的请求头里长得像一串加密后的天书。对于做数据采集、自动化测试或者单纯想研究其背后机制的朋友来说理解并生成这个参数就相当于拿到了一把打开特定功能大门的钥匙。这本质上是一个典型的Web逆向工程问题涉及到对前端JavaScript代码特别是其加密和混淆逻辑的分析与复现。今天我就以Python为主要工具带大家走一遍完整的分析、逆向和生成流程。这个过程不仅适用于这个特定参数其思路和方法也能迁移到分析其他平台的类似加密参数上比如X-Bogus、sign等。如果你对Python爬虫、JS逆向或者Web安全分析感兴趣这篇手把手的实战记录应该能给你不少启发。2. 核心思路与技术选型解析2.1 目标拆解我们要做什么我们的最终目标很明确在不依赖真实浏览器环境或App的情况下仅用Python代码为一个指定的视频或用户ID生成有效的、可被抖音服务端接受的bd-ticket-guard-client-data参数值。要达到这个目标我们需要解决几个核心问题定位参数生成逻辑这个参数是在哪里、由哪段JavaScript代码生成的是纯前端计算还是需要与后端交互理解算法与依赖生成算法是什么它依赖哪些输入如用户信息、视频ID、时间戳、设备指纹等这些输入是如何获取和组合的算法复现与脱钩能否将前端的JavaScript算法逻辑用Python完整、准确地复现出来需要模拟哪些浏览器环境或全局对象动态对抗处理该参数是否有时效性是否会随着App版本或前端代码更新而改变如何构建一个相对稳定的生成方案2.2 技术栈与工具选型工欲善其事必先利其器。针对以上问题我选择了以下工具链它们组合起来能覆盖从抓包、调试到代码复现的全流程网络抓包与分析工具Charles / Fiddler / mitmproxy用于拦截和查看移动端App抖音发出的HTTPS请求这是定位目标参数的起点。我更喜欢mitmproxy因为它的脚本化能力更强但Charles的UI对新手更友好。浏览器开发者工具对于Web端如抖音网页版直接使用Chrome或Edge的Network面板和Sources面板即可。逆向分析核心工具Node.js这是关键。很多前端加密库本身就是用Node.js写的或者其运行环境与Node.js高度兼容。我们可以直接尝试在Node.js环境中运行定位到的JS代码片段进行单步调试和逻辑验证这比纯靠人眼阅读混淆代码高效得多。浏览器Console用于快速执行代码片段、查看对象结构、验证函数输出是动态分析不可或缺的一环。Python实现环境Python 3.8我们的主战场。选择较新的版本以确保对各类现代库的良好支持。PyCharm / VSCode任选其一作为IDE。VSCode轻量且插件丰富PyCharm在项目管理和调试方面更强大。关键库requests用于发送最终的HTTP请求。execjs或PyExecJS用于在Python中执行JavaScript代码。这是连接Python和前端JS算法的桥梁。execjs是现在更主流的选择。json,time,random,hashlib用于处理数据、生成时间戳、随机数和基础哈希这些往往是加密算法的输入组成部分。注意逆向工程涉及对软件运行机制的分析请务必仅用于个人学习、安全研究和授权测试。未经授权对他人服务进行大规模自动化访问可能违反服务条款甚至相关法律法规。2.3 通用逆向流程方法论无论目标参数叫什么名字逆向的通用流程是相通的可以总结为“抓、寻、析、扣、补、测”六步法抓通过抓包工具捕获包含目标参数bd-ticket-guard-client-data的请求。记录下完整的URL、请求头、请求体Payload。同时注意观察同一个会话中是否有其他前置请求返回了关键信息如token、密钥等。寻在浏览器开发者工具的Sources面板中全局搜索这个参数名bd-ticket-guard-client-data或其值的一部分。如果代码被混淆直接搜索可能无果那就搜索它可能出现的上下文关键词如setRequestHeader、headers、bd-ticket等。也可以尝试在Initiator调用栈中寻找线索。析找到疑似生成该参数的函数后通过断点调试、控制台打印、跟踪变量值变化等方式分析该函数的输入参数、输出以及内部逻辑。关键是要理清它依赖了哪些外部变量或函数调用。扣将分析清楚的JavaScript函数及其所有依赖包括它调用的其他函数、使用的全局对象、浏览器特定API等提取出来形成一个独立的JS文件。这个过程就像把一颗大树连根拔起要确保根须依赖完整。补由于扣出来的JS代码可能依赖浏览器环境如window、document、navigator我们需要在Node.js或Python的execjs环境中模拟Mock这些环境让代码能够正常运行。这就是常说的“补环境”。测用Python调用处理好的JS代码传入已知的输入如视频ID生成参数值。然后用这个值去构造请求发送给服务器验证是否能够成功例如点赞成功。如果失败返回第3步进行调试。3. 实战定位与逆向bd-ticket-guard-client-data3.1 第一步抓包与初步观察我使用Charles配置好手机代理打开抖音App给一个视频点赞。在Charles的请求列表中很快就能找到发送点赞的请求其URL通常类似于https://www.douyin.com/aweme/v1/web/aweme/favorite/这样的模式。查看这个请求的Headers目标参数bd-ticket-guard-client-data赫然在列。它的值是一长串看似随机的字符像是Base64编码后的密文。同时我注意到请求头里通常还有X-Bogus、msToken等同样令人头疼的参数。这表明抖音的客户端防护是一个组合拳bd-ticket-guard-client-data只是其中一环。关键记录信息请求URL:https://www.douyin.com/aweme/v1/web/aweme/favorite/请求方法: POST重要请求头:bd-ticket-guard-client-data:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...(很长的一段)Content-Type:application/x-www-form-urlencoded请求体 (Form Data):aweme_id:7315834567890123456(视频ID)action:1(1代表点赞0代表取消)channel_id:03.2 第二步寻找生成位置由于移动端App的代码难以直接调试我转向抖音的网页端。很多情况下Web端和App端的核心加密逻辑是相似甚至相同的。打开抖音网页版使用浏览器开发者工具。在点赞一个网页视频时同样捕获到网络请求。这次我可以利用浏览器强大的调试功能。在Network面板找到点赞请求右键点击选择Initiator标签页这里显示了是哪个脚本文件发起了这个请求。沿着调用栈向上查找通常能找到负责设置请求头的函数。更直接的方法是进行全局搜索。在Sources面板按CtrlShiftF在整个项目代码中搜索bd-ticket-guard-client-data。如果代码未被重度混淆可能会直接找到类似headers[bd-ticket-guard-client-data] someFunction(...)的赋值语句。如果搜索不到可以尝试搜索setRequestHeader因为设置请求头通常是通过XMLHttpRequest或fetch的setRequestHeader方法完成的。经过一番搜索和调用栈分析我最终定位到了一个关键的JavaScript文件名字通常经过混淆比如vendor.xxxxxx.js或index.xxxxxx.js。在里面找到了一个函数我们暂且称它为function generateBdTicketGuardData(params)。3.3 第三步深入分析与算法剖析在Sources面板中给这个generateBdTicketGuardData函数打上断点再次触发点赞操作代码执行会在断点处暂停。这时我们需要在调试器中仔细观察输入 (params)查看传入这个函数的参数是什么。通常是一个对象里面包含了当前请求的上下文信息比如URL路径、请求方法GET/POST、时间戳、设备指纹的一部分、用户令牌token等。把这些参数的结构和示例值完整地记录下来。内部逻辑单步执行F10观察函数内部调用了哪些其他函数经历了怎样的处理流程。常见的流程包括信息收集从window、navigator、performance等浏览器API获取设备信息、屏幕分辨率、时间戳、内存状态等组合成一个原始数据对象。序列化将数据对象转换为JSON字符串。 *.加密/编码对字符串进行某种加密或编码。观察是否调用了CryptoJS、btoa、JSON.stringify后调用了encodeURIComponent或者调用了一些自定义的、名字被混淆的加密函数如_0x123abc。添加校验可能在加密数据的基础上再拼接一些其他信息如固定字符串、版本号然后计算一个MD5或SHA256的签名附在后面。输出最终return的值是否就是我们看到的bd-ticket-guard-client-data的值对比一下。通过反复调试和日志输出我梳理出了该参数的大致生成逻辑以下为模拟逻辑真实算法更复杂收集一个包含url、timestamp、user_agent、screen_width、screen_height等字段的dataObj。将dataObj按特定键顺序排序后序列化为JSON字符串jsonStr。对jsonStr进行AES加密密钥可能是固定的也可能从某个接口动态获取。加密结果通常是二进制数据。将二进制加密结果进行Base64编码得到最终的bd-ticket-guard-client-data值。3.4 第四步关键代码提取与“扣”代码分析清楚后就需要把相关的JavaScript函数“扣”出来。这不仅仅是复制generateBdTicket-guard-client-data这一个函数还要把它内部调用的所有子函数、它依赖的全局变量或对象都一并找到并复制出来。例如如果它内部调用了_0x123abc.encrypt()那么你必须找到_0x123abc这个对象的定义并把它也复制出来。如果它使用了CryptoJS.AES那么你需要把CryptoJS库的相关部分通常是核心的加密模块也引入。实操心得一个非常实用的技巧是在浏览器Console中尝试直接执行generateBdTicketGuardData.toString()可以快速得到这个函数的完整源码包括其内部定义的函数。对于它依赖的、在全局作用域的其他函数也可以尝试用同样的方法获取。然后将所有这些代码片段按照它们定义的顺序整合到一个单独的.js文件中。最终我得到了一个名为bd_ticket_guard.js的文件其结构大致如下// bd_ticket_guard.js // 可能包含一些全局变量定义或polyfill var _0x123abc (function() { // ... 某个复杂的、被混淆的加密模块 ... })(); // 工具函数例如排序对象键的函数 function _0xdef456(obj) { // ... } // 核心的生成函数 function generateBdTicketGuardClientData(requestInfo) { // 1. 收集和准备数据 var baseData { url: requestInfo.url, ts: Date.now(), // ... 其他字段 }; // 2. 排序和序列化 var sortedStr _0xdef456(baseData); // 3. 加密 (假设使用上面定义的 _0x123abc) var encryptedData _0x123abc.encrypt(sortedStr, some_secret_key); // 4. 编码并返回 return btoa(encryptedData); // 或使用其他Base64编码方式 } // 将函数暴露给外部以便Node.js或execjs调用 if (typeof module ! undefined module.exports) { module.exports generateBdTicketGuardClientData; }4. Python环境下的复现与集成4.1 搭建Python执行JS的环境“扣”出来的JS代码不能直接在Python里运行我们需要一个桥梁。这里选择execjs库。首先安装pip install PyExecJS同时确保你的系统里有一个JavaScript运行时环境。在Windows上execjs默认会使用JScriptIE引擎但功能有限。推荐安装Node.js。安装后execjs会自动优先使用Node.js作为运行时。验证环境import execjs print(execjs.get().name) # 应该输出 Node.js (V8)4.2 处理环境依赖与“补环境”我们的bd_ticket_guard.js很可能依赖浏览器特有的对象比如window、document、navigator、location、btoa/atob等。在Node.js或无头环境中这些对象是不存在的直接运行会报错ReferenceError: window is not defined。“补环境”就是在JS代码执行前在全局作用域模拟这些对象。有两种主要方式在JS文件内部模拟在bd_ticket_guard.js文件的开头添加模拟代码。// 补环境 - 在bd_ticket_guard.js开头添加 if (typeof window undefined) { global.window { navigator: { userAgent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ..., platform: Win32, // ... 其他属性 }, screen: { width: 1920, height: 1080 }, location: { href: https://www.douyin.com } }; global.document {}; // 模拟 btoa global.btoa function(str) { // 这里可以用Buffer模拟但注意Buffer是Node.js的 return Buffer.from(str).toString(base64); }; global.atob function(b64Encoded) { return Buffer.from(b64Encoded, base64).toString(); }; } // ... 原有的 generateBdTicketGuardClientData 函数定义 ...注意Buffer是Node.js的API在浏览器JS中不存在。因为我们是在Node.js环境通过execjs下运行所以可以使用。这正体现了“补环境”需要针对目标运行时。在Python调用时注入将模拟环境的JS代码作为字符串与核心算法代码拼接后再交给execjs编译。import execjs # 读取核心算法代码 with open(bd_ticket_guard.js, r, encodingutf-8) as f: core_js_code f.read() # 准备环境模拟代码 env_js_code // 模拟 window, document 等 const window this; window.navigator { userAgent: Mozilla/5.0 ..., platform: Win32 }; window.screen { width: 1920, height: 1080 }; // ... 其他模拟 // 将核心代码中的函数挂载到全局 core_js_code ctx execjs.compile(env_js_code) bd_ticket ctx.call(generateBdTicketGuardClientData, request_info_dict)实操心得“补环境”是个细致活常常需要根据运行时的报错信息缺什么补什么。最有效的方法是在Node.js命令行里直接运行你的JS文件根据报错一行行添加缺失的全局变量或函数模拟。4.3 构建完整的Python请求函数现在我们可以将JS生成函数集成到完整的Python请求流程中。import execjs import requests import time import json class DouyinTicketGuardGenerator: def __init__(self, js_file_pathbd_ticket_guard.js): 初始化加载并编译JS代码 with open(js_file_path, r, encodingutf-8) as f: js_code f.read() # 补环境的代码可能已集成在js_file_path中如果没有需要在此处拼接 self.ctx execjs.compile(js_code) print(fJS运行时: {execjs.get().name}) def generate_bd_ticket(self, request_info): 调用JS函数生成 bd-ticket-guard-client-data :param request_info: dict, 包含url、method、timestamp等信息的字典 :return: str, 生成的参数值 try: # 确保传入的参数格式与JS函数期望的一致 # 例如JS函数可能需要一个包含特定字段的对象 ticket_data self.ctx.call(generateBdTicketGuardClientData, request_info) return ticket_data except Exception as e: print(f生成bd-ticket-guard-client-data失败: {e}) # 这里可以添加更详细的错误日志比如打印传入的request_info return None def like_video(self, aweme_id, session_cookies): 执行点赞操作的完整示例 :param aweme_id: 视频ID :param session_cookies: requests.cookies.RequestsCookieJar 对象包含登录态如sessionid :return: bool, 是否成功 # 1. 准备请求基本信息 url https://www.douyin.com/aweme/v1/web/aweme/favorite/ # 通常还需要一个时间戳可能JS函数内部会生成也可能需要外部传入 current_ts int(time.time() * 1000) # 毫秒时间戳 # 2. 构造传给JS生成函数的信息 # 这个结构需要根据你逆向分析的结果来定 request_info_for_js { url: url, method: POST, timestamp: current_ts, userAgent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ..., # 可能还需要设备指纹、屏幕信息等这些可能从固定的配置或前一个接口获取 deviceId: 模拟的设备ID, screenWidth: 1920, screenHeight: 1080, } # 3. 生成加密参数 bd_ticket_guard self.generate_bd_ticket(request_info_for_js) if not bd_ticket_guard: print(生成参数失败终止操作) return False # 4. 构造请求头 headers { User-Agent: request_info_for_js[userAgent], Referer: https://www.douyin.com/, Content-Type: application/x-www-form-urlencoded, # 关键添加生成的参数 bd-ticket-guard-client-data: bd_ticket_guard, # 通常还需要其他参数如X-Bogus这里假设我们已经有了生成它的方法 # x-bogus: generate_x_bogus(...), } # 5. 构造请求体 data { aweme_id: aweme_id, action: 1, # 1点赞0取消 channel_id: 0, # 可能还有其他固定或动态字段 } # 6. 发送请求 session requests.Session() session.cookies.update(session_cookies) # 注入登录cookies try: resp session.post(url, headersheaders, datadata, timeout10) resp.raise_for_status() # 检查HTTP错误 result resp.json() # 根据实际接口返回判断成功与否 if result.get(status_code) 0: print(f视频 {aweme_id} 点赞成功) return True else: print(f点赞失败服务器返回: {result}) return False except requests.exceptions.RequestException as e: print(f网络请求异常: {e}) return False except json.JSONDecodeError as e: print(f响应解析失败: {e}, 响应文本: {resp.text[:200]}) return False # 使用示例 if __name__ __main__: generator DouyinTicketGuardGenerator(bd_ticket_guard.js) # 假设你已经通过其他方式登录并获取了cookies # cookies {sessionid: your_sessionid_here, ...} # success generator.like_video(7315834567890123456, cookies)5. 常见问题、调试技巧与长期维护5.1 逆向与复现过程中的典型问题JS代码高度混淆无法阅读技巧不要试图完全读懂混淆后的变量名。关注控制流和数据流。在调试器中观察函数输入和输出的具体值以及关键变量在断点处的值。你可以通过“黑盒测试”来推断函数功能固定其他输入只改变一个输入看输出如何变化。工具可以尝试使用反混淆工具如de4js在线工具或ast解析库但效果因混淆方案而异。对于简单的字符串数组映射混淆在Console里执行相关还原函数往往能直接看到原始字符串。依赖了未扣全的函数或外部变量现象在Node.js或execjs中运行时报错xxx is not defined或xxx is not a function。解决回到浏览器调试环境在Console里输入报错的变量或函数名查看它的定义。然后将其定义代码也扣出来。这是一个递归的过程直到所有依赖都被满足。生成的参数服务器不认可原因1输入不一致。检查传给JS生成函数的request_info字典是否和浏览器中调试时看到的完全一致字段名、字段值、数据类型。特别注意时间戳的精度秒还是毫秒。原因2环境模拟不充分。JS代码可能依赖performance.now()、navigator.plugins等更细致的浏览器属性。你需要更精确地模拟这些属性。在Node.js中直接运行你的JS文件根据报错信息逐一补充。原因3算法有动态因素。密钥或盐salt可能不是固定的而是从某个接口动态获取的。你需要分析在生成bd-ticket-guard-client-data之前是否有其他请求返回了关键信息并在生成时将其作为输入。调试方法在Python生成参数后与浏览器真实请求抓包得到的参数进行逐字符对比。如果可能在JS生成函数内部多打一些console.log将中间结果输出对比浏览器环境和Node.js环境下的输出是否每一步都相同。execjs执行效率低下或内存泄漏对于复杂的JS代码或高频调用每次都用execjs.compile和call可能较慢。优化尽量将execjs.compile只执行一次如在类初始化时然后多次调用call方法。确保JS代码本身没有内存泄漏。对于极其复杂的计算可以考虑使用PyMiniRacerV8引擎的Python绑定以获得原生性能但配置更复杂。5.2 参数更新与长期维护策略像bd-ticket-guard-client-data这类防护参数其生成算法很可能随着抖音前端的更新而改变。监控与发现定期如每周运行你的脚本检查点赞等操作是否依然成功。如果开始大量失败403错误等很可能算法已更新。快速定位一旦失败立即重新进行抓包和搜索流程。通常新算法的代码位置或函数名可能变化但搜索关键词如bd-ticket-guard或观察网络请求的调用栈仍然是有效的。模块化设计将参数生成器设计成独立的模块或类。当算法更新时你只需要替换或更新这个模块内部的JS代码和生成逻辑而不需要改动主业务流程代码。降级方案对于学习研究目的可以考虑在自动化脚本中集成一个“手动更新”机制。当检测到失败时提示用户需要重新获取最新的JS代码片段。5.3 安全与合规再强调我必须再次强调所有逆向工程行为都应严格限定在个人学习、安全研究、授权测试的范围内。未经平台明确许可利用自动化脚本进行大规模点赞、刷量、爬取非公开数据等行为违反抖音用户协议和服务条款可能导致账号被封禁。可能对平台服务器造成不必要的负担影响其他用户正常使用。在部分司法管辖区可能涉及法律风险。本篇文章分享的技术思路和方法旨在帮助开发者和安全研究人员理解现代Web应用的安全机制和客户端加密原理提升自身的技术分析能力。请务必以负责任的态度使用这些知识。