php 原理 淘口令 解密_说说PHP 代码加密实践

在我们开发的项目中,有一部分可能是用于商业用途,会部署在客户提供的机器环境中。因为 PHP 本身是解释型语言,所以未进行处理的代码,就会有泄露或被修改的风险。那么我们可能会想到最简单有效的方法就是进行加密混淆,然后配合一系列的校验,来保护我们的代码。那么本篇文章中,我们就采用了开源的 PHP 加密扩展 screw-plus 进行相关实践分析。

目前市场上有多种加密方案,但基本都是收费的。我们本次实践采用了开源的方案。screw-plus 是一款开源 php 加密运行扩展,基于 screw 二次开发,暂时只能运行在 linux 下。虽然有些不足,但是如果加强一下,也是可以满足一些场景。

95195102db68d426c4c1475ce461c003.png

在介绍实现之前,我们先简单介绍下 PHP 扩展的相关知识以及 Hook 机制。

1 PHP 扩展周期

5a0eb793c4029a018b55e2d3ebffd49e.png

一款扩展的主要生命周期,就是上面的四个宏定义。

  • MINIT:扩展模块初始化时执行的动作
  • MSHUTDOWN:扩展模块结束时执行的动作
  • RINIT:请求初始化时的动作
  • RSHUTDOWN:请求结束时的动作

对应于 zend_module_entry 结构中的四个函数指针:

int (*module_startup_func)(INIT_FUNC_ARGS);        /* MINIT() */int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS);   /* MSHUTDOWN() */int (*request_startup_func)(INIT_FUNC_ARGS);       /* RINIT() */int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS);  /* RSHUTDOWN() */

我们在实现一款扩展时,基本都会用到上面四个周期宏定义。完整的 PHP 周期见下图:

55d32a5acf3aebc2b28001927141ed69.png

本篇文章中我们只是介绍下跟该扩展相关的信息,如果想深入了解,可以参考底部链接,自行深入学习。

2 PHP 扩展钩子

在 PHP 内核中,各个执行周期几乎都有提供可重写的钩子,如下表所示。

// AST, Zend/zend_ast.h:void (*zend_ast_process_t)(zend_ast *ast) // Compiler, Zend/zend_compile.h:zend_op_array *(*zend_compile_file)(zend_file_handle *file_handle, int type)zend_op_array *(*zend_compile_string)(zval *source_string, char *filename) // Executor, Zend/zend_execute.h:void (*zend_execute_ex)(zend_execute_data *execute_data)void (*zend_execute_internal)(zend_execute_data *execute_data, zval *return_value) // GC, Zend/zend_gc.h:int (*gc_collect_cycles)(void) // TSRM, TSRM/TSRM.h:void (*tsrm_thread_begin_func_t)(THREAD_T thread_id)void (*tsrm_thread_end_func_t)(THREAD_T thread_id) // Error, Zend/zend.h:void (*zend_error_cb)(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args) / Exceptions, Zend/zend_exceptions.h:void (*zend_throw_exception_hook)(zval *ex) // Lifetime, Zend/zend.h:void (*zend_on_timeout)(int seconds)void (*zend_interrupt_function)(zend_execute_data *execute_data)void (*zend_ticks_function)(int ticks)

我们可以通过对函数指针进行重写,来在周期中执行我们自己的代码。

本次我们只关心 Compiler 中的 zend_compile_file 函数,该函数的功能就是用来编译 php 文件,或者我们指定的输入文件。

3 screw-plus 扩展

项目文件如下:

.├── aes.c├── aes_crypt.c├── aes.h├── b64.h├── config.h├── config.m4├── decode.c├── encode.c├── include├── LICENSE├── Makefile.in├── md5.c├── md5.h├── php_screw_plus.c├── php_screw_plus.h├── README.en├── README.jp├── README.md└── tools    ├── Makefile    └── screw.c

在 php_screw_plus.h 中第一行有定义宏 CAKEY,这个就是我们用来混淆代码的秘钥。

#define CAKEY  "FwWpZKxH7twCAG4JQMO"

tools 文件夹中,是用来加密 PHP 文件的脚本,我们直接执行 make,就会生成 screw 文件,然后用 screw 来加密 PHP 文件。tools/screw.c 中也是引入了上面的头文件,使用了 CAKEY 这个宏,用来加密代码。

4 流程分析

文件操作流程分析

4844e2bcabd3d77b79e0342d8985b33e.png

通过 tools/screw.c 文件我们可以发现,在 main 函数中,主要是对文件或文件夹进行遍历递归,使用固定的加解密步骤执行加密或者解密操作。

