php内核学习(1)
PG:存放全局变量
SG:存放SAPI所需的全局变量
CG:存放compiler需要的全局变量
EG:存放执行器需要的全局变量
其他的前置知识可以看我之前的先知文章https://xz.aliyun.com/t/7330
如果我们在php中执行phpinfo(),php底层到底是怎么处理的呢,毫无疑问,第一步肯定是先输入然后执行,再输出。那底层究竟做了什么呢。
新建一个php文件
现在来看下他的处理过程吧
调用函数栈
执行函数顺序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| php_output_op output.c:1073 php_output_write output.c:252 php_output_stack_pop output.c:1234 php_output_end output.c:325 zif_phpinfo info.c:1257 // ZEND_DO_ICALL_SPEC_RETVAL_UNUSED_HANDLER zend_vm_execute.h:1240//如果是自定义,调用自定义函数,如果是系统函数,则调用系统函数 execute_ex zend_vm_execute.h:51845//使用while循环执行文件中的函数 zend_execute zend_vm_execute.h:56139//zend虚拟机执行,将文件中的数据pull到栈上去 zend_execute_scripts zend.c:1661//按照prepend_file,primary_file,append_file来顺序编译,执行文件 php_execute_script main.c:2584//如果设置了auto_append_file,那么就将此文件加载进去目前执行的文件 do_cli php_cli.c:956//加载php文件路径,将文件信息加载进入zend_file_handle中 main php_cli.c:1351//加载php.ini进入PG start 0x00007fff70c1d7fd start 0x00007fff70c1d7fd
|
一、加载汇编
进入main函数入口
二、进入main()
这里具体初始化了哪些函数可以看下面sapi_module的结构体内容吧。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| struct _sapi_module_struct { char *name; char *pretty_name;
int (*startup)(struct _sapi_module_struct *sapi_module); int (*shutdown)(struct _sapi_module_struct *sapi_module);
int (*activate)(void); int (*deactivate)(void);
size_t (*ub_write)(const char *str, size_t str_length); void (*flush)(void *server_context); zend_stat_t *(*get_stat)(void); char *(*getenv)(char *name, size_t name_len);
void (*sapi_error)(int type, const char *error_msg, ...) ZEND_ATTRIBUTE_FORMAT(printf, 2, 3);
int (*header_handler)(sapi_header_struct *sapi_header, sapi_header_op_enum op, sapi_headers_struct *sapi_headers); int (*send_headers)(sapi_headers_struct *sapi_headers); void (*send_header)(sapi_header_struct *sapi_header, void *server_context);
size_t (*read_post)(char *buffer, size_t count_bytes); char *(*read_cookies)(void);
void (*register_server_variables)(zval *track_vars_array); void (*log_message)(char *message, int syslog_type_int); double (*get_request_time)(void); void (*terminate_process)(void);
char *php_ini_path_override;
void (*default_post_reader)(void); void (*treat_data)(int arg, char *str, zval *destArray); char *executable_location;
int php_ini_ignore; int php_ini_ignore_cwd;
int (*get_fd)(int *fd);
int (*force_http_10)(void);
int (*get_target_uid)(uid_t *); int (*get_target_gid)(gid_t *);
unsigned int (*input_filter)(int arg, char *var, char **val, size_t val_len, size_t *new_val_len);
void (*ini_defaults)(HashTable *configuration_hash); int phpinfo_as_text;
char *ini_entries; const zend_function_entry *additional_functions; unsigned int (*input_filter_init)(void); };
|
do_cli()
这是整个默认配置加载完成后调用do_cli(),在此函数中,系统获取之前传入的文件名,然后将文件名加载进入zend_file_handle结构体的filename中。
而后会调用php_request_startup()来执行zend引擎初始化
php_execute_script
我们继续进入到php_execute_script()函数在此函数中将初始化文件
如果在php.ini中设置了auto_prepend_file则加载其设置的文件,也可以在,user.ini中设置。
在这些文件中,prepend_file_p是之前auto_prepend_file设置的文件,此处注意他们的顺序,进入到zend_execute_scripts()函数中去
zend_execute_scripts()
在此处使用了一个for循环,而循环文件到顺序就是之前加载的顺序,这里就可以解释上传漏洞中.user.ini
照成的文件rce了,而在for循环中,先进入zend_compile_file()来编译函数,然后通过zend_execute()来执行前面编译完成的opcode。
info.c
在zend_execute()中则一行,一行opcode来进行执行,当检测到phpinfo()时,判断phpinfo()是一个内置函数,于是调用internal_function.handler()
来进行执行,然后就进入了info.c的PHP_FUNCTION(phpinfo)
了,下面是phpinfo()中的三个函数
output.c
然后进入php_output_start_default()来进行初始化,进入php_print_info()来输出消息
以上就是整个php执行phpinfo()的全过程了