4

使用APC来保护PHP代码

 3 years ago
source link: https://www.cnxct.com/use-php-apc-to-protected-your-code/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
使用APC来保护PHP代码 – CFC4N的博客

问题就那么发生了:
PHP的项目在生产服务器运行时,基本都是明文的文件,如果要保护代码的话,多数使用商业的加密软件,比如Zend Guard或者其他软件。zend也就是php程序语言Zend Engine语法分析引擎的公司出品。该产品会对php代码进行加密转换,费用大约是$600每年(大约4000人民币),按照公司授权。zend guard收费倒不是主要问题,如果单单购买加密功能的话,项目性能会奇差,相信很多朋友都遇到了,尤其是webgame中,刚开服时,几乎都是cpu 100%,load 奇高。使用时,需要服务端在装一个解密的程序,每个脚本文件,在每次被访问时,都需要解码、验证授权。这种繁琐的步骤,无疑浪费了时间、系统资源,也是我们一个项目当初在“友好邻邦”服务器上出现超高占用CPU的原因。(OS load占用高达100倍)

这些都是些什么玩意:
这产品是加密,如果想要程序执行流程优化的话,Zend又很“周到”的为我们提供另外一款产品 Zend Server,每年只需要最低支付$1695美元(大约11000人民币)(其他黄金版、白金版需要上万美元)。。。这样一来,我们的产品被绑架在zend上,如果我们需要定制功能的话,那是不可想象的。 如果是zend产品有bug,那我们只能干等着,等他们解决。除了zend这款,还有另外一款代码加密产品:ioncube,费用倒是不高,大约400美元每年。但用户量少,产品是否稳定不清楚,不开源,社区支持不是很好。

找寻:
有没有一款可以保护代码,社区支持好,用户量大,反馈处理及时,费用低的产品吗? 幸运的是,有这么一款开源产品APC:http://pecl.php.net/package/APC Alternative PHP Cache (APC)是一款php代码加速产品,该产品作为php程序的一个拓展,是一个开放自由的PHP opcode 缓存。它的目标是提供一个自由、 开放,和健全的框架用于缓存和优化PHP的中间代码。

早在3-4个月之前,鸟哥博客上一篇文章《关于PHP的编译和执行分离》中提到APC来作为PHP代码保护的方案。从文中可以看出,鸟哥的想法是每个php文件,导出一个opcode 的bin文件,加载时,也是挨个加载,这样也实现了代码保护,但一个项目几百个php文件的话,也得相应存在几百个bin文件,量比较大,操作比较复杂,管理不方便,不好做版本验证(以后会提到)。末学比较倾向于单个bin文件的导出,单个opcode bin文件的加载。而且,单个bin文件的加载,可以避免项目中出现部分文件跟整体版本不一致的情况发生,运维同事再也不用担心个别文件跟整个项目版本不一致的情况了。

原来是你:
APC是替换了php zend引擎的zend_compile_file函数,来决定是否重新对php脚本文件的读取,扫描、解释、编译等步骤。启用apc之后,将跳过读取、扫描、解释、编译这几步,节省内存、CPU开销,直接执行OPCODE。Apc还提供对php源码的opcode的缓存,导出到一个二进制文件中,还提供加载这个二进制文件。起初,我们开始使用这个功能时,遇到该拓展的多个BUG,都已经提交到php官方,分别是: BUG #62757BUG #62765BUG #62825 ,并积极配合php内核组开发成员重现BUG,抓获coredump,提取bug demo代码,在PHP官方成员-APC拓展开发组长Xinchen Hui(以下称鸟哥 laruence)的帮助下,很快的修复了这些BUG 。

如此繁琐:
这样仍存在一个繁琐的步骤,即php-fpm每次启动时,没有自动加载bin文件,需要管理员去手动执行或者访问脚本,(该脚本内使用apc_bin_load/apc_bin_loadfile函数去加载bin文件)让php-fpm加载bin文件,对于单大区多台前端,以及单服务器运行多个php-fpm主进程来说,如何判断那个php-fpm主进程是否成功加载,这是个很麻烦的问题。对于程序执行opcode清除之后,又需要再次加载,这样是个非常复杂繁琐的过程,也容易出问题。

自力更生:
可否在fpm启动时,自动加载bin的功能呢?末学阅读了APC的源码,照葫芦画瓢的添加了自动加载的opcode bin文件的功能。
我们是在 APC SVN http://svn.php.net/viewvc/pecl/apc/trunk/ 的 r327454版本基础上,在apc_module_init函数中,模块初始化时,增加了opcode bin文件的自动加载功能,patch代码见:https://github.com/cfc4n/cnxct/blob/master/apc_r327454_add_preload_binfile.patch 。有兴趣的朋友可以git使用下。当然,末学水平有限,难免存在BUG,还请见谅。

