PHP 扩展编写(二)

6 篇文章 1 订阅
4 篇文章 0 订阅

前面我们说了一个简单的PHP扩展的编写与实现,本次实现一个具有功能的复杂扩展。

求和函数phpadd

求和函数需要接受参数,先来看下参数的写法。

参数的提示与校验是通过ZEND_BEGIN_ARG_INFO_EX、ZEND_BEGIN_ARG_INFO两个宏定义函数完成的。,其中的ZEND_ARG_INFO设置了参数是否为引用、参数标识以及是否为可选参数等,本示例中,我们接受两个参数,均为整型,写法如下:

ZEND_BEGIN_ARG_INFO_EX(phpext_add_arginfo,0,0,1)
    ZEND_ARG_INFO(0,op1)
    ZEND_ARG_INFO(0,op2)
ZEND_END_ARG_INFO()

之后开始编写phpadd函数。

PHP_FUNCTION(phpadd){
    int argc = ZEND_NUM_ARGS();
    long op1,op2;
    if(zend_parse_parameters(argc,"ll",&op1,&op2) == FAILURE){
        return;
    }
    RETURN_LONG(op1+op2);
}

其中zend_parse_parameters用来获取函数参数并进行校验,第一个参数是接受的参数,第二个参数表示指定函数的参数类型,后面就是要解析的参数,参数类型对应说明符如下,

有些修饰符需要对应两个参数,比如s 字符串修饰符,需要传入两个值,一个是内容另外一个是字符串长度。

在获取op1、op2完毕之后,调用RETURN_LONG返回函数返回值,至此phpadd函数编写完毕,后面去function中注册即可。

const zend_function_entry phpext_functions[] = {
    PHP_FE(confirm_phpext_compiled, NULL)       /* For testing, remove later. */
    PHP_FE(phpext,NULL)
    PHP_FE(phpadd,phpext_add_arginfo)
    PHP_FE_END  /* Must be the last line in phpext_functions[] */
};

后面正常的编译扩展so,运行效果如下

[root@VM_0_4_centos phpext]# php -r "echo phpadd(3,27);"
30[root@VM_0_4_centos phpext]# php -r "echo phpadd(4,27);"
31

生命周期Hook扩展

上面简单演示了一个功能性扩展函数的实现过程与方法,下面来说一下如何在生命周期上做点文章。

首先之前了解到PHP的生命周期,MINIT周期会在初始化时加载一次,而RINIT针对每次请求都会加载,在我们编写hook函数时需要注意hook的时间点,一般常用的有hook opcode 和hook 内置function两种方式,本次就以hook system为例。

首先使用vld扩展看一下system函数调用的对应的opcode。

发现最后会调用DO_ICALL调用system函数,先来看一下DO_ICALL对应的过程。

ZEND_VM_HOT_HANDLER(129, ZEND_DO_ICALL, ANY, ANY, SPEC(RETVAL))
{
    USE_OPLINE
    zend_execute_data *call = EX(call);
    zend_function *fbc = call->func;
    zval *ret;
    zval retval;
​
    SAVE_OPLINE();
    EX(call) = call->prev_execute_data;
​
    call->prev_execute_data = execute_data;
    EG(current_execute_data) = call;
​
    ret = RETURN_VALUE_USED(opline) ? EX_VAR(opline->result.var) : &retval;
    ZVAL_NULL(ret);
​
    fbc->internal_function.handler(call, ret);
​
#if ZEND_DEBUG
    ZEND_ASSERT(
        EG(exception) || !call->func ||
        !(call->func->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) ||
        zend_verify_internal_return_type(call->func, ret));
    ZEND_ASSERT(!Z_ISREF_P(ret));
#endif
​
    EG(current_execute_data) = execute_data;
    zend_vm_stack_free_args(call);
    zend_vm_stack_free_call_frame(call);
​
    if (!RETURN_VALUE_USED(opline)) {
        zval_ptr_dtor(ret);
    }
​
    if (UNEXPECTED(EG(exception) != NULL)) {
        zend_rethrow_exception(execute_data);
        HANDLE_EXCEPTION();
    }
​
    ZEND_VM_SET_OPCODE(opline + 1);
    ZEND_VM_CONTINUE();
}

可以看到核心的执行点在于

fbc->internal_function.handler(call, ret);

首先会获取zend_function fbc变量,看一下zend_function结构体。

union _zend_function {
    zend_uchar type;    /* MUST be the first element of this struct! */
    uint32_t   quick_arg_flags;
​
    struct {
        zend_uchar type;  /* never used */
        zend_uchar arg_flags[3]; /* bitset of arg_info.pass_by_reference */
        uint32_t fn_flags;
        zend_string *function_name;
        zend_class_entry *scope;
        union _zend_function *prototype;
        uint32_t num_args;
        uint32_t required_num_args;
        zend_arg_info *arg_info;
    } common;
​
    zend_op_array op_array;
    zend_internal_function internal_function;
};

其中记录了函数名称、参数以及op_array、internal_function等,后面继续追踪下zend_internal_function执行结构体。

typedef struct _zend_internal_function {
    /* Common elements */
    zend_uchar type;
    zend_uchar arg_flags[3]; /* bitset of arg_info.pass_by_reference */
    uint32_t fn_flags;
    zend_string* function_name;
    zend_class_entry *scope;
    zend_function *prototype;
    uint32_t num_args;
    uint32_t required_num_args;
    zend_internal_arg_info *arg_info;
    /* END of common elements */
​
    zif_handler handler;
    struct _zend_module_entry *module;
    void *reserved[ZEND_MAX_RESERVED_RESOURCES];
} zend_internal_function;

这里的zif_handler相当于函数的指针地址,调用handler好比调用函数。

了解了这个结构体之后,后面就简单了,我们通过zend_set_user_opcode_handler函数设置我们要hook的DO_ICALL,然后判断function_name是否为system,如果是,则报警输出即可。

先写一下hook函数 system_fcall_handler

static int system_fcall_handler(zend_execute_data *execute_data) /* {{{ */ {
    const zend_op *opline = execute_data->opline;
    zend_execute_data *call = execute_data->call;
    zend_function *fbc = call->func;
​
    if (fbc->type == ZEND_INTERNAL_FUNCTION) {
        zend_string *fname = fbc->common.function_name;
        if(zend_string_equals_literal(fname,"system") ){
            printf("system hook!");
        }
    }
​
    return ZEND_USER_OPCODE_RETURN;
}

先是获取当先执行函数fbc,然后获取fbc的函数名称,通过zend_string_equals_literal判断函数名称是否为system,如果是,输出结果并进行相应的操作,最后返回ZEND_USER_OPCODE_RETURN,表示不执行system。此外ZEND_USER_OPCODE_DISPATCH表示继续执行,两个返回值的区别在于system是否执行。

之后通过zend_set_user_opcode_handler设置handler。

PHP_MINIT_FUNCTION(phpext)
{
    // REGISTER_INI_ENTRIES();
    zend_set_user_opcode_handler(ZEND_DO_ICALL,(user_opcode_handler_t)system_fcall_handler);
    return SUCCESS;
}

编译运行,效果如下:

[root@VM_0_4_centos phpext]# cat test.php 
<?php
system("id");
?>[root@VM_0_4_centos phpext]# php test.php 
system hook!

下面来看下动态调用system的hook过程类似同上,不过根据opcode显示需要hook DO_FCALL。

root@VM_0_4_centos phpext]# cat test.php
<?php
$a = "sys"."tem";
$a("id");
?>[root@VM_0_4_centos phpext]# php test.php
uid=0(root) gid=0(root) groups=0(root)
system hook!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值