screw_encrype 为加密函数,screw_decrypt 为解密函数。

62d260a2aa2faa0442b597d089dbcc6c.png

加密函数首先是对上面定义的 CAKEY 宏的值进行 md5 运算,得到 32 位的 md5 值,然后将前 16 存入 enTag 中,后面直接写入文件的前 16 个字节。接着会获取文件的长度,并且写入 16~32 的字节位置中。后续的内容以 32 位的 md5 值为秘钥,以 16 字节长度为小段,采用 cbc 方式进行加密,然后返回加密之后的长度,根据长度把加密之后的内容写入到文件中。

8bbae346b32c6d10745f433e94079dd5.png

解密为对应的逆操作,在此就不详细介绍了。

5 扩展分析

php_screw_plus.c 文件就是扩展的主要实现入口。我们可以看到上面提到的 Compiler 时期的钩子函数定义。

ZEND_API zend_op_array *(*org_compile_file)(zend_file_handle *file_handle, int type TSRMLS_DC);

以下是 screw-plus 扩展模块初始及结束的代码,就是将系统的编译函数暂存,然后用自己定义的编译函数替换为系统编译函数。再结束时再还原回去。

PHP_MINIT_FUNCTION(php_screw_plus){  CG(compiler_options) |= ZEND_COMPILE_EXTENDED_INFO;  org_compile_file = zend_compile_file;  zend_compile_file = pm9screw_compile_file;  return SUCCESS;}{1}PHP_MSHUTDOWN_FUNCTION(php_screw_plus){  CG(compiler_options) |= ZEND_COMPILE_EXTENDED_INFO;  zend_compile_file = org_compile_file;  return SUCCESS;}{1}

可以看到,在编译 PHP 文件的时候,是会用到自定义的 pm9screw_compile_file 函数来解析。

fbf73f520f78daf200d4651e8e028849.png

我们可以看到该函数中就是做了一些校验,然后使用自定义的名字为 pm9screw_ext_fopen 的 handler 对文件进行打开操作,之后又将打开的文件转交回了系统的原始解析器。

5afb373496399fc666983ea8d4041056.png

从上面 handler 的实现可知,就是使用 CAKEY 对加密的文件内容进行了解密,然后使用 tmpfile() 创建了临时文件,把内容保存进去。

6 思路梳理

1. 设定 CAKEY 秘钥
2. 对 CAKEY 取 md5 值,并且作为 aes 的 key。将 md5 值的前 0~15 字节,写入文件开始位置,获取文件长度,将长度写入前 16~32 字节。
3. 以 16 字节为一段进行 cbc 方式加密,使用上面的 key 作为秘钥。
4. 将加密之后的内容追加写入文件完成静态加密。
5. 扩展在初始化的时候,使用自定义的 compile 函数;
6. 在解析时,检测如果文件前 16 字节内容与 md5 值的前 16 字节相同,则使用解密函数对文件内容进行解密,之后写入临时文件中,返回给系统去执行。

以上就是整个加密解密过程。

7 优缺点

1. 加解密简单,而且相对于上面收费的产品,该项目是开源免费。
2. 安全木桶原理,最短板就是 CAKEY 如果泄露了,就可以解密出来。

用 vim 打开 .so 文件,然后使用 :%!xxd 使用 hex 方式显示,我们就可以看到明文的秘钥 CAKEY。

fa4a969e60a4b1fb7ac166c476b42d82.png

或者我们使用 gdb 对 php 进行跟踪调试,因为上面调用了 md5 函数,对 md5 进行设断点,可以直接追踪到秘钥。

c4f671f4d0a4121c590f2adbadae7497.png

8 思考及改进

1、如果我们是对生成的 .so 文件进行一些加壳压缩混淆,比如加上 upx 壳,就会增加追踪难度。
2、可以基于该项目,进行一些改进,比如对秘钥值进行稍微复杂一些的合并计算,不让秘钥仅为一个字符串。再配合混淆,也是可以适用于部分场景。

总结

以上就是本次分享的内容~

如果有什么改进建议,也可以在评论区留言,供大家参考

以上内容希望帮助到大家,更多PHP大厂PDF面试文档,PHP进阶架构视频资料,PHP精彩好文可以关注公众号:PHP开源社区,或者访问:

四年精华PHP技术文章整理合集——PHP框架篇

四年精华PHP技术文合集——微服务架构篇

四年精华PHP技术文合集——分布式架构篇

四年精华PHP技术文合集——高并发场景篇

四年精华PHP技术文章整理合集——数据库篇

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值