后来,末学将此patch提交到PHP官方,请他们审核一下,是否可以收入apc官方中,免得我们以后都一直作为patch来使用。或者协助完善,或者给出更好的解决办法。
很伤心,未被采纳,原因是这是小众的需求。但末学表示不解,那apc_preload_path这个自动加载user cache的功能不也是小众需求吗?或许,这是他们委婉的拒绝方式。
(同时,还发现了APC自带的一个未公布的功能,自动加载user cache的功能,配置项为apc.preload_path,配置的值是目录地址,目录下可有多个文件,这些文件也应该是apc_bin_dump/apc_bin_dumpfile函数导出的user cache。看来,官方也有这么个打算。但为何没有实现 opcode bin文件 的自动加载呢?)

如何使用:

  1. opcode bin文件导出与导入:

导出的php脚本,末学提供一份,需要的朋友,可以参照修改一下即可:
https://github.com/cfc4n/cnxct/blob/master/apc_dump.php
导出成功后,会在PROJECTROOT目录下面生成一个dumproot目录,dumproot目录中的所有文件,就是你需要上传到生产服务器上的文件。bin目录中,会生成两个文件,一个是opcode的bin文件,一个是生成之前的php文件的md5 hash值。其他目录就是项目被保护的目录,目录中文件都是空的php文件。将dumproot目录上传至生产服务器,保持项目路径正确,重启fpm即可。可通过apc拓展源码包内的apc.php查看被缓存文件数。如图:

  1. 版本更新、回滚:

遇到bug需要修复时,只需要在opcode bin文件导出服务器上,重新导出一个bin文件,将此bin文件上传到生产服务器,替换到之前的bin文件即可。偶尔会遇到项目因种种原因,需要对项目进行回滚,如果使用我们的方案之后,会发现所有php文件都已经是空的了。而我们的回滚更方便,只需要将之前需要回滚的版本的bin文件,替换到当前的文件即可。或者更改php.ini中apc.preload_binfile的路径。

当怀疑个别文件不是某个版本的程序是,怎么办呢?apc提供了另外一个参数 apc.file_md5来存储被cache文件的md5 hash。但当使用已到处的opcode bin文件时,apc却没有重新抓去md5 hash,也没有重新存入apc中,导致我们看到的md5值是个错误的hash值。为此,末学斗胆做了修改,并提供了patch,且提交该BUG #63491到pecl apc官方,斗胆请官方采纳。当打上该patch之后,您可以在apc.php 看到这些信息。并且,跟导出bin文件时,生成的md5 hash文件中校对以确认哦。(2012/12/17 更新,官方已修复)
批量检测也是可以的,参加末学另一个脚本:https://github.com/cfc4n/cnxct/blob/master/check.php

APC其他已知bug:

一、php5.4.x中,class中的array()静态成员属性在跨服务器使用时,产生core,已经提交到 BUG #63636(此bug由recoye发现)
临时解决方案如下:

  1. 使用php5.4.x时,不要在class中定义 $_user = array(),直接定义为 $_user;
  2. 不要使用php5.4.x
  3. 删除或注释apc_compile.c的1992、2003行(apc 3.1.13),即 zval_ptr_dtor(&src->default_static_members_table[i]); 字样的代码。
  4. 等待PHP官方解决,或微博@鸟哥,催鸟哥解决

二、web访问apc_dump.php 去生成的bin文件,在cli模式下使用时,会在RSHUTDOWN时,即请求结束时,回收系统资源时,会产生core。(该结果的php版本为php5.3.x,初步确认为上一个bug有密切关系,暂未提交至bugs.php.net)
这两个bug以末学的理解来看,与末学的patch无关,为apc的bug。

注意事项:

  • 导出bin文件的服务器上,不要开启 apc.preload_binfile ,避免bin文件存在时,使用bin文件内的代码,而不使用php文件内的代码,导致导出的代码不是你想要的。
  • php5.4.x class中array 静态成员以及属性
  • 操作系统32bit、64bit的导出的bin文件不能相互乱用。(乱用将产生core)
  • 导出服务器的web路径跟目标生产服务器的web路径一致
  • 生产服务器使用bin文件之后,必须存在同名的php文件,内容可以为空。
  • apc.ttl、apc.gc_ttl参数保持为0
  • apc.serializer 如果改用其他序列化函数发生问题,那么请保持为空,或者为 apc.serializer=php
  • apc.num_files_hint的值务必大于等于cache files 的数量,可以先设置大点,再通过生产服务器上观察一段时间来确认

啰嗦一下:
关于32bit\64bit服务器上导出opcode bin文件的乱用问题,将产生如下core

