PHP内核源代码、PHP Zend扩展、API Hook学习笔记

做为WebShell检测、CMS修复、WebShell攻防研究学习的第三篇文章php

本文旨在研究PHP内核的原理、PHP Zend扩展的编写方法、PHP API Hook技术。但愿能给研究这一领域的朋友带来一点点帮助,同时抛砖引玉,和你们共同讨论更多的技术细节, 共同窗习成长html

上一篇咱们学习了WebShell攻防、服务器安全加固、以及文件磁盘可疑行为的检测相关的知识java

http://www.cnblogs.com/LittleHann/p/3558685.htmlnode

本文主要分为3个部分
1. PHP内核、.php脚本的执行流程
2. PHP Zend扩展原理及编写方法
3. PHP API Hook技术

 

相关学习资料mysql

http://www.nowamagic.net/librarys/veda/detail/1325
http://www.nowamagic.net/librarys/veda/detail/1322
http://rapheal.sinaapp.com/2013/11/20/php_zend_hello_world/
http://hilojack.sinaapp.com/?p=891
http://www.nowamagic.net/librarys/veda/detail/1285
http://www.php.net/manual/zh/internals2.ze1.zendapi.php
http://blog.csdn.net/heiyeshuwu/article/details/3453854
http://www.php.net/manual/zh/internals2.buildsys.configunix.php
http://www.php.net/manual/zh/internals2.buildsys.php
http://www.ccvita.com/496.html
http://blog.csdn.net/taft/article/details/596291
http://weizhifeng.net/write-php-extension-part1.html
http://blog.csdn.net/hguisu/article/details/7414724
http://sebug.net/paper/pst_WebZine/pst_WebZine_0x05/0x07_浅谈从PHP内核层面防范PHP_WebShell.html
http://security.tencent.com/index.php/blog/msg/19
http://www.laruence.com/2012/02/18/2560.html
https://wiki.php.net/rfc/taint
http://www.php-internal.com/

 

1. PHP内核学习linux

从个人理解上来讲,PHP、包括zend(咱们这里暂时统称为PHP,以后会对他们的组成概念进行说明)本质上都是一个C程序。程序员

但和咱们传统意义上写的C程序不一样的是,PHP具备不少强大的功能:
  1) 虚拟机的功能: 和软件加密中的VMP概念相似,PHP的语法能够当作是一种虚拟指令集,咱们程序员在编写PHP脚本的时候其实就是在使用这种虚拟指令集进行代码逻辑、功能调用的编程。
    同时PHP则提供了对这些虚拟指令的解析、翻译,并调用相应的底层代码去完成程序员的调用请求
  
2) 内存管理: 这里说的内存管理和操做系统层面的那个内存管理并非一个概念。操做系统负责的是整个windows / linux 上所有应用的内存管理。而PHP的内存管理负责的是    
    2.1) 在每次请求的脚本的当前"运行空间(针对每一个脚本而言都有本身的运行空间)"     2.2) 以及PHP内核     2.3) 扩展模块中的永久内存   这些方面的内存管理,对于PHP来讲,内存管理是一个很是底层、很是重要的部分
  
3) 和服务器通讯: PHP做为一个编译(这里的编译指的是编译为opcode的概念)、解释系统、同时提供虚拟运行时环境的C程序,它最重要的方面是能和服务器(WEB容器)进行通讯,
    要注意的是,这里所说的"服务器"是一个概念上的意义,PHP对与上层"服务器"的通讯进行了抽象,把全部的逻辑都抽象、封装到了SAPI(server application programming
    interface),对于上层的服务器来讲,它们对PHP的调用就能够经过SAPI来进行,实现了"解耦和"。常见的调用SAPI方式有:     1) CGI:     CGI即通用网关接口(Common Gateway Interface),它是一段程序, 通俗的讲CGI就象是一座桥,把网页和WEB服务器中的执行程序链接起来     每有一个用户请求,都会先要建立CGI的子进程,而后处理请求,处理完后结束这个子进程,这就是Fork-And-Execute模式     因此用CGI方式的服务器有多少链接请求就会有多少CGI子进程,子进程反复加载是CGI性能低下的主要缘由     2) Fastcgi模式     FAST-CGI 是CGI的升级版本,FastCGI 像是一个常驻(Long-Live)型的CGI,它能够一直执行着,只要激活后,不会每次都要花费时间去Fork一次
    (解决了 CGI 最为人诟病的 Fork-And-Execute 模式)     3) CLI模式     CLI是PHP的命令行运行模式
    php -f script.php     
