资讯动态

TikTok滑块验证码逆向实战:从fp参数到captchabody加密全解析

发布时间:2026/8/9 6:42:36 来源:尧图企业网站定制
1. 项目概述与核心挑战最近在对接一些海外社交媒体数据时不可避免地遇到了TikTok的滑块验证码也就是他们内部称为verifyV2的这套机制。这玩意儿可以说是目前市面上防护等级相当高的验证码之一集成了设备指纹fp、行为验证和动态加密于一体。很多朋友在尝试自动化操作比如数据采集、批量管理或者研究接口时都会卡在这一步。网上关于verifyV2的完整逆向流程资料比较零散大多只讲了某个片段比如怎么生成fp或者怎么解captchabody但中间的逻辑串联和关键细节往往语焉不详。我花了相当一段时间去啃这块硬骨头从最开始的请求拦截、参数定位到一步步跟踪JavaScript执行堆栈最终把从触发滑块到最终提交验证的完整链条给跑通了。这个过程不仅仅是找到几个加密函数那么简单更关键的是理解TikTok如何将设备信息、用户行为和环境特征编织成一个动态的、一次性的验证凭证。这篇文章我就把自己实战逆向TikTok verifyV2滑块验证码的全过程从fp参数生成到captchabody加密的完整逻辑掰开揉碎了讲清楚。无论你是做爬虫逆向的工程师还是对前端安全机制感兴趣的研究者相信都能从中获得可以直接复现的路径和避坑经验。整个流程的核心可以概括为三个关键阶段首先是验证码触发后服务端返回的初始参数获取包括至关重要的fp和detail其次是滑块验证所需的图片、轨道数据等资源的获取最后也是逆向的重点即用户完成滑动操作后如何构造并加密那个包含滑动轨迹、时间戳等信息的captchabody参数并提交验证。下面我们就按照这个顺序一步步拆解。2. 验证码触发与初始参数fp detail获取解析当你以自动化方式如使用Selenium、Playwright或无头浏览器访问TikTok特定接口或页面触发频率或行为策略时服务端会在响应中返回验证码挑战。通常你会在一个接口例如登录、发帖、频繁刷新列表的接口的响应中收到一个状态码或特定的错误信息引导你到一个验证码页面。但更常见的情况是在关键的XHR或Fetch请求的响应体里直接包含了验证码所需的初始数据。2.1 识别验证码响应首先你需要在你使用的浏览器开发者工具DevTools的“网络”Network选项卡中筛选XHR或Fetch请求。寻找那些返回了类似滑块验证码相关数据的请求。一个典型的成功响应指成功触发了验证码而非业务请求成功可能包含如下结构{ code: 10000, message: verify, data: { verify_event: verifyV2, fp: verify_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, detail: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...很长的一段JWT字符串, verify_url: https://verification-va.tiktok.com/... } }这里有几个关键字段verify_event: 明确指出了验证类型是verifyV2。fp: 这是一个verify_开头的字符串看起来像是一个验证票据的ID。它是后续所有验证请求的基石必须妥善保存。detail: 一个非常长的字符串它实际上是一个JWTJSON Web Token。这个token里封装了本次验证会话的初始信息、密钥材料或种子数据是客户端生成后续加密参数的核心依据。verify_url: 验证码处理服务器的地址。注意这个响应结构可能因TikTok的版本更新而微调但fp和detail这两个核心参数的名字相对稳定。你的逆向脚本必须能够动态地从响应中提取它们而不是写死。2.2 深入剖析detail(JWT) 参数detail参数是整个流程中的第一个加密点。它虽然以JWT形式存在通常由Header.Payload.Signature三部分组成用点号分隔但其Payload部分很可能被进一步加密或编码过并非明文JSON。解码尝试你可以先尝试用Base64Url解码JWT的第二部分Payload。在浏览器控制台或Node.js中// 假设 detail “eyJhbGc...中间省略.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c” let detail “你的detail字符串”; let parts detail.split(.); if (parts.length 3) { let payloadBase64 parts[1]; // Base64Url解码将 ‘-‘ 替换成 ‘’ ‘_’ 替换成 ‘/’并补足等号 let padded payloadBase64.replace(/-/g, ).replace(/_/g, /); while (padded.length % 4) { padded ; } let payloadJson atob(padded); // 浏览器环境 // 或 Buffer.from(padded, ‘base64’).toString(‘utf-8’) // Node.js环境 console.log(‘解码后的Payload:’, payloadJson); }如果解码后是一段乱码或不可读字符说明Payload被额外加密了。如果是一段JSON那么恭喜你你可能直接拿到了包含key、iv或种子值的信息。定位解密逻辑如果Payload被加密就需要在TikTok的前端JavaScript代码中寻找解密函数。打开开发者工具的“源代码”Sources面板使用全局搜索CtrlShiftF功能搜索与detail、JWT、decode、decrypt相关的关键词。更有效的方法是在触发验证码的页面找到处理验证码初始化的JavaScript文件通常文件名包含verify、captcha等然后设置断点进行动态调试。实操心得在我逆向的版本中detail的Payload部分使用了AES加密。解密所需的密钥key和初始化向量iv并非直接硬编码而是通过一个名为$_CCC的全局对象名称可能混淆中的某个方法结合当前页面的其他环境变量如window._下的某个属性动态计算出来的。这就需要你仔细跟踪detail参数被消费的代码位置通常它会被传入一个像JSON.parse(decrypt(detail.split(‘.’)[1]))这样的函数中。3. 滑块图片与验证数据获取流程拿到fp和解析后的detail信息后下一步是获取滑块验证的UI素材和核心数据。3.1 构造获取滑块资源的请求客户端会向verify_url指示的服务器地址发起一个GET请求以获取滑块拼图、背景图、缺口位置等信息。这个请求需要携带之前获取的参数。一个典型的请求URL格式可能如下GET https://verification-va.tiktok.com/captcha/get?langzhapp_nametiktok_webh5_sdk_version...sdk_version...iid...device_id...fpverify_xxxxxxxxxxxxdetaileyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...你需要重点关注并正确构造的查询参数包括fp: 之前获取的fp值。detail: 原始的detail字符串注意这里是原始的JWT不是解密后的。lang: 语言。app_name: 应用标识。device_id,iid: 设备ID和安装ID可以从页面全局变量或之前的请求中捕获。这个请求的响应将包含滑块验证的核心数据{ code: 0, message: success, data: { id: captcha_xxxxxxxxxxxxxx, // 本次验证会话的唯一ID mode: slide, question: { bg_image_width: 340, bg_image_height: 212, bg_image_url: https://p16-verify-va.ibyteimg.com/...bg.jpg, fg_image_width: 68, fg_image_height: 68, fg_image_url: https://p16-verify-va.ibyteimg.com/...fg.png, tip_y: 45 // 缺口在背景图中的y轴坐标有时是x轴 } } }id: 极其重要在最终提交验证结果时必须回传。bg_image_url和fg_image_url: 分别是背景大图和滑块拼图缺口部分的URL。你需要下载这两张图片。tip_y: 这给出了缺口位置的一个坐标。对于左右滑动的滑块tip_y可能实际代表缺口的x坐标变量名可能混淆。你需要结合图片实际尺寸来理解。有时这个值可能是偏移量需要计算。3.2 计算滑块缺口位置得到图片后自动化脚本需要计算出滑块需要移动到的准确位置。tip_y或实际是tip_x通常是一个参考值但直接使用可能不够精确或者TikTok会加入随机扰动。更可靠的方法是使用图像识别。下载图片使用requestsPython或axiosNode.js下载背景图和滑块图。图像处理与识别方法一模板匹配这是最常用的方法。使用OpenCV的matchTemplate函数将滑块拼图fg_image作为模板在背景图bg_image上进行滑动匹配寻找最相似的位置。匹配方法通常用TM_CCOEFF_NORMED。import cv2 import numpy as np # 读取图片 bg_img cv2.imread(‘background.jpg’ 0) # 灰度图 fg_img cv2.imread(‘slider.png’ 0) # 执行模板匹配 result cv2.matchTemplate(bg_img fg_img cv2.TM_CCOEFF_NORMED) min_val max_val min_loc max_loc cv2.minMaxLoc(result) # max_loc 是匹配度最高点的左上角坐标 (x y) top_left max_loc # 缺口中心的x坐标假设水平滑动 target_x top_left[0] fg_img.shape[1] // 2方法二边缘检测轮廓匹配如果模板匹配受干扰较大可以尝试先对两张图进行边缘检测如Canny算法再对边缘图进行模板匹配有时抗干扰能力更强。注意事项TikTok的背景图可能会有随机噪点、干扰线或轻微色差来对抗识别。在实际操作中可能需要对图片进行预处理如高斯模糊降噪、调整对比度或者尝试多种匹配方法的组合以提高识别成功率。计算出的target_x需要记录下来它是生成滑动轨迹的目标终点。4. captchabody 参数生成与加密全流程这是整个逆向过程中最复杂、最核心的部分。captchabody是一个加密后的字符串它封装了用户的所有滑动行为数据服务端通过解密它来验证滑动是否是“人类行为”。4.1 构建待加密的原始数据对象在滑动操作完成后前端会收集一系列数据构建成一个JavaScript对象。这个对象的结构需要你通过逆向submit或verify相关的API请求来还原。一个典型的结构可能如下let raw_captcha_data { “fp”: “verify_xxxxxxxxxxxx”, // 最初的fp “detail”: { /* 从detail JWT的payload中解密出来的部分关键数据 */ }, “captcha_id”: “captcha_xxxxxxxxxxxxxx”, // 从get接口获取的id “mode”: “slide”, “track”: [ // 滑动轨迹数组每个点是一个对象 { “x”: 0 “y”: 10 “t”: 1621234567890 “type”: “mousemove” } { “x”: 5 “y”: 12 “t”: 1621234567895 “type”: “mousemove” } // ... 几十到上百个轨迹点 { “x”: target_x “y”: 基准y “t”: 1621234567990 “type”: “mouseup” } ] “action”: “verify” “lang”: “zh”, “app_name”: “tiktok_web”, “os”: “Windows”, // ... 其他设备信息、浏览器指纹等 };轨迹track的生成是关键轨迹算法不能是简单的匀速运动。人类的滑动有加速、减速、轻微抖动和停顿。你需要模拟生成一条符合人类行为特征的轨迹。基础位移使用缓动函数如easeOutQuad、easeInOutCubic来计算每个时间点“应该”到达的位置。添加噪声在基础位移上添加随机的、小幅度的x和y轴抖动模拟手部不稳。随机停顿在轨迹中随机插入几个时间点其位移与上一个点相同模拟短暂的犹豫或卡顿。时间戳每个点的t是毫秒级时间戳需要是递增的且点与点之间的时间间隔在10ms~30ms之间随机波动。轨迹长度通常需要30到60个点太少会被认为是机器行为。4.2 定位加密函数与加密流程构建好raw_captcha_data对象后前端会调用一个加密函数将其转换为字符串通常是JSON.stringify然后进行加密最终生成captchabody。搜索加密入口在开发者工具中对最终提交验证的POST请求URL通常包含/captcha/verify打上“XHR/Fetch断点”。当请求发起时调用栈会暂停。在调用栈中向上查找寻找一个将raw_captcha_data对象作为参数传入的函数这个函数很可能就是加密的入口点。函数名可能是混淆的如$a、encrypt、_encode等。分析加密算法进入该函数后逐步调试。常见的加密模式是AES加密非常普遍。你会看到类似CryptoJS.AES.encrypt(JSON.stringify(data) key { iv: iv mode: CryptoJS.mode.CBC padding: CryptoJS.pad.Pkcs7 })的代码。关键是要找到key和iv的来源。它们很可能来自于第一步中detail的Payload解密后得到的某个字段或者是用那个字段作为种子通过一个固定的算法如SHA256衍生出来的。自定义加密或编码有时可能不是标准AES而是TikTok自定义的一套字节操作、位移、替换的算法。这就需要你耐心地跟读每一行代码将其用Python或Node.js重写出来。关键点密钥key/iv的生成逻辑这是逆向的难点。密钥往往不是明文传输的。在我的案例中流程是这样的解密detail的Payload得到一个对象里面包含一个seed字符串。将这个seed与一个硬编码在JavaScript中的常量字符串可能通过全局变量window._访问进行拼接。对拼接后的字符串取MD5或SHA256哈希取前16位或32位作为AES的key再取另外16位作为iv。这个硬编码的常量字符串可能需要通过搜索detail解密后对象中被访问的属性名或者搜索CryptoJS相关的初始化代码来找到。4.3 完整加密代码还原示例Python模拟假设我们通过逆向确定了加密方式是AES-128-CBCkey和iv来源于detail_payload[‘secret’]的MD5值前16字节和后16字节。import json import hashlib from Crypto.Cipher import AES from Crypto.Util.Padding import pad import base64 def generate_track(target_x duration2000): “”“模拟生成人类滑动轨迹”“” track [] start_time int(time.time() * 1000) points 40 # 轨迹点数量 for i in range(points): progress i / (points - 1) # 使用easeOutQuad缓动函数计算当前进度下的理论x位移 # easeOutQuad: t 1 - (1 - progress)**2 t 1 - (1 - progress) ** 2 theoretical_x int(target_x * t) # 添加随机抖动-2到2像素 jitter_x random.randint(-2 2) jitter_y random.randint(-1 1) # y轴抖动小一些 current_x theoretical_x jitter_x current_y 10 jitter_y # 假设基准y是10 # 时间戳非匀速模拟人类节奏 time_gap random.randint(15 35) # 15-35毫秒间隔 if i 0: current_t start_time else: current_t track[-1][‘t’] time_gap # 随机插入一个“停顿点”复制上一个点的位置 if i 10 and random.random() 0.1: # 10%的概率在10个点之后插入停顿 current_x track[-1][‘x’] current_y track[-1][‘y’] current_t track[-1][‘t’] random.randint(50 150) # 停顿50-150ms track.append({“x”: current_x “y”: current_y “t”: current_t “type”: “mousemove”}) # 最后添加一个mouseup事件在终点 track.append({“x”: target_x “y”: 10 “t”: track[-1][‘t’] 50 “type”: “mouseup”}) return track def encrypt_captchabody(raw_data detail_secret): “”“加密生成最终的captchabody参数”“” # 1. 根据逆向得到的逻辑生成key和iv # 假设逻辑是将detail_secret与字符串“tiktok_slide_fix”拼接后取MD5 seed_str detail_secret “tiktok_slide_fix” md5_hash hashlib.md5(seed_str.encode(‘utf-8’)).hexdigest() # 前16字节作为key后16字节作为iv key md5_hash[:32].encode(‘utf-8’) # 32 hex chars 16 bytes iv md5_hash[32:].encode(‘utf-8’) # 32 hex chars 16 bytes # 2. 将原始数据转为JSON字符串 json_str json.dumps(raw_data separators(‘’ ‘:’)) # 紧凑格式无空格 # 3. AES-128-CBC加密 PKCS7填充 cipher AES.new(key AES.MODE_CBC iviv) encrypted_bytes cipher.encrypt(pad(json_str.encode(‘utf-8’) AES.block_size)) # 4. Base64编码 captchabody base64.b64encode(encrypted_bytes).decode(‘utf-8’) return captchabody # 使用示例 detail_payload {“secret”: “abc123xyz”} # 从detail解密后得到 target_x 185 # 计算出的缺口中心x坐标 raw_data { “fp”: initial_fp “detail”: {“some_key”: detail_payload.get(“some_key”)} # 可能需要detail里的部分数据 “captcha_id”: captcha_id “mode”: “slide”, “track”: generate_track(target_x) “action”: “verify”, “lang”: “zh”, “app_name”: “tiktok_web”, “os”: “Windows”, } captchabody encrypt_captchabody(raw_data detail_payload[‘secret’])5. 提交验证与结果处理生成captchabody后就可以向验证接口发起最终的POST请求了。5.1 构造验证请求请求的URL通常是https://verification-va.tiktok.com/captcha/verify。 请求体Content-Type: application/json通常包含{ “fp”: “verify_xxxxxxxxxxxx” “detail”: “eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...” // 原始的detail JWT “captcha_id”: “captcha_xxxxxxxxxxxxxx” “captchabody”: “加密后的字符串...” “lang”: “zh”, “app_name”: “tiktok_web” }发送这个请求后服务端会返回验证结果。5.2 验证结果解析与后续操作成功的响应通常如下{ “code”: 0, “message”: “success”, “data”: { “verify”: true, “verify_token”: “v_token_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx” // 验证通过令牌 “risk”: “low” } }verify_token这是最重要的成果你需要将这个token带回最初触发验证码的那个业务请求比如登录请求。通常是以一个特定的请求头例如X-Verify-Token或者作为请求参数如verify_token附加到原请求上然后重试原请求。risk风险评估等级low表示通过。如果验证失败轨迹不自然、超时、captchabody解密失败等code可能为非0verify为false并可能伴随error信息如“trajectory abnormal”轨迹异常。6. 逆向实战中的常见问题与排查技巧在实际操作中你会遇到各种各样的问题。这里记录了几个最典型的坑和解决思路。6.1 环境依赖与动态参数捕获问题fp、device_id、iid等参数从哪里来直接写死很快会失效。解决这些参数通常在页面加载时由服务器渲染在HTML的script标签内或通过初始的XHR请求如/passport/web/login/)返回。使用无头浏览器如Playwright加载页面通过page.evaluate()执行JavaScript代码从window全局对象如window.INIT_PROPS、window.__M或cookie中提取这些参数是最可靠的方式。对于fp在触发验证码的响应中获取是最直接和准确的。6.2 JavaScript代码混淆与反调试问题TikTok的前端JS经过了严重的混淆变量名都是$a、$b并且可能设置了反调试无限debugger、debugger语句。解决反反调试在开发者工具的“源代码”面板找到反调试的代码段右键选择“Never pause here”或直接在该行设置条件断点false。也可以使用插件如Tampermonkey脚本注入重写setInterval、Function构造函数等。“Hook”关键函数对于混淆的代码不要试图完全还原。使用Charles、Fiddler或Mitmproxy等代理工具配合Python脚本在请求发出前动态修改参数进行测试也是一种“黑盒”测试思路。更高级的是使用Frida或PyExecJS直接注入并调用关键函数。关注网络请求很多时候加密函数的入口可以通过追踪最终提交请求的调用栈来定位即使代码混淆调用关系在调试器中是清晰的。6.3 轨迹模拟与加密算法更新问题轨迹生成算法已经足够拟人但验证还是失败。解决采集真实轨迹手动滑动几次验证码用开发者工具监听mousemove事件将真实的轨迹数据记录下来分析其位移、速度、加速度的分布规律然后调整你的生成算法去拟合这个分布。检查加密细节AES加密的modeCBC/ECB、paddingPkcs7/ZeroPadding、输出格式Base64/Hex必须完全一致。一个字符的错误都会导致服务端解密失败。确保你的key和iv的生成逻辑与前端完全一致包括字符串拼接的顺序、大小写、编码方式。留意版本变化TikTok会不定期更新其验证码机制。如果某天脚本突然全部失效首先要检查detail的获取和解析逻辑是否变化其次是captchabody的加密算法和密钥派生逻辑。保持对网络请求和前端JS文件的观察。6.4 请求签名与风控升级问题即使captchabody正确提交验证的请求本身可能还需要额外的签名如对URL参数、请求体进行HMAC-SHA256签名否则会被拒绝。解决仔细观察/captcha/verify这个POST请求的Headers看是否有X-Signature、X-Timestamp之类的自定义头。在调用栈中寻找在fetch或XMLHttpRequest.send之前对请求头或URL进行处理的函数。签名逻辑通常在这里。风控升级时可能会引入更复杂的设备指纹如WebGL、Canvas、AudioContext指纹、更频繁的环境检测。此时使用高度模拟真实浏览器的无头浏览器方案如Playwright配合真实Chrome比纯requests模拟的成功率会高很多但代价是资源消耗更大。逆向TikTok verifyV2滑块验证码是一个持续对抗的过程没有一劳永逸的解决方案。核心在于耐心地动态调试理解其数据流和加密链并将关键逻辑准确地用你的编程语言还原出来。这篇文章提供的流程和思路是一个完整的框架但具体的函数名、密钥生成公式需要你根据当时面对的版本进行实际分析。记住在逆向的世界里细节决定成败每一个字节都可能至关重要。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价