You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PHP扩展解密代码后暴露类与函数及Laravel适配问题

问题:加密PHP文件执行后类无法被外部访问

问题背景

我正在保护Laravel项目核心代码,计划后续打造为SaaS产品,因此需要对大部分代码进行加密。基于C和Crypto构建了PHP扩展实现加解密逻辑,配套加密工具也已完成。扩展可正常运行,通过loader_decrypt(encrypted_file_path)函数能解密并执行加密文件,但执行成功后,加密文件内的类与函数无法被外部代码访问。


示例代码

待加密的Controller文件

namespace App\Http\Controllers;

class Controller
{
    public function hello() {
        echo "hello";
    }
}

调用加密Controller的文件

<?php
// 确保loader扩展已加载
if (!extension_loaded('loader')) {
    die('The loader extension is not loaded.');
}

// 加密文件路径
$encryptedFilePath = 'Controller.enc';

// 执行加密文件
if (loader_decrypt($encryptedFilePath)) {
    echo "Decryption and execution were successful.";

    // 尝试使用Controller类
    $controller = new Controller();
    $controller->hello();
} else {
    echo "Decryption or execution failed.";
}
?>

执行结果

Decryption and execution were successful. PHP Fatal error:  Uncaught Error: Class "Controller" not found in /home/.../Desktop/Controller.php:15

补充:继承加密Controller的类示例

<?php

namespace App\Http\Controllers\Management;
use App\Http\Controllers\Controller;

class MemberCourseDetailsController extends Controller
{
   public function  sayHelloFromController() {
        $this->hello();
   }
}

使用use声明后仍无法找到父类。


核心解密执行C++函数

bool decryptAndExecuteFile(const std::string& filePath) {
    std::ifstream file(filePath, std::ios::binary);
    if (!file) {
        std::cerr << "Error: Unable to open file." << std::endl;
        return false;
    }

    // 从文件读取IV
    SecByteBlock iv(AES::BLOCKSIZE);
    file.read(reinterpret_cast<char*>(iv.data()), iv.size());

    // 读取剩余内容作为密文
    std::string ciphertext((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
    file.close();

    // 从密文中提取密钥
    std::string key = extractKeyAndCleanCiphertext(ciphertext);

    // 确保密钥长度正确
    if (key.size() != AES_KEYLENGTH) {
        std::cerr << "Error: Key length is incorrect." << std::endl;
        return false;
    }

    // 解密数据
    std::string decryptedContent = decryptData(ciphertext, key, std::string(reinterpret_cast<const char*>(iv.data()), iv.size()));

    if (decryptedContent.empty()) {
        std::cerr << "Decryption failed!" << std::endl;
        return false;
    }

    // 调试:将解密内容写入文件检查
    std::ofstream debugFile("decrypted_debug.php");
    debugFile << decryptedContent;
    debugFile.close();

    // 检查并清理意外字符
    std::string cleanContent;
    for (char c : decryptedContent) {
        if (std::isprint(static_cast<unsigned char>(c)) || std::isspace(static_cast<unsigned char>(c))) {
        cleanContent += c;
    }
}

    // 调试:输出清理后内容长度及前几个字符
    std::cout << "Cleaned content length: " << cleanContent.size() << std::endl;

     // 检查内容是否以"<?php"开头
    if (cleanContent.find("<?php") != 0) {
        std::cerr << "Decrypted content does not start with '<?php'." << std::endl;
    }

    // 将清理后内容转换为zend_string
    zend_string *php_code = zend_string_init(cleanContent.c_str(), cleanContent.length(), 0);
    int result = zend_eval_string(ZSTR_VAL(php_code), nullptr, "Decrypted PHP Code");
    zend_string_release(php_code);

    if (result == FAILURE) {
        std::cerr << "Failed to execute decrypted PHP code." << std::endl;
        return false;
    }

    return true;
}

问题原因

使用zend_eval_string执行代码时,代码仅在当前作用域临时执行,命名空间类未正确注册到PHP全局类表;同时Laravel依赖Composer的PSR-4自动加载机制,直接eval的代码无法被自动加载器识别,导致后续代码无法找到类。


解决方法

1. 替换执行逻辑,模拟PHP文件包含

放弃zend_eval_string,改用zend_stream_include模拟PHP的include行为,让解密后的代码像普通文件一样被解析,确保类注册到全局类表。修改C++函数的执行部分:

// 替换原zend_eval_string相关代码
zend_stream stream;
stream.type = STREAM_TYPE_MEMORY;
stream.handle = zend_string_init(cleanContent.c_str(), cleanContent.length(), 0);
stream.free = (zend_stream_free_func_t)zend_string_release;
stream.size = cleanContent.length();

zend_file_handle file_handle;
memset(&file_handle, 0, sizeof(zend_file_handle));
file_handle.type = ZEND_HANDLE_STREAM;
file_handle.stream = &stream;
file_handle.filename = zend_string_init(filePath.c_str(), filePath.length(), 0);
file_handle.opened_path = file_handle.filename;
file_handle.free_filename = 1;

// 执行包含操作
zend_result include_result = zend_stream_include(&file_handle, USE_PATH | RETURN_VALUE);

zend_string_release(file_handle.filename);

if (include_result == FAILURE) {
    std::cerr << "Failed to include decrypted content." << std::endl;
    return false;
}

2. 确保解密内容完整性

检查cleanContent是否完整保留原文件的命名空间、类定义等结构,清理逻辑不要过滤掉合法的PHP语法字符。

3. 适配Laravel自动加载

在Laravel的bootstrap/app.php中添加自定义自动加载器,让框架优先加载加密文件:

spl_autoload_register(function ($class) {
    // 将类名转换为加密文件路径
    $file = base_path(str_replace('\\', '/', $class) . '.enc');
    if (file_exists($file) && extension_loaded('loader')) {
        loader_decrypt($file);
        if (!class_exists($class, false)) {
            return false;
        }
        return true;
    }
    return false;
}, true, true); // 设为优先执行的加载器

PHP扩展优化建议

  • 禁止临时文件泄露:生产环境删除调试用的decrypted_debug.php写入逻辑,所有操作在内存中完成。
  • 密钥安全存储:不要将密钥与密文共存,可将密钥编译进扩展或通过环境变量加载后存入内存。
  • 添加缓存机制:对已解密的类进行内存缓存,避免重复解密同一文件。
  • 增强错误日志:记录解密、加载过程中的错误细节,方便排查问题。
  • 兼容多PHP版本:针对PHP7.x/8.x测试Zend API兼容性,zend_stream_include比zend_eval_string兼容性更强。

Laravel兼容性验证

解决上述问题后,方案可完全兼容Laravel:

  • 自定义自动加载器能无缝集成到框架加载流程
  • 加密后的Controller可被正常继承,类能正确注册到全局类表
  • 路由、中间件等核心功能不受影响,只要对应控制器文件能被正确加载

内容的提问来源于stack exchange,提问作者MOM 74

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 03:00:55