October 2006
Monthly Archive
Thu 26 Oct 2006
Posted by yAnbiN under
PHPNo Comments
近日,关于如何加密 PHP 代码在 Exceed PHP 又被提起。但依我个人观点,作者可能对 PHP 的运行机制不太了解,因此所提出方案效果基本上可以说是聊胜于无。
获取 OPCode 有很多办法,也是很简单的事情,即使是加密过的 OPCode。稍微困难的是从 OPCode 还原成 PHP Code,这需要一种很好的逆向算法,但也并非不可实现。DeZend 就是一个公开的样例,更不要说还有很多半公开甚至不公开的了。
我觉得对版权的保护分为两个层次:二进制级和源代码级。所谓二进制级就是不让用户非法使用未授权的程序。但这一点很难做到。想想看 windows 下面的软件都是经过编译和加壳保护,依然不能阻挡 Cracker 的脚步,更何况是半编译的 PHP Code。所谓源代码级保护是指不让非法用户获得产品的源代码。获得源代码的非法途径通常是对代码进行反编译。目前看来很难有有效的工具来阻止这项工作,即使是 windows 下面的程序也不能阻止(windows 下一般都是“反汇编”,这个名词严格意义上不同于“反编译”)。既然不能阻止反编译,那就只好退而求次,就像把 windows 程序反汇编后得到的很少人能读懂的汇编代码一样,让非法用户即使获得了 PHP 源代码也很难读懂它。这项工作主要通过“混淆”来完成。
“混淆”有一个很常见的分支是“变量混淆”。“变量混淆”的主要原理就是机器(或者说虚拟机)对一个变量(包括函数名、类名等等)究竟是叫 $hello_world 还是叫 $#@!**& 并不关心,那只是一个代号而已。目前大部分具有混淆功能的 Encoder 基本都集中在这个部分。
“混淆”的另外一个分支就是“指令混淆”。所谓“指令混淆”就是额外插入一些垃圾代码来干扰第三方正常阅读。比如我们可以把
$a = 0;
替换成
$d = 1;
$e = $d ^ $d;
$a = $e;
结果是一样的,但要是再复杂一些保证你不明白这是怎么回事。这可以说是一种“化简为繁”的工作,也会影响些效率。但随着机器硬件的不断发展,而且有些应用对运行效率要求并不高,所以这是可以接受的(更典型的例子就是 dotNET 上桌面程序)。
“混淆”还有一个主要的分支就是“流程混淆”。流程混淆可以说是“混淆”工作的必杀技,类似汇编语言中“花指令”。它所依据的原理就是源代码和中间代码并无一一的对应关系。比如说有个 if ($a) {$b} 的语句,并非可以只能编译成“先判断 $a 是否为 true,是就跳转到 $b”这一种形式。我可以先跳转到 $c,再跳转到 $d,$d 经过 $e 等一系列垃圾运算之后再跳转到 $b。效果是一样的,貌似你很明白,但其实上你就是不明白。 8) PHP6 就要增加 goto 指令,这给各个 Encoder 的发挥空间也更大了。
目前在 Java 和 dotNET 上混淆工作大部分都集中在流程混淆上,但 PHP 方面目前大部分还停留在较初级的“变量混淆”上(ZendGuard 4 中的 Strong 级混淆也是属于“变量混淆”的范畴)。如果各位对 PHP 代码加密感兴趣,“混淆”尤其是“流程混淆”是一个很有意思的研究方向。当然反混淆是更有意思的一个方向。
Thu 26 Oct 2006
Posted by yAnbiN under
PHP[2] Comments
在继“是否要把 Filter 扩展集成到 PHP 5.2”这个问题被热闹讨论一番之后,最近 php internals 又开始热烈讨论起了另一个话题:E_DEPRECATED。
Marcus Boerger 说在经过了至少三个月的讨论与思考以后,他认为有必要在原来的 E_STRICT 之外再增加一个错误报告级别:E_DEPRECATED。这个 E_DEPRECATED 主要用于在用户使用了一些即将在近期 PHP 版本中被移除的某些语言特性时发出警告。这个“近期的 PHP 版本”具体来说就是与当前版本相隔了两个次(Minor)版本或一个主(major)版本。比如说,假如一个PHP 5.2 中的语言特性将要在 PHP 5.4 或者是 PHP 6.0 中被移除,那么当用户在使用这个语言特性时就应该发出这个 E_DEPRECATED 警告。
与此同时,原来的 E_STRICT 也将只负责其余的像“abstract static”这种不严格符合某些标准但用了也不会对 Zend Engine 造成太大麻烦的情况。而 E_NOTICE 和 E_WARNING 呢,也转为只负责那些输入性的错误(input validations)。
这应该说是一个非常好的建议,基本上所有人都赞成了这个建议。但随后 Marcus Boerger 又抛出了两个比较“激进”的建议:
1、舍弃现在的标准配置文件:php.ini,而改用两个新命名的配置文件:专为开发人员准备的 php-develop.ini (E_ALL|E_STRICT|E_DEPRECATED)和 用于产品部署的 php-production.ini (~(E_DEPRECATED|E_NOTICE|E_WARNING)),并且 E_ALL 也不再包含 E_STRICT or E_DEPRECATED 。
2、应该推迟 PHP 5.2 的发布,直到完成以上所述的变化的为止。
这一下就炸了锅了,各路天神纷纷表达了自己的看法。
Zeev Suraski(就是前两天来咱们这里的那位,我怀疑他是在咱们这里发出这个帖子的^_^)赞成增加 E_DEPRECATED 并支持为此而拖延 PHP 5.2 的发布,但反对再另外增加那两个配置文件。他觉得其实这两个并没有什么不同,而且也不觉得在产品部署时禁止 E_WARNING 和 E_NOTICE (可能还有 E_DEPRECATED)有多么好,万一你要是想同时进行一下日志记录呢?
Ilia Alshanetsky(PHP 5系列的 Release Manager)也同意增加一个E_DEPRECATED ,但他除了不赞成再增加两个配置文件外还反对为此而推迟 PHP 5.2 的发布。并且还为此专门另开一帖专门阐述了他的理由:PHP 5.2 并不是 PHP 5.x 的最后一个版本,没有必要把所有新东西都加在这个版本上。而且会导致目前代码的不稳定。况且目前 PHP 5.2 的发布已经推迟很久了,PHP 5.2 修复了大量的安全性方面的BUG,没有一个用户愿意受到这种攻击,推迟发布就是对所有的PHP用户的一种伤害(disservice)。他建议这些改变应该留到 PHP 6.0 中去完成。
Derick Rethans、Wez Furlong 还有 Pierre 等都发表了各自的意见,但大部分分歧都是集中在是否要为此而推迟 PHP 5.2 的发布上。基本上就是“一步到位”vs “步步为营”。
我个人是支持 Ilia Alshanetsky 的观点的,但目前看起来激进派略占上风。好事多磨,看来 PHP 5.2 至少还得等这件事完全确定下来才会发布。
Thu 12 Oct 2006
Posted by yAnbiN under
PHPNo Comments
这个版本提供了对 PHP 5.1 的完全支持!在目前所有免费的 PHP 加速器当中,eAccelerator 在性能方面的表现可以说是最优秀的。唯一美中不足的是近来开发较为缓慢,PHP 5.2 将在未来几天内正式发布,而对该版本提供支持的 eAccelerator 0.9.6 却似乎还需要更长一段时间。
但不管如何,PHP 5.1 在性能上也是非常优秀的,此时 eAccelerator 也对其提供完善的支持,升级的时间到了~
关于此版本的更多信息请访问 http://www.eaccelerator.net/wiki/Release-0.9.5
Mon 9 Oct 2006
这份代码转换自 OllyDbg 的反汇编引擎。我只转换了反汇编器部分,而没有转换汇编器部分。这本来是用于我自用某个程序的一部分,但是后来见到了 pvDasm,觉得 pvDasm 代码结构上的设计更为合理,因此决定采用 pvDasm 的引擎(当然还得转换为 Delphi 代码
)。不过使用 Ollydbg 的引擎也有另外一个好处:就是反汇编出来的代码和在 Ollydbg 里面是一样的,我们会很感到很亲切。
大家各取所需吧。
点击下载
Sun 8 Oct 2006
Posted by yAnbiN under
PHP[6] Comments
OK,现在你已经有了一个安全的构建环境,也可以把模块编译进 PHP 了。那么,现在就让我们开始详细讨论一下这里面究竟是如何工作的吧~
所有的 PHP 模块通常都包含以下几个部分:
-
包含头文件(引入所需要的宏、API定义等);
-
声明导出函数(用于 Zend 函数块的声明);
-
声明 Zend 函数块;
-
声明 Zend 模块;
-
实现 get_module() 函数;
-
实现导出函数。
模块所必须包含的头文件仅有一个 php.h,它位于 main 目录下。这个文件包含了构建模块时所必需的各种宏和API 定义。
小提示: 专门为模块创建一个含有其特有信息的头文件是一个很好的习惯。这个头文件应该包含 php.h 和所有导出函数的定义。如果你是使用 ext_skel 来创建模块的话,那么你可能已经有了这个文件,因为这个文件会被 ext_skel 自动生成。
声明导出函数
为了声明导出函数(也就是让其成为可以被 PHP 脚本直接调用的原生函数),Zend 提供了一个宏来帮助完成这样一个声明。代码如下:
ZEND_FUNCTION ( my_function );
ZEND_FUNCTION 声明了一个使用 Zend 内部 API 来编译的新的C 函数。这个 C 函数是 void 类型,以 INTERNAL_FUNCTION_PARAMETERS (这是另一个宏)为参数,而且函数名字以 zif_ 为前缀。把上面这句声明展开可以得到这样的代码:
void zif_my_function ( INTERNAL_FUNCTION_PARAMETERS );
接着再把 INTERNAL_FUNCTION_PARAMETERS 展开就会得到这样一个结果:
void zif_my_function( int ht
, zval * return_value
, zval * this_ptr
, int return_value_used
, zend_executor_globals * executor_globals
);
在解释器(interpreter)和执行器(executor)被分离出PHP 包后,这里面(指的是解释器和执行器)原有的一些 API 定义及宏也渐渐演变成了一套新的 API 系统:Zend API。如今的 Zend API 已经承担了很多原来(指的是分离之前)本属于 PHP API的职责,大量的 PHP API被以别名的方式简化为对应的Zend API。我们推荐您应该尽可能地使用 Zend API,PHP API 只是因为兼容性原因才被保留下来。举例来说, zval 和 pval其实是同一类型,只不过 zval 定义在 Zend 部分,而 pval 定义在 PHP 部分(实际上 pval 根本就是 zval 的一个别名)。但由于 INTERNAL_FUNCTION_PARAMETERS 是一个 Zend 宏,因此我们在上面的声明中使用了 zval 。在编写代码时,你也应该总是使用 zval 以遵循新的 Zend API 规范。
这个声明中的参数列表非常重要,你应该牢记于心。(表 3.1 “PHP 调用函数的 Zend 参数”详细介绍了这些参数)
|
参数
|
说明
|
| ht |
这个参数包含了Zend 参数的个数。但你不应该直接访问这个值,而是应该通过 ZEND_NUM_ARGS() 宏来获取参数的个数。 |
| return_value |
这个参数用来保存函数向 PHP 返回的值。访问这个变量的最佳方式也是用一系列的宏。后面我们会有详细说明。 |
| this_ptr |
根据这个参数你可以访问该函数所在的对象(换句话说,此时这个函数应该是一个类的“方法”)。推荐使用函数 getThis() 来得到这个值。 |
| return_value_used |
这个值主要用来标识函数的返回值是否为脚本所使用。0 表示脚本不使用其返回值,而 1 则相反。通常用于检验函数是否被正确调用以及速度优化方面,这是因为返回一个值是一种代价很昂贵的操作(可以在 array.c 里面看一下是如何利用这一特性的)。 |
| executor_globals |
这个变量指向 Zend Engine 的全局设置,在创建新变量时这个这个值会很有用。我们也可以函数中使用宏 TSRMLS_FETCH() 来引用这个值。 |
声明 Zend 函数块
现在你已经声明了导出函数,除此之外你还必须得将其引入 Zend 。这些函数的引入是通过一个包含有 N 个zend_function_entry 结构的数组来完成的。数组的每一项都对应于一个外部可见的函数,每一项都包含了某个函数在 PHP 中出现的名字以及在 C 代码中所定义的名字。zend_function_entry 的内部定义如“例3.4 zend_function_entry 的内部声明”所示:
typedef struct _zend_function_entry {
char *fname;
void (*handler)(INTERNAL_FUNCTION_PARAMETERS);
unsigned char *func_arg_types;
} zend_function_entry;
对于上面的例子,我们可以这样来声明:
zend_function_entry firstmod_functions[] =
{
ZEND_FE(first_module, NULL)
{NULL, NULL, NULL}
};
你可能已经看到了,这个结构的最后一项是 {NULL, NULL, NULL} 。事实上,这个结构的最后一项也必须始终是 {NULL, NULL, NULL} ,因为 Zend Engine 需要靠它来确认这些导出函数的列表是否列举完毕。
注意:
你不应该使用一个预定义的宏来代替列表的结尾部分(即{NULL, NULL, NULL}),因为编译器会尽量寻找一个名为 “NULL” 的函数的指针来代替 NULL !
宏 ZEND_FE(“Zend Function Entry”的简写)将简单地展开为一个zend_function_entry 结构。不过需要注意,这些宏对函数采取了一种很特别的命名机制:把你的C函数前加上一个 zif_ 前缀。比方说,ZEND_FE(first_module) 其实是指向了一个名为 zif_first_module() 的 C 函数。如果你想把宏和一个手工编码的函数名混合使用时(这并不是一个好的习惯),请你务必注意这一点。
小提示: 如果出现了一些引用某个名为 zif_*() 函数的编译错误,那十有八九与 ZEND_FE 所定义的函数有关。
“表 3.2 可用来定义函数的宏”给出了一个可以用来定义一个函数的所有宏的列表:
|
宏
|
说明
|
| ZEND_FE(name, arg_types) |
定义了一个zend_function_entry 内字段name为 “name” 的函数。arg_types 应该被设定为 NULL。这个声明需要有一个对应的 C 函数,该这个函数的名称将自动以 zif_ 为前缀。举例来说, ZEND_FE("first_module", NULL) 就引入了一个名为 first_module() 的 PHP 函数,并被关联到一个名为 zif_first_module() 的C函数。这个声明通常与 ZEND_FUNCTION 搭配使用。 |
| ZEND_NAMED_FE(php_name, name, arg_types) |
定义了一个名为 php_name 的 PHP 函数,并且被关联到一个名为 name 的 C 函数。arg_types 应该被设定为 NULL。 如果你不想使用宏 ZEND_FE 自动创建带有 zif_ 前缀的函数名的话可以用这个来代替。通常与 ZEND_NAMED_FUNCTION搭配使用。 |
| ZEND_FALIAS(name, alias, arg_types) |
为 name 创建一个名为 alias 的别名。arg_types 应该被设定为 NULL。这个声明不需要有一个对应的 C 函数,因为它仅仅是创建了一个用来代替 name 的别名而已。 |
| PHP_FE(name, arg_types) |
以前的 PHP API,等同于 ZEND_FE 。仅为兼容性而保留,请尽量避免使用。 |
| PHP_NAMED_FE(runtime_name, name, arg_types) |
以前的 PHP API,等同于ZEND_NAMED_FE 。仅为兼容性而保留,请尽量避免使用。 |
注意:你不能将 ZEND_FE 和 PHP_FUNCTION 混合使用,也不能将PHP_FE 和 ZEND_FUNCTION 混合使用。但是将 ZEND_FE + ZEND_FUNCTION 和 PHP_FE + PHP_FUNCTION 一起混合使用是没有任何问题的。当然我们并不推荐这样的混合使用,而是建议你全部使用 ZEND_* 系列的宏。
Zend 模块的信息被保存在一个名为zend_module_entry 的结构,它包含了所有需要向 Zend 提供的模块信息。你可以在“例 3.5 zend_module_entry 的内部声明”中看到这个 Zend 模块的内部定义:
typedef struct _zend_module_entry zend_module_entry;
struct _zend_module_entry {
unsigned short size;
unsigned int zend_api;
unsigned char zend_debug;
unsigned char zts;
char *name;
zend_function_entry *functions;
int (*module_startup_func)(INIT_FUNC_ARGS);
int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS);
int (*request_startup_func)(INIT_FUNC_ARGS);
int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS);
void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS);
char *version;
… // 其余的一些我们不感兴趣的信息
};
在我们的例子当中,这个结构被定义如下:
zend_module_entry firstmod_module_entry =
{
STANDARD_MODULE_HEADER,
"First Module",
firstmod_functions,
NULL, NULL, NULL, NULL, NULL,
NO_VERSION_YET,
STANDARD_MODULE_PROPERTIES,
};
这基本上是你可以设定最简单、最小的一组值。该模块名称为“First Module”,然后是所引用的函数列表,其后所有的启动和关闭函数都没有使用,均被设定为了 NULL。
作为参考,你可以在表 3.3 “所有可声明模块启动和关闭函数的宏”中找到所有的可设置启动与关闭函数的宏。这些宏暂时在我们的例子中还尚未用到,但稍后我们将会示范其用法。你应该使用这些宏来声明启动和关闭函数,因为它们都需要引入一些特殊的变量(INIT_FUNC_ARGS 和 SHUTDOWN_FUNC_ARGS),而这两个参数宏将在你使用下面这些预定义宏时被自动引入(其实就是图个方便)。如果你是手工声明的函数或是对函数的参数列表作了一些必要的修改,那么你就应该修改你的模块相应的源代码来保持兼容。
|
宏
|
描述
|
| ZEND_MINIT(module) |
声明一个模块的启动函数。函数名被自动设定为zend_minit_<module> (比如:zend_minit_first_module)。通常与ZEND_MINIT_FUNCTION 搭配使用。 |
| ZEND_MSHUTDOWN(module) |
声明一个模块的关闭函数。函数名被自动设定为zend_mshutdown_<module> (比如:zend_mshutdown_first_module)。通常与ZEND_MSHUTDOWN_FUNCTION搭配使用。 |
| ZEND_RINIT(module) |
声明一个请求的启动函数。函数名被自动设定为zend_rinit_<module> (比如:zend_rinit_first_module)。通常与ZEND_RINIT_FUNCTION搭配使用。 |
| ZEND_RSHUTDOWN(module) |
声明一个请求的关闭函数。函数名被自动设定为zend_rshutdown_<module> (比如:zend_rshutdown_first_module)。通常与ZEND_RSHUTDOWN_FUNCTION 搭配使用。 |
| ZEND_MINFO(module) |
声明一个输出模块信息的函数,用于phpinfo()。函数名被自动设定为zend_info_<module> (比如:zend_info_first_module)。通常与ZEND_MINFO_FUNCTION搭配使用。 |
实现 get_module() 函数
这个函数只用于动态可加载模块。我们先来看一下如何通过宏ZEND_GET_MODULE 来创建这个函数:
#if COMPILE_DL_FIRSTMOD
ZEND_GET_MODULE(firstmod)
#endif
这个函数的实现被一条件编译语句所包围。这是很有必要的,因为 get_module() 函数仅仅在你的模块想要编译成动态模块时才会被调用。通过在编译命令行指定编译条件:COMPILE_DL_FIRSTMOD (也就是上面我们设置的那个预定义)的打开与否,你就可以决定是编译成一个动态模块还是编译成一个内建模块。如果想要编译成内建模块的话,那么这个 get_module() 将被移除。
get_module() 函数在模块加载时被 Zend 所调用,你也可以认为是被你 PHP 脚本中的 dl() 函数所调用。这个函数的作用就是把模块的信息信息块传递 Zend 并通知 Zend 获取这个模块的相关内容。
如果你没有在一个动态可加载模块中实现 get_module() 函数,那么当你在访问它的时候 Zend 就会向你抛出一个错误信息。
导出函数的实现是我们构建扩展的最后一步。在我们的first_module 例子中,函数被实现如下:
ZEND_FUNCTION(first_module)
{
long parameter;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", ¶meter) == FAILURE) {
return;
}
RETURN_LONG(parameter) ;
}
这个函数是用宏 ZEND_FUNCTION 来声明的,和前面我们讨论的 Zend 函数块中的 ZEND_FE 声明相对应。在函数的声明之后,我们的代码便开始检查和接收这个函数的参数。在将参数进行转换后将其值返回。(参数的接收和处理我们马上会在下一节中讲到)。
一切基本上就这样了 ―― 我们在实现一个模块时不会再遇到其他方面的事了。内建模块也基本上同动态模块差不多。因此,有了前面几节我们所掌握的信息,再在你遇到 PHP 源代码的时候你就有能力去搞定这些小麻烦。
在下面的几个小节里,我们将会学习到如何利用 PHP 内核来创建一个更为强大的扩展!
Thu 5 Oct 2006
Posted by yAnbiN under
PHPNo Comments
一直在关注 php.internals 邮件列表,也很关注 PHP 5.2 的动态。PHP 5.2 是一个非常值得关注和升级的版本,增加了许多很实用的特性,也对运行效率进行了很大的改善。
众所周知,对不安全的数据(比如来自客户端的请求等)进行过滤和处理是每个 PHP 程序员的必修课,也是每个 PHP 项目中不可或缺的一个过程。基于安全性考虑,几乎每个来自客户端的请求都必须经过该过程处理。长时间以来我们都是在“脚本级”代码中处理这些请求,如果能在编译后的二进制代码中完成这些处理那自然是再好不过了。一则可以减少代码量、增加清晰度和改善可读性,二则更可以大幅提高程序性能。PHP 5.2 新增的 Filter 扩展正是为此应运而生。
不过好事自然要“多磨”。原定于 9 月 28 日发布的 PHP 5.2 由于 Filter 扩展的 API 兼容性问题被迫延迟发布。所谓“API 兼容性”问题说的是 Filter 扩展的两个 Leader:Derick 和 Pierre 分别为这个扩展设计了两个不同“风格”的API,这两套 API 都似乎很有道理但却似乎毫不相容。PHP 5.2 预定发布在即,然而问题却没有丝毫可以解决的迹象,以致于作为 Release Manager 的 Ilia 专门设立了一个投票来决定是否要暂时把 Filter 扩展从源码树中移除来确保 PHP 5.2 的按时发布。最终很幸运,经过两个主角的协商,决定以 Pierre 的代码为基础并作了一些修订,这个问题基本获得了圆满解决。
开发小组已经决定在今天发布一个 RC5 版本作为正式版的缓冲和热身,略做一些修订和调整后,PHP 5.2 将会在大约一周后正式发布!热烈期待ing…