想做这个的起因是因为在AWD比赛中,如果能获取到页面返回的response,就有可能能发现返回flag的流量,从而拿到对应exp的流量
最简单最有效的方法是ob_start
在每个php源码最上面挂一个ob_start();
最下面来一个ob_get_contents
就可以获取到
但这样也存在一些问题,有一些框架它本身是自带ob_start
的
如上面那做情况,他就获取不到1,会产生一些偏差
这个问题再php层面好像没有比较好的解决方案,于是就想到了php扩展
先看看ob_start的实现
在main/output.c
和main/php_output.h
下
PHP_FUNCTION(ob_start) { zval *output_handler = NULL; zend_long chunk_size = 0; zend_long flags = PHP_OUTPUT_HANDLER_STDFLAGS; if (zend_parse_parameters(ZEND_NUM_ARGS(), "|z/ll", &output_handler, &chunk_size, &flags) == FAILURE) { return; } if (chunk_size < 0) { chunk_size = 0; } if (php_output_start_user(output_handler, chunk_size, flags) == FAILURE) { php_error_docref("ref.outcontrol", E_NOTICE, "failed to create buffer"); RETURN_FALSE; } RETURN_TRUE; }
跟进到最后会发现php_output_handler_start
函数
PHPAPI int php_output_handler_start(php_output_handler *handler) { ... /* zend_stack_push returns stack level */ handler->level = zend_stack_push(&OG(handlers), &handler); OG(active) = handler; return SUCCESS; }
其中OG是一个叫output_globals
的全局变量ob_start()
后的缓冲区
经过调试,发现所有于输出有关的函数都会调用一个叫php_output_op
的函数
static inline void php_output_op(int op, const char *str, size_t len) { php_output_context context; ... if (OG(active) && (obh_cnt = zend_stack_count(&OG(handlers)))) { context.in.data = (char *) str; context.in.used = len; ... } else { context.out.data = (char *) str; context.out.used = len; } ... }
通过判断OG(active)
是否为NULL来决定进不进入缓存区
接着来看看字符串如何进入缓冲区
static inline int php_output_handler_append(php_output_handler *handler, const php_output_buffer *buf) { if (buf->used) { OG(flags) |= PHP_OUTPUT_WRITTEN; /* store it away */ //空间不够时会去申请 if ((handler->buffer.size - handler->buffer.used) <= buf->used) { size_t grow_int = PHP_OUTPUT_HANDLER_INITBUF_SIZE(handler->size); size_t grow_buf = PHP_OUTPUT_HANDLER_INITBUF_SIZE(buf->used - (handler->buffer.size - handler->buffer.used)); size_t grow_max = MAX(grow_int, grow_buf); handler->buffer.data = erealloc(handler->buffer.data, handler->buffer.size + grow_max); handler->buffer.size += grow_max; } //将数据复制过去 memcpy(handler->buffer.data + handler->buffer.used, buf->data, buf->used); handler->buffer.used += buf->used; /* chunked buffering */ if (handler->size && (handler->buffer.used >= handler->size)) { /* store away errors and/or any intermediate output */ return OG(running) ? 1 : 0; } } return 1; }
可以和output_globals.active->buffer
相似,创造一个全局的缓存区
在MINIT
阶段初始化这个全局变量并hook各输出函数的opcode
,写入缓冲区
在RSHUTDOWN
阶段将全局变量的数据保存在文件内
在php的源码下进入ext
目录,输入
./ext_skel --extname=myext
编辑php_hook_output_ext.h
先来看一下output_globals.active->buffer
的结构
typedef struct _php_output_buffer { char *data; size_t size; size_t used; uint free:1; uint _reserved:31; } php_output_buffer;
在上文的php_output_handler_append
函数中可看到只用了前3个
于是编写全局变量如下
ZEND_BEGIN_MODULE_GLOBALS(myext) char *data; //缓存区 size_t size; //缓存区大小 size_t used; //数据长度 ZEND_END_MODULE_GLOBALS(myext)
完成定义,在hook_output_ext.c
下进行初始化与析构
static void php_myext_globals_ctor(zend_myext_globals *G TSRMLS_DC) { G->data = NULL; G->size = 0; G->used = 0; } static void php_myext_globals_dtor(zend_myext_globals *G TSRMLS_DC) { efree(G->data); }
并分别在MINIT
阶段和RSHUTDOWN
调用
这里以ZEND_ECHO
这条opcode为例
当php执行echo xxxx;
时会调用这条opcode
关于如何hook具体可以参考https://xz.aliyun.com/t/4214#toc-2
这里主要讲hook后数据的处理
static int get_data(char *str, size_t str_len) { if(str_len){ //size如果不够就申请更大的空间 if ((MYEXT_G(size) - MYEXT_G(used)) <= str_len){ size_t grow_int = PHP_OUTPUT_HANDLER_INITBUF_SIZE(MYEXT_G(size)); size_t grow_buf = PHP_OUTPUT_HANDLER_INITBUF_SIZE(str_len - (MYEXT_G(size) - MYEXT_G(used))); size_t grow_max = MAX(grow_int, grow_buf); MYEXT_G(data) = erealloc(MYEXT_G(data), MYEXT_G(size) + grow_max); MYEXT_G(size) += grow_max; } memcpy(MYEXT_G(data) + MYEXT_G(used), str, str_len); MYEXT_G(used) += str_len; } return 1; } static int hook_echo(ZEND_OPCODE_HANDLER_ARGS) { zend_op *opline = execute_data->opline; zval *z = EX_CONSTANT(opline->op1); if (Z_TYPE_P(z) == IS_STRING) { zend_string *str = Z_STR_P(z); if (ZSTR_LEN(str) != 0) { get_data(ZSTR_VAL(str), ZSTR_LEN(str)); } } else { zend_string *str = _zval_get_string_func(z); if (ZSTR_LEN(str) != 0) { get_data(ZSTR_VAL(str), ZSTR_LEN(str)); } zend_string_release(str); } return ZEND_USER_OPCODE_DISPATCH; }
可以看到get_data
是直接根据php_output_handler_append
改的
hookecho
是根据ZEND_ECHO
的一个hander编成的
static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ECHO_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE zval *z; SAVE_OPLINE(); z = _get_zval_ptr_cv_undef(execute_data, opline->op1.var); if (Z_TYPE_P(z) == IS_STRING) { zend_string *str = Z_STR_P(z); if (ZSTR_LEN(str) != 0) { zend_write(ZSTR_VAL(str), ZSTR_LEN(str)); } } else { zend_string *str = _zval_get_string_func(z); if (ZSTR_LEN(str) != 0) { zend_write(ZSTR_VAL(str), ZSTR_LEN(str)); } else if (IS_CV == IS_CV && UNEXPECTED(Z_TYPE_P(z) == IS_UNDEF)) { GET_OP1_UNDEF_CV(z, BP_VAR_R); } zend_string_release(str); } ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); }
在RSHUTDOWN
处保存,文件名可以根据时间\,如果是用apache或者nginx起的话,默认是要将文件放在web根目录里否则要更改相关配置
//php_myext.h /* +----------------------------------------------------------------------+ | PHP Version 7 | +----------------------------------------------------------------------+ | Copyright (c) 1997-2018 The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | [email protected] so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: lou00 | +----------------------------------------------------------------------+ */ /* $Id$ */ #ifndef PHP_MYEXT_H #define PHP_MYEXT_H extern zend_module_entry myext_module_entry; #define phpext_myext_ptr &myext_module_entry #define PHP_MYEXT_VERSION "0.1.0" /* Replace with version number for your extension */ #ifdef PHP_WIN32 # define PHP_MYEXT_API __declspec(dllexport) #elif defined(__GNUC__) && __GNUC__ >= 4 # define PHP_MYEXT_API __attribute__ ((visibility("default"))) #else # define PHP_MYEXT_API #endif #ifdef ZTS #include "TSRM.h" #endif /* Declare any global variables you may need between the BEGIN and END macros here: ZEND_BEGIN_MODULE_GLOBALS(myext) zend_long global_value; char *global_string; ZEND_END_MODULE_GLOBALS(myext) */ /* Always refer to the globals in your function as MYEXT_G(variable). You are encouraged to rename these macros something shorter, see examples in any other php module directory. */ #if defined(ZTS) && defined(COMPILE_DL_MYEXT) ZEND_TSRMLS_CACHE_EXTERN() #endif #endif /* PHP_MYEXT_H */ /* * Local variables: * tab-width: 4 * c-basic-offset: 4 * End: * vim600: noet sw=4 ts=4 fdm=marker * vim<600: noet sw=4 ts=4 */ # define MYEXT_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(myext,v) ZEND_BEGIN_MODULE_GLOBALS(myext) char *data; size_t size; size_t used; ZEND_END_MODULE_GLOBALS(myext) # define ZEND_OPCODE_HANDLER_ARGS zend_execute_data *execute_data PHP_FUNCTION(confirm_myext_compiled); static int hookecho(ZEND_OPCODE_HANDLER_ARGS); static int get_data(char *str, size_t str_len); static void init_myext_global();
//myext.c /* +----------------------------------------------------------------------+ | PHP Version 7 | +----------------------------------------------------------------------+ | Copyright (c) 1997-2018 The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | [email protected] so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: lou00 | +----------------------------------------------------------------------+ */ /* $Id$ */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "php.h" #include "php_ini.h" #include "ext/standard/info.h" #include "php_myext.h" #include "ext/standard/head.h" #include "ext/standard/url_scanner_ex.h" #include "main/php_output.h" #include "SAPI.h" #include "zend_stack.h" static int le_myext; //resgin from TRSM ZEND_DECLARE_MODULE_GLOBALS(myext); PHP_FUNCTION(confirm_myext_compiled) { char *arg = NULL; size_t arg_len, len; zend_string *strg; if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &arg, &arg_len) == FAILURE) { return; } strg = strpprintf(0, "Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP.", "myext", arg); RETURN_STR(strg); } static void php_myext_globals_ctor(zend_myext_globals *G TSRMLS_DC) { G->data = NULL; G->size = 0; G->used = 0; } static void php_myext_globals_dtor(zend_myext_globals *G TSRMLS_DC) { efree(G->data); } static int get_data(char *str, size_t str_len) { if(str_len){ //size如果不够就申请更大的空间 if ((MYEXT_G(size) - MYEXT_G(used)) <= str_len){ size_t grow_int = PHP_OUTPUT_HANDLER_INITBUF_SIZE(MYEXT_G(size)); size_t grow_buf = PHP_OUTPUT_HANDLER_INITBUF_SIZE(str_len - (MYEXT_G(size) - MYEXT_G(used))); size_t grow_max = MAX(grow_int, grow_buf); MYEXT_G(data) = erealloc(MYEXT_G(data), MYEXT_G(size) + grow_max); MYEXT_G(size) += grow_max; } memcpy(MYEXT_G(data) + MYEXT_G(used), str, str_len); MYEXT_G(used) += str_len; } return 1; } static int hookecho(ZEND_OPCODE_HANDLER_ARGS) { zend_op *opline = execute_data->opline; zval *z = EX_CONSTANT(opline->op1); if (Z_TYPE_P(z) == IS_STRING) { zend_string *str = Z_STR_P(z); if (ZSTR_LEN(str) != 0) { get_data(ZSTR_VAL(str), ZSTR_LEN(str)); } } else { zend_string *str = _zval_get_string_func(z); if (ZSTR_LEN(str) != 0) { get_data(ZSTR_VAL(str), ZSTR_LEN(str)); } zend_string_release(str); } return ZEND_USER_OPCODE_DISPATCH; } PHP_MINIT_FUNCTION(myext) { #ifdef ZTS ts_allocate_id(&myext_globals_id, sizeof(zend_myext_globals), (ts_allocate_ctor)php_myext_globals_ctor, (ts_allocate_dtor)php_myext_globals_dtor); #else php_myext_globals_ctor(&myext_globals TSRMLS_CC); #endif zend_set_user_opcode_handler(ZEND_ECHO, hookecho); return SUCCESS; } PHP_MSHUTDOWN_FUNCTION(myext) { /* uncomment this line if you have INI entries UNREGISTER_INI_ENTRIES(); */ return SUCCESS; } PHP_RINIT_FUNCTION(myext) { #if defined(COMPILE_DL_MYEXT) && defined(ZTS) ZEND_TSRMLS_CACHE_UPDATE(); #endif //init_myext_global(); return SUCCESS; } PHP_RSHUTDOWN_FUNCTION(myext) { #ifndef ZTS php_myext_globals_dtor(&myext_globals TSRMLS_CC); #endif FILE *fp; fp = fopen("/web/php/log","a"); fwrite(MYEXT_G(data),MYEXT_G(used) , 1, fp ); fwrite("\n------------------\n",21 , 1, fp ); fclose(fp); return SUCCESS; } PHP_MINFO_FUNCTION(myext) { php_info_print_table_start(); php_info_print_table_header(2, "myext support", "enabled"); php_info_print_table_end(); /* Remove comments if you have entries in php.ini DISPLAY_INI_ENTRIES(); */ } const zend_function_entry myext_functions[] = { PHP_FE(confirm_myext_compiled, NULL) /* For testing, remove later. */ PHP_FE_END /* Must be the last line in myext_functions[] */ }; zend_module_entry myext_module_entry = { STANDARD_MODULE_HEADER, "myext", myext_functions, PHP_MINIT(myext), PHP_MSHUTDOWN(myext), PHP_RINIT(myext), /* Replace with NULL if there's nothing to do at request start */ PHP_RSHUTDOWN(myext), /* Replace with NULL if there's nothing to do at request end */ PHP_MINFO(myext), PHP_MYEXT_VERSION, STANDARD_MODULE_PROPERTIES }; #ifdef COMPILE_DL_MYEXT #ifdef ZTS ZEND_TSRMLS_CACHE_DEFINE() #endif ZEND_GET_MODULE(myext) #endif
访问前
不受ob_start的影响