逆向实战:从抓包到解密某游戏混合加密协议
1. 游戏通信协议逆向分析基础当你打开游戏客户端时所有看似简单的点击操作背后都隐藏着复杂的数据交换过程。游戏客户端与服务器之间的通信就像两个人在用密文交谈而我们今天要做的就是破解这套加密语言。抓包工具是这场解密之旅的第一把钥匙。以Fiddler为例安装后需要配置HTTPS解密功能打开Tools Options HTTPS勾选Decrypt HTTPS traffic。这时你会发现游戏请求中出现了两种截然不同的数据——明晃晃的文本和看不懂的乱码这就是典型的混合加密协议特征。我曾在分析某款游戏时发现登录请求中的密码字段显示为e10adc3949ba59abbe56e057f20f883e这明显是MD5的特征哈希值。而服务器返回的数据则像md5AES:w3cqakJcff674yZ...这样的复合加密标识暗示着多层加密机制的存在。要破解这套系统我们需要像剥洋葱一样逐层解析。2. 前端加密逻辑定位实战前端的JavaScript代码往往藏着加密的第一道关卡。对于Cocos Creator开发的游戏核心逻辑通常藏在src目录下的index.js中。这里有个实用技巧在Chrome开发者工具中按CtrlShiftF全局搜索md5、encrypt等关键词能快速定位加密入口。有次我分析某游戏登录流程时发现如下关键代码t.prototype.get_md5 function(t) { return this.md5(t) }这就是典型的MD5加密入口。更狡猾的游戏会把加密函数名混淆成a1b2c3这样的无意义字符串这时就需要结合函数调用栈分析——在Network面板找到登录请求右键选择Replay XHR并在调用堆栈中逐步回溯。在Cocos引擎中cc._RF.push/pop这类函数是重要的路标。它们像书签一样标记着代码模块的加载顺序。我曾通过追踪这些标记最终在层层嵌套的模块中找到被刻意隐藏的AES加密逻辑。3. 动态调试与代码插桩技术光静态分析还不够我们需要让代码开口说话。在Chrome的Sources面板给加密函数打上断点后可以在Console中实时修改变量值。比如输入console.log(当前加密输入:, t); t 123456; // 强制修改测试值这样就能验证加密函数的具体行为。更暴力的方法是直接修改源码。找到加密函数后插入调试语句t.prototype.md5 function(t) { alert(加密触发! 输入值: t); console.trace(); // 打印调用栈 // ...原有加密逻辑... }保存后刷新页面当游戏触发登录时这些插桩代码就会像探针一样把加密过程的关键信息暴露出来。我曾用这个方法成功捕获到某个游戏在MD5加密前会先对密码进行UTF-16到UTF-8的转换这个细节差点让我之前的破解尝试功亏一篑。4. MD5参数加密逆向还原当确认使用MD5加密后我们需要验证其具体实现。标准的MD5加密123456应该得到e10adc3949ba59abbe56e057f20f883e但游戏可能会加盐或多次哈希。通过Python可以快速验证import hashlib def game_md5(password): # 模拟游戏可能存在的额外处理 salt game_salt.encode() mixed password.encode() salt first_hash hashlib.md5(mixed).hexdigest() return hashlib.md5(first_hash.encode()).hexdigest() print(game_md5(123456)) # 输出双层MD5结果如果发现游戏使用了非标准MD5比如修改了初始魔数或循环次数就需要用JavaScript重写加密逻辑。这时可以复制游戏的原始加密函数用NodeJS构建测试环境const crypto require(crypto); function customMd5(text) { // 粘贴游戏中的MD5实现代码 } console.log(customMd5(test));5. AES响应数据解密实战当面对md5AES:w3cqakJcff...这样的响应时首先要提取AES的关键参数。通过调试通常能找到如下配置function CryptoConfig() { this.aes_key 2c4add8f849a7bea; this.aes_iv dc4b73b33e69eaff; }在Python中可以用PyCryptodome库实现对应解密from Crypto.Cipher import AES from Crypto.Util.Padding import unpad import base64 def decrypt_aes(ciphertext, key, iv): cipher AES.new(key.encode(), AES.MODE_CBC, iv.encode()) decrypted cipher.decrypt(base64.b64decode(ciphertext)) return unpad(decrypted, AES.block_size).decode() # 示例解密游戏返回数据 enc_data w3cqakJcff674yZSTLRgOck0lJ4SiaKjhO9Greq3hOdpirWmG9nB/tPuvl1BYA2... key 2c4add8f849a7bea iv dc4b73b33e69eaff print(decrypt_aes(enc_data.split(:)[1], key, iv))特别注意有些游戏会动态生成AES密钥。我曾遇到一个案例密钥是通过MD5(服务器时间戳)计算得出的这就需要先拦截密钥生成接口。6. 完整加解密流程模拟将前几步的分析成果整合我们可以用Python模拟整个认证流程import requests import hashlib from Crypto.Cipher import AES SESSION requests.Session() HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } def game_login(username, password): # 1. 客户端MD5加密 md5_pwd hashlib.md5(password.encode()).hexdigest() # 2. 发送登录请求 login_url http://game.example.com/api/login params { username: username, password: md5_pwd } resp SESSION.get(login_url, paramsparams, headersHEADERS).json() # 3. 解密AES响应 if resp[code] 200: encrypted_data resp[data] ciphertext encrypted_data.split(md5AES:)[1] decrypted decrypt_aes(ciphertext, AES_KEY, AES_IV) return decrypted对于更复杂的协议建议使用mitmproxy中间人代理来自动化处理加密解密。下面是一个处理流量的示例脚本from mitmproxy import http import hashlib class GameDecoder: def request(self, flow: http.HTTPFlow): if login in flow.request.url: # 拦截请求并修改加密参数 original_pwd flow.request.query.get(password) flow.request.query[password] custom_md5(original_pwd) def response(self, flow: http.HTTPFlow): if get_data in flow.request.url: # 自动解密响应数据 encrypted flow.response.content.decode() flow.response.text decrypt_aes(encrypted)7. 对抗反调试与代码混淆现代游戏往往会设置防御机制。当遇到无限debugger时可以在Chrome的Sources面板找到对应的行号右键选择Never pause here来跳过。对于代码混淆可以使用de4js等反混淆工具预处理。我曾遇到过一个棘手的案例游戏会检测console.log的调用栈。解决方案是重写console方法const nativeLog console.log; console.log function() { const stack new Error().stack; if(!stack.includes(detect.js)) { nativeLog.apply(console, arguments); } }对于WebSocket加密通信需要使用Chrome的Replay WebSocket功能捕获原始帧数据。某次分析中我发现游戏在建立WS连接后会先发送一个128字节的握手密钥后续所有数据都用这个密钥进行XOR异或加密。8. 自动化脚本开发技巧将逆向成果转化为自动化工具时建议使用PyQt5构建可视化界面。下面是一个简单的解密工具框架from PyQt5.QtWidgets import (QApplication, QTextEdit, QPushButton, QVBoxLayout, QWidget) class DecryptTool(QWidget): def __init__(self): super().__init__() self.setWindowTitle(游戏协议解密工具) self.input QTextEdit() self.output QTextEdit() self.btn QPushButton(解密) self.btn.clicked.connect(self.decrypt) layout QVBoxLayout() layout.addWidget(self.input) layout.addWidget(self.btn) layout.addWidget(self.output) self.setLayout(layout) def decrypt(self): text self.input.toPlainText() if text.startswith(md5AES:): self.output.setText(decrypt_aes(text[7:], KEY, IV))对于需要持久化的场景可以配合SQLite存储解密规则import sqlite3 conn sqlite3.connect(game_rules.db) conn.execute(CREATE TABLE IF NOT EXISTS rules (api TEXT PRIMARY KEY, encrypt_method TEXT, key TEXT))记得处理各种边界情况比如某次我发现游戏在凌晨3点会切换加密算法最终发现是开发者的定时任务在更新密钥。