const fs = require('fs');
const path = require('path');
const argv = process.argv.slice(2);
const args = {};
argv.forEach((arg, index) => {
if (arg.startsWith("/")) {
let endIndex = argv.slice(index + 1).findIndex((arg) => arg.startsWith('/')) + 1;
endIndex = endIndex == 0 ? undefined : index + endIndex;
args[arg.substring(1)] = argv.slice(index + 1, endIndex);
}
});
if (!args.files || args.files.length == "0") {
console.error(`缺少 /files 指定需要嵌入的文件`);
process.exit(1);
}
if (!args.output) {
console.error(`缺少 /output 指定需要输出的文件`);
process.exit(1);
}
if (!args.output[0].toLowerCase().endsWith('.bat') && !args.output[0].toLowerCase().endsWith('.cmd')) {
console.error(`/output 文件必须是批处理格式`);
process.exit(1);
}
if (args.files && args.files.some((filepath) => filepath.includes("/"))) {
console.error(`你似乎在使用 / 作为路径分割符号,请修改为 \\`);
process.exit(1);
}
if (!args.dir && ( args.files && args.files.some((filepath) => filepath.includes("\\")) )) {
console.error(`你没有使用 /dir 创建目录树,但是在文件路径使用了路径符号,如果你希望打包目录,请直接写进去目录名`);
process.exit(1);
}
function readDirectory(dir, parentPath = '', result = []) {
const fullPath = path.join(parentPath, dir);
const items = fs.readdirSync(fullPath);
let isLeaf = true; // 用于判断当前目录是否为叶子节点
items.forEach(item => {
const itemPath = path.join(fullPath, item);
const stats = fs.statSync(itemPath);
if (stats.isDirectory()) {
isLeaf = false; // 如果有子目录,则不是叶子节点
readDirectory(item, fullPath, result); // 递归读取子目录
}
});
// 如果是叶子节点
if (isLeaf) {
result.push(fullPath.replace(/\//g, '\\')); // 替换为反斜杠并添加到结果数组
}
return result; // 返回结果数组
}
async function readDirectoryRecursively(dir) {
const filePaths = []; // 创建一个数组来存储文件路径
try {
const files = fs.readdirSync(dir, { withFileTypes: true });
for (const file of files) {
const filePath = path.join(dir, file.name);
if (file.isDirectory()) {
// 如果是目录,递归调用
const subDirPaths = await readDirectoryRecursively(filePath);
filePaths.push(...subDirPaths); // 将子目录的文件路径添加到数组中
} else {
// 如果是文件,添加路径到数组
filePaths.push(filePath.replace(/\//g, '\\')); // 替换为反斜杠
}
}
} catch (err) {
console.error(`无法读取目录 ${dir}: ${err}`);
}
return filePaths; // 返回文件路径数组
}
async function makeBase64(file) {
console.log(`打包中 - ${file}`);
let data = fs.readFileSync(file);
let b64 = data.toString('base64');
return makeUnzip(b64, file);
}
function makeUnzip(b64, filepath) {
return [
`(`,
b64.split(/(\n|.{1,3000})/).filter(d => d).map((line) => `echo;${line}`).join("\r\n"),
`)>"${filepath.replace(/"/g, '\\"')}.b64"`,
`IF EXIST "${filepath.replace(/"/g, '\\"')}" del "${filepath.replace(/"/g, '\\"')}"`,
`certutil -decode "${filepath.replace(/"/g, '\\"')}.b64" "${filepath.replace(/"/g, '\\"')}" > nul`,
`IF NOT EXIST "${filepath.replace(/"/g, '\\"')}" echo;Failed to Create ${filepath}`,
`del "${filepath.replace(/"/g, '\\"')}.b64"`,
].join("\r\n");
}
async function main() {
let packed = [
'@echo off',
];
let file = '';
let tf = [];
while(file = args.files.shift()) {
if (fs.statSync(file).isDirectory()) {
let n_files = await readDirectoryRecursively(file) || [];
let n_dirs = await readDirectory(file) || [];
console.log([
`已展开:`,
` 目录:`,
` ${n_dirs.join('\n ')}`,
` 文件:`,
` ${n_files.join('\n ')}`,
].join("\n"));
tf = [ ...tf, ...n_files ];
args.dir = [ ...(args.dir || []), ...n_dirs ];
} else tf.push(file);
};
if (args.dir) packed.push(`md ${args.dir.map((d) => `"${d.replace(/"/g, '\\"')}"`).join(" ")}`);
while(file = tf.shift()) {
let data = await makeBase64(file);
packed.push(data);
};
if (args.run) {
args.run.forEach((r) => {
packed.push(`start "" "${r.replace(/"/g, '\\"')}"`);
});
}
packed.push(`del "%~0"`);
console.log(`正在保存 ${args.output[0]}`);
fs.writeFileSync(args.output[0], packed.join("\r\n"), { encoding: 'latin1' });
}
main();
我用这个打包了曾经举办的ctf东西源代码

下面是打包后的 .bat 实例
似乎被论坛机制改名了,下载后请重命名 .bat ,然后双击打开运行之前请确保:
- 安装了 nodejs LTS 版本
- 安装了 express、body-parse、fs-extra、escape-html、express-session 模块(
npm install expexpress body-parse fs-extra escape-html express-session -g --save
)
最后打开后,就可以发现释放了个文件夹ctf_game
,打开浏览器访问 localhost:6566
看见这个批处理脚本释放的网站内容