reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict';
|
||||
|
||||
const { spawnSync } = require('child_process');
|
||||
|
||||
function parseArgs(argv) {
|
||||
const args = { python: '', json: false, markdown: false };
|
||||
for (let i = 2; i < argv.length; i++) {
|
||||
const a = argv[i];
|
||||
if (a === '--python') args.python = argv[++i] || '';
|
||||
else if (a === '--json') args.json = true;
|
||||
else if (a === '--markdown') args.markdown = true;
|
||||
else if (a === '--help' || a === '-h') args.help = true;
|
||||
else throw new Error(`未知参数:${a}`);
|
||||
}
|
||||
if (!args.json && !args.markdown) args.markdown = true;
|
||||
return args;
|
||||
}
|
||||
|
||||
function usage() {
|
||||
return `用法:
|
||||
node scripts/check_tls_clients.js --markdown
|
||||
node scripts/check_tls_clients.js --python python --json
|
||||
|
||||
说明:检测最终请求阶段可选的 TLS 指纹兼容客户端:Node.js CycleTLS / impers / curl-cffi-node、Python curl_cffi / cffi_curl / cyCronet;真实请求交付时必须再封装为 Session 模式。`;
|
||||
}
|
||||
|
||||
function run(cmd, args, timeout = 12000) {
|
||||
const ret = spawnSync(cmd, args, { encoding: 'utf8', timeout, windowsHide: true });
|
||||
return {
|
||||
ok: ret.status === 0,
|
||||
status: ret.status,
|
||||
stdout: (ret.stdout || '').trim(),
|
||||
stderr: (ret.stderr || '').trim(),
|
||||
error: ret.error ? ret.error.message : '',
|
||||
command: [cmd].concat(args).join(' '),
|
||||
};
|
||||
}
|
||||
|
||||
function nodeRequire(name) {
|
||||
const code = `
|
||||
(async()=>{
|
||||
const name=${JSON.stringify(name)};
|
||||
try {
|
||||
const m=require(name);
|
||||
console.log(JSON.stringify({ok:true, loader:"require", keys:Object.keys(m).slice(0,20)}));
|
||||
} catch (e1) {
|
||||
try {
|
||||
const m=await import(name);
|
||||
console.log(JSON.stringify({ok:true, loader:"import", keys:Object.keys(m).slice(0,20)}));
|
||||
} catch (e2) {
|
||||
console.log(JSON.stringify({ok:false,error:e1.message + " / " + e2.message}));
|
||||
process.exit(2);
|
||||
}
|
||||
}
|
||||
})();`;
|
||||
const ret = run(process.execPath, ['-e', code]);
|
||||
try {
|
||||
const parsed = JSON.parse(ret.stdout.replace(/^\uFEFF/, ''));
|
||||
return { name, installed: !!parsed.ok, loader: parsed.loader || '', keys: parsed.keys || [], error: parsed.error || '' };
|
||||
} catch {
|
||||
return { name, installed: false, loader: '', keys: [], error: ret.stderr || ret.error || '检测失败' };
|
||||
}
|
||||
}
|
||||
|
||||
function pythonCandidates(explicit) {
|
||||
const out = [];
|
||||
if (explicit) out.push({ cmd: explicit, prefix: [] });
|
||||
out.push({ cmd: 'python', prefix: [] });
|
||||
out.push({ cmd: 'python3', prefix: [] });
|
||||
out.push({ cmd: 'py', prefix: ['-3'] });
|
||||
const seen = new Set();
|
||||
return out.filter(x => {
|
||||
const k = x.cmd + ' ' + x.prefix.join(' ');
|
||||
if (seen.has(k)) return false;
|
||||
seen.add(k);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
function detectPythonModules(explicitPython) {
|
||||
const modules = ['curl_cffi', 'cffi_curl', 'cycronet', 'cyCronet'];
|
||||
const code = [
|
||||
'import importlib, json',
|
||||
`mods=${JSON.stringify(modules)}`,
|
||||
'out={}',
|
||||
'for name in mods:',
|
||||
' try:',
|
||||
' m=importlib.import_module(name)',
|
||||
' out[name]={"installed": True, "version": getattr(m, "__version__", "")}',
|
||||
' except Exception as e:',
|
||||
' out[name]={"installed": False, "error": str(e)}',
|
||||
'print(json.dumps(out, ensure_ascii=False))',
|
||||
].join('\n');
|
||||
for (const c of pythonCandidates(explicitPython)) {
|
||||
const ret = run(c.cmd, c.prefix.concat(['-c', code]));
|
||||
if (!ret.ok && !ret.stdout) continue;
|
||||
try {
|
||||
const parsed = JSON.parse(ret.stdout.replace(/^\uFEFF/, ''));
|
||||
return { python: c.cmd, pythonArgsPrefix: c.prefix, modules: parsed };
|
||||
} catch {}
|
||||
}
|
||||
return {
|
||||
python: '',
|
||||
pythonArgsPrefix: [],
|
||||
modules: Object.fromEntries(modules.map(m => [m, { installed: false, error: '未找到可用 Python 或检测失败' }])),
|
||||
};
|
||||
}
|
||||
|
||||
function detect(args) {
|
||||
const nodePackages = ['cycletls', '@luminati-io/cycletls', 'cycle-tls', 'cycleTls', 'impers', 'curl-cffi', 'curl-cffi-node'].map(nodeRequire);
|
||||
const python = detectPythonModules(args.python);
|
||||
const cycleTlsAvailable = nodePackages.some(p => p.installed && /cycle/i.test(p.name));
|
||||
const impersAvailable = nodePackages.some(p => p.installed && p.name === 'impers');
|
||||
const curlCffiNodeAvailable = nodePackages.some(p => p.installed && p.name === 'curl-cffi');
|
||||
return {
|
||||
node: {
|
||||
executable: process.execPath,
|
||||
packages: nodePackages,
|
||||
cycleTlsAvailable,
|
||||
impersAvailable,
|
||||
curlCffiNodeAvailable,
|
||||
nodeTlsAvailable: cycleTlsAvailable || impersAvailable || curlCffiNodeAvailable,
|
||||
},
|
||||
python,
|
||||
pythonTlsAvailable: Object.values(python.modules).some(v => v && v.installed),
|
||||
note: '这些客户端只用于授权范围内的最终低频请求验证;如果本 case 需要最终发送真实请求,应在前置阶段先选择 Node.js CycleTLS/impers/curl-cffi-node 或 Python curl_cffi/cyCronet,并在最终项目中封装为 Session 模式,不要等普通客户端失败后才考虑。',
|
||||
};
|
||||
}
|
||||
|
||||
function renderMarkdown(result) {
|
||||
const lines = ['# TLS 指纹兼容客户端检测结果', '', `说明:${result.note}`, '', '## Node.js / CycleTLS / impers'];
|
||||
lines.push(`- Node.js:${result.node.executable}`);
|
||||
lines.push(`- 是否检测到 CycleTLS:${result.node.cycleTlsAvailable ? '是' : '否'}`);
|
||||
lines.push(`- 是否检测到 impers:${result.node.impersAvailable ? '是' : '否'}`);
|
||||
lines.push(`- 是否检测到 Node.js TLS 指纹兼容客户端:${result.node.nodeTlsAvailable ? '是' : '否'}`);
|
||||
for (const p of result.node.packages) {
|
||||
lines.push(`- ${p.name}:${p.installed ? '已安装' : '未安装'}${p.loader ? `(${p.loader})` : ''}${p.error ? `(${p.error})` : ''}`);
|
||||
}
|
||||
lines.push('', '## Python / curl_cffi / cffi_curl / cyCronet');
|
||||
lines.push(`- Python:${result.python.python ? `${result.python.python} ${result.python.pythonArgsPrefix.join(' ')}`.trim() : '未检测到'}`);
|
||||
lines.push(`- 是否检测到 Python TLS 客户端:${result.pythonTlsAvailable ? '是' : '否'}`);
|
||||
for (const [name, info] of Object.entries(result.python.modules)) {
|
||||
lines.push(`- ${name}:${info.installed ? '已安装' : '未安装'}${info.version ? `(版本 ${info.version})` : ''}${info.error && !info.installed ? `(${info.error})` : ''}`);
|
||||
}
|
||||
lines.push('', '## 下一步');
|
||||
if (!result.node.nodeTlsAvailable && !result.pythonTlsAvailable) {
|
||||
lines.push('- 未检测到可用 TLS 指纹兼容客户端;如果本 case 需要最终发送真实请求,需在前置阶段询问用户安装 CycleTLS / impers / curl_cffi / cyCronet,或改为只输出本地 sign 结果。');
|
||||
} else {
|
||||
lines.push('- 在任务确认阶段选择一个最终请求客户端,并限制为少量授权验证请求;最终项目中只保留所选客户端的 Session 请求逻辑。');
|
||||
}
|
||||
return lines.join('\n') + '\n';
|
||||
}
|
||||
|
||||
try {
|
||||
const args = parseArgs(process.argv);
|
||||
if (args.help) { console.log(usage()); process.exit(0); }
|
||||
const result = detect(args);
|
||||
if (args.json) console.log(JSON.stringify(result, null, 2));
|
||||
if (args.markdown) process.stdout.write(renderMarkdown(result));
|
||||
} catch (err) {
|
||||
console.error(err.message || String(err));
|
||||
console.error(usage());
|
||||
process.exit(1);
|
||||
}
|
||||
Reference in New Issue
Block a user