4) 模块模式     模块模式是以mod_php5模块的形式集成,此时mod_php5模块的做用是接收Apache传递过来的PHP文件请求,并处理这些请求,而后将处理后的结果返回给Apache。     httpd.conf:     ...     LoadModule php5_module "E:/wamp/bin/php/php5.4.12/php5apache2_4.dll"     ...     若是咱们在Apache启动前在其配置文件中配置好了PHP模块(mod_php5),PHP模块经过注册apache2的ap_hook_post_config挂钩,在Apache启动的时候启动此模块以接受PHP文件
    的请求。   全部这些调用方式,对PHP来讲本质上都是同样的,PHP把它们都当作是来自
"服务器"的调用。这种架构方式提供了很美丽的解耦和方式,同时,为PHP在不一样操做系统、不一样服务
  器(WEB容器)上的跨平台操做提供了支持。

咱们能够从PHP的官网上下载到PHP的源代码:sql

http://cn2.php.net/downloads.phpapache

解压后编程

咱们先来看看PHP的源码的目录结构:

build: 和编译有关的目录
ext: 扩展库代码
例如 Mysql、zlib、iconv 等咱们熟悉的扩展库。咱们在程序中调用mysql_connect()这个函数就是由于在php.ini加入"extension=php_mysql.dll" 来加载php_mysql.dll这个扩展,
从而使咱们在代码空间中可使用mysql_connect()这个导出函数 main: 主目录,其中包含了:   
1) php中最重要头文件php.h   2) 输入输出函数spprintf.h   3) 服务器抽象层SAPI.h   4) 内存管理alloca.c   5) 流式逻辑的实现\streams\..   .. pear: 这里是Pear核心文件。http://pear.php.net/ sapi: 各类服务器的接口调用,例如apache、IIS等,也包含通常的fastcgi、cgi等。PHP经过SAPI来封装对服务器通讯的抽象 scripts: Linux 下的脚本目录 tests: 测试脚本目录,官方默认提供了一些基本的测试.php脚本供咱们学习之用 win32: Windows 下编译 PHP 有关的脚本。在windows下编译PHP使用了WSH(windows script hosting) Zend: 核心的引擎,zend是PHP的核心,它主要实现了: 1) 把人类能够理解的脚本解析成机器能够理解的符号(token), 而后在一个进程空间内执行这些符号。 2) ZE还负责内存管理 3) 变量做用域 4) 以及函数调用的调度

了解了PHP的源码目录

接下来咱们学习一下怎么本身动手对这些源代码进行编译

os: RedHat Linux Enterprice 5
kernal: 2.6.18-371.4.1.el5
php-version: php-5.5.9.tar.gz
http://cn2.php.net/downloads.php
make: GNU Make 3.81
gcc: gcc version 4.1.2 20080704 (Red Hat 4.1.2-54)

选定一个文件夹做为源码编译的目录,这里选择,我这里的状况是/php/..(路径、文件名能够任选)

1. 切换目录: cd /php

2. 解压源代码压缩包: tar -zvxf php-5.5.9.tar.gz

3. 解压完毕后,进入源码目录: cd php-5.5.9

4. 强制从新生成配置文件configure(不是必要、可选): autoconf -f  http://man.chinaunix.net/linux/lfs/htmlbook/appendixa/autoconf.html

5. 更改脚本的执行权限: chmod 777 configure

6. 根据configure生成makefile文件: ./configure --enable-debug (--enable-debug表示编译时附带GDB调试信息,这样编译出来的版本会比release更大,但同时也容许咱们用NetBeans这样的工具进行C源码级别的单步调试)

7. 根据上一步生成的makefile文件进行编译: make

8. 安装完成,进入CLI测试一下,由于咱们如今尚未和apache等服务器"关联"起来,因此这里就使用CLI接口的PHP进行测试

cd sapi/cli

9. 运行测试语句:

./php -m  (若是你本机已经安装了PHP、或者lamp的话,这里须要明确指定调用当前路径下的php可执行文件)

