-
-
2025 全国大学生信息安全竞赛初赛 Reverse WriteUp
-
发表于: 2025-12-29 09:54 3763
-
页面中有一个「登录」按钮,点击后会调用 WASM 中的authenticate函数。
前端会计算一个 check 值:
因此要 枚举时间戳,找到一个能满足该前缀要求的 check。
最终提交格式:
使用 PowerShell 从 release.wasm.map 中提取 TypeScript 源码,这些源码原本就是有注释的:
在 assembly_index.ts 中可以看到核心认证逻辑:
签名函数:
自定义 Base64:
自定义 HMAC-SHA256 关键点:
总结不同点:
HTML 最后一行注释:
所以可以确定:
对每一个候选时间戳:
用自定义 Base64 编码密码 admin。
构造 message:
使用时间戳字符串做key,按**自定义 HMAC-SHA256** 计算签名。
对 HMAC 结果再做自定义 Base64,得到 signature。
构造最终 JSON:
计算 check = MD5(JSON.stringify(finalJSON))。
判断 check 是否以 ccaf33e3512e31f3 开头。
根据文件时间(题目提示「爆肝到周一凌晨」对,题目描述是这样的):
推测时间戳大致落在 2025-12-22 00:00:00 ~ 02:00:00(UTC+8)之间,直接在该区间内暴力搜索即可。
Node.js 脚本如下:
爆破结果:
在 scripts/flag.gd 中初始化:
仅依赖这里的 key 还不足以得到最终 flag,因为游戏过程中 key 会被修改。
在 scripts/game_manager.gd:
也就是说,当获得第一个金币时:
再次回到 flag.gd 的加密逻辑(AES-ECB):
**目标:**找到一个输入,对 修改后的 key (FbnBglFbnBglOoO!)做 AES-ECB 加密,密文为
可以直接在本地用 Python 复现 Godot 的 AES 行为。
字符生成函数 f:
可以看成 Fibonacci 序列模 16 后索引一个 16 字节表,再配合特定递推生成的 v。
Fibonacci 序列模 16 是周期性的,周期为 24。预先把 F(n) mod 16 的 24 项打表即可。
运行结果:
从生成的日志中可以定位 main.main:
在逆向过程中,iupHvc2q4 包含核心逻辑(而且主要部分都在混淆过函数名的函数下,那种一眼就能看到干什么的函数都显得不重要了):
iupHvc2q4.(*H1eV17y).Run 主要逻辑:
函数 HaNDRB_IhET:
byte_98F158 内容:
与 0x99 异或后的 ASCII:
这与 pcap中每个应用层负载开头的 8 字节完全一致。
OnJCbKpp 中调用 AES 相关函数,密钥位于只读段:
转为 ASCII:
这是一条 32 字节 AES 密钥,用于 AES‑GCM。
结合逆向代码和流量分析,可得每条消息格式:
解密后 payload 为 zstd 压缩的 protobuf。
大致流程:
脚本如下:
查找 base32 字符串
将其以 base32 解码即可得到 flag:
import { authenticate } from "./build/release.js"; // 初始化 WASM 模块async function initWasm() { const wasmStatus = document.getElementById('wasm-status'); const loginForm = document.getElementById('login-form'); const loginBtn = document.getElementById('login-btn'); const loginSpinner = document.getElementById('login-spinner'); const statusMessage = document.getElementById('status-message'); const errorMessage = document.getElementById('error-message'); const passwordInput = document.getElementById('password'); const togglePasswordBtn = document.getElementById('toggle-password'); try { // 初始化完成 wasmStatus.textContent = 'WASM 已加载'; wasmStatus.classList.add('text-success'); // 切换密码可见性 togglePasswordBtn.addEventListener('click', function() { const type = passwordInput.getAttribute('type') === 'password' ? 'text' : 'password'; passwordInput.setAttribute('type', type); const icon = this.querySelector('i'); const text = this.querySelector('span'); if (type === 'text') { icon.classList.remove('fa-eye-slash'); icon.classList.add('fa-eye'); text.textContent = '隐藏'; } else { icon.classList.remove('fa-eye'); icon.classList.add('fa-eye-slash'); text.textContent = '显示'; } }); // 登录表单提交处理 loginForm.addEventListener('submit', async function(e) { e.preventDefault(); // 显示加载状态 loginBtn.disabled = true; loginSpinner.classList.remove('hidden'); statusMessage.classList.add('hidden'); try { const username = document.getElementById('username').value; const password = document.getElementById('password').value; // 调用 WASM 中的 authenticate 函数 const authResult = authenticate(username, password); const authData = JSON.parse(authResult); // 模拟发送到服务器 console.log('发送到服务器的数据:', authData); // 模拟服务器响应 simulateServerRequest(authData) .then(response => { if (response.success) { // 登录成功 alert('登录成功!'); } else { // 登录失败 showError(response.message || '登录失败,请重试'); } }) .catch(error => { console.error('登录错误:', error); showError('网络错误,请稍后重试'); }) .finally(() => { // 恢复按钮状态 loginBtn.disabled = false; loginSpinner.classList.add('hidden'); }); } catch (error) { console.error('WASM 处理错误:', error); showError('内部错误,请联系管理员'); // 恢复按钮状态 loginBtn.disabled = false; loginSpinner.classList.remove('hidden'); } }); // 显示错误消息 function showError(message) { errorMessage.textContent = message; statusMessage.classList.remove('hidden'); // 添加动画效果 const errorBox = statusMessage.querySelector('div'); errorBox.classList.add('animate-shake'); setTimeout(() => { errorBox.classList.remove('animate-shake'); }, 500); } // 模拟服务器请求 function simulateServerRequest(data) { return new Promise(resolve => { // 模拟网络延迟 setTimeout(() => { // 实际应用中这里应该是真实的 API 请求 // 这里仅作演示,使用本地判断 const check = CryptoJS.MD5(JSON.stringify(data)).toString(CryptoJS.enc.Hex); if (check.startsWith("ccaf33e3512e31f3")){ resolve({ success: true }); }else{ resolve({ success: false }); } }, 1000); }); } } catch (error) { console.error('WASM 加载失败:', error); wasmStatus.textContent = 'WASM 加载失败'; wasmStatus.classList.add('text-danger'); // 禁用登录按钮 loginBtn.disabled = true; loginBtn.classList.add('bg-neutral-400'); loginBtn.classList.remove('bg-primary', 'hover:bg-primary/90'); } } // 页面加载完成后初始化 WASM window.addEventListener('load', initWasm); import { authenticate } from "./build/release.js"; // 初始化 WASM 模块async function initWasm() { const wasmStatus = document.getElementById('wasm-status'); const loginForm = document.getElementById('login-form'); const loginBtn = document.getElementById('login-btn'); const loginSpinner = document.getElementById('login-spinner'); const statusMessage = document.getElementById('status-message'); const errorMessage = document.getElementById('error-message'); const passwordInput = document.getElementById('password'); const togglePasswordBtn = document.getElementById('toggle-password'); try { // 初始化完成 wasmStatus.textContent = 'WASM 已加载'; wasmStatus.classList.add('text-success'); // 切换密码可见性 togglePasswordBtn.addEventListener('click', function() { const type = passwordInput.getAttribute('type') === 'password' ? 'text' : 'password'; passwordInput.setAttribute('type', type); const icon = this.querySelector('i'); const text = this.querySelector('span'); if (type === 'text') { icon.classList.remove('fa-eye-slash'); icon.classList.add('fa-eye'); text.textContent = '隐藏'; } else { icon.classList.remove('fa-eye'); icon.classList.add('fa-eye-slash'); text.textContent = '显示'; } }); // 登录表单提交处理 loginForm.addEventListener('submit', async function(e) { e.preventDefault(); // 显示加载状态 loginBtn.disabled = true; loginSpinner.classList.remove('hidden'); statusMessage.classList.add('hidden'); try { const username = document.getElementById('username').value; const password = document.getElementById('password').value; // 调用 WASM 中的 authenticate 函数 const authResult = authenticate(username, password); const authData = JSON.parse(authResult); // 模拟发送到服务器 console.log('发送到服务器的数据:', authData); // 模拟服务器响应 simulateServerRequest(authData) .then(response => { if (response.success) { // 登录成功 alert('登录成功!'); } else { // 登录失败 showError(response.message || '登录失败,请重试'); } }) .catch(error => { console.error('登录错误:', error); showError('网络错误,请稍后重试'); }) .finally(() => { // 恢复按钮状态 loginBtn.disabled = false; loginSpinner.classList.add('hidden'); }); } catch (error) { console.error('WASM 处理错误:', error); showError('内部错误,请联系管理员'); // 恢复按钮状态 loginBtn.disabled = false; loginSpinner.classList.remove('hidden'); } }); // 显示错误消息 function showError(message) { errorMessage.textContent = message; statusMessage.classList.remove('hidden'); // 添加动画效果 const errorBox = statusMessage.querySelector('div'); errorBox.classList.add('animate-shake'); setTimeout(() => { errorBox.classList.remove('animate-shake'); }, 500); } // 模拟服务器请求 function simulateServerRequest(data) { return new Promise(resolve => { // 模拟网络延迟 setTimeout(() => { // 实际应用中这里应该是真实的 API 请求 // 这里仅作演示,使用本地判断 const check = CryptoJS.MD5(JSON.stringify(data)).toString(CryptoJS.enc.Hex); if (check.startsWith("ccaf33e3512e31f3")){ resolve({ success: true }); }else{ resolve({ success: false }); } }, 1000); }); } } catch (error) { console.error('WASM 加载失败:', error); wasmStatus.textContent = 'WASM 加载失败'; wasmStatus.classList.add('text-danger'); // 禁用登录按钮 loginBtn.disabled = true; loginBtn.classList.add('bg-neutral-400'); loginBtn.classList.remove('bg-primary', 'hover:bg-primary/90'); } } // 页面加载完成后初始化 WASM window.addEventListener('load', initWasm); check = MD5(JSON.stringify(authData));// 要求:check 必须以 "ccaf33e3512e31f3" 开头check = MD5(JSON.stringify(authData));// 要求:check 必须以 "ccaf33e3512e31f3" 开头flag{正确的check值}flag{正确的check值}$map = Get-Content -Raw -Path .\[release.wasm.map](8b2K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4u0W2L8r3g2S2M7$3g2Q4x3X3g2%4j5i4y4E0i4K6u0W2L8h3q4H3i4K6t1&6 | ConvertFrom-Json$idx = $map.sources.IndexOf('assembly/index.ts')$map.sourcesContent[$idx] | Out-File -Encoding ascii .\assembly_index.ts$map = Get-Content -Raw -Path .\[release.wasm.map](b61K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4u0W2L8r3g2S2M7$3g2Q4x3X3g2%4j5i4y4E0i4K6u0W2L8h3q4H3i4K6t1&6 | ConvertFrom-Json$idx = $map.sources.IndexOf('assembly/base64.ts')$map.sourcesContent[$idx] | Out-File -Encoding ascii .\assembly_base64.ts$map = Get-Content -Raw -Path .\[release.wasm.map](347K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4u0W2L8r3g2S2M7$3g2Q4x3X3g2%4j5i4y4E0i4K6u0W2L8h3q4H3i4K6t1&6 | ConvertFrom-Json$idx = $map.sources.IndexOf('assembly/index.ts')$map.sourcesContent[$idx] | Out-File -Encoding ascii .\assembly_index.ts$map = Get-Content -Raw -Path .\[release.wasm.map](241K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4u0W2L8r3g2S2M7$3g2Q4x3X3g2%4j5i4y4E0i4K6u0W2L8h3q4H3i4K6t1&6 | ConvertFrom-Json$idx = $map.sources.IndexOf('assembly/base64.ts')$map.sourcesContent[$idx] | Out-File -Encoding ascii .\assembly_base64.tsexport function authenticate(username: string, password: string): string { // 1. Base64编码密码 const encodedPassword = encode(stringToUint8Array(password)); //console.log(encodedPassword); // 2. 获取当前时间戳(毫秒) const timestamp = Date.now().toString(); //console.log(timestamp); // 3. 构建原始JSON消息 const message = `{"username":"${username}","password":"${encodedPassword}"}`; //console.log(message); // 4. 使用HMAC-SHA256签名 const signature = signMessage(message, timestamp); //console.log(signature); // 5. 构建最终JSON消息 const finalMessage = ` {"username":"${username}","password":"${encodedPassword}","signature":"${signature}"}`; return finalMessage; //return "ok";}export function authenticate(username: string, password: string): string { // 1. Base64编码密码 const encodedPassword = encode(stringToUint8Array(password)); //console.log(encodedPassword); // 2. 获取当前时间戳(毫秒) const timestamp = Date.now().toString(); //console.log(timestamp); // 3. 构建原始JSON消息 const message = `{"username":"${username}","password":"${encodedPassword}"}`; //console.log(message); // 4. 使用HMAC-SHA256签名 const signature = signMessage(message, timestamp); //console.log(signature); // 5. 构建最终JSON消息 const finalMessage = ` {"username":"${username}","password":"${encodedPassword}","signature":"${signature}"}`; return finalMessage; //return "ok";}function signMessage(message: string, secret: string): string { const messageBytes = String.UTF8.encode(message); const secretBytes = String.UTF8.encode(secret); /** const messageBytesPtr = changetype<usize>(messageBytes); const secretBytesPtr = changetype<usize>(secretBytes); const hashInput = new ArrayBuffer(messageBytes.byteLength + secretBytes.byteLength); const hashInputPtr = changetype<usize>(hashInput); memory.copy(hashInputPtr, messageBytesPtr, messageBytes.byteLength); memory.copy(hashInputPtr + messageBytes.byteLength, secretBytesPtr, secretBytes.byteLength); const signatureBytes = new ArrayBuffer(32); const signatureBytesPtr = changetype<usize>(signatureBytes); init(); update(hashInputPtr, hashInput.byteLength); final(signatureBytesPtr) */ const signatureBytes = hmacSHA256(secretBytes,messageBytes); return encode(ArrayBufferToUint8Array(signatureBytes));} function signMessage(message: string, secret: string): string { const messageBytes = String.UTF8.encode(message); const secretBytes = String.UTF8.encode(secret); /** const messageBytesPtr = changetype<usize>(messageBytes); const secretBytesPtr = changetype<usize>(secretBytes); const hashInput = new ArrayBuffer(messageBytes.byteLength + secretBytes.byteLength); const hashInputPtr = changetype<usize>(hashInput); memory.copy(hashInputPtr, messageBytesPtr, messageBytes.byteLength); memory.copy(hashInputPtr + messageBytes.byteLength, secretBytesPtr, secretBytes.byteLength); const signatureBytes = new ArrayBuffer(32); const signatureBytesPtr = changetype<usize>(signatureBytes); init(); update(hashInputPtr, hashInput.byteLength); final(signatureBytesPtr) */ const signatureBytes = hmacSHA256(secretBytes,messageBytes); return encode(ArrayBufferToUint8Array(signatureBytes));} // @ts-ignore: decorator@lazy const PADCHAR = "=";// @ts-ignore: decorator@lazy// const ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; //这是标准base64映射表const ALPHA = "NhR4UJ+z5qFGiTCaAIDYwZ0dLl6PEXKgostxuMv8rHBp3n9emjQf1cWb2/VkS7yO";//这是本题中使用的新表 /** * Encode Uint8Array as a base64 string. * @param bytes Byte array of type Uint8Array. */ export function encode(bytes: Uint8Array): string { let i: i32, b10: u32; const extrabytes = (bytes.length % 3); let imax = bytes.length - extrabytes; const len = ((bytes.length / 3) as i32) * 4 + (extrabytes == 0 ? 0 : 4); let x = changetype<string>(__new(<usize>(len << 1), idof<string>())); if (bytes.length == 0) { return ""; } let ptr = changetype<usize>(x) - 2; for (i = 0; i < imax; i += 3) { b10 = ((bytes[i] as u32) << 16) | ((bytes[i + 1] as u32) << 8) | (bytes[i + 2] as u32); store<u16>(ptr+=2, (ALPHA.charCodeAt(b10 >> 18) as u16)); store<u16>(ptr+=2, (ALPHA.charCodeAt(((b10 >> 12) & 63)) as u16)); store<u16>(ptr+=2, (ALPHA.charCodeAt(((b10 >> 6) & 63)) as u16)); store<u16>(ptr+=2, (ALPHA.charCodeAt((b10 & 63)) as u16)); } switch (bytes.length - imax) { case 1: b10 = (bytes[i] as u32) << 16; store<u16>(ptr+=2, ((ALPHA.charCodeAt(b10 >> 18)) as u16)); store<u16>(ptr+=2, ((ALPHA.charCodeAt((b10 >> 12) & 63)) as u16)); store<u16>(ptr+=2, ((PADCHAR.charCodeAt(0)) as u16)); store<u16>(ptr+=2, ((PADCHAR.charCodeAt(0)) as u16)); break; case 2: b10 = ((bytes[i] as u32) << 16) | ((bytes[i + 1] as u32) << 8); store<u16>(ptr+=2, ((ALPHA.charCodeAt(b10 >> 18)) as u16)); store<u16>(ptr+=2, ((ALPHA.charCodeAt((b10 >> 12) & 63)) as u16)); store<u16>(ptr+=2, ((ALPHA.charCodeAt((b10 >> 6) & 63)) as u16)); store<u16>(ptr+=2, ((PADCHAR.charCodeAt(0)) as u16)); break; } return x; }// @ts-ignore: decorator@lazy const PADCHAR = "=";// @ts-ignore: decorator@lazy// const ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; //这是标准base64映射表const ALPHA = "NhR4UJ+z5qFGiTCaAIDYwZ0dLl6PEXKgostxuMv8rHBp3n9emjQf1cWb2/VkS7yO";//这是本题中使用的新表 /** * Encode Uint8Array as a base64 string. * @param bytes Byte array of type Uint8Array. */ export function encode(bytes: Uint8Array): string { let i: i32, b10: u32; const extrabytes = (bytes.length % 3); let imax = bytes.length - extrabytes; const len = ((bytes.length / 3) as i32) * 4 + (extrabytes == 0 ? 0 : 4); let x = changetype<string>(__new(<usize>(len << 1), idof<string>())); if (bytes.length == 0) { return ""; } let ptr = changetype<usize>(x) - 2; for (i = 0; i < imax; i += 3) { b10 = ((bytes[i] as u32) << 16) | ((bytes[i + 1] as u32) << 8) | (bytes[i + 2] as u32); store<u16>(ptr+=2, (ALPHA.charCodeAt(b10 >> 18) as u16)); store<u16>(ptr+=2, (ALPHA.charCodeAt(((b10 >> 12) & 63)) as u16)); store<u16>(ptr+=2, (ALPHA.charCodeAt(((b10 >> 6) & 63)) as u16)); store<u16>(ptr+=2, (ALPHA.charCodeAt((b10 & 63)) as u16)); } switch (bytes.length - imax) { case 1: b10 = (bytes[i] as u32) << 16; store<u16>(ptr+=2, ((ALPHA.charCodeAt(b10 >> 18)) as u16)); store<u16>(ptr+=2, ((ALPHA.charCodeAt((b10 >> 12) & 63)) as u16)); store<u16>(ptr+=2, ((PADCHAR.charCodeAt(0)) as u16)); store<u16>(ptr+=2, ((PADCHAR.charCodeAt(0)) as u16)); break; case 2: b10 = ((bytes[i] as u32) << 16) | ((bytes[i + 1] as u32) << 8); store<u16>(ptr+=2, ((ALPHA.charCodeAt(b10 >> 18)) as u16)); store<u16>(ptr+=2, ((ALPHA.charCodeAt((b10 >> 12) & 63)) as u16)); store<u16>(ptr+=2, ((ALPHA.charCodeAt((b10 >> 6) & 63)) as u16)); store<u16>(ptr+=2, ((PADCHAR.charCodeAt(0)) as u16)); break; } return x; }function hmacSHA256(key: ArrayBuffer, message: ArrayBuffer): ArrayBuffer { const blockSize = 64; // SHA256 ????????64 ??? // ?????? const keyPtr = changetype<usize>(key); const paddedKey = new ArrayBuffer(blockSize); const paddedKeyPtr = changetype<usize>(paddedKey); if (key.byteLength > blockSize) { // ????????????????????????????????n init(); update(keyPtr, key.byteLength); final(paddedKeyPtr); }else{ // ???????????? memory.copy(paddedKeyPtr, keyPtr, key.byteLength); fill(paddedKeyPtr + key.byteLength, 0, blockSize - key.byteLength) } //console.log(ArrayBufferToUint8Array(paddedKey).toString()); // ??? ipad ??opad const ipad = new ArrayBuffer(blockSize); const opad = new ArrayBuffer(blockSize); const ipadPtr = changetype<usize>(ipad); const opadPtr = changetype<usize>(opad); for (let i = 0; i < blockSize; i++) { store<u8>(ipadPtr + i , load<u8>(paddedKeyPtr + i) ^ 0x76); store<u8>(opadPtr + i , load<u8>(paddedKeyPtr + i) ^ 0x3C); } //console.log(ArrayBufferToUint8Array(ipad).toString()); //console.log(ArrayBufferToUint8Array(opad).toString()); // ??? innerHash const innerInput = new ArrayBuffer(ipad.byteLength + message.byteLength); const innerInputPtr = changetype<usize>(innerInput); const messagePtr = changetype<usize>(message) memory.copy(innerInputPtr, ipadPtr, ipad.byteLength); memory.copy(innerInputPtr + ipad.byteLength, messagePtr, message.byteLength); //console.log(ArrayBufferToUint8Array(innerInput).toString()); init(); update(innerInputPtr,innerInput.byteLength); //update(ipadPtr,ipad.byteLength); //update(messagePtr,message.byteLength); const innerHash = new ArrayBuffer(32); const innerHashPtr = changetype<usize>(innerHash); final(innerHashPtr); //console.log(ArrayBufferToUint8Array(innerHash).toString()); // ??? outerHash const outerInput = new ArrayBuffer(opad.byteLength + innerHash.byteLength); const outerInputPtr = changetype<usize>(outerInput); memory.copy(outerInputPtr, innerHashPtr, innerHash.byteLength); memory.copy(outerInputPtr + innerHash.byteLength, opadPtr, opad.byteLength); //console.log(ArrayBufferToUint8Array(outerInput).toString()); init(); update(outerInputPtr,outerInput.byteLength); //update(opadPtr,opad.byteLength); //update(innerHashPtr,innerHash.byteLength); const outerHash = new ArrayBuffer(32); const outerHashPtr = changetype<usize>(outerHash); final(outerHashPtr); //console.log(ArrayBufferToUint8Array(outerHash).toString()); return outerHash;}function hmacSHA256(key: ArrayBuffer, message: ArrayBuffer): ArrayBuffer { const blockSize = 64; // SHA256 ????????64 ??? // ?????? const keyPtr = changetype<usize>(key); const paddedKey = new ArrayBuffer(blockSize); const paddedKeyPtr = changetype<usize>(paddedKey); if (key.byteLength > blockSize) { // ????????????????????????????????n init(); update(keyPtr, key.byteLength); final(paddedKeyPtr); }else{ // ???????????? memory.copy(paddedKeyPtr, keyPtr, key.byteLength); fill(paddedKeyPtr + key.byteLength, 0, blockSize - key.byteLength) } //console.log(ArrayBufferToUint8Array(paddedKey).toString()); // ??? ipad ??opad const ipad = new ArrayBuffer(blockSize); const opad = new ArrayBuffer(blockSize); const ipadPtr = changetype<usize>(ipad); const opadPtr = changetype<usize>(opad); for (let i = 0; i < blockSize; i++) { store<u8>(ipadPtr + i , load<u8>(paddedKeyPtr + i) ^ 0x76); store<u8>(opadPtr + i , load<u8>(paddedKeyPtr + i) ^ 0x3C); } //console.log(ArrayBufferToUint8Array(ipad).toString()); //console.log(ArrayBufferToUint8Array(opad).toString()); // ??? innerHash const innerInput = new ArrayBuffer(ipad.byteLength + message.byteLength); const innerInputPtr = changetype<usize>(innerInput); const messagePtr = changetype<usize>(message) memory.copy(innerInputPtr, ipadPtr, ipad.byteLength); memory.copy(innerInputPtr + ipad.byteLength, messagePtr, message.byteLength); //console.log(ArrayBufferToUint8Array(innerInput).toString()); init(); update(innerInputPtr,innerInput.byteLength); //update(ipadPtr,ipad.byteLength); //update(messagePtr,message.byteLength); const innerHash = new ArrayBuffer(32); const innerHashPtr = changetype<usize>(innerHash); final(innerHashPtr); //console.log(ArrayBufferToUint8Array(innerHash).toString()); // ??? outerHash const outerInput = new ArrayBuffer(opad.byteLength + innerHash.byteLength); const outerInputPtr = changetype<usize>(outerInput); memory.copy(outerInputPtr, innerHashPtr, innerHash.byteLength); memory.copy(outerInputPtr + innerHash.byteLength, opadPtr, opad.byteLength); //console.log(ArrayBufferToUint8Array(outerInput).toString()); init(); update(outerInputPtr,outerInput.byteLength); //update(opadPtr,opad.byteLength); //update(innerHashPtr,innerHash.byteLength); const outerHash = new ArrayBuffer(32); const outerHashPtr = changetype<usize>(outerHash); final(outerHashPtr); //console.log(ArrayBufferToUint8Array(outerHash).toString()); return outerHash;}<!-- 测试账号 admin 测试密码 admin --><!-- 测试账号 admin 测试密码 admin -->username = "admin"password = "admin"username = "admin"password = "admin"const crypto = require('crypto');// 自定义 Base64 字母表const ALPHA = "NhR4UJ+z5qFGiTCaAIDYwZ0dLl6PEXKgostxuMv8rHBp3n9emjQf1cWb2/VkS7yO";function base64Custom(buf) { let out = ''; let i = 0; for (; i + 2 < buf.length; i += 3) { const b10 = (buf[i] << 16) | (buf[i + 1] << 8) | buf[i + 2]; out += ALPHA[(b10 >> 18) & 63]; out += ALPHA[(b10 >> 12) & 63]; out += ALPHA[(b10 >> 6) & 63]; out += ALPHA[b10 & 63]; } const rem = buf.length - i; if (rem === 1) { const b10 = buf[i] << 16; out += ALPHA[(b10 >> 18) & 63]; out += ALPHA[(b10 >> 12) & 63]; out += '='; out += '='; } else if (rem === 2) { const b10 = (buf[i] << 16) | (buf[i + 1] << 8); out += ALPHA[(b10 >> 18) & 63]; out += ALPHA[(b10 >> 12) & 63]; out += ALPHA[(b10 >> 6) & 63]; out += '='; } return out;}function sha256(buf) { return crypto.createHash('sha256').update(buf).digest();}// 自定义 HMAC (ipad/opad 常量不同)function hmacSHA256(key, message) { const blockSize = 64; let paddedKey = Buffer.alloc(blockSize, 0); if (key.length > blockSize) { sha256(key).copy(paddedKey, 0); } else { key.copy(paddedKey, 0); } const ipad = Buffer.alloc(blockSize); const opad = Buffer.alloc(blockSize); for (let i = 0; i < blockSize; i++) { const b = paddedKey[i]; ipad[i] = b ^ 0x76; opad[i] = b ^ 0x3C; } const innerHash = sha256(Buffer.concat([ipad, message])); const outerHash = sha256(Buffer.concat([innerHash, opad])); return outerHash;}const username = 'admin';const password = 'admin';const encodedPassword = base64Custom(Buffer.from(password, 'utf8'));const message = `{"username":"${username}","password":"${encodedPassword}"}`;const messageBytes = Buffer.from(message, 'utf8');const prefix = 'ccaf33e3512e31f3';const start = Date.parse('2025-12-22T00:00:00+08:00');const end = Date.parse('2025-12-22T02:00:00+08:00');for (let ts = start; ts < end; ts++) { const key = Buffer.from(String(ts), 'utf8'); const sigBytes = hmacSHA256(key, messageBytes); const signature = base64Custom(sigBytes); const finalMessage = `{"username":"${username}","password":"${encodedPassword}","signature":"${signature}"}`; const check = crypto.createHash('md5').update(finalMessage).digest('hex'); if (check.startsWith(prefix)) { console.log('FOUND', ts, check); break; }}const crypto = require('crypto');// 自定义 Base64 字母表const ALPHA = "NhR4UJ+z5qFGiTCaAIDYwZ0dLl6PEXKgostxuMv8rHBp3n9emjQf1cWb2/VkS7yO";function base64Custom(buf) { let out = ''; let i = 0; for (; i + 2 < buf.length; i += 3) { const b10 = (buf[i] << 16) | (buf[i + 1] << 8) | buf[i + 2]; out += ALPHA[(b10 >> 18) & 63]; out += ALPHA[(b10 >> 12) & 63]; out += ALPHA[(b10 >> 6) & 63]; out += ALPHA[b10 & 63]; } const rem = buf.length - i; if (rem === 1) { const b10 = buf[i] << 16; out += ALPHA[(b10 >> 18) & 63]; out += ALPHA[(b10 >> 12) & 63]; out += '='; out += '='; } else if (rem === 2) { const b10 = (buf[i] << 16) | (buf[i + 1] << 8); out += ALPHA[(b10 >> 18) & 63]; out += ALPHA[(b10 >> 12) & 63]; out += ALPHA[(b10 >> 6) & 63]; out += '='; } return out;}function sha256(buf) { return crypto.createHash('sha256').update(buf).digest();}// 自定义 HMAC (ipad/opad 常量不同)function hmacSHA256(key, message) { const blockSize = 64; let paddedKey = Buffer.alloc(blockSize, 0); if (key.length > blockSize) { sha256(key).copy(paddedKey, 0); } else { key.copy(paddedKey, 0); } const ipad = Buffer.alloc(blockSize); const opad = Buffer.alloc(blockSize); for (let i = 0; i < blockSize; i++) { const b = paddedKey[i]; ipad[i] = b ^ 0x76; opad[i] = b ^ 0x3C; } const innerHash = sha256(Buffer.concat([ipad, message])); const outerHash = sha256(Buffer.concat([innerHash, opad])); return outerHash;}const username = 'admin';const password = 'admin';const encodedPassword = base64Custom(Buffer.from(password, 'utf8'));const message = `{"username":"${username}","password":"${encodedPassword}"}`;const messageBytes = Buffer.from(message, 'utf8');const prefix = 'ccaf33e3512e31f3';const start = Date.parse('2025-12-22T00:00:00+08:00');const end = Date.parse('2025-12-22T02:00:00+08:00');for (let ts = start; ts < end; ts++) { const key = Buffer.from(String(ts), 'utf8'); const sigBytes = hmacSHA256(key, messageBytes); const signature = base64Custom(sigBytes); const finalMessage = `{"username":"${username}","password":"${encodedPassword}","signature":"${signature}"}`; const check = crypto.createHash('md5').update(finalMessage).digest('hex'); if (check.startsWith(prefix)) { console.log('FOUND', ts, check); break; }}时间戳:1766334550699check:ccaf33e3512e31f36228f0b97ccbc8f1时间戳:1766334550699check:ccaf33e3512e31f36228f0b97ccbc8f1flag{ccaf33e3512e31f36228f0b97ccbc8f1}flag{ccaf33e3512e31f36228f0b97ccbc8f1}<!-- Brute-force timestamp helper (CTF) --> <div class="mt-6 text-center text-sm text-neutral-500"> <button id="bruteforce-btn" class="mt-2 px-4 py-2 rounded-lg bg-secondary text-white">?????</button> <div id="bruteforce-status" class="mt-2 text-neutral-400">???</div> </div><script type="module"> import { authenticate } from "./build/release.js"; const bfBtn = document.getElementById('bruteforce-btn'); const bfStatus = document.getElementById('bruteforce-status'); bfBtn.addEventListener('click', async () => { const prefix = "ccaf33e3512e31f3"; const username = "admin"; const password = "admin"; const start = Date.parse("2025-12-22T00:00:00+08:00"); const end = Date.parse("2025-12-22T02:00:00+08:00"); const oldNow = Date.now; bfStatus.textContent = "???..."; bfBtn.disabled = true; for (let ts = start; ts < end; ts++) { Date.now = () => ts; const authResult = authenticate(username, password); const data = JSON.parse(authResult); const check = CryptoJS.MD5(JSON.stringify(data)).toString(CryptoJS.enc.Hex); if (check.startsWith(prefix)) { bfStatus.textContent = `FOUND: ${ts} | ${check}`; Date.now = oldNow; bfBtn.disabled = false; return; } } Date.now = oldNow; bfStatus.textContent = "???"; bfBtn.disabled = false; });</script><!-- Brute-force timestamp helper (CTF) --> <div class="mt-6 text-center text-sm text-neutral-500"> <button id="bruteforce-btn" class="mt-2 px-4 py-2 rounded-lg bg-secondary text-white">?????</button> <div id="bruteforce-status" class="mt-2 text-neutral-400">???</div> </div><script type="module"> import { authenticate } from "./build/release.js"; const bfBtn = document.getElementById('bruteforce-btn'); const bfStatus = document.getElementById('bruteforce-status'); bfBtn.addEventListener('click', async () => { const prefix = "ccaf33e3512e31f3"; const username = "admin"; const password = "admin"; const start = Date.parse("2025-12-22T00:00:00+08:00"); const end = Date.parse("2025-12-22T02:00:00+08:00"); const oldNow = Date.now; bfStatus.textContent = "???..."; bfBtn.disabled = true; for (let ts = start; ts < end; ts++) { Date.now = () => ts; const authResult = authenticate(username, password); const data = JSON.parse(authResult); const check = CryptoJS.MD5(JSON.stringify(data)).toString(CryptoJS.enc.Hex); if (check.startsWith(prefix)) { bfStatus.textContent = `FOUND: ${ts} | ${check}`; Date.now = oldNow; bfBtn.disabled = false; return; } } Date.now = oldNow; bfStatus.textContent = "???"; bfBtn.disabled = false; });</script>利用项目直接解包: [a3bK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6s2c8q4u0q4g2r3!0G2L8s2y4Q4x3V1k6Y4k6s2y4V1k6h3y4G2L8i4m8Q4x3V1k6J5k6h3I4W2j5i4y4W2M7#2)9J5c8Y4c8S2k6#2)9J5c8Y4j5J5i4K6u0W2y4q4)9J5k6e0m8Q4y4f1c8Q4x3U0S2Z5N6s2c8H3M7#2)9K6b7g2)9J5c8W2)9J5c8X3N6A6N6r3S2#2j5W2)9J5k6h3y4G2L8g2)9J5c8V1N6p5f1V1g2f1L8$3!0D9M7#2)9J5c8X3N6V1M7$3c8W2j5$3!0E0M7q4)9J5c8Y4u0W2L8r3g2S2M7$3g2K6i4K6u0r3N6r3q4Y4i4K6u0r3N6U0u0Q4x3X3f1@1i4K6u0W2x3q4)9J5z5b7`.`. //Godot引擎可以解包源码利用项目直接解包: [c60K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6s2c8q4u0q4g2r3!0G2L8s2y4Q4x3V1k6Y4k6s2y4V1k6h3y4G2L8i4m8Q4x3V1k6J5k6h3I4W2j5i4y4W2M7#2)9J5c8Y4c8S2k6#2)9J5c8Y4j5J5i4K6u0W2y4q4)9J5k6e0m8Q4y4f1c8Q4x3U0S2Z5N6s2c8H3M7#2)9K6b7g2)9J5c8W2)9J5c8X3N6A6N6r3S2#2j5W2)9J5k6h3y4G2L8g2)9J5c8V1N6p5f1V1g2f1L8$3!0D9M7#2)9J5c8X3N6V1M7$3c8W2j5$3!0E0M7q4)9J5c8Y4u0W2L8r3g2S2M7$3g2K6i4K6u0r3N6r3q4Y4i4K6u0r3N6U0u0Q4x3X3f1@1i4K6u0W2x3q4)9J5z5b7`.`. //Godot引擎可以解包源码extends CenterContainer@onready var flagTextEdit: Node = $PanelContainer / VBoxContainer / FlagTextEdit@onready var label2: Node = $PanelContainer / VBoxContainer / Label2static var key = "FanAglFanAglOoO!"var data = ""func _on_ready() -> void : Flag.hide()func get_key() -> String: return keyfunc submit() -> void : data = flagTextEdit.text var aes = AESContext.new() aes.start(AESContext.MODE_ECB_ENCRYPT, key.to_utf8_buffer()) var encrypted = aes.update(data.to_utf8_buffer()) aes.finish() if encrypted.hex_encode() == "d458af702a680ae4d089ce32fc39945d": label2.show() else: label2.hide()func back() -> void : get_tree().change_scene_to_file("res://scenes/menu.tscn")extends CenterContainer@onready var flagTextEdit: Node = $PanelContainer / VBoxContainer / FlagTextEdit@onready var label2: Node = $PanelContainer / VBoxContainer / Label2static var key = "FanAglFanAglOoO!"var data = ""func _on_ready() -> void : Flag.hide()func get_key() -> String: return keyfunc submit() -> void : data = flagTextEdit.text var aes = AESContext.new() aes.start(AESContext.MODE_ECB_ENCRYPT, key.to_utf8_buffer()) var encrypted = aes.update(data.to_utf8_buffer()) aes.finish() if encrypted.hex_encode() == "d458af702a680ae4d089ce32fc39945d": label2.show() else: label2.hide()func back() -> void : get_tree().change_scene_to_file("res://scenes/menu.tscn")@onready var fan = $"../Fan"var score = 0func add_point(): score += 1 if score == 1: Flag.key = Flag.key.replace("A", "B") fan.visible = true@onready var fan = $"../Fan"var score = 0func add_point(): score += 1 if score == 1: Flag.key = Flag.key.replace("A", "B") fan.visible = trueFanAglFanAglOoO! -> F an A gl F an A gl OoO! B B (A→B)最终 key = FbnBglFbnBglOoO!FanAglFanAglOoO! -> F an A gl F an A gl OoO! B B (A→B)最终 key = FbnBglFbnBglOoO!func submit() -> void : data = flagTextEdit.text var aes = AESContext.new() aes.start(AESContext.MODE_ECB_ENCRYPT, key.to_utf8_buffer()) var encrypted = aes.update(data.to_utf8_buffer()) aes.finish() if encrypted.hex_encode() == "d458af702a680ae4d089ce32fc39945d": label2.show() else: label2.hide()func back() -> void : get_tree().change_scene_to_file("res://scenes/menu.tscn")func submit() -> void : data = flagTextEdit.text var aes = AESContext.new() aes.start(AESContext.MODE_ECB_ENCRYPT, key.to_utf8_buffer()) var encrypted = aes.update(data.to_utf8_buffer()) aes.finish() if encrypted.hex_encode() == "d458af702a680ae4d089ce32fc39945d": label2.show() else: label2.hide()func back() -> void : get_tree().change_scene_to_file("res://scenes/menu.tscn")d458af702a680ae4d089ce32fc39945dd458af702a680ae4d089ce32fc39945dimport sysimport iosys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')from Crypto.Cipher import AESfrom Crypto.Util.Padding import pad, unpad# 初始keyinitial_key = "FanAglFanAglOoO!"# 得分为1后,key中的'A'被替换为'B'final_key = initial_key.replace("A", "B")print(f"初始 key: {initial_key}")print(f"最终 key: {final_key}")# 目标加密结果target_encrypted_hex = "d458af702a680ae4d089ce32fc39945d"target_encrypted = bytes.fromhex(target_encrypted_hex)print(f"\n目标密文 (hex): {target_encrypted_hex}")print(f"目标密文 (bytes): {target_encrypted}")print(f"密文长度: {len(target_encrypted)} bytes")# 使用AES-ECB模式解密try: # 创建AES cipher对象 (ECB模式) cipher = AES.new(final_key.encode('utf-8'), AES.MODE_ECB) # 解密 decrypted = cipher.decrypt(target_encrypted) # 尝试去除padding try: flag = unpad(decrypted, AES.block_size).decode('utf-8') print(f"\n[OK] 成功解密 (带padding): {flag}") except: # 如果去除padding失败,直接解码 flag = decrypted.decode('utf-8').rstrip('\x00') print(f"\n[OK] 成功解密 (无padding): {flag}") # 验证 - 重新加密看是否匹配 cipher_verify = AES.new(final_key.encode('utf-8'), AES.MODE_ECB) # Godot的AES会自动padding到16字节的倍数 flag_padded = flag.encode('utf-8') if len(flag_padded) % 16 != 0: flag_padded = pad(flag_padded, AES.block_size) encrypted_verify = cipher_verify.encrypt(flag_padded) if encrypted_verify.hex() == target_encrypted_hex: print(f"[OK] 验证成功!加密结果匹配") else: print(f"[FAIL] 验证失败") print(f" 预期: {target_encrypted_hex}") print(f" 实际: {encrypted_verify.hex()}") print(f"\n{'='*50}") print(f"FLAG: {flag}") print(f"{'='*50}")except Exception as e: print(f"\n[FAIL] 解密失败: {e}") import traceback traceback.print_exc()import sysimport iosys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')from Crypto.Cipher import AESfrom Crypto.Util.Padding import pad, unpad# 初始keyinitial_key = "FanAglFanAglOoO!"# 得分为1后,key中的'A'被替换为'B'final_key = initial_key.replace("A", "B")print(f"初始 key: {initial_key}")print(f"最终 key: {final_key}")[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!