electron 代码 解密

解密模块需要解决的问题

  • 在读取指定路径的加密文件
  • AES 解密代码文件
  • 加载 JS 解密之后的内容

怎么加载

nodejs模块加载机制

require() 源码解读

通过了解nodejs 的模块加载机制 其实执行的主要代码类似

(function (exports, require, module, __filename, __dirname) {
  // 模块源码
});

那么,我们在C++中也用此模型来加载代码文件

nodejs C++ 插件开发

这一块的学习资料具体参考官方文档官方例子

nodejs C++插件中 我们是可以使用V8的,这是我们执行 js 代码的重要前提条件

获取加密JS代码路径

//file_path 需要返回的完整路径
//resources_dir_path 运行时获取的electron资源文件夹路径
//加密之后的js文件md5值

bool LoadJS::GenerateFilePath(std::string& file_path, const std::string& resources_dir_path, const std::string& code_id)
{
    if (resources_dir_path.length() > 0 && code_id.length() > 0)
    {
        file_path = resources_dir_path;
        //加密的代码不能放入asar 中,不然C++无法加载,这里的路径为加密文件时定义好的路径
        file_path += u8"/app.asar.unpacked/encryption/";
        file_path += code_id;
        return true;
    }
    return false;
}

AES 解密实现

通过 openssl来实现AES 解密,下面这个例子,来自openssl官方文档

int do_crypt(FILE *in, FILE *out, int do_encrypt)
 {
     /* Allow enough space in output buffer for additional block */
     unsigned char inbuf[1024], outbuf[1024 + EVP_MAX_BLOCK_LENGTH];
     int inlen, outlen;
     EVP_CIPHER_CTX *ctx;
     /*
      * Bogus key and IV: we'd normally set these from
      * another source.
      */
     unsigned char key[] = "0123456789abcdeF";
     unsigned char iv[] = "1234567887654321";

     /* Don't set key or IV right away; we want to check lengths */
     ctx = EVP_CIPHER_CTX_new();
     EVP_CipherInit_ex(&ctx, EVP_aes_128_cbc(), NULL, NULL, NULL,
                       do_encrypt);
     OPENSSL_assert(EVP_CIPHER_CTX_key_length(ctx) == 16);
     OPENSSL_assert(EVP_CIPHER_CTX_iv_length(ctx) == 16);

     /* Now we can set key and IV */
     EVP_CipherInit_ex(ctx, NULL, NULL, key, iv, do_encrypt);

     for (;;) {
         inlen = fread(inbuf, 1, 1024, in);
         if (inlen <= 0)
             break;
         if (!EVP_CipherUpdate(ctx, outbuf, &outlen, inbuf, inlen)) {
             /* Error */
             EVP_CIPHER_CTX_free(ctx);
             return 0;
         }
         fwrite(outbuf, 1, outlen, out);
     }
     if (!EVP_CipherFinal_ex(ctx, outbuf, &outlen)) {
         /* Error */
         EVP_CIPHER_CTX_free(ctx);
         return 0;
     }
     fwrite(outbuf, 1, outlen, out);

     EVP_CIPHER_CTX_free(ctx);
     return 1;
 }

加载加密的代码内容

//获取解密的key 和向量 iv
bool LoadJS::GenerateKeyAndIv(unsigned char* key, unsigned char*& iv, const std::string& code_id)
{
    if (code_id.length() == 32 && key != nullptr)
    {
        const char* ch_code_id = code_id.c_str();
        key = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        iv = (unsigned char*)code_id.c_str();
        iv += 4;
        return true;
    }
    return false;
}



