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
相关产品推荐
相关产品推荐