./php -v

10. 安装成功

11. 安装遇到的问题: 以上的步骤是标准的步骤,我实际在安装的时候会遇到不少的问题,下面把我找到的这些相关问题的连接分享出来,但愿对朋友有帮助

http://asange.blog.51cto.com/7125040/1229976

http://linux.chinaitlab.com/administer/850484.html

configure: error: xml2-config not found. Please check your libxml2 installation.  ----   安装libxml2-dev依赖包便可: yum -y install libxml2-devel.i386

./configure --enable-debug 若是你想要以后能够debug调试这个PHP的内核代码的话,这里必定要注意加上--enable-debug

http://man.chinaunix.net/linux/lfs/htmlbook/appendixa/autoconf.html

在windows下编译PHP不太方便,会遇到编译器、编译环境版本、额外的依赖库、配置环境、甚至还有字符编码等问题,不太推荐在windows进行PHP源码的编译

http://blog.linuxphp.org/archives/1592/

http://www.kissthink.com/archive/zai--i-n-d-o-w-s-shang-bian-yi-zi-ji-de----yin-qing.html

http://ms.n.blog.163.com/blog/static/185953520134992322690/

https://wiki.php.net/internals/windows/stepbystepbuild

 

编译完成PHP的C源代码以后,咱们继续学习怎么调试PHP代码

这里要注意一下,若是直接google"调试PHP代码"这几个字,搜索到的结果可能会让人产生歧义,这里实际上是两个概念:

1) 单步调试脚本语言级别的PHP代码,例如咱们写一下几句话:

<?php
    $i = 'Hello world!';
    echo $i;

咱们能够对这两行PHP脚本代码作单步调试,并查看它们对应的opcode。

怎么对"脚本代码"做单步调试

对PHP脚本代码(为了区分以后要说的PHP的C源代码,因此这里称之为PHP脚本代码)进行单步调试最好的工具就是xdebug了,它是一个PHP扩展,同时不少IDE都继承对PHP单步调试的支持(eclipse、zend studio、NetBeans)

多是我研究的还太浅了,我在学习的时候,感受脚本代码级的单步调试对个人帮助不是特别大,我基本均可以用die、var_dump来打到个人需求,因此并无深刻研究单步调试的东西,这里给出NetBeans配置单步调试的相关资料,留待之后学习

https://netbeans.org/kb/docs/php/debugging_zh_CN.html
http://www.zvv.cn/blog/show-101-1.html

 

说到这里,咱们要注意的一点是,咱们在使用xdebug的时候,调试的对象实际上是PHP的中间语言: opcode.

关于PHP代码和opcode的关系

咱们能够结合

1) C和汇编的关系(虽然不是很准确)
2) C#和CIL: Common Intermeditate Language中间语言的关系
3) java和JVM字节码的关系

PHP在执行的时候,会被Zend先翻译成opcode中间语言(opcode就是一行行的代码),而后再一条条的单独执行,咱们知道,PHP是构建在Zend VM(Zend虚拟机)之上的,因此这些opcode其实就是Zend VM中的指令(和VMP: virtual machine protect 的概念相似)。

既然opcode是一个指令系统,那么opcode就必然包含一个指令系统所必须的组成部分,包括:

1) 指令集(各类操做符)
2) 指令的格式和规范(由处理器指令规范规定)
3) 操做数
  3.1) 显示的常量操做数
  3.2) 保存在寄存器中的操做数
  3.3) 保存在堆栈中的操做数
  3.4) 保存在内存中的操做数
  3.5) 保存在IO端口中的操做数
4) 保存指令、及相关信息的数据结构(struct _zend_op)(PHP这类虚拟机语言特有的)

为了更好地理解opcode和PHP代码的关系,咱们从一个脚本的完整执行流程来看看PHP是怎么对代码进行翻译、并执行的

例如,咱们在请求这段代码的执行的时候

<?php
  echo 1;
?>

PHP会作以下的事情:

1) Zend启动引擎"词法"分析,将代码切分为一个个的标记Toekn,你能够用token_get_all()函数来查看PHP在这一步都作了什么
2) 使用"语法"分析器(注意和词法作区分)(PHP使用bison生成语法分析器,规则见$PHP_SRC/Zend/zend_language_parser.y), bison根据规则进行相应的处理, 若是代码找不到匹配
  的规则,也就是语法错误时Zend引擎会中止,并输出错误信息。 好比缺乏括号,或者不符合语法规则的状况都会在这个环节检查