//file_data 文件内容
//file_length 文件大小
//need_decrypt 是否需要解密
//file_path 加密文件路径
// code_id 加密文件md5 值
bool LoadJS::LoadFileData(char*& file_data, int& file_length, const bool& need_decrypt, const std::string& file_path, const std::string& code_id)
{
    bool success = false;
    FOpen file_open;
    //打开文件
    file_open.Open(file_path, FOpen::R_B_MODE_TYPE);
    if (file_open.IsOpen())
    {
        unsigned int file_decrypt_length = file_open.GetLength();
        if (file_decrypt_length <= 0)
        {
            return false;
        }
        //设置缓存大小
        char* ch_file_decrypt_data = new char[(unsigned int)file_decrypt_length];
        //读取加密文件内容
        file_open.ReadChar(ch_file_decrypt_data, file_decrypt_length);
        file_open.Close();

        if (need_decrypt)
        {
            //
            unsigned char* ch_key = new unsigned char[33];
            ch_key[32] = '\0';
            unsigned char* ch_iv = nullptr;
            if (GenerateKeyAndIv(ch_key, ch_iv, code_id))
            {
                //解密缓存ch_file_decrypt_data 中的内容
                //把openssl 官方加解密的例子稍微封装一下
                if (Decrypt((const unsigned char*)ch_file_decrypt_data, file_decrypt_length, (unsigned char*&)file_data, (unsigned int&)file_length, ch_key, ch_iv, false))
                {
                    success = true;
                }
            }
            if (ch_key != nullptr)
            {
                memset(ch_key, 0, sizeof(unsigned char) * 32);
                delete[] ch_key;
            }

            if (ch_file_decrypt_data != nullptr)
            {
                memset(ch_file_decrypt_data, 0, sizeof(char) * file_decrypt_length);
                delete[] ch_file_decrypt_data;
            }
        }
        else
        {
            file_data = ch_file_decrypt_data;
            file_length = file_decrypt_length;
            success = true;
        }
    }
    return success;
}

加载 JavaScript

