前言在做数据采集和安全研究的过程中我们经常会遇到各种前端加密防护。最近在对某“蓝厂”懂的都懂国内头部手机厂商社区进行协议分析时发现其登录及核心业务接口的请求参数不再是简单的MD5签名而是采用了AES加密数据 RSA加密AES密钥的混合加密方案。这种方案在金融级应用和大型厂商中越来越常见。很多初学者在遇到这种情况时往往因为找不到密钥生成逻辑或者无法处理非对称加密而卡壳。本文将从零开始记录一次完整的逆向分析过程包括加密点定位、混淆代码还原、算法识别以及最终的Python模拟实现。⚠️ 免责声明本文所涉及的技术仅用于安全研究与学习交流请勿用于任何非法用途。文中出现的网址、接口均已做脱敏处理。尊重知识产权遵守相关法律法规。一、 抓包分析与加密特征识别1.1 接口观察打开浏览器开发者工具F12切换到Network面板触发登录操作。我们可以观察到关键的login请求其Form Data中包含两个核心加密字段encData: 一串很长的Base64编码字符串目测是加密后的业务数据。encKey: 另一串Base64字符串长度固定疑似RSA加密后的密文。encVer: 版本号标识如 “1_1_2”。1.2 响应数据不仅请求加密响应体也是一串密文。这说明服务端和客户端约定了一套完整的加解密协议。如果我们不能还原这套协议就无法正常解析返回的业务数据。蓝厂服务器浏览器/脚本蓝厂服务器浏览器/脚本生成随机AES密钥AES加密(明文数据) - encDataRSA公钥加密(AES密钥) - encKeyPOST /login (encData, encKey)RSA私钥解密(encKey) - AES密钥AES解密(encData) - 明文数据业务处理 AES加密响应返回加密响应体AES解密响应体 - 明文结果二、 加密入口定位面对压缩混淆过的JS代码全局搜索是最直接的手段。2.1 关键字搜索我们在Sources面板中全局搜索encData。由于代码经过Webpack打包和混淆变量名可能变成了单字母。但字符串常量通常不会被完全改变。搜索后发现约4处匹配。通过观察上下文我们发现一处赋值逻辑e.encDatat.ciphertext,e.encKeyn,e.encVer1_1_2这与我们抓包看到的结构完全一致在此处打下断点重新触发登录成功断住。2.2 调用栈回溯断住后查看右侧Call Stack调用栈。我们需要找到加密函数的“源头”。逐层向上点击观察Scope中的变量值直到找到一个函数它的入参包含了我们的明文密码和手机号而出参正是加密后的对象。这个函数就是我们要逆向的核心加密入口。三、 算法还原与代码剥离3.1 识别加密库在核心加密函数内部我们可以看到大量特征代码。通过搜索CryptoJS或AES等关键字确认该站点使用的是标准的crypto-js库。但关键在于AES的密钥是如何生成的RSA的公钥写在哪里3.2 提取RSA公钥在加密函数附近我们发现了一个硬编码的长字符串以MIGfMA0GCSqGSIb3DQEBAQUAA4GN...开头。这是标准的PKCS#1格式RSA公钥的Base64编码。将其保存下来后续Python复现时需要用到。3.3 梳理加密逻辑通过单步调试我们还原出如下加密流程生成一个随机的16字节字符串作为AES密钥aesKey。使用AES-CBC模式以aesKey为密钥对JSON序列化后的表单数据进行加密得到encData。将aesKey转为Hex字符串使用预置的RSA公钥进行加密得到encKey。组装最终请求体。 踩坑提示在扣取JS代码时不要试图把整个webpack模块都搬走。只提取加密相关的纯函数并手动补齐navigator、window等必要的环境变量即可。过多的环境依赖会导致Node.js运行报错。四、 Python协议还原逆向的终点不是看懂JS而是用Python稳定地模拟请求。以下是核心还原代码已脱敏importjsonimportbase64fromCrypto.CipherimportAES,PKCS1_v1_5fromCrypto.PublicKeyimportRSAfromCrypto.Randomimportget_random_bytesfromCrypto.Util.PaddingimportpadclassLanChangCrypto:def__init__(self):# 从JS中提取的RSA公钥self.rsa_public_key-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC...(脱敏)...IDAQAB -----END PUBLIC KEY-----defencrypt(self,plain_data:dict)-dict:# 1. 生成随机AES密钥aes_keyget_random_bytes(16)# 2. AES加密业务数据plain_textjson.dumps(plain_data,separators(,,:))cipher_aesAES.new(aes_key,AES.MODE_CBC,ivaes_key)# 注意该站IV与Key相同encrypted_datacipher_aes.encrypt(pad(plain_text.encode(),AES.block_size))enc_database64.b64encode(encrypted_data).decode()# 3. RSA加密AES密钥rsa_keyRSA.import_key(self.rsa_public_key)cipher_rsaPKCS1_v1_5.new(rsa_key)enc_keybase64.b64encode(cipher_rsa.encrypt(aes_key.hex().encode())).decode()return{encData:enc_data,encKey:enc_key,encVer:1_1_2}# 测试cryptoLanChangCrypto()payloadcrypto.encrypt({phone:13800138000,password:test123})print(payload)4.1 关键细节对齐在还原过程中最容易出错的是Padding方式和IV向量。通过JS调试发现该站AES使用的是Pkcs7填充Python的pycryptodome默认也是此填充。IV并没有单独生成而是直接复用了AES Key本身。这一点如果不通过动态调试很难从静态混淆代码中直接看出来。五、 响应数据解密请求搞定后响应解密就简单了。因为我们自己生成了AES密钥所以只需要用同一个密钥对响应体进行AES-CBC解密即可。需要注意的是服务端返回的密文可能包含额外的校验位或头部信息解密前可能需要先截取特定长度的字节。建议先在浏览器中断点观察解密函数的输入输出确认密文的实际格式。六、 总结与思考本次某蓝厂社区的逆向实战核心难点不在于算法本身都是标准算法而在于混合加密的定位如何在海量混淆代码中快速找到AES和RSA的结合点。细节参数的确认IV是否等于KeyPadding模式是什么公钥格式是否需要转换这些都必须通过动态调试验证不能靠猜。环境最小化扣代码时保持克制只取必要逻辑避免陷入补环境的泥潭。给新手的建议不要过度依赖AST还原或自动化补环境框架。对于这种标准加密库的场景手动调试理解原理才是最快的路径。当你能够看着混淆代码就在脑海中勾勒出数据流向时才算真正入了逆向的门。 参考资料CryptoJS官方文档PyCryptodome使用指南《JavaScript逆向开发实战》如果这篇文章对你有帮助欢迎点赞、收藏、关注三连支持有任何问题欢迎在评论区交流讨论。