Program received signal SIGSEGV, Segmentation fault.
0x00002aaab0af8442 in apc_unswizzle_bd (bd=0x2aaaaaaf3798, flags=) at /home/cfc4n/APC-3.1.13/apc_bin.c:598
598             if(bd->swizzled_ptrs[i]) {
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.80.el6_3.6.x86_64 libxml2-2.7.6-8.el6_3.3.x86_64 nss-softokn-freebl-3.12.9-11.el6.x86_64 zlib-1.2.3-27.el6.x86_64
(gdb) bt
#0  0x00002aaab0af8442 in apc_unswizzle_bd (bd=0x2aaaaaaf3798, flags=) at /home/cfc4n/APC-3.1.13/apc_bin.c:598
#1  apc_bin_load (bd=0x2aaaaaaf3798, flags=) at /home/cfc4n/APC-3.1.13/apc_bin.c:887
#2  0x00002aaab0ae829b in zif_apc_bin_loadfile (ht=, return_value=0x2aaaaaaf1f88, return_value_ptr=, this_ptr=, return_value_used=)
at /home/cfc4n/APC-3.1.13/php_apc.c:1549
#3  0x00000000006ef31a in zend_do_fcall_common_helper_SPEC (execute_data=) at /home/cfc4n/php-5.4.8/Zend/zend_vm_execute.h:642
#4  0x00000000006dca10 in execute (op_array=0x2aaaaaaf16a8) at /home/cfc4n/php-5.4.8/Zend/zend_vm_execute.h:410
#5  0x0000000000676f7e in zend_execute_scripts (type=8, retval=0x0, file_count=3) at /home/cfc4n/php-5.4.8/Zend/zend.c:1309
#6  0x000000000061c7ce in php_execute_script (primary_file=0x7fffffffe2a0) at /home/cfc4n/php-5.4.8/main/main.c:2482
#7  0x000000000071c763 in do_cli (argc=2, argv=0x7fffffffe6a8) at /home/cfc4n/php-5.4.8/sapi/cli/php_cli.c:988
#8  0x000000000071ce64 in main (argc=2, argv=0x7fffffffe6a8) at /home/cfc4n/php-5.4.8/sapi/cli/php_cli.c:1364
(gdb) f 0
#0  0x00002aaab0af8442 in apc_unswizzle_bd (bd=0x2aaaaaaf3798, flags=) at /home/cfc4n/APC-3.1.13/apc_bin.c:598
598             if(bd->swizzled_ptrs[i]) {
(gdb) l
593         bd->crc = crc_orig;
594
595         UNSWIZZLE(bd, bd->entries);
596         UNSWIZZLE(bd, bd->swizzled_ptrs);
597         for(i=0; i < bd->num_swizzled_ptrs; i++) {
598             if(bd->swizzled_ptrs[i]) {
599                 UNSWIZZLE(bd, bd->swizzled_ptrs[i]);
600                 if(*bd->swizzled_ptrs[i] && (*bd->swizzled_ptrs[i] < (void*)bd)) { 601                     UNSWIZZLE(bd, *bd->swizzled_ptrs[i]);
602                 }

因为long之类类型,在不同bit操作系统上的长度不一致导致内存越界的BUG。 末学不知道这算不算bug,或者您别混用,或者APC程序上也可以解决…

惊喜:
不光可以代码保护,性能提升,方便运维,还可以防黑客入侵哦。(项目目录不允许创建新文件,那么即使黑客改了php脚本,php仍会去apc里读,也不会去读取它。哪怕黑客执行了 apc_cache_clean清除opcode cache,我们的补丁仍会自动加载,那么黑客的行为还是阻拦了。)

备注:
此patch已经在我们的“友好邻邦”服务器上稳定运行3-4个月左右,无异常案例,各位可放心使用。
Apc不算一个php encoder,算是个cache,一个opcode cache,起到中间码缓存的作用,故本文中用“代码保护”,而不是“代码加密”。 但实质上实现了我们的目的,起到保护源代码的作用,那怕可以逆向,也是有一定难度的。

PS:感谢Ivon的openstack,感谢终极修炼师的协助测试。

PPS:
如果你能保证php文件不会被SAPI形式访问到的话,只会被include/require等访问到的话,甚至连同名空文件都不用放。比如单入口框架的项目,只要放个空的入口文件即可。 (感谢recoye纠正)

缓存450多个文件,命中率100%,只是20个配置文件没导出在bin文件里,导致第一次没命中。

PPPS:
更新日期:2014/10/17
近来,好多朋友问我要完整版,鉴于apc已经被官方删了,那个SVN地址无法正常checkout,故我在硬盘里找了个压缩包,应该是可用的版本,如果有其他异常,请留言,谢谢。
适用于PHP5.3的完整拓展:点击这里下载包含patch的完整版apc

莿鸟栖草堂 由 CFC4N 创作,采用 知识共享 署名-非商业性使用-相同方式共享(3.0未本地化版本)许可协议进行许可。基于http://www.cnxct.com上的作品创作。转载请注明转自:使用APC来保护PHP代码

No related posts.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK