AES-ECB前端加密登录逆向实战:从JS混淆到Python模拟

📅 2026/7/23 11:19:07
AES-ECB前端加密登录逆向实战:从JS混淆到Python模拟
1. 项目概述与核心挑战最近在分析一个教育类网站的登录流程时遇到了一个典型的“前端加密后端验证”的案例目标站点是某闽南地区师范类大学的门户或教务系统。这类系统通常承载着学生选课、成绩查询等核心功能其登录接口的安全防护往往比普通网站要强一些。我拿到的这个案例前端登录密码使用了AES-ECB模式进行加密并且整个JavaScript代码经过了Obfuscator简称ob的混淆处理整体难度可以归为中等偏上非常适合用来深入理解现代Web逆向中常见的加密与混淆对抗技术。这个项目对于从事数据采集、安全研究或者单纯想理解前端安全机制的朋友来说是一个很好的练手材料。它不像那些只用Base64或简单MD5的站点那么直白也不至于像遇到WebAssembly或强VMP保护那样让人无从下手。通过拆解它你不仅能学会如何定位关键加密函数、理解AES的工作模式更能掌握一套应对代码混淆的通用分析思路。无论你是想补全爬虫的登录模块还是想深入前端安全领域这个案例中的技术点——Python模拟请求、JS逆向、加密算法识别、混淆代码还原——都是必须跨越的关卡。接下来我就把这次逆向分析的完整过程、踩过的坑以及最终成型的Python代码分享给大家。2. 逆向环境准备与工具链选型工欲善其事必先利其器。面对混淆的JS和加密的请求一套顺手的工具能极大提升效率。我的核心思路是“浏览器调试定位Node.js模拟验证Python最终实现”。2.1 浏览器开发者工具定位入口的侦察兵Chrome或Edge的开发者工具是第一步的主战场。关键操作在于登录流程的录制与监听。打开无痕窗口避免浏览器插件和缓存干扰确保每次都是干净的会话。进入登录页并打开DevTools直接按F12切换到“网络(Network)”标签页。勾选“保留日志(Preserve log)”防止页面跳转时请求记录被清空。开启“停用缓存(Disable cache)”确保每次都能获取到最新的JS文件。执行登录操作在页面输入账号密码点击登录按钮。筛选与分析在网络请求列表中重点关注类型为XHR或Fetch的请求。通常登录请求的initiator发起者会指向一个具体的JS文件这就是我们的突破口。查看该请求的Payload如果password字段是一长串无规律的字符如a7f4e8c1b2...基本可以确定前端做了加密处理。2.2 逆向辅助工具破解混淆的钥匙面对ob混淆代码通常被压缩、变量名被替换成单字符、字符串被编码可读性极差。我们需要工具来“美化”和“解密”。AST解析与反混淆工具这是应对高级混淆的利器。我推荐使用babel库配合自定义脚本或者使用现成的在线工具如jsnice.org、de4js进行初步反混淆。它们能尝试解析JavaScript的抽象语法树AST还原变量名、解析简单的字符串加密。对于这个案例ob混淆的强度中等使用这些工具通常能显著提升代码可读性。浏览器控制台补环境这是动态调试的核心。当加密函数依赖浏览器的某些环境如window、document、navigator对象时我们需要在Node.js中模拟这些环境。可以使用jsdom库或者更轻量级地直接定义一个包含必要属性的全局对象。// 一个简单的补环境示例 global.window { location: {}, document: {} }; global.navigator { userAgent: Mozilla/5.0... };Node.js环境这是将浏览器中找到的加密函数“剥离”出来进行独立测试和验证的关键环境。确保你的系统安装了Node.js我们将在这里运行经过反混淆和补环境后的关键加密函数确保其输出与浏览器中完全一致。2.3 Python最终实现稳定运行的采集端当我们在Node.js中成功模拟出加密函数后最后一步就是将其移植到Python构建稳定的爬虫。加密库选择Python的cryptography或pycryptodome库都提供了完善的AES支持。我习惯用pycryptodome因为它API清晰文档齐全。pip install pycryptodome请求库选择requests库是绝对的主流简单易用足以应对绝大多数场景。pip install requests代码结构规划我会将整个登录流程封装成一个类包含初始化、加密函数、请求发送和会话维持等方法这样代码更清晰也便于复用。注意在整个过程中务必遵守目标网站的robots.txt协议控制请求频率避免对对方服务器造成压力。逆向分析的目的应是学习技术与解决合法需求下的自动化问题。3. 登录流程分析与关键加密函数定位分析登录流程就像是拆解一个黑盒。我们的目标是找到那个把明文密码变成密文的“魔法盒子”。3.1 网络请求抓取与初步判断按照2.1的步骤操作后我捕获到了登录请求。它是一个POST请求发送到类似/api/login或/auth/login的端点。请求负载Request Payload通常是一个JSON对象结构如下{ username: 2023111001, password: U2FsdGVkX19qBzP6...很长一串Base64样式的字符串, captcha: 1234, rememberMe: false }这里最引人注目的就是password字段。它明显不是明文长度固定且包含/、、等字符非常像Base64编码后的密文。这证实了前端加密的存在。3.2 加密函数入口追踪接下来需要在发起这个登录请求的JS文件中找到加密逻辑。查看请求发起者(Initiator)在网络请求列表中点击登录请求在详情面板中找到Initiator标签页。它会显示调用栈最顶层通常是一个事件监听函数如onclick向下追踪最终会定位到一个执行了XMLHttpRequest.send()或fetch()的函数。搜索关键词在疑似的主JS文件通常是app.[hash].js或login.[hash].js中使用开发者工具的“搜索(Search)”功能CtrlShiftF搜索关键词如passwordencryptAESCryptoJS(一个常用的前端加密库)登录接口的URL片段如/api/login定位加密调用经过搜索我找到了类似下面的混淆代码段// 混淆后的代码示例 var a password; var b d[encrypt](c[a], e); f[password] b;这里的d[encrypt]很可能就是加密函数。通过查看d对象的定义或者追踪encrypt方法的来源逐步向上回溯。3.3 逆向核心AES-ECB加密算法识别经过对encrypt函数内部逻辑的梳理可能需要结合反混淆工具我确认了加密算法是AES模式是ECB。算法特征识别密钥长度代码中可能有一个固定的字符串被用作密钥key长度可能是16、24或32字节对应AES-128, AES-192, AES-256。在这个案例中我找到了一个长度为16的字符串。加密模式代码中调用了CryptoJS.AES.encrypt()并且第二个参数密钥是字符串或WordArray没有出现iv初始化向量参数。这是ECB模式的典型特征。ECB模式不需要IV安全性较低相同的明文块会加密成相同的密文块。填充方式前端常用的填充是PKCS7。在CryptoJS中这是默认填充方式。关键代码还原经过反混淆和整理加密的核心逻辑大致如下// 还原后的伪代码 function encryptPassword(password, key) { // CryptoJS 库的典型用法 var key CryptoJS.enc.Utf8.parse(key); // 将字符串密钥转换成WordArray var srcs CryptoJS.enc.Utf8.parse(password); var encrypted CryptoJS.AES.encrypt(srcs, key, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }); // 返回Base64格式的密文 return encrypted.toString(); }这里明确显示了mode: CryptoJS.mode.ECB。至此加密算法、模式、填充、密钥都已确定。4. 混淆代码处理与Node.js环境模拟找到加密函数只是第一步把它从复杂的网页环境中“剥离”出来能在独立环境中运行才是逆向成功的关键。4.1 代码提取与反混淆将包含encryptPassword函数及其所有依赖的代码段从原JS文件中提取出来。由于原代码被混淆提取的代码可能包含许多无用的变量和函数。使用在线工具初步美化将提取的代码粘贴到jsnice.org或de4js。这些工具会尝试重命名变量、解析简单的十六进制或Unicode编码的字符串输出可读性更好的代码。手动分析与精简工具反混淆的结果并非完美。需要人工阅读识别出哪些是加密真正需要的函数如CryptoJS的相关方法或自定义的加密函数哪些是混淆用的“垃圾代码”。逐步删除无关代码保留核心逻辑。这个过程需要耐心可以边删边在浏览器控制台测试函数是否还能正常工作。4.2 补环境让代码在Node.js中“活”起来浏览器中的JavaScript运行在拥有window、document等丰富BOM/DOM对象的环境中。我们的加密函数可能间接依赖这些环境。当把它放到Node.js中执行时可能会报错“xxx is not defined”。识别缺失对象在Node.js中直接运行精简后的加密代码根据报错信息确定缺失的全局变量或函数。最小化补环境原则是“缺什么补什么”且只补最必要的属性。例如如果代码只是判断typeof window ! undefined我们可以直接定义global.window {}。如果代码用到了navigator.userAgent我们就补上它。// nodejs_env.js - 补环境文件 global.window global; // 有时window就是global自身 global.navigator { userAgent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 }; // 如果加密库依赖document可能需要更复杂的模拟但本例中AES加密通常不依赖DOM // global.document {};引入加密库如果原代码使用了CryptoJS我们需要在Node.js项目中安装它。npm install crypto-js然后在我们的测试脚本中引入const CryptoJS require(crypto-js); // 将我们精简并补环境后的加密函数代码放在这里4.3 功能验证与调试编写一个简单的测试脚本验证我们的Node.js加密函数输出是否与浏览器完全一致。// test_encrypt.js const CryptoJS require(crypto-js); // 粘贴我们处理好的encryptPassword函数 const key 找到的16位密钥字符串; const password test123456; const encryptedInBrowser ...; // 从浏览器网络请求中复制一次加密结果 const encryptedInNode encryptPassword(password, key); console.log(Node加密结果:, encryptedInNode); console.log(浏览器加密结果:, encryptedInBrowser); console.log(是否一致:, encryptedInNode encryptedInBrowser);如果输出一致恭喜你最核心的逆向部分已经完成。如果不一致需要回头检查密钥是否正确、加密模式/填充是否一致、字符串编码UTF-8处理是否相同、补环境是否遗漏了关键点。5. Python登录模块完整实现当我们在Node.js中完美复现了加密过程后最后一步就是用Python实现整个登录流程构建一个可用的爬虫会话。5.1 AES-ECB加密的Python实现我们将使用pycryptodome库来实现与前端完全一致的AES-ECB-PKCS7加密。import base64 from Crypto.Cipher import AES from Crypto.Util.Padding import pad class UniversityLoginSpider: def __init__(self, login_url, username, password): self.login_url login_url self.username username self.plain_password password self.session requests.Session() self.session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Content-Type: application/json;charsetUTF-8 }) # 这是逆向分析找到的固定密钥长度必须为16/24/32字节 self.aes_key ThisIsASecretKey # 示例密钥实际替换为找到的16位密钥 def _encrypt_password(self): 模拟前端AES-ECB-PKCS7加密 # 1. 处理密钥和明文转为bytes key_bytes self.aes_key.encode(utf-8) plaintext_bytes self.plain_password.encode(utf-8) # 2. 创建AES加密器模式为ECB # ECB模式不需要iv初始化向量 cipher AES.new(key_bytes, AES.MODE_ECB) # 3. 对明文进行PKCS7填充 # AES块大小是16字节 padded_bytes pad(plaintext_bytes, AES.block_size, stylepkcs7) # 4. 加密 encrypted_bytes cipher.encrypt(padded_bytes) # 5. 将加密后的bytes进行Base64编码得到最终密文 encrypted_b64 base64.b64encode(encrypted_bytes).decode(utf-8) return encrypted_b64这里有几个关键点AES.new(key, AES.MODE_ECB)明确指定ECB模式。pad(data, block_size, stylepkcs7)使用PKCS7填充确保明文长度是块大小的整数倍。base64.b64encode()前端CryptoJS的toString()默认输出就是Base64字符串。5.2 构建请求与处理会话登录请求通常需要携带账号、加密后的密码有时还有验证码等其他参数。import requests import json class UniversityLoginSpider: # ... __init__ 和 _encrypt_password 方法 ... def login(self, captchaNone): 执行登录操作 :param captcha: 验证码如果登录需要的话 :return: 登录成功返回True否则False # 加密密码 encrypted_pwd self._encrypt_password() # 构建请求数据 login_data { username: self.username, password: encrypted_pwd, # 使用加密后的密码 # captcha: captcha, # 如果需要验证码 # rememberMe: False } try: response self.session.post(self.login_url, datajson.dumps(login_data), timeout10) response.raise_for_status() # 检查HTTP错误 result response.json() # 根据实际接口返回判断登录成功 # 常见成功标识 result[code] 200, result[success] True, 或包含特定token if result.get(code) 200 or result.get(success): print(f用户 {self.username} 登录成功) # 登录成功后session会自动管理cookies后续请求可直接用self.session return True else: print(f登录失败: {result.get(message, 未知错误)}) return False except requests.exceptions.RequestException as e: print(f网络请求异常: {e}) return False except json.JSONDecodeError: print(响应不是有效的JSON格式) return False5.3 完整示例与后续请求将以上部分组合并演示如何用登录后的会话访问需要认证的页面。if __name__ __main__: # 配置信息实际使用时需要替换 LOGIN_URL https://目标大学官网/api/login # 实际的登录接口 USERNAME 你的学号 PASSWORD 你的密码 TARGET_URL https://目标大学官网/student/grades # 登录后才能访问的成绩页 spider UniversityLoginSpider(LOGIN_URL, USERNAME, PASSWORD) if spider.login(): # 登录成功 # 使用同一个session访问受保护页面 try: resp spider.session.get(TARGET_URL, timeout10) if resp.status_code 200: # 解析成绩页面数据... print(成功获取到受保护页面内容) # 这里可以开始你的数据解析逻辑 else: print(f访问受保护页面失败状态码: {resp.status_code}) except Exception as e: print(f访问目标页面时出错: {e}) else: print(登录失败无法进行后续操作)这个spider.session对象在登录成功后会自动保存服务器返回的Cookie通常是JSESSIONID等在后续的get/post请求中会自动携带模拟了浏览器的登录状态。6. 常见问题排查与实战心得在实际操作中几乎不可能一帆风顺。下面是我在类似项目中总结的一些常见“坑点”和解决思路。6.1 加密结果不一致问题排查表这是最令人头疼的问题。如果你的Python加密结果和浏览器不一致请按以下顺序排查排查步骤可能原因解决方案1. 密钥是否正确密钥字符串找错或编码方式不对。确认密钥是原样字符串。在JS和Python中都使用UTF-8编码。2. 加密模式是否匹配Python用了CBC模式而JS是ECB模式。确认JS代码中mode参数为CryptoJS.mode.ECBPython使用AES.MODE_ECB。3. 填充方式是否一致填充方式不同如PKCS5 vs PKCS7。AES中PKCS5和PKCS7在实现上等价但最好明确指定。Python用pad(..., stylepkcs7)。4. 明文处理是否一致明文在加密前是否经过了其他处理如哈希仔细阅读JS加密函数看密码在传入AES前是否被MD5或SHA1处理过。5. 输出格式是否一致JS输出可能是Hex或Base64Python可能默认输出bytes。JS的encrypted.toString()通常是Base64。Python需用base64.b64encode()。6. 字符编码是否统一中文字符或特殊符号编码不一致。在JS和Python中明文和密钥在加密前都明确使用UTF-8编码。7. 是否存在随机盐(Salt)加密时引入了随机数导致每次结果不同。查看JS代码如果使用了CryptoJS.lib.WordArray.random(8)生成salt则需要将salt也一同获取并传入Python。本例ECB模式通常无salt。8. 是否依赖环境变量JS函数依赖window.btoa等浏览器特有API。在Node.js测试时需要补全这些环境如global.btoa (str) Buffer.from(str).toString(base64)。一个实用的调试技巧是分步对比在Node.js还原的代码和Python代码中分别打印出密钥的字节、填充前的明文字节、填充后的明文字节逐环节比对一定能找到差异点。6.2 请求失败与反爬虫策略应对登录接口往往伴有反爬措施。请求头校验服务器会检查User-Agent、Referer、Origin、Content-Type等头部。务必使用浏览器的真实User-Agent并正确设置Content-Type如application/json。Cookie前置依赖有些网站需要先访问登录页获取一个初始的Cookie如JSESSIONID登录请求中必须携带这个Cookie。解决方案是在login()方法前先用session.get()访问一次登录页。def login(self): # 先GET一次登录页获取初始Cookie self.session.get(https://目标大学官网/login.html) # 再执行POST登录 # ... 后续登录代码Token/签名验证越来越常见。登录请求可能需要携带一个从首页或单独接口获取的动态token或sign参数。这需要你先分析登录页的HTML源码或额外请求一个接口来获取这些值并将其加入到登录的POST数据中。验证码本文案例假设无验证码。如果遇到需要额外处理图形验证码可以尝试OCR准确率低或打码平台滑动、点选等行为验证码则难度极大可能需要考虑其他技术路线。6.3 混淆代码分析的心得不要试图完全还原我们的目标不是把几万行混淆代码都读懂而是找到加密的入口和核心逻辑。学会“跳读”快速定位到encrypt、password、AES等关键调用点。善用断点调试在浏览器Sources面板中在疑似加密函数入口处打上断点然后执行登录。程序会在断点处暂停你可以查看此时的调用栈、变量值这是理解代码执行流程最直观的方法。控制台是试验场在定位到关键函数后可以将其复制到控制台传入测试参数直接执行验证其功能。这比反复修改Python代码要快得多。保持耐心与记录逆向是一个反复试错的过程。每做一个修改、每验证一个猜想最好记录下来。清晰的笔记能帮你快速回溯思路尤其是在排查“结果不一致”这种复杂问题时。7. 项目总结与扩展思考通过这个“某闽南师范类大学登录接口逆向”的完整案例我们走完了一个典型的Web登录逆向流程从网络抓包定位加密请求到前端JS代码中追踪加密函数再到处理ob混淆、补环境并在Node.js中验证算法最后用Python实现完整的模拟登录。AES-ECB模式因其无IV、确定性加密的特性在逆向中算是相对友好的一种。这个案例的“中等”难度主要体现在ob混淆对代码阅读的干扰上。但只要掌握了“搜索关键词 - 定位函数 - 反混淆美化 - 提取精简 - 补环境测试”这套组合拳绝大多数类似强度的混淆都能被攻克。真正困难的是那些使用了动态JS加载、WebAssembly执行核心逻辑、或配合强图形/行为验证码的网站那些需要更高级的工具和更深厚的功底。我个人在完成这个案例后有两点体会最深一是环境一致性至关重要差一个字符编码、多一个空格都可能导致加密失败必须分步骤严格比对二是工具只是辅助最重要的还是对HTTP协议、加密算法原理和JavaScript运行机制的扎实理解。逆向就像解谜工具帮你看到了更多的碎片但如何拼出完整的图画还得靠你自己的逻辑和知识。最后一个小技巧对于这类固定密钥的AES加密一旦在Python中调试成功可以将加密函数缓存起来。如果爬虫需要频繁登录不必每次登录都重新执行完整的JS逆向和加密过程直接调用这个调试好的Python加密函数即可效率会高很多。当然前提是网站的加密算法没有更新。