void LoadJS::V8Load(const Nan::FunctionCallbackInfo<v8::Value>& info)
{
    bool success = false;             // 前置返回值
    //v8 对象域
    Nan::HandleScope scope;
    if (info.Length() < 9)
    {
        Nan::ThrowError("Wrong number of arguments");
        return;
    }
    //获取Global 对象 
    v8::Local<v8::Object> global = Nan::GetCurrentContext()->Global();
    //获取传入的参数
    v8::Local<v8::Value> need_decrypt = info[0];
    v8::Local<v8::Value> resources_dir_path_arg = info[1];
    v8::Local<v8::Value> code_id_arg = info[2];
    v8::Local<v8::Value> exports_arg = info[3];
    v8::Local<v8::Value> require_arg = info[4];
    v8::Local<v8::Value> module_arg = info[5];
    v8::Local<v8::Value> file_name_arg = info[6];
    v8::Local<v8::Value> dir_name_arg = info[7];
    v8::Local<v8::Value> process_arg = info[8];
    //参数判断你是否 传入正确
    if (!need_decrypt->IsBoolean() || !resources_dir_path_arg->IsString() || !code_id_arg->IsString() || !require_arg->IsFunction() || !file_name_arg->IsString() || !dir_name_arg->IsString())
    {
        Nan::ThrowError("Wrong argument");
        return;
    }
    //判断md5值是否为32 位
    v8::Local<v8::String> v8_str_code_id = code_id_arg.As<v8::String>();
    if (v8_str_code_id->Length() != 32)
    {
        Nan::ThrowError("Wrong code_id length");
        return;
    }

    // 保存运行时electron的资源文件夹路径 变量
    std::string str_resources_dir_path;
    if (v8convert::v8String2UTF8String(str_resources_dir_path, resources_dir_path_arg.As<v8::String>()))
    {
        // 保存 加密文件md5值 变量
        std::string str_code_id;
        if (v8convert::v8String2UTF8String(str_code_id, code_id_arg))
        {
            std::string str_error;
            std::string str_file_path;
            if (GenerateFilePath(str_file_path, str_resources_dir_path, str_code_id))
            {
                char* ch_file_data = nullptr;
                int file_length = 0;
                bool b_need_decrypt = need_decrypt->BooleanValue();
                //代码文件解密
                if (LoadFileData(ch_file_data, file_length, b_need_decrypt, str_file_path, str_code_id))
                {
                    std::string str_code = "(function (global, exports, require, module, __filename, __dirname, process) { return function (global, exports, require, module, __filename, __dirname, process) {\n";
                    str_code += std::string(ch_file_data, file_length);
                    str_code += "\n}.call(this, global, exports, require, module, __filename, __dirname, process); })";
                    memset(ch_file_data, 0, sizeof(char) * file_length);
                    if (b_need_decrypt)
                    {
                        free(ch_file_data);
                    }
                    else
                    {
                        delete[] ch_file_data;
                    }
                    v8::Local<v8::String> l_v8_str_code = Nan::New(str_code).ToLocalChecked();
                    memset((void*)str_code.c_str(), 0, sizeof(char) * str_code.size());
                    v8::Local<v8::Script> l_script_code;
                    //C++ 字符串 JavaScript脚本转换成v8执行脚本
                    v8::MaybeLocal<v8::Script> ml_script_code = Nan::CompileScript(l_v8_str_code);
                    if (ml_script_code.ToLocal(&l_script_code))
                    {
                        if (!l_script_code.IsEmpty())
                        {
                            // 执行获得函数
                            v8::Local<v8::Value> l_v8_script_code_run_result = l_script_code->Run();
                            if (l_v8_script_code_run_result->IsFunction())
                            {
                                v8::Local<v8::Function> l_v8_script_code_fun = l_v8_script_code_run_result.As<v8::Function>();
                                //参数列表
                                v8::Local<v8::Value> l_v8_code_argv[] = { global, exports_arg, require_arg, module_arg, file_name_arg, dir_name_arg, process_arg };
                                // v8 调用函数
                                l_v8_script_code_fun->Call(global, 7, l_v8_code_argv);
                                success = true;
                            }
                            else
                            {
                                str_error = u8"script_code is not function:";
                            }
                        }
                        else
                        {
                            str_error = u8"script_code is empty:";
                        }
                    }
                    else
                    {
                        str_error = u8"compileScript failed:";
                    }
                }
                else
                {
                    str_error = u8"loadFileData failed:";
                }
            }
            else
            {
                str_error = u8"generateFilePath failed:";
            }
            if (!success)
            {
                str_error += u8" codeid = ";
                str_error += str_code_id;
                Nan::ThrowError(str_error.c_str());
            }
        }
    }
    info.GetReturnValue().Set(Nan::New(success));
}

执行说明

上面C++ 代码其实执行的是这样一段JS 代码

(function (global, exports, require, module, __filename, __dirname, process) 
{ 
    return function (global, exports, require, module, __filename, __dirname, process)        {
        // 解密后的JS代码内容
    }.call(this, global, exports, require, module, __filename, __dirname, process); 
}
)

然后执 闭包,最后调用函数

总结

把加密封装成一个nodejs模块,然后生产环境打包时完成代码加密,运行时依赖它解密即可,加密和解密配套使用,还可以使用zip等压缩包配套再来一层加密

至此,electron的源码加解密全部完成



所有图片均来自网络


   转载规则


《electron 代码 解密》 Smoking 采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。
 上一篇
windows 数字签名 windows 数字签名
获取数字签名证书​ 这个不具体分析,网上可以找到很多文章怎么获取数字签名证书 签名工具 signtool.exesigntool [command] [options] [file_name | ...] 全部指令详解参考 文档
2019-05-24
下一篇 
electron 代码 加密 electron 代码 加密
常见前端代码加密方式​ 如果“加密”就是让别人无法反向工程摸索你的代码逻辑的话,那是没办法的,其实任何一种语言都避免不了被反向工程,而JavaScript的特点是下载到了客户的机器上执行,所以无论如何都可以被摸清楚,只是困难度而已!那
2019-05-24
  目录