3) Zend引擎对这些Token进行编译, 将代码编译为opcode,并绑定相应的参数、和函数调用 4) Zend引擎执行这些opcode,在执行opcode的过程当中还有可能会继续重复进行编译-执行,例如执行eval,include/require等语句,由于这些语句还会包含或者执行其余文件或者字符串中
  的脚本
5) 总的来讲,咱们的脚本代码最终会以opcode的形式在Zend RunTime中出现,咱们知道,opcode本质上是一些"虚拟机指令",因此每一条opcode都对应着Zend中对应的一个函数调用,
  即"opcode-Zend_Function" 6) Zend_Function就是在底层用C实现的一些代码逻辑,PHP的全部上层功能最终由底层的C来实现

1. 词法分析

array (size=7)
  0 => 
    array (size=3)
      0 => int 372
      1 => string '<?php ' (length=6)
      2 => int 1
  1 => 
    array (size=3)
      0 => int 316
      1 => string 'echo' (length=4)
      2 => int 1
  2 => 
    array (size=3)
      0 => int 375
      1 => string ' ' (length=1)
      2 => int 1
  3 => 
    array (size=3)
      0 => int 305
      1 => string '1' (length=1)
      2 => int 1
  4 => string ';' (length=1)
  5 => 
    array (size=3)
      0 => int 375
      1 => string ' ' (length=1)
      2 => int 1
  6 => 
    array (size=3)
      0 => int 374
      1 => string '?>' (length=2)
      2 => int 1

2. 语法分析(这一步是在检查代码的语法合理性)

3. 编译Token

在PHP实现内部,opcode由以下的结构体表示:

struct _zend_op
{
    opcode_handler_t handler; // 执行该opcode时调用的处理函数
    znode result;
    znode op1;
    znode op2;
    ulong extended_value;
    uint lineno;
    zend_uchar opcode;  // opcode代码,咱们能够根据这个opcode代码推测出它可能调用的Zend函数
};

下面这个函数是在"编译器"遇到echo语句的时候进行编译的函数(注意,这里是编译过程,即(3)步,编译器在这一步的目的是生成相应的opcode,并绑定参数)

void zend_do_echo(const znode *arg TSRMLS_DC)
{
    zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC);  //首先经过get_next_op在当前的op_array的最后边"生成"一条opcode
  
    opline->opcode = ZEND_ECHO;  //opcode代码
    opline->op1 = *arg;     //绑定这个opcode须要的参数
    SET_UNUSED(opline->op2);
}

PHP脚本编译为opcode保存在op_array中(注意,这仍是第(3)步),其内部存储的结构以下:

struct _zend_op_array
{
    /* Common elements */
    zend_uchar type;
    char *function_name;  // 若是是用户定义的函数则,这里将保存函数的名字
    zend_class_entry *scope;
    zend_uint fn_flags;
    union _zend_function *prototype;
    zend_uint num_args;
    zend_uint required_num_args;
    zend_arg_info *arg_info;
    zend_bool pass_rest_by_reference;
    unsigned char return_reference;
    /* END of common elements */
 
    zend_bool done_pass_two;
 
    zend_uint *refcount;
 
    zend_op *opcodes;  // opcode数组
 
    zend_uint last,size;
 
    zend_compiled_variable *vars;
    int last_var,size_var;
 
    // ...
}

opcodes保存在op_array后,在执行的时候由下面的execute函数执行

ZEND_API void execute(zend_op_array *op_array TSRMLS_DC)
{
    // ... 循环执行op_array中的opcode或者执行其余op_array中的opcode
}

4. Zend引擎逐条(实际上是op_array中的逐个元素项)执行opcode

咱们知道,opcode本质上是一鞋"虚拟指令",这些虚拟指令必须在底层有具体的实现代码,这个系统才能正常运转,而咱们在第(3)步已经由zend_do_echo生成了相应的opcode,如今是执行的时候了

"echo 1;"是一条输出常量的PHP脚本代码,它在Zend中对应的调用以下:

