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

📅 2026/7/24 11:17:16 👁️ 阅读次数 📝 编程学习
Web逆向实战:Python复现抖音bd-ticket-guard-client-data加密参数

1. 项目概述:从点赞到逆向,一个典型的Web安全分析实战

最近在分析一些短视频平台的交互逻辑时,我发现了一个挺有意思的参数:bd-ticket-guard-client-data。这个参数通常出现在点赞、评论、关注等核心用户交互请求的请求头里,长得像一串加密后的天书。对于做数据采集、自动化测试或者单纯想研究其背后机制的朋友来说,理解并生成这个参数,就相当于拿到了一把打开特定功能大门的钥匙。这本质上是一个典型的Web逆向工程问题,涉及到对前端JavaScript代码,特别是其加密和混淆逻辑的分析与复现。今天,我就以Python为主要工具,带大家走一遍完整的分析、逆向和生成流程。这个过程不仅适用于这个特定参数,其思路和方法也能迁移到分析其他平台的类似加密参数上,比如X-Bogussign等。如果你对Python爬虫、JS逆向或者Web安全分析感兴趣,这篇手把手的实战记录应该能给你不少启发。

2. 核心思路与技术选型解析

2.1 目标拆解:我们要做什么?

我们的最终目标很明确:在不依赖真实浏览器环境或App的情况下,仅用Python代码,为一个指定的视频或用户ID,生成有效的、可被抖音服务端接受的bd-ticket-guard-client-data参数值。

要达到这个目标,我们需要解决几个核心问题:

  1. 定位参数生成逻辑:这个参数是在哪里、由哪段JavaScript代码生成的?是纯前端计算,还是需要与后端交互?
  2. 理解算法与依赖:生成算法是什么?它依赖哪些输入(如用户信息、视频ID、时间戳、设备指纹等)?这些输入是如何获取和组合的?
  3. 算法复现与脱钩:能否将前端的JavaScript算法逻辑,用Python完整、准确地复现出来?需要模拟哪些浏览器环境或全局对象?
  4. 动态对抗处理:该参数是否有时效性?是否会随着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请求。
      • execjsPyExecJS:用于在Python中执行JavaScript代码。这是连接Python和前端JS算法的桥梁。execjs是现在更主流的选择。
      • json,time,random,hashlib:用于处理数据、生成时间戳、随机数和基础哈希,这些往往是加密算法的输入组成部分。

注意:逆向工程涉及对软件运行机制的分析,请务必仅用于个人学习、安全研究和授权测试。未经授权对他人服务进行大规模自动化访问可能违反服务条款,甚至相关法律法规。

2.3 通用逆向流程方法论

无论目标参数叫什么名字,逆向的通用流程是相通的,可以总结为“抓、寻、析、扣、补、测”六步法:

  1. :通过抓包工具,捕获包含目标参数(bd-ticket-guard-client-data)的请求。记录下完整的URL、请求头、请求体(Payload)。同时,注意观察同一个会话中,是否有其他前置请求返回了关键信息(如token、密钥等)。
  2. :在浏览器开发者工具的Sources面板中,全局搜索这个参数名(bd-ticket-guard-client-data)或其值的一部分。如果代码被混淆,直接搜索可能无果,那就搜索它可能出现的上下文关键词,如setRequestHeaderheadersbd-ticket等。也可以尝试在Initiator调用栈中寻找线索。
  3. :找到疑似生成该参数的函数后,通过断点调试、控制台打印、跟踪变量值变化等方式,分析该函数的输入(参数)、输出以及内部逻辑。关键是要理清它依赖了哪些外部变量或函数调用。
  4. :将分析清楚的JavaScript函数及其所有依赖(包括它调用的其他函数、使用的全局对象、浏览器特定API等)提取出来,形成一个独立的JS文件。这个过程就像把一颗大树连根拔起,要确保根须(依赖)完整。
  5. :由于扣出来的JS代码可能依赖浏览器环境(如windowdocumentnavigator),我们需要在Node.js或Python的execjs环境中,模拟(Mock)这些环境,让代码能够正常运行。这就是常说的“补环境”。
  6. :用Python调用处理好的JS代码,传入已知的输入(如视频ID),生成参数值。然后用这个值去构造请求,发送给服务器,验证是否能够成功(例如点赞成功)。如果失败,返回第3步进行调试。

3. 实战:定位与逆向bd-ticket-guard-client-data

3.1 第一步:抓包与初步观察

我使用Charles配置好手机代理,打开抖音App,给一个视频点赞。在Charles的请求列表中,很快就能找到发送点赞的请求,其URL通常类似于https://www.douyin.com/aweme/v1/web/aweme/favorite/这样的模式。

查看这个请求的Headers,目标参数bd-ticket-guard-client-data赫然在列。它的值是一长串看似随机的字符,像是Base64编码后的密文。同时,我注意到请求头里通常还有X-BogusmsToken等同样令人头疼的参数。这表明抖音的客户端防护是一个组合拳,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:0

3.2 第二步:寻找生成位置

由于移动端App的代码难以直接调试,我转向抖音的网页端。很多情况下,Web端和App端的核心加密逻辑是相似甚至相同的。打开抖音网页版,使用浏览器开发者工具。

在点赞一个网页视频时,同样捕获到网络请求。这次,我可以利用浏览器强大的调试功能。在Network面板找到点赞请求,右键点击,选择Initiator标签页,这里显示了是哪个脚本文件发起了这个请求。沿着调用栈向上查找,通常能找到负责设置请求头的函数。

更直接的方法是进行全局搜索。在Sources面板按Ctrl+Shift+F,在整个项目代码中搜索bd-ticket-guard-client-data。如果代码未被重度混淆,可能会直接找到类似headers['bd-ticket-guard-client-data'] = someFunction(...)的赋值语句。如果搜索不到,可以尝试搜索setRequestHeader,因为设置请求头通常是通过XMLHttpRequestfetchsetRequestHeader方法完成的。

经过一番搜索和调用栈分析,我最终定位到了一个关键的JavaScript文件,名字通常经过混淆,比如vendor.xxxxxx.jsindex.xxxxxx.js。在里面找到了一个函数,我们暂且称它为function generateBdTicketGuardData(params)

3.3 第三步:深入分析与算法剖析

在Sources面板中给这个generateBdTicketGuardData函数打上断点,再次触发点赞操作,代码执行会在断点处暂停。

这时,我们需要在调试器中仔细观察:

  1. 输入 (params):查看传入这个函数的参数是什么。通常是一个对象,里面包含了当前请求的上下文信息,比如URL路径、请求方法(GET/POST)、时间戳、设备指纹的一部分、用户令牌(token)等。把这些参数的结构和示例值完整地记录下来
  2. 内部逻辑:单步执行(F10),观察函数内部调用了哪些其他函数,经历了怎样的处理流程。常见的流程包括:
    • 信息收集:从windownavigatorperformance等浏览器API获取设备信息、屏幕分辨率、时间戳、内存状态等,组合成一个原始数据对象。
    • 序列化:将数据对象转换为JSON字符串。 *.加密/编码:对字符串进行某种加密或编码。观察是否调用了CryptoJSbtoaJSON.stringify后调用了encodeURIComponent,或者调用了一些自定义的、名字被混淆的加密函数(如_0x123abc)。
    • 添加校验:可能在加密数据的基础上,再拼接一些其他信息(如固定字符串、版本号),然后计算一个MD5或SHA256的签名附在后面。
  3. 输出:最终return的值,是否就是我们看到的bd-ticket-guard-client-data的值?对比一下。

通过反复调试和日志输出,我梳理出了该参数的大致生成逻辑(以下为模拟逻辑,真实算法更复杂):

  1. 收集一个包含urltimestampuser_agentscreen_widthscreen_height等字段的dataObj
  2. dataObj按特定键顺序排序后,序列化为JSON字符串jsonStr
  3. jsonStr进行AES加密(密钥可能是固定的,也可能从某个接口动态获取)。加密结果通常是二进制数据。
  4. 将二进制加密结果进行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默认会使用JScript(IE引擎),但功能有限。推荐安装Node.js。安装后,execjs会自动优先使用Node.js作为运行时。

验证环境:

import execjs print(execjs.get().name) # 应该输出 'Node.js (V8)'

4.2 处理环境依赖与“补环境”

我们的bd_ticket_guard.js很可能依赖浏览器特有的对象,比如windowdocumentnavigatorlocationbtoa/atob等。在Node.js或无头环境中,这些对象是不存在的,直接运行会报错ReferenceError: window is not defined

“补环境”就是在JS代码执行前,在全局作用域模拟这些对象。有两种主要方式:

  1. 在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)下运行,所以可以使用。这正体现了“补环境”需要针对目标运行时。

  2. 在Python调用时注入:将模拟环境的JS代码作为字符串,与核心算法代码拼接后,再交给execjs编译。

    import execjs # 读取核心算法代码 with open('bd_ticket_guard.js', 'r', encoding='utf-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_path='bd_ticket_guard.js'): """初始化,加载并编译JS代码""" with open(js_file_path, 'r', encoding='utf-8') as f: js_code = f.read() # 补环境的代码可能已集成在js_file_path中,如果没有,需要在此处拼接 self.ctx = execjs.compile(js_code) print(f"JS运行时: {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, headers=headers, data=data, timeout=10) 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 逆向与复现过程中的典型问题

  1. JS代码高度混淆,无法阅读

    • 技巧:不要试图完全读懂混淆后的变量名。关注控制流数据流。在调试器中,观察函数输入输出的具体值,以及关键变量在断点处的值。你可以通过“黑盒测试”来推断函数功能:固定其他输入,只改变一个输入,看输出如何变化。
    • 工具:可以尝试使用反混淆工具(如de4js在线工具或ast解析库),但效果因混淆方案而异。对于简单的字符串数组映射混淆,在Console里执行相关还原函数往往能直接看到原始字符串。
  2. 依赖了未扣全的函数或外部变量

    • 现象:在Node.js或execjs中运行时报错xxx is not definedxxx is not a function
    • 解决:回到浏览器调试环境,在Console里输入报错的变量或函数名,查看它的定义。然后将其定义代码也扣出来。这是一个递归的过程,直到所有依赖都被满足。
  3. 生成的参数服务器不认可

    • 原因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环境下的输出是否每一步都相同。
  4. execjs执行效率低下或内存泄漏

    • 对于复杂的JS代码或高频调用,每次都用execjs.compilecall可能较慢。
    • 优化:尽量将execjs.compile只执行一次(如在类初始化时),然后多次调用call方法。确保JS代码本身没有内存泄漏。对于极其复杂的计算,可以考虑使用PyMiniRacer(V8引擎的Python绑定)以获得原生性能,但配置更复杂。

5.2 参数更新与长期维护策略

bd-ticket-guard-client-data这类防护参数,其生成算法很可能随着抖音前端的更新而改变。

  • 监控与发现:定期(如每周)运行你的脚本,检查点赞等操作是否依然成功。如果开始大量失败(403错误等),很可能算法已更新。
  • 快速定位:一旦失败,立即重新进行抓包和搜索流程。通常新算法的代码位置或函数名可能变化,但搜索关键词(如bd-ticket-guard)或观察网络请求的调用栈仍然是有效的。
  • 模块化设计:将参数生成器设计成独立的模块或类。当算法更新时,你只需要替换或更新这个模块内部的JS代码和生成逻辑,而不需要改动主业务流程代码。
  • 降级方案:对于学习研究目的,可以考虑在自动化脚本中集成一个“手动更新”机制。当检测到失败时,提示用户需要重新获取最新的JS代码片段。

5.3 安全与合规再强调

我必须再次强调,所有逆向工程行为都应严格限定在个人学习、安全研究、授权测试的范围内。未经平台明确许可,利用自动化脚本进行大规模点赞、刷量、爬取非公开数据等行为:

  1. 违反抖音用户协议和服务条款,可能导致账号被封禁。
  2. 可能对平台服务器造成不必要的负担,影响其他用户正常使用。
  3. 在部分司法管辖区可能涉及法律风险

本篇文章分享的技术思路和方法,旨在帮助开发者和安全研究人员理解现代Web应用的安全机制和客户端加密原理,提升自身的技术分析能力。请务必以负责任的态度使用这些知识。