refactor: 将文件从子目录提升到仓库根目录

所有文件从 hello_js_reverse_skill/ 子目录移至仓库根目录,
使项目结构更清晰,便于作为 Skill 直接引用。

Made-with: Cursor
This commit is contained in:
白夜
2026-03-18 22:55:27 +08:00
parent cfd0cc919d
commit fb14cf7916
29 changed files with 0 additions and 0 deletions
+121
View File
@@ -0,0 +1,121 @@
/**
* 主脚本模板 - 纯 Node.js 算法还原
*
* 适用场景:加密逻辑可完整提取,无浏览器环境依赖
*
* TODO: 根据逆向分析结果修改以下配置和逻辑
*/
const { generateM, generateSign } = require('./utils/encrypt');
const { RequestClient } = require('./utils/request');
// ============ 配置区域 ============
const CONFIG = {
// 目标信息
name: '示例项目',
description: '采集全部5页数据,计算数值总和',
// 接口配置
baseURL: 'https://target.com',
apiPath: '/api/data',
referer: 'https://target.com/page',
// 采集配置
totalPages: 5,
delay: 1500,
// 认证信息
cookies: {
// sessionid: 'your_session_id',
},
// 自定义请求头
headers: {
// 'X-Custom-Header': 'value',
},
};
// ============ 主逻辑 ============
async function fetchPage(client, page) {
// TODO: 根据实际接口修改参数构造逻辑
const m = generateM(page);
const timestamp = Math.floor(Date.now() / 1000);
const params = {
page,
m,
t: timestamp,
};
const response = await client.requestWithDelay({
method: 'GET',
url: CONFIG.apiPath,
params,
});
return response.data;
}
function extractData(responseData) {
// TODO: 根据实际响应格式修改数据提取逻辑
if (responseData && responseData.data) {
return responseData.data;
}
return [];
}
function calculateResult(allData) {
// TODO: 根据题目要求修改计算逻辑
// 示例:求所有数值的总和
const sum = allData.reduce((acc, item) => {
const value = typeof item === 'number' ? item : (item.value || 0);
return acc + value;
}, 0);
return sum;
}
async function main() {
console.log(`[*] 项目:${CONFIG.name}`);
console.log(`[*] 目标:${CONFIG.description}`);
console.log('');
const client = new RequestClient({
baseURL: CONFIG.baseURL,
cookies: CONFIG.cookies,
headers: CONFIG.headers,
referer: CONFIG.referer,
delay: CONFIG.delay,
});
const allData = [];
for (let page = 1; page <= CONFIG.totalPages; page++) {
try {
const responseData = await fetchPage(client, page);
const pageData = extractData(responseData);
allData.push(...pageData);
console.log(`[+] 正在采集第 ${page}/${CONFIG.totalPages} 页... ✓ 获取 ${pageData.length} 条数据`);
} catch (error) {
console.error(`[-] 第 ${page} 页采集失败: ${error.message}`);
if (error.response) {
console.error(` 状态码: ${error.response.status}`);
console.error(` 响应: ${JSON.stringify(error.response.data).substring(0, 200)}`);
}
}
}
console.log(`\n[+] 采集完成,共 ${allData.length} 条数据`);
const result = calculateResult(allData);
console.log('\n' + '='.repeat(30) + ' 计算结果 ' + '='.repeat(30));
console.log(`答案:${result}`);
console.log('='.repeat(70));
}
main().catch(error => {
console.error('\n[!] 运行错误:', error.message);
process.exit(1);
});
+14
View File
@@ -0,0 +1,14 @@
{
"name": "js-reverse-project",
"version": "1.0.0",
"description": "JS逆向 - 纯Node.js算法还原模板",
"main": "main.js",
"scripts": {
"start": "node main.js",
"test": "node main.js --test"
},
"dependencies": {
"axios": "^1.7.0",
"crypto-js": "^4.2.0"
}
}
+71
View File
@@ -0,0 +1,71 @@
/**
* 加密参数生成模块
* 根据逆向分析还原的加密逻辑,生成请求所需的加密参数
*/
const crypto = require('crypto');
function md5(str) {
return crypto.createHash('md5').update(str).digest('hex');
}
function sha256(str) {
return crypto.createHash('sha256').update(str).digest('hex');
}
function hmacSha256(message, secret) {
return crypto.createHmac('sha256', secret).update(message).digest('hex');
}
function aesEncrypt(plaintext, key, iv, mode = 'cbc') {
const algorithm = `aes-${key.length * 8}-${mode}`;
const cipher = crypto.createCipheriv(algorithm, key, mode === 'ecb' ? null : iv);
cipher.setAutoPadding(true);
let encrypted = cipher.update(plaintext, 'utf8', 'base64');
encrypted += cipher.final('base64');
return encrypted;
}
function aesDecrypt(ciphertext, key, iv, mode = 'cbc') {
const algorithm = `aes-${key.length * 8}-${mode}`;
const decipher = crypto.createDecipheriv(algorithm, key, mode === 'ecb' ? null : iv);
decipher.setAutoPadding(true);
let decrypted = decipher.update(ciphertext, 'base64', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
/**
* 生成请求签名
* TODO: 根据实际逆向分析修改此函数
* @param {Object} params - 请求参数
* @returns {string} 签名值
*/
function generateSign(params) {
const { page, timestamp } = params;
// 示例:sign = md5(page + "|" + timestamp + "|" + SECRET)
const SECRET = 'your_secret_here';
return md5(`${page}|${timestamp}|${SECRET}`);
}
/**
* 生成加密参数 m
* TODO: 根据实际逆向分析修改此函数
* @param {number} page - 页码
* @returns {string} 加密后的参数
*/
function generateM(page) {
const timestamp = Math.floor(Date.now() / 1000);
const sign = generateSign({ page, timestamp });
return `${sign}|${timestamp}`;
}
module.exports = {
md5,
sha256,
hmacSha256,
aesEncrypt,
aesDecrypt,
generateSign,
generateM,
};
+128
View File
@@ -0,0 +1,128 @@
/**
* 请求封装模块
* 封装 HTTP 请求,处理 Headers、Cookie、重试、频率控制
*/
const axios = require('axios');
const DEFAULT_HEADERS = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept': 'application/json, text/javascript, */*; q=0.01',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Accept-Encoding': 'gzip, deflate, br',
'Connection': 'keep-alive',
'X-Requested-With': 'XMLHttpRequest',
};
class RequestClient {
constructor(options = {}) {
this.baseURL = options.baseURL || '';
this.cookies = options.cookies || {};
this.headers = { ...DEFAULT_HEADERS, ...options.headers };
this.delay = options.delay || 1000;
this.maxRetries = options.maxRetries || 3;
this.referer = options.referer || '';
this.client = axios.create({
baseURL: this.baseURL,
timeout: options.timeout || 30000,
headers: this.headers,
maxRedirects: 5,
});
}
getCookieString() {
return Object.entries(this.cookies)
.map(([k, v]) => `${k}=${v}`)
.join('; ');
}
setCookie(name, value) {
this.cookies[name] = value;
}
parseCookies(setCookieHeader) {
if (!setCookieHeader) return;
const headers = Array.isArray(setCookieHeader) ? setCookieHeader : [setCookieHeader];
headers.forEach(h => {
const mainPart = h.split(';')[0];
const eqIdx = mainPart.indexOf('=');
if (eqIdx > 0) {
this.cookies[mainPart.substring(0, eqIdx).trim()] = mainPart.substring(eqIdx + 1).trim();
}
});
}
async request(config) {
const finalConfig = {
...config,
headers: {
...this.headers,
...config.headers,
'Cookie': this.getCookieString(),
},
};
if (this.referer) {
finalConfig.headers['Referer'] = this.referer;
}
let lastError;
for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
try {
const response = await this.client.request(finalConfig);
this.parseCookies(response.headers['set-cookie']);
return response;
} catch (error) {
lastError = error;
const status = error.response?.status;
console.error(`[Request] 第 ${attempt}/${this.maxRetries} 次请求失败: ${status || error.message}`);
if (status === 429) {
const waitTime = this.delay * attempt * 2;
console.log(`[Request] 频率限制,等待 ${waitTime}ms...`);
await this.sleep(waitTime);
} else if (status >= 500) {
await this.sleep(this.delay * attempt);
} else if (status === 403 || status === 412) {
console.error('[Request] 访问被拒绝,可能需要更新 Cookie 或检查加密参数');
throw error;
} else {
throw error;
}
}
}
throw lastError;
}
async get(url, params = {}, extraHeaders = {}) {
return this.request({
method: 'GET',
url,
params,
headers: extraHeaders,
});
}
async post(url, data = {}, extraHeaders = {}) {
return this.request({
method: 'POST',
url,
data,
headers: extraHeaders,
});
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async requestWithDelay(config) {
const response = await this.request(config);
const jitter = Math.random() * this.delay * 0.5;
await this.sleep(this.delay + jitter);
return response;
}
}
module.exports = { RequestClient, DEFAULT_HEADERS };