php Zend虚拟机的两种hook方式

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

前言

这节讲一下PHP 的hook的两种方式,opcode handler hook和method hook,没有然后。

handler

PHP提供了内置opcode handler替换函数zend_user_opcode_handlers。

ZEND_API int zend_set_user_opcode_handler(zend_uchar opcode, user_opcode_handler_t handler)
{
	if (opcode != ZEND_USER_OPCODE) {
		if (handler == NULL) {
			/* restore the original handler */
			zend_user_opcodes[opcode] = opcode;
		} else {
			zend_user_opcodes[opcode] = ZEND_USER_OPCODE;
		}
		zend_user_opcode_handlers[opcode] = handler;
		return SUCCESS;
	}
	return FAILURE;
}

原理很简单,内置维护了一个zend_user_opcode_handlers表,直接替换表中user_opcode_handler_t对象即可。

其实user_opcode_handler_t就是一个函数指针。

typedef int (*user_opcode_handler_t) (zend_execute_data *execute_data);

替换完函数地址之后,当zend虚拟机执行到改opcode时,会查询该表找到对应的替换之后的handler函数地址并执行。

替换示例:

static int func(zend_execute_data *execute_data){
	return ZEND_USER_OPCODE_DISPATCH;
}
zend_set_user_opcode_handler(ZEND_DO_FCALL,func);

这里的返回值需要说明下:

#define ZEND_USER_OPCODE_CONTINUE   0 /* execute next opcode */
#define ZEND_USER_OPCODE_RETURN     1 /* exit from executor (return from function) */
#define ZEND_USER_OPCODE_DISPATCH   2 /* call original opcode handler */
#define ZEND_USER_OPCODE_ENTER      3 /* enter into new op_array without recursion */
#define ZEND_USER_OPCODE_LEAVE      4 /* return to calling op_array within the same executor */

#define ZEND_USER_OPCODE_DISPATCH_TO 0x100 /* call original handler of returned opcode */

不同的返回值会走不同的处理逻辑。

因此如果是ZEND_USER_OPCODE_DISPATCH,整个hook的过程可以理解为先执行新的替换之后的handler,之后交给原始handler继续执行。

事实上,经过分析Zend虚拟机执行过程之后,我们知道,其实都是翻译成一个个opcode进行执行,然后寻找这个opcode中的handler执行,再来看一下内置的handler hook函数。

ZEND_API int zend_set_user_opcode_handler(zend_uchar opcode, user_opcode_handler_t handler)
{
	if (opcode != ZEND_USER_OPCODE) {
		if (handler == NULL) {
			/* restore the original handler */
			zend_user_opcodes[opcode] = opcode;
		} else {
		  // 原始opcode一并替换为ZEND_USER_OPCODE
			zend_user_opcodes[opcode] = ZEND_USER_OPCODE;
		}
		// 替换该opcode对应的handler函数地址。
		zend_user_opcode_handlers[opcode] = handler;
		return SUCCESS;
	}
	return FAILURE;
}

因此之后所有替换之后的handler本质上都会走到ZEND_USER_OPCODE这个分支里面,继续追踪下ZEND_USER_OPCODE分支。

static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_USER_OPCODE_SPEC_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
	USE_OPLINE
	int ret;

	SAVE_OPLINE();
	// 获取替换之后的handler返回值
	ret = zend_user_opcode_handlers[opline->opcode](execute_data);
	opline = EX(opline);

	// 返回值判断,并继续走相应的流程。
	switch (ret) {
		case ZEND_USER_OPCODE_CONTINUE:
			ZEND_VM_CONTINUE();
		case ZEND_USER_OPCODE_RETURN:
			if (UNEXPECTED((EX_CALL_INFO() & ZEND_CALL_GENERATOR) != 0)) {
				zend_generator *generator = zend_get_running_generator(EXECUTE_DATA_C);
				zend_generator_close(generator, 1);
				ZEND_VM_RETURN();
			} else {
				ZEND_VM_TAIL_CALL(zend_leave_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU));
			}
		case ZEND_USER_OPCODE_ENTER:
			ZEND_VM_ENTER();
		case ZEND_USER_OPCODE_LEAVE:
			ZEND_VM_LEAVE();
		case ZEND_USER_OPCODE_DISPATCH:
			ZEND_VM_DISPATCH(opline->opcode, opline);
		default:
			ZEND_VM_DISPATCH((zend_uchar)(ret & 0xff), opline);
	}
}

关注下DISPATCH时的执行流程,

#define ZEND_VM_DISPATCH(opcode, opline) ZEND_VM_TAIL_CALL(((opcode_handler_t)zend_vm_get_opcode_handler_func(opcode, opline))(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU));

method

PHP维护了一个全局函数表,其中包含了所有的内置函数,可以通过CG(function_table)的函数获取。因此内置函数hook方法: 在全局函数表中寻找函数zend_function结构体,之后替换handler为自己的即可,taint的实现过程如下:

typedef void (*php_func)(INTERNAL_FUNCTION_PARAMETERS);

static void php_taint_override_func(const char *name, php_func handler, php_func *stash) /* {{{ */ {
	zend_function *func;
	if ((func = zend_hash_str_find_ptr(CG(function_table), name, strlen(name))) != NULL) {
		// 原始函数指针备份
		if (stash) {
			*stash = func->internal_function.handler;
		}
		// 替换为新的函数
		func->internal_function.handler = handler;
	}
} 

内部函数的基本结构体为:

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;

因此函数hook也是在执行之间进行替换,之后可以先获取备份的原始函数指针,之后指针调用之前的函数。

void (*zif_handler)(INTERNAL_FUNCTION_PARAM_PASSTHRU)
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值