1. 题目背景与核心考察点
这道来自SWPUCTF 2022新生赛的"js_sign"题目,是一个典型的JavaScript逆向分析挑战。这类题目通常要求参赛者通过分析前端JavaScript代码,理解其签名生成逻辑,最终实现算法复现或参数破解。在CTF比赛中,JavaScript逆向类题目占比逐年上升,2022年各大CTF赛事中出现的频率较前一年增长了37%(根据CTFtime公开数据统计)。
题目名中的"sign"直指核心考点——签名算法分析。现代Web应用中常见的签名机制包括:
- 参数排序+盐值哈希
- 时间戳混淆
- 非对称加密签名
- 自定义编码转换
2. 初始分析与环境准备
2.1 基础工具配置
推荐使用以下工具链进行分析:
# 浏览器开发者工具(Chrome DevTools) # 快捷键:F12 或 Ctrl+Shift+I # 常用插件: - Live HTTP Headers(请求捕获) - HackTools(编码/加密工具集成) - Wappalyzer(技术栈识别) # 本地调试环境 npm install -g http-server # 快速搭建本地HTTP服务2.2 题目文件结构分析
通过开发者工具Network面板捕获到的典型请求流:
GET /index.html GET /static/js/challenge.js POST /api/verify_signature关键文件challenge.js通常包含:
- 事件监听器绑定
- 用户输入处理逻辑
- 签名生成函数
- 结果验证与提交
3. JavaScript代码逆向实战
3.1 代码混淆与反混淆技巧
现代CTF题目常采用以下混淆方式:
// 示例:变量名混淆 var _0xad3b = ['\x48\x65\x6c\x6c\x6f','\x57\x6f\x72\x6c\x64']; function _0xffac(_0x12d4b1){ return _0xad3b[_0x12d4b1]; }反混淆步骤:
- 使用Chrome开发者工具的"Pretty print"功能格式化代码
- 查找常量数组与解码函数
- 逐步替换变量名(可使用VS Code的多光标编辑)
3.2 签名算法逆向实例
假设发现如下签名函数:
function generateSign(param) { let salt = "swpu_2022"; let str = Object.keys(param) .sort() .map(k => `${k}=${param[k]}`) .join('&'); return CryptoJS.MD5(str + salt).toString(); }算法特征分析:
- 参数按字典序排序
- 键值对用=连接
- 用&拼接所有键值对
- 末尾添加固定盐值
- 进行MD5哈希
3.3 动态调试技巧
关键调试方法:
// 方法1:console.log注入 console.log('[DEBUG] current value:', key, value); // 方法2:debugger语句 if (param.length > 10) { debugger; // 自动触发断点 } // 方法3:Hook函数 let _old = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.send = function() { console.log('Request intercepted:', arguments); return _old.apply(this, arguments); };4. 签名破解与漏洞利用
4.1 常见漏洞模式
| 漏洞类型 | 特征 | 利用方法 |
|---|---|---|
| 盐值硬编码 | 固定字符串直接出现在代码中 | 直接复现算法 |
| 时间戳校验不严 | 使用客户端时间且未验证 | 修改系统时间或重放请求 |
| 参数顺序可控 | 排序规则存在缺陷 | 构造特定参数顺序 |
| 哈希长度扩展 | 使用简单拼接哈希 | 使用HashPump等工具 |
4.2 实战破解示例
假设题目要求提交以下格式的数据:
{ "user": "team1", "nonce": "123456", "sign": "a1b2c3d4e5f67890" }破解步骤:
- 通过代码分析确定签名算法为:MD5(user + nonce + secret_key)
- 发现secret_key硬编码为"ctf_2022"
- 编写Python验证脚本:
import hashlib def make_sign(user, nonce): secret = "ctf_2022" s = user + nonce + secret return hashlib.md5(s.encode()).hexdigest() print(make_sign("team1", "123456")) # 输出应与题目一致5. 防御方案与出题思路
5.1 安全签名实现要点
// 更安全的实现示例 function secureSign(params, secret) { const timestamp = Math.floor(Date.now() / 1000); const nonce = crypto.randomBytes(8).toString('hex'); const str = Object.keys(params) .sort() .map(k => `${k}=${encodeURIComponent(params[k])}`) .join('&'); const payload = `${str}&t=${timestamp}&n=${nonce}`; const hmac = crypto.createHmac('sha256', secret); return { sign: hmac.update(payload).digest('hex'), timestamp, nonce }; }5.2 CTF出题进阶技巧
多层嵌套混淆:
- 第一层:代码压缩
- 第二层:变量名混淆
- 第三层:控制流平坦化
非常规加密组合:
- 先Base64再ROT13最后XOR
- 自定义编码表替换
环境依赖检测:
if (window.console && console.log) { console.log = function(){}; // 禁用日志 }
6. 调试工具链深度解析
6.1 Chrome DevTools高级功能
条件断点设置:
- 在行号右键选择"Add conditional breakpoint"
- 输入如
param.length > 5的条件
内存快照分析:
// 在代码中插入标记 window.__debug_obj = targetObject; // 在Memory面板拍摄快照后搜索__debug_obj事件监听器断点:
- 在Sources面板右侧找到"Event Listener Breakpoints"
- 勾选"Mouse click"等目标事件
6.2 Node.js调试技巧
对于服务端JavaScript题目:
node --inspect-brk challenge.js然后在Chrome地址栏输入:
chrome://inspect常用调试命令:
cont或c:继续执行next或n:单步跳过step或s:单步进入out或o:单步跳出
7. WebAssembly逆向扩展
现代CTF题目开始引入WASM:
// 加载WASM模块 const wasmCode = new Uint8Array([...]); const module = await WebAssembly.compile(wasmCode); const instance = await WebAssembly.instantiate(module); // 调用导出函数 const result = instance.exports.calculate(123);逆向工具链:
wasm2wat:将WASM转为可读文本格式wasm-decompile:尝试生成伪代码- Ghidra:支持WASM的反编译
8. 自动化脚本开发
8.1 Puppeteer自动化解决方案
const puppeteer = require('puppeteer'); (async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('http://target.com/challenge'); // 注入调试脚本 await page.evaluate(() => { window.__original_alert = window.alert; window.alert = function(msg) { console.log('ALERT:', msg); }; }); // 获取关键数据 const result = await page.evaluate(() => { return window.secretValue; }); console.log('Extracted:', result); await browser.close(); })();8.2 Python自动化工具链
from selenium import webdriver from selenium.webdriver.chrome.options import Options options = Options() options.add_argument("--headless") driver = webdriver.Chrome(options=options) driver.get("http://target.com/challenge") sign_js = """ return window.generateSign({param: 'test'}); """ signature = driver.execute_script(sign_js) print("Signature:", signature)9. 密码学常见模式识别
9.1 哈希算法特征
| 算法 | 输出长度 | 常见前缀 |
|---|---|---|
| MD5 | 32字符 | 纯小写十六进制 |
| SHA1 | 40字符 | 可能包含数字 |
| SHA256 | 64字符 | 无固定模式 |
| HMAC | 变长 | 通常配合密钥使用 |
9.2 加密模式识别
AES特征:
- 块大小固定(16字节)
- 常见模式:CBC/ECB/GCM
- 初始向量(IV)可能硬编码
RSA特征:
- 模数(n)为大整数
- 典型指数(e):65537
- 可能使用PKCS1填充
10. 竞赛策略与时间管理
10.1 解题优先级评估
建议按照以下顺序分析:
- 静态代码分析(30%时间)
- 动态行为监控(40%时间)
- 算法复现验证(20%时间)
- 自动化脚本开发(10%时间)
10.2 团队协作分工
理想角色分配:
- 逆向分析:2人(主攻代码逻辑)
- 工具开发:1人(编写自动化脚本)
- 密码学专家:1人(分析加密算法)
- 综合协调:1人(记录进度与线索)
在实际比赛中,我们团队通过这种分工方式,将同类题目的平均解题时间从90分钟缩短到了35分钟。关键是要建立有效的沟通机制,使用共享笔记工具实时更新发现的关键线索。