static int ZEND_FASTCALL  ZEND_ECHO_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
    USE_OPLINE

    zval *z;

    SAVE_OPLINE();
    z = opline->op1.zv; //

    if (IS_CONST == IS_TMP_VAR && Z_TYPE_P(z) == IS_OBJECT) 
    {
        INIT_PZVAL(z);
    }
    zend_print_variable(z);

    CHECK_EXCEPTION();
    ZEND_VM_NEXT_OPCODE();
}

代码最终获得了执行,看到这里,我以为咱们能够想的更多一点,PHP的这种opcode本质上和VMP的虚拟指令是同样的,可能不太准确,可是我以为不少技术实际上是相同的,去除表面的特性,回到技术自己的原理,它们具备很大的类似性

http://www.cnblogs.com/LittleHann/p/3344261.html

更多详细细节能够参阅这些paper

http://www.nowamagic.net/librarys/veda/detail/1322
http://www.nowamagic.net/librarys/veda/detail/1325
http://rapheal.sinaapp.com/2013/11/20/php_zend_hello_world/

 

了解了PHP代码和opcode的基本关系,咱们接下来学习一下:

怎么查看PHP代码对应的opcode

使用vld(Vulcan Logic Dumper)这款php扩展,能够查看对应PHP代码的opcode

1. 下载VLD源代码

http://pecl.php.net/package/vld

我下的是 0.12.0 版本,下载后,解压到一个文件夹中

2. 编译源代码

2.1) phpize
2.2) 生成makefile文件爱你:  ./configure --with-php-config=/usr/bin/php-config --enable-vld

(这里的php-config路径可能会不同,根据本身的机器上的状况修改)
2.3) 编译完成后,复制.so文件到执行的目录

cp modules/vld.so /usr/lib/php/modules
cp modules/vld.so /usr/include/php/ext

关于PHP的扩展目录能够在php.ini中查看到(在php.ini中搜索extention_dir)
3. 修改.php.ini配置,并重启httpd服务

3.1) vim /etc/php.ini

3.2) 在文件中加上 extension=vld.so
4. 测试运行效果

vim test.php
<?php
    echo 1;
?>
php -dvld.active=1 ./test.php

Finding entry points
Branch analysis from position: 0
Return found
filename:       /var/www/html/index.php
function name:  (null)
number of ops:  4
compiled vars:  none
line     # *  op                           fetch          ext  return  operands
---------------------------------------------------------------------------------
   2     0  >   ECHO                                                     1
   5     1      ECHO                                                     '%0A'
         2    > RETURN                                                   1
         3*   > HANDLE_EXCEPTION                                         

branch: #  0; line:     2-    5; sop:     0; eop:     3
path #1: 0, 
1

了解了PHP的单步调试、opcode的概念以后,咱们回到最初的问题上来. 咱们以前说过:

若是直接google"调试PHP代码"这几个字,搜索到的结果可能会让人产生歧义,这里实际上是两个概念:
1) 单步调试脚本语言级别的PHP代码
...

这里就要引入另外一个概念了

2) 对PHP的底层C代码的单步调试

关于对底层C代码的单步调试,我以为有如下几点须要理解:
1) PHP在底层是用C实现的
2) PHP本质上是一个C程序(php.exe),负责对脚本进行解释和执行
3) 全部的PHP脚本代码,都会被翻译为opcode,并对应于某个zend_funtion,全部咱们对这个相应的zend_function进行下断点,就能够针对执行的PHP脚本代码,单步调试它对应的底层C代码

结合咱们写C程序的概念,咱们须要单步调试一个程序,就须要编译出一个debug版本的exe程序。对于PHP的编译也是一样的道理。为了debug单步调试PHP,咱们须要作以下准备工做:

1) 编译个debug版本的PHP: 编译出的可执行程序会比较大,附带了调试信息
2) 用于单步调试PHP的IDE: 这里选用NetBeans,eclipse也是能够的

1. 编译一个PHP(Debug),咱们在文章的开始已经学习了怎么从源代码编译个debug版本的PHP,注意到:

./configure --enable-debug

这里的 "--enable-debug"是一个关键,只有这样编译出的PHP版本才是能够单步调试的

2.在NetBeans中建立工程

将咱们进行编译的PHP源码目录添加进工程中

选择好类型,并选择利用现存的源代码建立工程

填写上源代码的路径,我这里是 /php/php-5.5.9

添加成功后,能够开始下断点调试了(在第一次运行的时候NetBeans会让咱们选择要debug的可执行程序,咱们选cli.exe便可),咱们的测试语句是:

<?php
  echo 1;
?>

咱们已经知道,这个语句对应的opcode调用的zend中的ZEND_ECHO_SPEC_CONST_HANDLER函数,故咱们的断点应该下在这个函数体中

点击开始debug,当PHP解析这段脚本的时候,由于会调用这个函数,因此天然断了下来

以后,咱们就能够像咱们平时编程同样,去单步跟踪、调试PHP的内部运行机制。掌握了对PHP底层代码的单步调试对咱们深刻学习PHP是颇有帮助的,这可让咱们从一个不一样的角度去研究、学习PHP的运行机制、语言的特性,例以下面的情景

1) 你编写了一个PHP脚本
2) 其中遇到一个PHP的特性不是很理解
3) 使用vld翻译为opcode
4) 根据opcode去推测、寻找(有时候不必定能准确地找到)这个语法对应的zend_function
5) 在指定的zend_function中下断点,从C的层面单步跟踪,帮助咱们更好、更深刻地理解问题的成因

文章写到这里,我再看看个人标题: "PHP内核、.php脚本的执行流程".......当真是以为我有些蚍蜉撼树,夸下海口了,PHP内核博大精深,并非在一篇文章中能研究地清楚的,最好的方法应该是针对某个的问题、某个特性做针对性的研究,但我认为这篇文章会是一个很好的开始,这里分享一些不错的资料,也留待之后研究继续

http://www.nowamagic.net/librarys/veda/detail/1285

 

PHP脚本的执行步骤

一切的开始: SAPI接口
命令行程序和Web程序相似, 命令行参数传递给要执行的脚本,至关于经过url 请求一个PHP页面.。
PHP脚本完成执行后返回响应结果,只不过命令行响应的结果是显示在终端上,而WEB服务器的返回结果回传给浏览器。
脚本执行的开始都是经过SAPI接口进行的

1) 启动apache:
当给定的SAPI启动时,例如在对/usr/local/apache/bin/apachectl start的响应中,PHP从初始化其内核子系统开始。
在接近启动例程的末尾,它加载"每一个"(逐一调用)扩展的代码并调用其模块初始化例程(MINIT)。这使得:
    1.1) 每一个扩展能够初始化内部变量
    1.2) 分配资源
    1.3) 注册资源处理器
    1.4) 以及向ZE注册本身的函数,以便于脚本调用某个函数的时候这个函数的代码的入口地址在哪里

2) 请求初始化处理:
接下来,PHP等待SAPI层请求要处理的页面
    2.1) 对于CGI或CLI这种类型的SAPI来讲,这将马上发生且只发生一次
    2.2) 对于apache、IIS或其余WEB容器来讲,每次远程用户请求页面(请求执行脚本)时都将发生,所以重复不少次,也可能并发。
无论请求如何产生,PHP要求ZE创建脚本的运行环境后再开始接受请求(PHP负责和WEB服务器的通讯)。
在某个请求到达的时候,PHP会逐个调用"每一个"扩展的请求初始化(RINIT)函数。RINIT使得扩展有机会:
    1) 设定特定的环境变量
    2) 根据请求分配资源
    3) 执行其余任务: 如审核
session扩展中有个RINIT做用的典型示例: 若是启用session.auto_start,RINIT将自动触发用户空间的session_start()函数以及"预组装"$_SESSION变量

3) 执行PHP代码:
一旦请求被初始化了,ZE开始接管控制权,将PHP脚本翻译成符号(Token),最终造成操做码(Opcode)并逐步执行之。若是操做码中有涉及到扩展的调用,ZE将会把参数绑定到该函数,而且临时
交出控制权直到函数运行结束
4) 脚本结束: 每一次的脚本运行结束后,PHP会逐一调用"每一个"扩展的请求关闭(RSHUTDOWN)函数以执行最后的清理工做(如将session变量存入磁盘)。接下来,ZE执行清理过程(垃圾收集): 即有效地对以前
的请求期间用到的每一个变量执行unset()
5) SAPI关闭: 一旦完成,PHP继续等待SAPI的其余文档请求、或者关闭信号(对于CGI和CLI等SAPI,没有"下一个请求",因此SAPI马上开始关闭)。 关闭期间,PHP再次遍历每一个扩展,调用其模块关闭(MSHUTDOWN)函数,并最终关闭本身的内核子系统 MINIT->RINIT->RSHUTDOWN->MSHUTDOWN

还能够参阅一下这篇文章

http://sebug.net/paper/pst_WebZine/pst_WebZine_0x05/0x07_浅谈从PHP内核层面防范PHP_WebShell.html

以上就是google告诉咱们的PHP的脚本的执行步骤,可是在这里小瀚以为咱们能够更深刻地学习一下,但愿接下来能亲自动手分析、调试一下源码,从源码的角度去分析一下这个过程,我在google上没有找到相关的资料,若是有这方面源码调试方面资料的朋友,但愿能在留言中分享一下,不胜感激

 

2. PHP扩展学习

在学习了PHP内核的基本知识,咱们接下来能够学习一下PHP的一个很重要的功能(或者叫架构): 扩展

关于PHP的扩展,PHP.NET官方给出了一个详细的解释

http://www.php.net/manual/zh/internals2.ze1.zendapi.php

paper中,我以为最重要的是那张图,它帮助咱们理解了PHP的总体架构、以及PHP和Zend的关系

Zend 和PHP的关系
Zend 指的是语言引擎,PHP 指的是咱们从外面看到的一套完整的系统。
为了实现一个 WEB 脚本的解释器,你须要完成如下三个部分的工做

1. 解释器部分,负责对输入代码的分析、翻译和执行: 
(zend: 100%)
    1.1 Zend 构成了语言的核心
    1.2 同时也包含了一些最基本的 PHP 预约义函数的实现
    1.3 将PHP脚本代码翻译为Token、以及将opcode翻译为对zend_function的调用
    1.4 提供opcode的虚拟机运行时runtime
    1.5 内存管理
也就是说,zend至关于真个整个程序的大架构,起核心做用

2. 功能性部分,负责具体实现语言的各类功能(好比它的函数等等): 
(zend: 50%、 php: 50%)
    2.1 IO管理
    2.2 流式输入输入Stream的实现

3. 接口部分,负责同 WEB 服务器的会话等功能: (php: 100%)
    3.1 实现SAPI,封装了PHP和上层WEB容器的通讯
    3.2 包含了全部创造出语言自己各类显著特性的模块(也就是常说的module模块)
        3.2.1) xml
        3.2.2) mysql模块等
    PHP的"不少"(并非所有)核心函数功能、代码逻辑的实现都依靠扩展来实现

以上3个部分,他们合起来称之为 PHP 包

咱们能够把PHP的扩展理解为C编程中的库(.dll、.so)(PHP的扩展本质上也是库),在须要使用这些库中的"导出函数"的时候,就在代码中使用如下方式调用

HMODULE LoadLibrary(LPCTSTR lpFileName);
FARPROC GetProcAddress(HMODULE hModule, LPCWSTR lpProcName);
BOOL FreeLibrary(HMODULE hModule);

而PHP中的扩展在本质上也是这个意思,咱们在编译出相应的库文件(.dll、.so)文件后,可使用如下方式引入这个库文件

1. 修改php.ini,增长 extension=lib_name.so
2. 在代码中调用dl()函数,去动态地加载外部库文件

接下来,咱们一块儿来学习一下怎么本身动手编译一个最简单的PHP扩展(代码库),而后调用PHP中实现的函数(导出函数)

 

编译PHP扩展

google上关于PHP扩展的编译的文章有不少,我在这里尽量地给出一些实验步骤,并分享出一些我在学习过程当中的学习资料

http://blog.csdn.net/heiyeshuwu/article/details/3453854
http://www.php.net/manual/zh/internals2.buildsys.configunix.php
http://www.php.net/manual/zh/internals2.buildsys.php

http://www.ccvita.com/496.html

http://blog.csdn.net/taft/article/details/596291
http://weizhifeng.net/write-php-extension-part1.html

1. 在PHP的源代码目录下生成一个新的文件夹: hello

