PHP是一门基于C语言编写的高级语言,历史悠久。它支持使用C语言编写可直接用于PHP文件的二进制.so库文件。PHP PWN考的还是用户态PWN,具体而言,赛题一般使用的都是使用C语言编写的PHP扩展库文件,需要发现扩展库里的漏洞,进行利用。
ext_skrl.php,我需要利用他来构建php扩展的基础文件。--ext 和--dir选项。--ext 用来指定php扩展的名字。--dir 设置php扩展基础文件生成的目录。--onlyunix只生成适用于Unix/Linux系统的扩展代码,不包含Windows相关的支持。ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineroot@b993cb0f9752:/var/www/php-8.3.28/ext# ./ext_skel.php --dir /var/www/myphpext/ --ext hello_phpext --onlyunixCopying config scripts... doneCopying sources... doneCopying tests... doneSuccess. The extension is now ready to be compiled. To do so, use thefollowing steps:cd /var/www/myphpext/hello_phpextphpize./configuremakeDon't forget to run tests once the compilation is done:make testThank you for using PHP!
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineroot@b993cb0f9752:/var/www/myphpext/hello_phpext# ls -ltotal 28-rw-r--r-- 1 root root 3490 Nov 22 08:30 config.m4-rw-r--r-- 1 root root 253 Nov 22 08:30 config.w32-rw-r--r-- 1 root root 1968 Nov 22 08:40 hello_phpext.c-rw-r--r-- 1 root root 133 Nov 22 08:30 hello_phpext.stub.php-rw-r--r-- 1 root root 558 Nov 22 08:30 hello_phpext_arginfo.h-rw-r--r-- 1 root root 372 Nov 22 08:30 php_hello_phpext.hdrwxr-xr-x 2 root root 4096 Nov 22 08:30 tests
现在我们要构建扩展和将扩展加载到PHP中。
通过下面的命令可以完成扩展加载:
ounter(lineounter(lineounter(lineounter(linephpize./configuremakemake install
执行上述命令后,我们的PHP扩展库文件就被复制到了/usr/local/lib/php/extensions/no-debug-non-zts-20230831/ 目录中。
ounter(lineounter(lineroot@7207566d19d8:/var/www# ls /usr/local/lib/php/extensions/no-debug-non-zts-20230831/hello_phpext.so opcache.so sodium.so
随后,我们还需要创建扩展文件。在·8.3版本的PHP中,通过php --ini查找拓展文件目录。
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineroot@7207566d19d8:/var/www/myphpext/hello_phpext# php --iniConfiguration File (php.ini) Path: /usr/local/etc/phpLoaded Configuration File: (none)Scan for additional .ini files in: /usr/local/etc/php/conf.dAdditional .ini files parsed: /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini,/usr/local/etc/php/conf.d/docker-php-ext-sodium.ini
创建配置文件。
ounter(lineecho "extension=hello_phpext.so" > /usr/local/etc/php/conf.d/docker-php-ext-hello_phpext.ini
或者直接执行命令执行。
ounter(linephp -d extension=./modules/easy_phppwn.so test.php
重启apache2服务,即可将我们的扩展加载到PHP中。
在php中调用我们的扩展里的函数。
ounter(line<?php test1()?>
这里的test1()对应,hello_phpext.c中的这段代码:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line/* {{{ void test1() */PHP_FUNCTION(test1){ZEND_PARSE_PARAMETERS_NONE();php_printf("The extension %s is loaded and working!\r\n", "hello_phpext");}/* }}} */
hello_phpext.counter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line/* hello_phpext extension for PHP */#ifdef HAVE_CONFIG_H# include"config.h"#endif#include"php.h"#include"ext/standard/info.h"#include"php_hello_phpext.h"#include"hello_phpext_arginfo.h"/* For compatibility with older PHP versions */#ifndef ZEND_PARSE_PARAMETERS_NONE#define ZEND_PARSE_PARAMETERS_NONE() \ZEND_PARSE_PARAMETERS_START(0, 0) \ZEND_PARSE_PARAMETERS_END()#endif/* {{{ void test1() */PHP_FUNCTION(test1){ZEND_PARSE_PARAMETERS_NONE();php_printf("The extension %s is loaded and working!\r\n", "hello_phpext");}/* }}} *//* {{{ string test2( [ string $var ] ) */PHP_FUNCTION(test2){char *var = "World";size_t var_len = sizeof("World") - 1;zend_string *retval;ZEND_PARSE_PARAMETERS_START(0, 1)Z_PARAM_OPTIONALZ_PARAM_STRING(var, var_len)ZEND_PARSE_PARAMETERS_END();retval = strpprintf(0, "Hello %s", var);RETURN_STR(retval);}/* }}}*//* {{{ PHP_RINIT_FUNCTION */PHP_RINIT_FUNCTION(hello_phpext){#if defined(ZTS) && defined(COMPILE_DL_HELLO_PHPEXT)ZEND_TSRMLS_CACHE_UPDATE();#endifreturn SUCCESS;}/* }}} *//* {{{ PHP_MINFO_FUNCTION */PHP_MINFO_FUNCTION(hello_phpext){php_info_print_table_start();php_info_print_table_row(2, "hello_phpext support", "enabled");php_info_print_table_end();}/* }}} *//* {{{ hello_phpext_module_entry */zend_module_entry hello_phpext_module_entry = {STANDARD_MODULE_HEADER,"hello_phpext", /* Extension name */ext_functions, /* zend_function_entry */NULL, /* PHP_MINIT - Module initialization */NULL, /* PHP_MSHUTDOWN - Module shutdown */PHP_RINIT(hello_phpext), /* PHP_RINIT - Request initialization */NULL, /* PHP_RSHUTDOWN - Request shutdown */PHP_MINFO(hello_phpext), /* PHP_MINFO - Module info */PHP_HELLO_PHPEXT_VERSION, /* Version */STANDARD_MODULE_PROPERTIES};/* }}} */#ifdef COMPILE_DL_HELLO_PHPEXT# ifdef ZTSZEND_TSRMLS_CACHE_DEFINE()# endifZEND_GET_MODULE(hello_phpext)#endif
ounter(lineounter(lineounter(line#ifdef HAVE_CONFIG_H#include"config.h"#endif
HAVE_CONFIG_H 宏,则包含 config.h 配置文件ounter(lineounter(lineounter(lineounter(line#include"php.h"#include"ext/standard/info.h"#include"php_hello_phpext.h"#include"hello_phpext_arginfo.h"
php.h:PHP核心头文件,提供PHP扩展APIext/standard/info.h:PHP信息函数头文件php_hello_phpext.h:扩展的自定义头文件hello_phpext_arginfo.h:参数信息头文件(自动生成)ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line/* {{{ void test1() */PHP_FUNCTION(test1){ZEND_PARSE_PARAMETERS_NONE();php_printf("The extension %s is loaded and working!\r\n", "hello_phpext");}/* }}} */
PHP_FUNCTION(test1):定义PHP函数 test1ZEND_PARSE_PARAMETERS_NONE():确保函数不接受任何参数php_printf():输出格式化字符串到PHP输出流ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line/* {{{ string test2( [ string $var ] ) */PHP_FUNCTION(test2){char *var = "World";size_t var_len = sizeof("World") - 1;zend_string *retval;ZEND_PARSE_PARAMETERS_START(0, 1)Z_PARAM_OPTIONALZ_PARAM_STRING(var, var_len)ZEND_PARSE_PARAMETERS_END();retval = strpprintf(0, "Hello %s", var);RETURN_STR(retval);}/* }}}*/
test2,返回字符串类型var = "World", var_len 是字符串长度ZEND_PARSE_PARAMETERS_START(0, 1):定义参数范围(0-1个参数)Z_PARAM_OPTIONAL:参数是可选的Z_PARAM_STRING(var, var_len):解析字符串参数strpprintf(0, "Hello %s", var):创建格式化字符串RETURN_STR(retval):返回字符串结果PHP_FUNCTION定义PHP库函数的宏定义,php8.3源码
ounter(lineounter(lineounter(lineounter(line// Zend/zend_API.h, line 71#define ZEND_NAMED_FUNCTION(name) void ZEND_FASTCALL name(INTERNAL_FUNCTION_PARAMETERS)#define ZEND_FUNCTION(name) ZEND_NAMED_FUNCTION(zif_##name)
PHP_FUNCTION(test1)相当于void ZEND_FASTCALL test1(INTERNAL_FUNCTION_PARAMETERS)
INTERNAL_FUNCTION_PARAMETERSounter(lineounter(lineounter(line// Zend/zend.h, line 49#define INTERNAL_FUNCTION_PARAMETERS zend_execute_data *execute_data, zval *return_value
zend_execute_data 结构体用于跟踪 PHP 代码执行时的函数上下文信息,相当于stack。
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line// /Zend/zend_types.h, line 91typedef struct _zend_execute_data zend_execute_data;// Zend/compile.h, line 544#define EX(element) ((execute_data)->element)//zend_compile.hstruct _zend_execute_data {const zend_op *opline; //指向当前执行的opcode,初始时指向zend_op_array起始位置zend_execute_data *call; /* current call */zval *return_value; //返回值指针zend_function *func; //当前执行的函数(非函数调用时为空)zval This; //这个值并不仅仅是面向对象的this,还有另外两个值也通过这个记录:call_info + num_args,分别存在zval.u1.reserved、zval.u2.num_argszend_class_entry *called_scope; //当前call的类zend_execute_data *prev_execute_data; //函数调用时指向调用位置作用空间zend_array *symbol_table; //全局变量符号表#if ZEND_EX_USE_RUN_TIME_CACHEvoid **run_time_cache; /* cache op_array->run_time_cache */#endif#if ZEND_EX_USE_LITERALSzval *literals; //字面量数组,与func.op_array->literals相同#endif};

zval 结构体PHP使用zend_value定义PHP数据类型,包括整数、浮点数、数组、对象、函数、类等。变量有两个组成部分:变量名、变量值,PHP中可以将其对应为:zval、zend_value,这两个概念一定要区分开,PHP中变量的内存是通过引用计数进行管理的,引用计数是在zend_value而不是zval上,变量之间的传递、赋值通常也是针对zend_value。
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line// /Zend/zend_types.h, line 93typedef struct _zval_struct zval;typedef union _zend_value {zend_long lval; //int整形double dval; //浮点型zend_refcounted *counted;zend_string *str; //string字符串zend_array *arr; //array数组zend_object *obj; //object对象zend_resource *res; //resource资源类型zend_reference *ref; //引用类型,通过&$var_name定义的zend_ast_ref *ast; //下面几个都是内核使用的valuezval *zv;void *ptr;zend_class_entry *ce;zend_function *func;struct {uint32_t w1;uint32_t w2;} ww;} zend_value;struct _zval_struct {zend_value value; //变量实际的valueunion {struct {ZEND_ENDIAN_LOHI_4( //这个是为了兼容大小字节序,小字节序就是下面的顺序,大字节序则下面4个顺序翻转zend_uchar type, //变量类型zend_uchar type_flags, //类型掩码,不同的类型会有不同的几种属性,内存管理会用到zend_uchar const_flags,zend_uchar reserved) //call info,zend执行流程会用到} v;uint32_t type_info; //上面4个值的组合值,可以直接根据type_info取到4个对应位置的值} u1;union {uint32_t var_flags;uint32_t next; //哈希表中解决哈希冲突时用到uint32_t cache_slot; /* literal cache slot */uint32_t lineno; /* line number (for ast nodes) */uint32_t num_args; /* arguments number for EX(This) */uint32_t fe_pos; /* foreach position */uint32_t fe_iter_idx; /* foreach iterator index */} u2; //一些辅助值};
zval结构比较简单,内嵌一个union类型的zend_value保存具体变量类型的值或指针,zval中还有两个union:u1、u2:
u1.v.type区分,另外一个值type_flags为类型掩码,在变量的内存管理、gc机制中会用到,第三部分会详细分析,至于后面两个const_flags、reserved暂且不管zval只有:value、u1两个值,整个zval的大小也会对齐到16byte,既然不管有没有u2大小都是16byte,把多余的4byte拿出来用于一些特殊用途还是很划算的,比如next在哈希表解决哈希冲突时会用到,还有fe_pos在foreach会用到……从zend_value可以看出,除long、double类型直接存储值外,其它类型都为指针,指向各自的结构。
zval.u1.type类型:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line/* regular data types */#define IS_UNDEF 0#define IS_NULL 1#define IS_FALSE 2#define IS_TRUE 3#define IS_LONG 4#define IS_DOUBLE 5#define IS_STRING 6#define IS_ARRAY 7#define IS_OBJECT 8#define IS_RESOURCE 9#define IS_REFERENCE 10/* constant expressions */#define IS_CONSTANT 11#define IS_CONSTANT_AST 12/* fake types */#define _IS_BOOL 13#define IS_CALLABLE 14/* internal types */#define IS_INDIRECT 15#define IS_PTR 17
PHP中字符串通过zend_string表示:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(linestruct _zend_string {zend_refcounted_h gc;zend_ulong h; /* hash value */`size_t len;char val[1];};
事实上字符串又可具体分为几类:IS_STR_PERSISTENT(通过malloc分配的)、IS_STR_INTERNED(php代码里写的一些字面量,比如函数名、变量值)、IS_STR_PERMANENT(永久值,生命周期大于request)、IS_STR_CONSTANT(常量)、IS_STR_CONSTANT_UNQUALIFIED,这个信息通过flag保存:zval.value->gc.u.flags,后面用到的时候再具体分析。
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line/* {{{ PHP_RINIT_FUNCTION */PHP_RINIT_FUNCTION(hello_phpext){#if defined(ZTS) && defined(COMPILE_DL_HELLO_PHPEXT)ZEND_TSRMLS_CACHE_UPDATE();#endifreturn SUCCESS;}/* }}} */
ZTS 检查:如果是线程安全模式且动态编译,更新线程本地存储缓存SUCCESS 表示初始化成功ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line/* {{{ PHP_MINFO_FUNCTION */PHP_MINFO_FUNCTION(hello_phpext){php_info_print_table_start();php_info_print_table_row(2, "hello_phpext support", "enabled");php_info_print_table_end();}/* }}} */
phpinfo() 页面显示扩展信息ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line/* {{{ hello_phpext_module_entry */zend_module_entry hello_phpext_module_entry = {STANDARD_MODULE_HEADER,"hello_phpext", /* Extension name */ext_functions, /* zend_function_entry */NULL, /* PHP_MINIT - Module initialization */NULL, /* PHP_MSHUTDOWN - Module shutdown */PHP_RINIT(hello_phpext), /* PHP_RINIT - Request initialization */NULL, /* PHP_RSHUTDOWN - Request shutdown */PHP_MINFO(hello_phpext), /* PHP_MINFO - Module info */PHP_HELLO_PHPEXT_VERSION, /* Version */STANDARD_MODULE_PROPERTIES};/* }}} */
这是最重要的部分,定义了扩展模块的结构:
STANDARD_MODULE_HEADER:标准模块头部信息"hello_phpext":扩展名称ext_functions:函数列表(在arginfo.h中定义)NULL:模块初始化函数(未使用)NULL:模块关闭函数(未使用)PHP_RINIT(hello_phpext):请求初始化函数NULL:请求关闭函数(未使用)PHP_MINFO(hello_phpext):模块信息函数PHP_HELLO_PHPEXT_VERSION:扩展版本号STANDARD_MODULE_PROPERTIES:标准模块属性ounter(lineounter(lineounter(lineounter(lineounter(lineounter(line#ifdef COMPILE_DL_HELLO_PHPEXT# ifdef ZTSZEND_TSRMLS_CACHE_DEFINE()# endifZEND_GET_MODULE(hello_phpext)#endif
ZEND_GET_MODULE(hello_phpext):注册模块获取函数这个扩展提供了两个简单的PHP函数:
test1() - 显示扩展工作状态test2([$str]) - 返回 "Hello $str" 字符串,默认 "Hello World"hello_phpext_arginfo.hounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line/* This is a generated file, edit the .stub.php file instead.* Stub hash: 54b0ffc3af871b189435266df516f7575c1b9675 */ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_test1, 0, 0, IS_VOID, 0)ZEND_END_ARG_INFO()ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_test2, 0, 0, IS_STRING, 0)ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, str, IS_STRING, 0, "\"\"")ZEND_END_ARG_INFO()ZEND_FUNCTION(test1);ZEND_FUNCTION(test2);static const zend_function_entry ext_functions[] = {ZEND_FE(test1, arginfo_test1)ZEND_FE(test2, arginfo_test2)ZEND_FE_END};
static const zend_function_entry ext_functions[]其中即保存了本扩展中导出的,可在PHP代码中直接调用的函数。
之前我们定义的函数没有接收任何参数,那么扩展定义的内部函数如何读取参数呢?首先回顾下函数参数的实现:用户自定义函数在编译时会为每个参数创建一个zend_arg_info结构,这个结构用来记录参数的名称、是否引用传参、是否为可变参数等,在存储上函数参数与局部变量相同,都分配在zend_execute_data上,且最先分配的就是函数参数,调用函数时首先会进行参数传递,按参数次序依次将参数的value从调用空间传递到被调函数的zend_execute_data,函数内部像访问普通局部变量一样通过存储位置访问参数,这是用户自定义函数的参数实现。
内部函数与用户自定义函数最大的不同在于内部函数就是一个普通的C函数,除函数参数以外在zend_execute_data上没有其他变量的分配,函数参数是从PHP用户空间传到函数的,它们与用户自定义函数完全相同,包括参数的分配方式、传参过程,也是按照参数次序依次分配在zend_execute_data上,所以在扩展中定义的函数直接按照顺序从zend_execute_data上读取对应的值即可,PHP中通过zend_parse_parameters()这个函数解析zend_execute_data上保存的参数:
ounter(linezend_parse_parameters(int num_args, constchar *type_spec, ...);
l - long (整型)d - double (浮点型)s - string (字符串)z - zval (任意类型)a - array (数组)o - object (对象)r - resource (资源)b - boolean (布尔值ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(linePHP_FUNCTION(my_func_1){zend_long lval;zval *arr;if(zend_parse_parameters(ZEND_NUM_ARGS(), "la", &lval, &arr) == FAILURE){RETURN_FALSE;}...}
对应的内存关系:
注意:解析时除了整形、浮点型、布尔型是直接硬拷贝value外,其它解析到的变量只能是指针,arr为zend_execute_data上param_1的地址,即:zval *arr = ¶m_1,也就是说参数始终存储在zend_execute_data上,解析获取的是这些参数的地址。zend_parse_parameters()调用了zend_parse_va_args()进行处理,简单看下解析过程:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line//va就是定义的要解析到的各个变量的地址staticintzend_parse_va_args(int num_args, constchar *type_spec, va_list *va, int flags){const char *spec_walk;int min_num_args = -1; //最少参数数int max_num_args = 0; //要解析的参数总数int post_varargs = 0;zval *arg;int arg_count; //实际传参数//遍历type_spec计算出min_num_args、max_num_argsfor (spec_walk = type_spec; *spec_walk; spec_walk++) {...}...//检查数目是否合法if (num_args < min_num_args || (num_args > max_num_args && max_num_args >= 0)) {...}//获取实际传参数:zend_execute_data.This.u2.num_argsarg_count = ZEND_CALL_NUM_ARGS(EG(current_execute_data));...i = 0;//逐个解析参数while (num_args-- > 0) {...//获取第i个参数的zval地址:arg就是在zend_execute_data上分配的局部变量arg = ZEND_CALL_ARG(EG(current_execute_data), i + 1);//解析第i个参数if (zend_parse_arg(i+1, arg, va, &type_spec, flags) == FAILURE) {if (varargs && *varargs) {*varargs = NULL;}return FAILURE;}i++;}}
欢迎师傅们加入我们:
纳新群1:222328705
纳新群2:346014666
有兴趣的师傅欢迎一起来讨论!
PS:团队纳新简历投递邮箱:xmcve@qq.com