# 1. 前置知识
# 2. 环境搭建
这里有个坑,在 php8.1.12 版本有些代码行不通,应该是版本问题,后来改成 php7.2.24 后就可以了,所以下面的配置最好按照 7.2.24 来进行
# 1.PHP 拓展安装
下面是是 PHP 拓展模块的安装流程,我这个是在 ubuntu22.04 下安装的
首先是安装 php,以及 php 开发包
sudo apt install php php-dev |
安装完后利用 php --version
查看当前 php 版本
我这里是 PHP 8.1.12-1ubuntu4.3 (cli)
, 所以从下面的链接找到 8.1.12 的源码下载下来,然后解压
https://github.com/php/php-src/releases
源代码目录结构如下所示 (这里是 php7.2.24 的,新版本可能会有所出入):
php-7.2.24
|____build --和编译有关的目录,里面包括wk,awk和sh脚本用于编译处理,其中m4文件是linux下编译程序自动生成的文件,可以使用buildconf命令操作具体的配置文件。
|____ext --扩展库代码,例如Mysql,gd,zlib,xml,iconv 等我们熟悉的扩展库,ext_skel是linux下扩展生成脚本,windows下使用ext_skel_win32.php。
|____main --主目录,包含PHP的主要宏定义文件,php.h包含绝大部分PHP宏及PHP API定义。
|____netware --网络目录,只有sendmail_nw.h和start.c,分别定义SOCK通信所需要的头文件和具体实现。
|____pear --扩展包目录,PHP Extension and Application Repository。
|____sapi --各种服务器的接口调用,如Apache,IIS等。
|____scripts --linux下的脚本目录。
|____tests --测试脚本目录,主要是phpt脚本,由--TEST--,--POST--,--FILE--,--EXPECT--组成,需要初始化可添加--INI--部分。
|____TSRM --线程安全资源管理器,Thread Safe Resource Manager保证在单线程和多线程模型下的线程安全和代码一致性。
|____win32 --Windows下编译PHP 有关的脚本。
|____Zend --包含Zend引擎的所有文件,包括PHP的生命周期,内存管理,变量定义和赋值以及函数宏定义等等。
# 拓展模块的开发
首先进入源代码目录 ext
文件夹,使用如下目录生成拓展模块的工程项目。该程序会直接为我们生成一个模板 (这里在看别人教程的时候发现是用的 ./ext_skel --extname=easy_phppwn
,这应该是老版本,新版本下会有个 ext_skel.php
文件)
./ext_skel.php --ext easy_phppwn #php8.1.12 | |
./ext_skel --extname=easy_phppwn #php7.2.24 |
执行完后就会在 ext
文件夹里产生一个 easy_phppwn
文件夹
在 easy_phppwn
文件夹内查看 easy_phppwn.c
,初始代码 (这里是 php8.1.12 的,在 php7.2.24 就不会有 test 函数):
/* easy_phppwn extension for PHP */
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "php.h"
#include "ext/standard/info.h"
#include "php_easy_phppwn.h"
#include "easy_phppwn_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", "easy_phppwn");
}
/* }}} */
/* {{{ 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_OPTIONAL
Z_PARAM_STRING(var, var_len)
ZEND_PARSE_PARAMETERS_END();
retval = strpprintf(0, "Hello %s", var);
RETURN_STR(retval);
}
/* }}}*/
/* {{{ PHP_RINIT_FUNCTION */
PHP_RINIT_FUNCTION(easy_phppwn)
{
#if defined(ZTS) && defined(COMPILE_DL_EASY_PHPPWN)
ZEND_TSRMLS_CACHE_UPDATE();
#endif
return SUCCESS;
}
/* }}} */
/* {{{ PHP_MINFO_FUNCTION */
PHP_MINFO_FUNCTION(easy_phppwn)
{
php_info_print_table_start();
php_info_print_table_header(2, "easy_phppwn support", "enabled");
php_info_print_table_end();
}
/* }}} */
/* {{{ easy_phppwn_module_entry */
zend_module_entry easy_phppwn_module_entry = {
STANDARD_MODULE_HEADER,
"easy_phppwn", /* Extension name */
ext_functions, /* zend_function_entry */
NULL, /* PHP_MINIT - Module initialization */
NULL, /* PHP_MSHUTDOWN - Module shutdown */
PHP_RINIT(easy_phppwn), /* PHP_RINIT - Request initialization */
NULL, /* PHP_RSHUTDOWN - Request shutdown */
PHP_MINFO(easy_phppwn), /* PHP_MINFO - Module info */
PHP_EASY_PHPPWN_VERSION, /* Version */
STANDARD_MODULE_PROPERTIES
};
/* }}} */
#ifdef COMPILE_DL_EASY_PHPPWN
# ifdef ZTS
ZEND_TSRMLS_CACHE_DEFINE()
# endif
ZEND_GET_MODULE(easy_phppwn)
#endif
我们只需要在该模板代码中,加上属于我们自己的函数,以及为函数进行注册,即可完成编写一个拓展模块;添加自己的函数 (在生成的模板函数中,添加一个如下所示的拓展函数,其含有一个简单的栈溢出漏洞):
PHP_FUNCTION(easy_phppwn) | |
{ | |
char *arg = NULL; | |
size_t arg_len, len; | |
char buf[100]; | |
if(zend_parse_parameters(ZEND_NUM_ARGS(), "s", &arg, &arg_len) == FAILURE){ | |
return; | |
} | |
memcpy(buf, arg, arg_len); | |
php_printf("The baby phppwn.\n"); | |
return SUCCESS; | |
} |
其中 PHP_FUNCTION
修饰的函数表示该函数可以直接在 php
中进行调用。而 zend_parse_parameters
函数是获取用户输入的参数。 arg
代表参数字符, arg_len
代表参数长度
然后,需要在 zend_function_entry easy_phppwn_functions
中对该函数进行注册,(也就是配置该拓展函数,将需要在 php 中调用的函数指针写到一个统一的数组中)
const zend_function_entry easy_phppwn_functions[] = { | |
PHP_FE(confirm_easy_phppwn_compiled, NULL) /* For testing, remove later. */ | |
PHP_FE(easy_phppwn,NULL) // 只需要添加这一行就行,在这里进行注册,然后注册个刚刚写的函数 | |
PHP_FE_END /* Must be the last line in webpwn_functions[] */ | |
}; |
接着配置编译
/usr/bin/phpize | |
./configure --with-php-config=/usr/bin/php-config |
然后在生成的 Makefile 文件中,在如下位置设置编译参数,记得取消 -O2
优化,否则会加上 FORTIFY
保护,导致 memcpy
函数加上长度检查变为 __memcpy_chk
函数
在第 25 行(php7.2.24 在 27 行):
CFLAGS = -g -O2
去掉这个 -O2
可以使用 make
指令编译生成拓展模块,新生成的拓展模块会被放在同目录下 ./modules
中
然后将新生成的拓展模块放置到 本地 php 所在的拓展库路径下,使用如下命令,查找本地 php 拓展库路径:
php -i | grep -i extension_dir |
将新生成的 拓展模块放置在系统 php
的 拓展模块路径内
mv easy_phppwn.so /usr/lib/php/20210902/ |
随后找到系统 php.ini
路径 php -i | grep -i php.ini
:
在 php.ini 文件里添加自行编写的拓展库名称即可,直接在文件末尾添加如下代码:
extension=easy_phppwn.so
完成之后,可以尝试写一个 test.php 文件,在其中调用 phpinfo () 函数,然后可以看到 easy_phppwn 已经被加载到系统中
<?php | |
phpinfo(); | |
?> |
执行 php test.php | grep easy_phppwn
# 3.php 拓展模块调试
可以先用 gdb 看一下(这里不能进行动态调试):
想要动态调试,需要对 php
进行调试(需要在根目录下),通过 gdb php
,然后可以进行下断点,这里要调试 easy_phppwn
就对函数 zif_easy_phppwn
下断点,然后 run 即可
将得到的 php 拓展文件( .so
文件)利用 ida
打开 ,找到 zif_easy_phppwn
函数,这里重点分析该函数
# php8.1.12 中:
【在 php8.1.12 中少了 memcpy 函数,这就导致无法溢出。。。。】
gdb 查看 (也没有 T^T):
# php7.2.24 下:
编写一个 php 文件 (test.php) 调用 easy_phppwn函数
试一下:
<?php | |
$a="11223"; | |
easy_phppwn($a); | |
?> |
php test.php
运行:
可以看到输出了 The baby phppwn
运行成功了,而上面一行说已经加载了,可能是之前编译了好几次导致的