咱们知道,扩展的框架生成可使用ext_skel这个脚本帮助咱们构建,可是为了理解更加清楚理解各个代码文件的意义、及它们之间的关系,咱们这里所有用手工建立

这里ext就是源码目录下的ext文件夹,咱们就是要在这里新建咱们的扩展目录,以后咱们会把扩展须要的文件都统一放在这个文件夹中

2. 新建配置文件: config.m4

config.m4
PHP_ARG_ENABLE(hello, whether to enable Hello World support,
[ --enable-hello Enable Hello World support])

if test "$PHP_HELLO" = "yes"; then
    AC_DEFINE(HAVE_HELLO, 1, [Whether you have Hello World])
    PHP_NEW_EXTENSION(hello, hello.c, $ext_shared)
fi

3. 新建扩展所须要用到的头文件: php_hello.h

php_hello.h
#ifndef PHP_HELLO_H
    #define PHP_HELLO_H 1
    #define PHP_HELLO_WORLD_VERSION "1.0"
    #define PHP_HELLO_WORLD_EXTNAME "hello"

    PHP_FUNCTION(hello_world);
    extern zend_module_entry hello_module_entry;
    #define phpext_hello_ptr &hello_module_entry
#endif

4. 新建扩展所实现功能的代码逻辑的文件: hello.c

hello.c
#ifdef HAVE_CONFIG_H
    #include "config.h"
#endif

#include "php.h"
#include "php_hello.h"

static function_entry hello_functions[] = {
    PHP_FE(hello_world, NULL)
    {NULL, NULL, NULL}
};

zend_module_entry hello_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
    STANDARD_MODULE_HEADER,
#endif
    PHP_HELLO_WORLD_EXTNAME,
    hello_functions,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
#if ZEND_MODULE_API_NO >= 20010901
    PHP_HELLO_WORLD_VERSION,
#endif
    STANDARD_MODULE_PROPERTIES
};

#ifdef COMPILE_DL_HELLO
    ZEND_GET_MODULE(hello)
#endif

PHP_FUNCTION(hello_world)
{
    //RETURN_STRING("Hello World", 1);
    char *arg = NULL;
    int arg_len, len;
    char *strg;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &arg, &arg_len) == FAILURE) {
        return;
    }
    len = spprintf(&strg, 0, "Your input string: %s/n", arg);
    RETURN_STRINGL(strg, len, 0);
}

5. 编译扩展源代码

phpize
./configure --enable-hello
make


vim /etc/php.ini
增长这一条: extension=hello.so


cp modules/hello.so /usr/lib/php/modules/


service httpd restart

6. 测试新扩展的效果

7. 实验成功

以上就是一个基本的PHP扩展的编译过程,PHP的扩展是在zend的基础上进行的,因此咱们在编写源代码的时候会涉及到大量的zend的宏调用,涉及到不少的数据结构,这一块也是须要重点学习的,更多的信息能够参考这篇文章

http://blog.csdn.net/hguisu/article/details/7414724

 

3. PHP API Hook技术

谈到PHP API Hook技术,咱们就要回到我写这篇文章的目的了"WebShell攻防对抗",咱们能够很容易地想到如下几点来进行WebShell动态检测:

1) 对PHP中可能产生安全问题的函数进行Hook劫持,在代码执行的以前,先运行咱们的"代码安全检测模块",从而达到动态检测的目的
2) 该怎么实现API Hook的目的呢,我以为有如下几种思路
  2.1) 利用zend自己提供的函数回调机制对代码流进行Hook绑定
  2.2) PHP中必定有一个数据结构存储的是全部函数的入口地址的(相似windows内核中的IDT同样),咱们找到这个"函数调用地址表",而后修改其中某个函数的入口指针,修改成咱们本身
      定义的函数,这样,本来的函数调用就会先执行咱们自定义的函数,而后咱们再把控制权交回本来在这个位置的函数,完成Hook的效果

以上几点是咱们本身猜想的,具体的状况还有待继续深刻学习,PHP API Hook的研究将放到下一篇文章中继续学习,这里分享几个这方面的连接

http://security.tencent.com/index.php/blog/msg/19
http://www.laruence.com/2012/02/18/2560.html
https://wiki.php.net/rfc/taint
http://www.php-internal.com/

 

Copyright (c) 2014 LittleHann All rights reserved

相关文章
相关标签/搜索