逆向Akamai Bot Manager:从F12调试到Node.js复现的完整实战

发布时间:2026/7/27 8:19:32
逆向Akamai Bot Manager:从F12调试到Node.js复现的完整实战 1. 项目概述一次与Akamai Bot Manager的正面交锋最近在做一个数据采集项目时遇到了一个老朋友也是很多爬虫工程师的“噩梦”——Akamai Bot Manager。目标网站的保护级别相当高常规的请求头伪装、IP代理轮换、甚至简单的Selenium模拟都瞬间被识别为机器人。浏览器里手动操作一切正常但代码一上就被拦截返回的要么是验证码要么是403。这摆明了就是Akamai的防护在起作用。于是一场从浏览器开发者工具F12开始深入JavaScript逻辑最终生成可用Cookie的逆向工程攻坚战就此展开。这个过程不仅仅是技术对抗更像是一次对现代Web反爬虫机制核心的深度剖析。如果你也正被类似的问题困扰或者对Web安全、JavaScript逆向感兴趣那么这次完整的调试记录或许能给你带来一些启发和可复现的思路。Akamai Bot Manager以下简称ABM不是简单的验证码或规则匹配它是一个运行在客户端的JavaScript SDK负责收集浏览器环境、用户行为等上百个信号经过本地计算后生成一个“令牌”或“Cookie”随请求发送给服务器。服务器端再根据这个令牌的有效性来决定是否放行。因此我们的目标非常明确理解这个SDK是如何生成关键凭证的并能在无头环境中复现这一过程。整个逆向之旅可以概括为“观察 - 定位 - 分析 - 复现”四个阶段下面我就结合实战一步步拆解。2. 逆向工程的核心思路与准备工作逆向ABM这类混淆严重的商业JS SDK不能靠蛮力需要有清晰的策略。我的核心思路是“以终为始动态追踪”。最终目标是拿到一个有效的、能让服务器认可的ak_bmsc之类的Cookie。那么我们就从浏览器中一次成功的请求入手反向推导这个Cookie是如何产生的。2.1 工具链准备你的数字手术刀工欲善其事必先利其器。以下是本次逆向过程中我用到并强烈推荐的工具组合浏览器与开发者工具主力是Google Chrome或Microsoft EdgeChromium内核。其开发者工具F12中的Sources、Network、Debugger和Console面板是主战场。Firefox的开发者工具同样强大在某些JS调试方面甚至有独特优势可以作为备选。代理抓包工具Charles或Fiddler Everywhere。它们能捕获所有HTTP/HTTPS流量方便我们查看请求/响应的每一个细节特别是设置断点、修改请求、重发请求对于理解交互流程至关重要。mitmproxy命令行适合自动化程度高的场景。反混淆与代码分析工具AST解析器对于简单的格式化浏览器自带的“Pretty Print”按钮{}就够了。但对于复杂的混淆变量名混淆、控制流平坦化需要用到基于AST抽象语法树的工具。我主要使用**astexplorer.net** 在线网站进行初步的语法树分析和理解复杂的本地处理则用Node.js Babel库自己写脚本。js-beautify一个基础的代码格式化工具能解决压缩代码的阅读问题。浏览器控制台本身就是最强大的动态调试和代码执行环境。Node.js环境这是最终复现算法、生成Cookie的执行环境。确保安装好Node.js并准备好axios、puppeteer可选等常用库。注意在开始之前请务必在法律和道德允许的范围内进行测试。仅针对你有权测试的网站或公开的、允许爬取的接口。未经授权的逆向可能违反服务条款甚至法律。2.2 环境隔离与纯净启动为了防止浏览器插件、缓存干扰我们的分析第一步是创建一个干净的调试环境。打开Chrome在地址栏输入chrome://version/找到“个人资料路径”。关闭所有Chrome窗口。打开终端或命令提示符使用以下命令启动一个全新的、无扩展的Chrome实例并指定一个全新的用户数据目录# macOS/Linux /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --user-data-dir/tmp/chrome_test --disable-extensions # Windows (假设Chrome安装在默认位置) C:\Program Files\Google\Chrome\Application\chrome.exe --user-data-dirC:\temp\chrome_test --disable-extensions在这个新窗口中打开目标网站。这样能确保我们看到的JS和网络请求是最原始的状态没有受到任何插件如广告拦截器、脚本管理器的影响。3. 动态分析与关键逻辑定位一切就绪现在打开F12进入实战分析阶段。我们的首要任务是找到生成关键Cookie的那段JavaScript代码。3.1 网络请求追踪与关键点标记清空并记录打开Network面板勾选Preserve log保留日志然后刷新目标页面或触发那个被保护的请求比如点击搜索按钮。寻找可疑请求在瀑布流中重点关注请求被阻塞Status为Pending然后失败或返回403/429。请求的Initiator发起者是某个JS文件。请求的URL中包含akamai、bm、bot、sensor等关键词。请求的Response Headers中可能包含Set-Cookie: ak_bmsc...或者请求的Request Headers中带有这个Cookie。定位入口找到那个最初设置ak_bmscCookie的请求通常是第一个返回Set-Cookie的请求。点击这个请求查看Headers详情。在Request Headers中你可能会看到一个携带了大量参数如b、s等的请求这就是传感器数据提交。在Initiator列点击调用栈它会把我们直接引向发起这个请求的JavaScript代码位置。3.2 调试器中的攻防战通过Initiator找到的JS文件通常是被严重混淆的变量名是a,b,c,_0x12ab3c这种。直接阅读是天书。我们需要借助调试器。事件监听器断点在Sources面板的右侧找到Event Listener Breakpoints。展开Network勾选XHR/Fetch的readystatechange、load、send等事件。这样当页面发起任何Ajax请求时调试器就会自动暂停我们可以查看此时的调用栈和变量。XHR/Fetch全局断点在Console中注入以下代码可以重写原生的XMLHttpRequest.send和fetch方法在其中加入debugger语句。这是非常暴力但有效的一招。(function() { var origSend XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.send function() { debugger; // 执行到此处会暂停 console.trace(XHR send called); return origSend.apply(this, arguments); }; var origFetch window.fetch; window.fetch function() { debugger; // 执行到此处会暂停 console.trace(Fetch called); return origFetch.apply(this, arguments); }; })();注入后触发那个可疑请求浏览器会自动在debugger处暂停。然后在Call Stack调用栈中一步步向上回溯找到属于网站业务逻辑或ABM SDK的代码段。Cookie访问断点ABM最终要生成Cookie。我们可以在Application面板的Cookies中找到目标域名下的ak_bmsc右键点击它选择Add breakpoint on cookie change。这样任何JS代码尝试修改这个Cookie时调试器都会暂停。这是定位生成逻辑的终极方法之一。3.3 代码提取与初步清理当调试器在关键函数处暂停后我们可以在Scope窗口中看到当前的局部变量、闭包变量。更重要的是在Sources面板中我们可以看到暂停点的代码上下文。格式化代码如果代码是压缩的点击左下角的{}Pretty print按钮让代码变得可读一些。定位核心函数通过调用栈和单步执行F10步过F11步入慢慢理清逻辑。ABM的核心通常是一个庞大的自执行函数里面包含传感器数据收集、加密、编码和最终提交的逻辑。我们需要找到数据收集函数收集浏览器指纹UserAgent, screen, plugins, fonts, WebGL等、行为事件鼠标移动、点击、滚动。处理与加密函数将收集的数据进行拼接、排序、计算哈希可能是MD5、SHA系列或进行特定的编码Base64、自定义编码。提交函数将处理后的数据通过POST请求发送到Akamai的传感器端点。回调函数服务器验证传感器数据后返回一个令牌客户端JS用这个令牌生成最终的ak_bmscCookie。提取代码将疑似核心函数的代码段通常是整个大的匿名函数复制出来保存为一个本地的.js文件。注意要复制从(function(){ ... })()开始的完整片段确保其上下文完整。4. 静态分析与算法还原拿到混淆的代码后真正的逆向工作才刚开始。我们需要让这段代码“说话”揭示其算法。4.1 反混淆策略从混沌到清晰混淆代码通常有以下几层我们需要逐层剥开变量名/函数名混淆将getBrowserFingerprint变成_0x12a3b。这一步相对简单我们可以通过AST工具进行重命名但更实用的方法是在动态调试中理解其功能。在Chrome调试器中当执行到该函数时观察其输入输出然后给这个变量起一个语义化的名字在脑子里或注释里。例如看到_0x12a3b返回一个包含userAgent和plugins的对象我们就可以把它记为collectFingerprint。控制流平坦化这是最恶心的一种混淆。它把原本线性的if-else、switch逻辑打散用一个“分发器”和一堆“基本块”来跳转执行极大地干扰阅读。对付它可以动态跟蹤单步执行记录每个基本块的实际执行顺序还原出原始逻辑流。使用反平坦化工具有一些开源的AST工具例如de4js的某些插件可以尝试自动化还原但针对ABM这种强对抗的效果可能有限手动分析仍是主力。字符串混淆字符串被加密或编码如\x68\x65\x6c\x6c\x6f或atob(‘aGVsbG8’)。在调试器中当代码执行到使用该字符串的地方时其值已经被解密我们可以直接读取。或者在代码中搜索atob、fromCharCode等函数找到解密逻辑将其提取为一个独立的decodeString函数。常量加密数字、API端点URL等常量可能被计算得出。同样在动态执行时观察其值或分析其计算逻辑。4.2 关键算法钩取与验证在Node.js中复现前最好先在浏览器的控制台里进行验证。环境模拟ABM的代码严重依赖浏览器环境window,document,navigator,screen等。我们需要在Node.js中模拟这些对象。一个简单的方法是使用jsdom库来创建一个虚拟的DOM环境。但更轻量级的方法是只提取核心计算函数而将环境依赖作为参数传入。函数提取与隔离从混淆的代码中找出那个最核心的、输入是传感器数据、输出是待提交字符串或最终Cookie值的函数。把它单独抠出来放在一个独立的JS文件中。补环境分析这个函数内部调用了哪些浏览器API。例如navigator.userAgent- 我们可以传入一个伪造的UA字符串。screen.width/height- 传入固定的数值。Date.now()- Node.js本身就有。Math.random()- 需要处理因为ABM可能检测其随机性有时我们需要固定种子或记录真实浏览器生成的序列。Canvas、WebGL指纹这些比较复杂ABM可能会调用Canvas的toDataURL并计算哈希。在Node中我们可以使用canvas这个npm包来模拟或者更常见的策略是直接记录下真实浏览器生成的结果并复用。因为很多指纹在单次会话中是固定的。控制台验证在浏览器执行到核心函数时在Console中手动调用它传入你记录的环境参数看输出是否与网络请求中发送的数据一致。尝试修改输入参数观察输出变化理解每个参数的影响。4.3 传感器数据解析与构造ABM提交的传感器数据通常是一个很长的字符串或JSON经过编码。在Network面板中找到那个提交请求查看它的Request Payload。它可能像这样seyJh...很长一串...b123456.789012。解码s参数通常是Base64编码的JSON字符串。你可以复制出来在控制台里用atob()解码然后用JSON.parse()解析就能看到一个结构化的传感器数据对象。里面包含了极其详尽的浏览器和环境信息。分析结构这个JSON对象就是我们需要在Node.js中构造的东西。仔细分析其字段bm基础信息如时间戳、页面URL。t事件类型如pageLoad,mousemove。d设备与浏览器指纹UserAgent, language, screen, plugins, fonts等。m鼠标移动轨迹坐标数组和时间戳。k键盘事件如果有。p性能API数据如navigation timing。构造策略完全模拟尝试用jsdom和puppeteer在Node中启动一个无头浏览器运行ABM的JS代码让它自己生成数据。这是最准确但最重的方法。关键字段复现分析发现服务器可能只校验其中几个关键字段的合理性和一致性例如屏幕分辨率与UserAgent中的设备信息是否匹配时间戳是否在合理范围内。我们可以从一次成功的请求中“录制”下这些数据在后续请求中复用或进行微调。这是最常用的方法。动态参数计算像鼠标轨迹m需要模拟人类行为生成合理的坐标和时间序列。可以写一个函数来生成平滑的随机移动路径。5. Node.js环境下的完整复现流程经过动态和静态分析我们终于可以尝试在Node.js中复现整个流程了。目标是输入一个目标URL输出一个有效的ak_bmscCookie。5.1 项目结构与依赖初始化创建一个新的项目目录并初始化mkdir akamai-bm-cracker cd akamai-bm-cracker npm init -y安装必要的依赖。我们选择axios进行HTTP请求canvas用于可能的Canvas指纹生成如果分析确定需要jsdom用于模拟基础DOM环境。npm install axios canvas jsdom5.2 核心JS模块的移植与封装将我们分析、清理并验证过的核心JavaScript函数比如传感器数据生成函数、Cookie生成函数保存为本地模块。这里假设我们提取出了两个核心函数generateSensorData(envConfig): 根据传入的环境配置生成传感器数据对象。generateCookieFromToken(serverToken): 根据服务器返回的令牌生成最终的ak_bmscCookie值。创建一个文件akamaiCore.js// akamaiCore.js - 移植并清理后的核心逻辑 // 注意这是经过大量逆向分析后简化的示意代码真实代码极其复杂且混淆严重。 // 模拟的浏览器环境配置从一次成功会话中录制 const defaultEnv { userAgent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ..., screen: { width: 1920, height: 1080, availWidth: 1920, availHeight: 1040 }, language: zh-CN, platform: Win32, // ... 几十个其他字段 }; // 从混淆代码中提取并重构的传感器生成函数 function generateSensorData(env defaultEnv, eventType pageLoad) { // 这里原本是数千行混淆代码现在被重构为清晰逻辑 const baseInfo { bm: { t: Date.now(), u: env.currentUrl, // ... }, t: eventType, d: { ua: env.userAgent, sr: ${env.screen.width}x${env.screen.height}, l: env.language, // ... 收集各种指纹 }, // 模拟鼠标轨迹简化版 m: generateMouseMovements(), }; // 可能包含复杂的排序、哈希计算 const processedData someComplexProcessing(baseInfo); return processedData; // 返回待编码的数据结构 } // 另一个核心函数处理服务器响应生成Cookie function generateCookieFromToken(tokenResponse) { // tokenResponse 是服务器对传感器数据的响应 // 这里包含将token与本地信息结合进行特定计算可能是HMAC的逻辑 const cookieValue calculateFinalValue(tokenResponse, defaultEnv); return ak_bmsc${cookieValue}; Max-Age3600; Path/; } // 导出函数 module.exports { generateSensorData, generateCookieFromToken, defaultEnv };5.3 主流程脚本编写创建主文件index.js它负责协调整个流程const axios require(axios); const { generateSensorData, generateCookieFromToken, defaultEnv } require(./akamaiCore.js); // 1. 初始化会话获取初始Cookie如果有的话 async function initSession(targetUrl) { const headers { User-Agent: defaultEnv.userAgent, Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, }; try { const response await axios.get(targetUrl, { headers, maxRedirects: 5 }); // 提取响应中的Cookie特别是可能的初始bm_sz等 const cookies response.headers[set-cookie] || []; console.log(初始响应Cookies:, cookies); return { initialCookies: cookies, finalUrl: response.request.res.responseUrl }; } catch (error) { console.error(初始化请求失败:, error.message); throw error; } } // 2. 生成并提交传感器数据 async function submitSensorData(targetUrl, initialCookies) { // 更新环境中的当前URL const envWithUrl { ...defaultEnv, currentUrl: targetUrl }; // 生成传感器数据 const sensorDataObj generateSensorData(envWithUrl, pageLoad); // 将数据转换为ABM期望的格式例如JSON字符串 - Base64 - 作为s参数 const encodedSensorData Buffer.from(JSON.stringify(sensorDataObj)).toString(base64); const sensorEndpoint https://[目标网站域名]/akamai/sensor_data; // 需要从网络请求中确认真实端点 const payload s${encodeURIComponent(encodedSensorData)}b${Date.now()}.${Math.random().toString().slice(2, 15)}; const headers { User-Agent: envWithUrl.userAgent, Content-Type: application/x-www-form-urlencoded, Cookie: initialCookies.join(; ), // 携带初始Cookie Origin: new URL(targetUrl).origin, Referer: targetUrl, }; try { const response await axios.post(sensorEndpoint, payload, { headers }); // 传感器提交的响应通常是一个包含令牌的JSON或一段JS代码 console.log(传感器响应状态:, response.status); console.log(传感器响应体:, response.data); return response.data; // 返回服务器令牌 } catch (error) { console.error(提交传感器数据失败:, error.response?.data || error.message); throw error; } } // 3. 主函数串联整个流程 async function getAkamaiCookie(targetUrl) { console.log(目标URL: ${targetUrl}); // 步骤1: 初始化获取初始上下文 const { initialCookies, finalUrl } await initSession(targetUrl); // 步骤2: 提交传感器数据获取令牌 const tokenResponse await submitSensorData(finalUrl, initialCookies); // 步骤3: 使用令牌生成最终Cookie const finalCookieHeader generateCookieFromToken(tokenResponse); console.log(\n 生成的最终Cookie ); console.log(finalCookieHeader); // 步骤4: 验证Cookie可选 const verifyHeaders { User-Agent: defaultEnv.userAgent, Cookie: finalCookieHeader, }; const verifyResponse await axios.get(finalUrl, { headers: verifyHeaders }); if (verifyResponse.status 200 !verifyResponse.data.includes(blocked)) { console.log(Cookie验证成功); return finalCookieHeader; } else { console.warn(Cookie验证可能失败状态码:, verifyResponse.status); return null; } } // 执行 (async () { const targetUrl https://your-target-website.com; try { const cookie await getAkamaiCookie(targetUrl); if (cookie) { // 可以将cookie保存下来用于后续的请求 console.log(可用Cookie已生成。); } } catch (e) { console.error(流程执行失败:, e); } })();5.4 参数调优与行为模拟上面的代码只是一个骨架。要让其真正工作需要大量的调优指纹一致性确保defaultEnv对象中的所有指纹信息是自洽的。一个Windows 10的Chrome浏览器不可能有macOS的字体列表。从一次真实的浏览器会话中完整地导出所有这些数据是最稳妥的。时间戳与随机数ABM会检查传感器数据中的时间戳是否与服务器时间相差过大。需要使用同步的时间戳。Math.random()的序列可能也被监控可以考虑使用固定种子或从真实会话中录制随机数序列。鼠标轨迹模拟generateMouseMovements()函数需要生成看起来像人类的移动数据包含移动、暂停、小幅抖动。轨迹点(x, y)和时间戳t需要合理。请求头完整性除了User-Agent和CookieAccept-Language、Sec-*系列头如Sec-CH-UA、Upgrade-Insecure-Requests等都可能被检查。务必从浏览器中复制完整的请求头。端点与参数名传感器提交的URL端点sensorEndpoint和参数名s,b,k等必须完全准确这些都需要从网络请求中仔细确认。6. 实战中遇到的典型问题与解决方案逆向ABM的过程绝非一帆风顺以下是我踩过的一些坑和解决办法6.1 环境检测与对抗问题在Node.js中运行复现的代码生成的Cookie被服务器拒绝。通过对比发现传感器数据中某个字段在Node环境和真实浏览器中不同。排查在浏览器控制台中在传感器数据生成后、发送前用JSON.stringify()将其完整打印出来。在Node.js脚本中在调用generateSensorData后也打印出来。使用对比工具如diff命令或在线对比网站逐字段比较两个JSON对象。解决发现是navigator.plugins在Node中为空数组而在真实浏览器中有值。ABM可能检测了这一点。解决方案是在Node环境中通过jsdom模拟一个基本的navigator.plugins对象或者更简单——直接从录制的浏览器数据中复制这个字段的值硬编码到defaultEnv中。对于高度静态的指纹录制并复用是最可靠的。6.2 代码混淆与动态加载问题核心的生成函数不是一次性加载的而是被分割成多个chunk或者通过eval、Function构造函数动态生成增加了定位难度。排查在Network面板中过滤JS文件观察页面加载后是否还有新的、名称可疑的JS文件被加载。在Sources面板的Page标签下查看所有加载的脚本源。解决全局搜索在所有的JS文件内容中可以在Sources面板中打开每个文件然后使用CtrlShiftF进行全局搜索搜索关键词如ak_bmsc、sensor、bm等。监听脚本加载在Console中注入代码重写document.createElement当创建script标签时打日志看是否有动态加载的脚本。断点于代码执行在debugger语句或XHR断点暂停后在调用栈中仔细查看即使函数来自一个很奇怪的eval代码块调用栈也会显示其来源如VMxxx。可以尝试在Scope中查看该函数的toString()有时能得到清晰的源码。6.3 算法更新与时效性问题今天还能用的脚本明天就失效了。ABM的JS SDK可能更新了算法或参数。解决监控与告警将数据采集脚本加入监控一旦连续失败或返回验证码立即告警。版本感知在初始化请求时留意JS文件的版本号可能在URL或文件内容中。发现版本变化时触发重新分析流程。降级策略准备备用方案如使用高质量的住宅代理IP、更慢的请求频率、甚至引入puppeteer进行全浏览器模拟作为保底手段。完全逆向的解决方案维护成本高通常与模拟浏览器方案结合使用。6.4 性能与效率优化问题复现的JS算法计算缓慢影响采集速度。解决算法精简分析传感器数据确定哪些字段是服务器严格校验的哪些是可选的或可固定的。只动态计算必要的部分。缓存与复用同一个会话中很多指纹数据是不变的。可以生成一次后缓存起来在会话有效期内复用。并行计算如果有多目标需要获取Cookie可以使用Node.js的异步特性并行处理但要注意IP和用户代理的隔离。7. 总结与进阶思考通过这样一次从F12到生成Cookie的完整逆向我们不仅得到了一个可用的工具更重要的是深入理解了现代客户端反爬虫技术的核心逻辑。ABM这类方案的本质是信任计算通过在客户端运行一段可信代码收集难以伪造的环境和行为证据生成一个短期有效的“通行证”。对于爬虫工程师来说完全逆向并复现这套逻辑是最高难度的挑战但往往也是性价比最高的方案一旦成功速度快、资源消耗低。然而它需要持续投入精力进行对抗。在实际项目中我通常会采用分层策略第一层基础伪装完善的请求头、合理的IP代理、遵守robots.txt。第二层逆向复现针对像ABM、Datadome、PerimeterX这样的专项防护进行逆向实现核心算法的复现。这是本篇文章讨论的重点。第三层模拟浏览器使用puppeteer、playwright或Selenium进行自动化浏览器控制。这是最通用但最重的方法用于应对无法逆向或逆向成本过高的场景。第四层人工介入预留人工处理验证码的接口。最后有几个心得分享第一耐心比技术更重要。逆向是一个需要反复尝试、观察和推理的细致活。第二善用调试工具。Chrome DevTools是你最好的朋友它的每一个功能都可能成为突破口。第三做好记录。分析过程中将关键的函数、变量、参数、网络请求截图都保存下来形成自己的知识库。第四尊重规则。技术探索的乐趣在于过程但务必在合法合规的范围内使用这些知识。