# 1. 前置知识

# 2. 环境搭建

这里有个坑,在 php8.1.12 版本有些代码行不通,应该是版本问题,后来改成 php7.2.24 后就可以了,所以下面的配置最好按照 7.2.24 来进行

# 1.PHP 拓展安装

下面是是 PHP 拓展模块的安装流程,我这个是在 ubuntu22.04 下安装的

首先是安装 php,以及 php 开发包

h
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 文件)

h
./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

我们只需要在该模板代码中,加上属于我们自己的函数,以及为函数进行注册,即可完成编写一个拓展模块;添加自己的函数 (在生成的模板函数中,添加一个如下所示的拓展函数,其含有一个简单的栈溢出漏洞):

c
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 中调用的函数指针写到一个统一的数组中)

c
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[] */
};

接着配置编译

h
/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 拓展库路径:

h
php -i | grep -i extension_dir


将新生成的 拓展模块放置在系统 php 的 拓展模块路径内

h
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 已经被加载到系统中

p
<?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函数 试一下:

p
<?php
$a="11223";
easy_phppwn($a);
?>

php test.php 运行:

可以看到输出了 The baby phppwn 运行成功了,而上面一行说已经加载了,可能是之前编译了好几次导致的

# 调试以及编写 exp: