一款强大的JavaScript加密工具《JShaman》,我把它集成到了鼠标菜单中

1,555 阅读6分钟

领导:有个需求啊,基座代码要加密啊,不能让甲方看到,看到就GG了呀,把我们技术全学去了呀。

我:en......😬。

JShaman

之前了解过JShaman,有20多项混淆加密功能,官网提供免登录在线加密。复制进去,再复制出来,覆盖原代码,代码照样能跑,贼厉害。关键是提供了Web API,有开发接口,这可玩性就高了。

截取一段加密后的代码

function get_copyright(){var _0x55bf5e="moc.namahsj".split("").reverse().join("");var _0x403775=0xe56f9^0xe5118;var _0x17cff7="\u0028\u0063\u0029"+_0x403775+"\u002d"+new Date()["\u0067\u0065\u0074\u0046\u0075\u006c\u006c\u0059\u0065\u0061\u0072"]()+"\u002c"+_0x55bf5e;return _0x17cff7;}console["\u006c\u006f\u0067"](get_copyright());

是吧,完全没有读代码的欲望。

ok,目标鼠标右键菜单加密代码,走起。

我的开发环境

node V16.x npm V7.x

首先创建文件夹JShaman

根目录运行 npm install readline request --save

创建两个js文件,一个是右键js文件执行、一个是右键文件夹执行;

分别是:JShaman.js、JShamans.js

添加注册表

. 能在右键菜单中执行js代码,这里是关键

  1. 创建一个txt文件,放入以下脚本,这是往鼠标右键添加菜单选项
Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\Directory\shell\JShaman加密]
"Icon"="D:\\JShaman\\encyt.ico"

[HKEY_CLASSES_ROOT\Directory\shell\JShaman加密\command]
@="\"C:\\Program Files\\nodejs\\node.exe\" \"D:\\JShaman\\JShamans.js\" \"%1\""

[HKEY_CLASSES_ROOT\SystemFileAssociations\.js\Shell\JShaman加密]
"Icon"="D:\\JShaman\\encyt.ico"

[HKEY_CLASSES_ROOT\SystemFileAssociations\.js\Shell\JShaman加密\command]
@="\"C:\\Program Files\\nodejs\\node.exe\" \"D:\\JShaman\\JShaman.js\" \"%1\""

D:\\JShaman\\encyt.ico是右键菜单的图标,去iconfont随便找一个,修改后缀为ico即可。

  1. 将txt后缀修改为reg
  2. 执行该设置,一路确定。建议备份一下自己注册表哈,搞砸了,还有回头路。

image.png 4. 随后就可以右键菜单看到这个选项了,只有文件夹和js才有哦。

image.png

右键执行JS脚本

const vip_code = "free";
const config = {
    compact: "true",//压缩代码 
    renameGlobalFunctionVariable: "false",//混淆全局变量名和函数名 
    controlFlowFlattening: "true",//平展控制流 
    deadCodeInjection: "false",//僵尸代码植入 
    stringArray: "true",//字符串阵列化 
    stringArrayEncoding: "false",//阵列字符串加密
    disableConsoleOutput: "false",//禁用命令行输出
    debugProtection: "false",//反浏览器调试
    time_range: "false",//时间限定
    time_start: "20240118",//时间限定起始时间、结束时间,时间限定启用时此2参数生效
    time_end: "20240118",
    domainLock: [],//域名锁定
    reservedNames: [],//保留关键字
}
const fs = require("fs");
const readline = require("readline");
const request = require("request");
const filePath = process.argv[2];
if (!filePath) {
    console.error("未选中文件");
    process.exit(1);
}

if (!fs.existsSync(filePath)) {
    console.error(`文件 ${filePath} 不存在`);
    process.exit(1);
}

console.log(`正在处理文件:${filePath}`);
var javascript_code = fs.readFileSync(filePath, "utf8").toString();
var options = {
    url: "https://www.jshaman.com:4430/submit_js_code/",
    method: "POST",
    json: true,
    body: {
        "js_code": javascript_code, //JavaScript代码
        "vip_code": vip_code,//JShaman VIP码
    }
};
if (options.body.vip_code != "free") {
    options.body.config = config;//混淆加密参数
}
console.log("正在向JShaman.com提交混淆加密请求...")
request(options, function (error, response, body) {
    if (!error && response.statusCode === 200) {
        if (body?.message.indexOf("保护代码时发生错误") > -1) {
            console.log("JShaman服务器拒绝加密,需要启用以下语法分析器:jsx", "flow", "typescript");
        } else {
            console.log(body.message);
        }
        if (body.status == 0) {
            const rl = readline.createInterface({
                input: process.stdin,
                output: process.stdout
            });
            rl.question("是否覆盖原文件?y为是,n则在同目录下另存。(y/n) ", (answer) => {
                if (answer.toLowerCase() === "y") {
                    fs.writeFileSync(filePath, body.content.toString());
                    console.log("文件已覆盖。");
                } else {
                    function getFileName(name) { return name.substring(0, name.lastIndexOf(".")) }
                    var obfuscated_file = getFileName(filePath) + "-JShaman.js";
                    fs.writeFileSync(obfuscated_file, body.content.toString());
                    console.log("混淆加密后的Js文件:", obfuscated_file);
                }
                rl.close();
            });
        }
    } else {
        console.error("向JShaman.com发送请求时出现错误:", error);
    }
});
process.on("beforeExit", (code) => {
    const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout
    });
    console.log("按下任意键退出程序...");
    rl.on("line", (input) => {
        rl.close();
        process.exit(0);
    });
});

image.png 这时候就可以把js文件加密了,有两个选项,一个是覆盖文件加密,一个是加密到新文件。

以上操作借鉴了这位网友的分享

到这里已经很不错了,可是我的js代码多啊,又在多个文件夹中。每次更新我还是要花时间一个一个加密,这不行啊,影响我摸鱼时间啊,基于上述操作,我拓展了文件夹加密。

核心代码上述已经交代清楚了,如果没有其他需求可以移步了哈。

对文件夹加密

1. 获取文件夹中所有js文件

async function getFilesInDirectory(dirPath) {
    const files = [];
    try {
        const entries = await fs.promises.readdir(dirPath, { withFileTypes: true });
        for (const entry of entries) {
            const fullPath = path.join(dirPath, entry.name);
            if (entry.isDirectory()) {
                const subFiles = await getFilesInDirectory(fullPath);
                files.push(...subFiles);
            } else {
                if (path.extname(fullPath).toLowerCase() === ".js") {// 过滤文件
                    files.push(fullPath);
                }
            }
        }
    } catch (error) {
        console.error(`读取目录时出错 ${dirPath}:`, error);
    }
    return files;
}

2. 将代码交换的方法包装一下

async function getEncryption(file) {
    return new Promise((resolve, reject) => {
        if (!fs.existsSync(file)) {
            resolve({ success: false, state: "201", message: "待加密文件不存在!" })
        }
        var codeJS = fs.readFileSync(file, "utf8").toString();
        var options = {
            url: "https://www.jshaman.com:4430/submit_js_code/",
            method: "POST",
            json: true,
            body: {
                "js_code": codeJS, //JavaScript代码
                "vip_code": vip_code,//JShaman VIP码
            }
        };
        if (options.body.vip_code != "free") {
            options.body.config = config;//混淆加密参数
        }
        request(options, (error, response, body) => {
            if (error) {
                console.error("向JShaman.com发送请求时出现错误:", error);
                reject({ success: false, message: error });
            } else {
                if (response.statusCode === 200 && body.status == 0) {
                    resolve({ code: body.content.toString(), success: true });
                } else {
                    if (body?.message.indexOf("保护代码时发生错误") > -1) {
                        resolve({ success: false, state: "201", message: "JShaman服务器拒绝加密,需要启用以下语法分析器:jsx flow typescript" })
                    } else {
                        resolve({ success: false, message: "加密失败!" })
                    }
                }
            }
        });
    });
}

3. 加密后写入新文件

async function createFile(pathFile) {
    return new Promise((resolve, reject) => {
        console.log("进行中:", pathFile)
        const result = { success: true, data: {} };
        function nextStep() {
            index += 1;
            if (pathList[index]) {
                console.log("进行中:", pathList[index])
                setTimeout(() => {
                    carryOut(pathList[index])
                }, 5000);
            } else {
                resolve(result)
            }
        }
        async function carryOut(file) {
            if (file) {
                const res = await getEncryption(file);
                if (res.code) {
                    result.data[file] = "成功";
                    function getFileName(name) { return name.substring(0, name.lastIndexOf(".")) }
                    var newFile = getFileName(file) + "-JShaman.js";
                    fs.writeFileSync(newFile, res.code);
                    console.log("加密成功", file)
                    nextStep()
                } else {
                    result.data[file] = res.message || "失败"
                    if (res.state === "201") {
                        result.data[file] = res.message;
                        console.log("加密失败", res.message)
                        nextStep()
                    } else {
                        console.log("失败了,3秒后重试", file);
                        setTimeout(() => {
                            carryOut(file)
                        }, 3000);
                    }
                }
            } else {
                if (pathList[index]) {
                    nextStep();
                } else {
                    result.success = false;
                    reject("非法路径");
                }
            }
        }
        carryOut(pathFile);
    });
}

4. 加密后覆盖源文件

function overwriteFile(pathFile) {
    return new Promise((resolve, reject) => {
        console.log("进行中:", pathFile)
        const result = { success: true, data: {} };
        function nextStep() {
            index += 1;
            if (pathList[index]) {
                console.log("进行中:", pathList[index])
                setTimeout(() => {
                    carryOut(pathList[index])
                }, 5000);
            } else {
                resolve(result)
            }
        }
        async function carryOut(file) {
            if (file) {
                const res = await getEncryption(file);
                if (res.code) {
                    result.data[file] = "成功";
                    fs.writeFileSync(file, res.code);
                    console.log("加密成功", file)
                    nextStep()
                } else {
                    result.data[file] = res.message || "失败"
                    if (res.state === "201") {
                        result.data[file] = res.message;
                        console.log("加密失败", res.message)
                        nextStep()
                    } else {
                        console.log("失败了,3秒后重试", file);
                        setTimeout(() => {
                            carryOut(file)
                        }, 3000);
                    }
                }
            } else {
                if (pathList[index]) {
                    nextStep();
                } else {
                    result.success = false;
                    reject("非法路径");
                }
            }
        }
        carryOut(pathFile)
    });
}

5. 收到指令后执行

getFilesInDirectory(filePath)
    .then(files => {
        index = 0;
        pathList = files;
        console.log('即将对这些文件进行加密:', files);
        const rl = readline.createInterface({
            input: process.stdin,
            output: process.stdout
        });
        rl.question("受API限制,多文件每5秒进行一次加密,是否继续? (y/n) ", (answer) => {
            if (answer.toLowerCase() === 'y') {
                rl.question("是否覆盖原文件? (y/n) ,按 t 退出! ", async (reply) => {
                    if (reply.toLowerCase() === 't') {
                        rl.close();// 结束对话
                    } else if (reply.toLowerCase() === 'y') {
                        const res = await overwriteFile(pathList[index]);
                        console.log("覆盖源文件,加密结束!")
                        console.log(res.data)
                        process.exit(0);
                    } else {
                        const res = await createFile(pathList[index]);
                        console.log("创建新文件,加密结束!")
                        console.log(res.data)
                        process.exit(0);
                    }
                });
            } else {
                process.exit(0);// 终止程序
            }
        });
    })
    .catch(error => {
        console.error('出现错误:', error);
    });

看看效果

右键文件夹时 右键文件夹时

加密前 加密前

执行中 执行中

image.png

加密效果

image.png

image.png

ok可以交差啦