찾다
백엔드 개발PHP 튜토리얼PHP中的内存破坏漏洞利用(CVE-2014-8142和CVE-2015-0231)(连载之第三篇)

0x00 前言

  • 作者: Cigital公司的安全顾问Qsl1pknotp
  • 题目: Exploiting memory corruption bugs in PHP Part 3: Popping Remote Shells
  • 地址: http://www.inulledmyself.com/2015/05/exploiting-memory-corruption-bugs-in.html

这片文章所花的时间比我想象中的要长, 不过这时间花得值! 我想通过视频的方式来讲解如何利用这漏洞, 所以这片文章没有之前两篇描述得详细.

可能会让一些人失望的是, 这篇文章只是介绍了如何写一个 POC (并没有实际放出 POC). 文章末尾的视频会给你们展示我的自动化远程利用工具, 以及在现实环境中为了能让这 POC 成功执行所使用的 tips & trick.

0x01 查找数据

为了能更简单的解释如何利用这个漏洞, 我使用下面的代码作为演示, 具体情况具体分析.

#!php<?phpecho serialize(unserialize(base64_decode($_GET['data'])));?>

我们的目的是能够执行任意 PHP 代码. 当然啦, 我们可以尝试去注入 shellcode 来达到目的, 但是这种方法既没有创造性, 也不优雅(高版本的 PHP 可能不会成功). 如果你还记得Part1, 为了能够执行任意 PHP 代码, 我们需要调用 php_execute_script 和 zend_eval_string . 然而, 我们希望能够进行远程攻击, 所以我们必须找到 executor_globals 以及 JMP_BUF . 至于为什么要这样做, 后面会详细介绍.

简而言之, 我们需要找到 (没有特别的顺序):

  • executor_globals
  • zend_eval_string
  • JMP_BUF
  • 将任意数据写入 stack 的方法

比较幸运的是, 上面所列举的要求, 有些还是比较好找的, 因为它们就在 binary 中. 我们直接 dump PHP binay 的 strtab.

Great! 我们直接从里面找到 zend_eval_string 的地址, 然后在 GDB 中验证这个地址是否正确.

  1. 查找 zend_eval_string 的地址

  2. 在 gdb 中查看对应的地址

  3. 查找 executor_global 的地址

  4. 在 gdb 中查看对应的地址

Awesome! 现在我们要怎么找到 JMP_BUF 呢? 通过阅读代码, 我们找到了 _zend_executor_globals 对象, 并在其中发现了一个 JMP_BUF 指针, 名为 bailout . 让我们挂上 GDB 看看地址是否正确.

  1. 查看 zend_executor_globals 对象

  2. 打印 zend_executor_globals->bailout

好, 我们拿到了这个地址, 但是这个地址指向的是什么? 有什么用? 好吧, 在 PHP 中 JMP_BUF 被用来实现 PHP 的 "try{} - catch{}" 机制. 后面会详细介绍这个.

0x02 利用方法 1 - ROP

我们现在还只差一样东西: 将任意数据写入 stack. 利用方法 2 会详细讨论 Stefan 在 2010 Syscan 公布的方法. 既然我们可以释放任意内存, 那我们下一步该干什么呢? 我们该怎么写数据到 stack 中? 怎么确保写入的数据以后不会被覆盖? (Google is your friend :))

RFC, 更确切的说是: RFC 1867

这个 RFC 指明允许带有 multipart/form-data 的 POST 请求的数据写入到 stack 中, 而且完全不会被 php 覆盖(由于各种各样的原因). 让我们上传一个"普通"的文件吧.

Awesome! 我们可以写入任意数据到 stack 中. 但是我们要写什么到 stack 中?

Hint: 我们之前所寻找的东西 : )

既然之前我们花了那么多时间去找那么多地址, 该时候使用它们了! 所以, 我们应该如何使用之前的地址呢? 经过一些研究, 我们需要这样布局:

从最简单的开始: 一开始我们就用 readelf 获取到了 zend_eval_string 的地址. Ret Pointer 和 Zend_Bailout 我们都不需要理会(会导致 PHP crash). 我们在 stack 中填写两次指向 eval_string 的指针, 下面就是我们现有的数据:

  • POP; RET - ?????
  • XCHG EAX, ESP; RET - ????
  • Zend_Eval_String - 0x082da150
  • Zend_Bailout - 0x00000000
  • Pointer_To_Eval_String - 0xbfffda04
  • Ret Pointer - 0x00000000
  • Pointer_To_Eval_String - 0xbfffda04

Sweet! 我们快填写完这些东西了, excellent! 但是, 看起来我们还需要一些 ROP gadgets. 我个人比较喜欢用 ROPGadget, 不过其它工具也是可以的. 我们需要查找 XCHG EAX, ESP; RET (0x94 0xc3), 还需要找 POP EBP; RET (0x5d 0x3c). 一旦找到了这些 gadgets, 我们就可以继续下一步了.

我们拿到了这两个地址了(为啥这些地址相差那么大, 因为它们是相对地址), 我们可以继续完成 stack 中的数据:

  • POP; RET - 0x000e8e68
  • XCHG EAX, ESP; RET - 0x000057b7
  • Zend_Eval_String - 0x082da150
  • Zend_Bailout - 0x00000000
  • Pointer_To_Eval_String - 0xbfffda04
  • Ret Pointer - 0x00000000
  • Pointer_To_Eval_String - 0xbfffda04

好了, 该是时候测试了.

Hmmm, 这不是我想要的结果, 现在怎么办? 看起来好像我们的代码尝试跳到我们的 gadget (c394). 不幸的是, 你还需要知道一些事情. SPLObjectStorage 要求这些 gadget 在 php 是可以访问的, 所以我们还需要修改一下. 经过修改之后:

0x03 利用方法 2 - Stefan

方法 1 就到此为止了, 方法 1 只能影响老版本的 PHP. 我们继续研究新版本的 PHP 利用方法.

比较走运的是, 之前找到 php_execute_script 和 jmp_buf 地址, 在新 exploit 中都会被用到.

jmp_buf 在 setjmp & longjmp 中被用来保存 "环境" 以预防 "不可恢复" 的错误. 在 32 位系统中, jmp_buf 是一个存储 6 个 int 的数组, 在 64 位系统中, jmp_buf 存储的是 8 个 int 的数组. 不幸的是, 需要自己查看代码来判断 jmp_buf 保存的寄存器的顺序. 这里有个 jmp_buf 样例布局. 让我们看一下 PHP 中的内容...

在我的机器上, 寄存器的顺序是: ebx, esp, ebp, esi, edi, eip. 值得完成的事情一般都不怎么容易完成, 在这里也一样, 我们的 edi & eip 看起来貌似被 Glibc 混淆了, Glibc 有个宏叫 PTR_MANGLE , 在视频中, 我们会讲解如何破解 JMPBUF.

一旦破解出了 edi & eip, 我们就可以继续重写和释放内存了. 幸运的是, 我们可以继续利用 SPLObjectStorage 远程释放内存. 剩下的事情就是将如何写到 stack 中. 和Part 2, 我们可以任意操纵 PHP 内存. 我们先释放一些内存, 然后再写 7 byte 数据填充, 当 php 重写我们的数据时, 再重复之前的操作. 第二次重写能够让我们写入任意长度的数据到 stack 中 (我测试的时候, 这个长度大概可以达到 2048 byte). 我们写入的数据和之前使用 ROP 的那个例子差不多. 我们还要继续 "加密" 我们写入 stack 中的数据. 这是攻击效果:

0x04 视频地址

video , 自备梯子

视频笔记

  • x86 Instruction Chart - http://sparksandflames.com/files/x86InstructionChart.html
  • Elf Header lowest 3 bits are 000
  • Elf layout - http://geezer.osdevbrasil.net/osd/exec/elf.txt
  • PMAP is your friend when trying to find the "Magic"
  • A look at PTR_MANGLE http://hmarco.org/bugs/CVE-2013-4788.html

0x05 译者总结

就和作者说的一样, 这篇文章没有之前两篇写得详细.

1. 作者那个 PHP binary 文件从哪来的?

原文下有评论, 作者说他通过 memory leak 获取了整个 php binary 文件.

正常情况下, 一般的套路就是:

  1. 查找 ELF magic header \x7fELF 找到起始地址
  2. 通过 strtab , symtab 找到 zend_eval_string , php_execute_script , executor_globals 地址.

2. jmpbuf 是什么?

jmpbuf 是 setjmp, longjmp 所使用的数据结构, 以实现 try--catch 机制的东西, 和 goto 语法效果差不多, setjmp 相当于在某个位置的 label, longjmp 相当于 goto, 但是 goto 语法并不能跨函数跳转. jmpbuf 主要保存着 caller 的寄存器信息以方便 longjmp 恢复. 另外 glibc 会混淆一些寄存器的值(除了有漏洞的 glibc ).

3. 如何解出 jmpbuf?

先查看 setjmp 代码:

#!bash(gdb) disassemble setjmpDump of assembler code for function setjmp:   0xb7c94410 <+0>: mov    eax,DWORD PTR [esp+0x4]   0xb7c94414 <+4>: mov    DWORD PTR [eax],ebx        # 1. 保存 ebx   0xb7c94416 <+6>: mov    DWORD PTR [eax+0x4],esi    # 2. 保存 esi   0xb7c94419 <+9>: mov    DWORD PTR [eax+0x8],edi    # 3. 保存 edi   0xb7c9441c <+12>:    lea    ecx,[esp+0x4]   0xb7c94420 <+16>:    xor    ecx,DWORD PTR gs:0x18   0xb7c94427 <+23>:    rol    ecx,0x9   0xb7c9442a <+26>:    mov    DWORD PTR [eax+0x10],ecx # 4. 保存 esp   0xb7c9442d <+29>:    mov    ecx,DWORD PTR [esp]   0xb7c94430 <+32>:    xor    ecx,DWORD PTR gs:0x18   0xb7c94437 <+39>:    rol    ecx,0x9                     0xb7c9443a <+42>:    mov    DWORD PTR [eax+0x14],ecx # 5. 保存 eip   0xb7c9443d <+45>:    mov    DWORD PTR [eax+0xc],ebp  # 6. 保存 ebp   0xb7c94440 <+48>:    push   0x1   0xb7c94442 <+50>:    push   DWORD PTR [esp+0x8]   0xb7c94446 <+54>:    call   0xb7c943c0 <__sigjmp_save>   0xb7c9444b <+59>:    pop    ecx   0xb7c9444c <+60>:    pop    edx   0xb7c9444d <+61>:    ret

上面的寄存器保存的都是 caller 的寄存器状态, 其中 esp, eip 都被混淆过了(作者自己的图也是 esp 和 eip 被混淆), 就是使用 PTR_MANGLE 进行混淆.

PTR_MANGLE 和 PTR_DEMANGLE 宏定义如下:

#!cpp#  define PTR_MANGLE(reg)   xorl %gs:POINTER_GUARD, reg;              \                 roll $9, reg#  define PTR_DEMANGLE(reg) rorl $9, reg;                     \                 xorl %gs:POINTER_GUARD, reg

其中 gs:0x18 就是上面的 POINTER_GUARD

setjmp() 使用 PTR_MANGLE 进行混淆寄存器, longjmp() 使用 PTR_DEMANGLE 解出正常的寄存器. 为了后续能过正常覆盖 jmpbuf, 所以我们需要获得 POINTER_GUARD 的值, 由于 jmpbuf 数据结构可以越界读, caller 的 eip 也可以拿到, 所以通过 PTR_DEMANGLE 就可以获得 POINTER_GUARD 的值.

4. 如何获取到 setjmp caller 的 eip ?

通过阅读代码, 我们可以知道 php_execute_script 调用了 setjmp, 并将 jmpbuf 保存到 EG(bailout) 中, 通过泄漏 php_execute_script 地址 即可知道调用 setjmp 时到 eip.

5. 如何覆盖到 jmpbuf ?

jmpbuf 地址向前搜索数值 XX 00 00 00 (XX>0x0c and XX

先看看 ZMM 的几个结构体:

#!cpp/* mm block type */typedef struct _zend_mm_block_info {    size_t _size;    size_t _prev;} zend_mm_block_info;

.

#!cpptypedef struct _zend_mm_free_block {    zend_mm_block_info info;    struct _zend_mm_free_block *prev_free_block;    struct _zend_mm_free_block *next_free_block;    struct _zend_mm_free_block **parent;    struct _zend_mm_free_block *child[2];} zend_mm_free_block;

.

#!cppstruct _zend_mm_heap {    int                 use_zend_alloc;    void               *(*_malloc)(size_t);    void                (*_free)(void*);    void               *(*_realloc)(void*, size_t);    size_t              free_bitmap;    size_t              large_free_bitmap;    size_t              block_size;    size_t              compact_size;    zend_mm_segment    *segments_list;    zend_mm_storage    *storage;    size_t              real_size;    size_t              real_peak;    size_t              limit;    size_t              size;    size_t              peak;    size_t              reserve_size;    void               *reserve;    int                 overflow;    int                 internal;#if ZEND_MM_CACHE    unsigned int        cached;    zend_mm_free_block *cache[ZEND_MM_NUM_BUCKETS];#endif    zend_mm_free_block *free_buckets[ZEND_MM_NUM_BUCKETS*2];    zend_mm_free_block *large_free_buckets[ZEND_MM_NUM_BUCKETS];    zend_mm_free_block *rest_buckets[2];    int                 rest_count;};

我们需要关注的是 _zend_mm_heap 中的 cached. ZMM 会将 0x10 大小的内存块放进 cached 中, 所以当我们找到一个可以当做 memory block 之后, 最后几个字节(7 byte 数据)伪造一个 memory header (_zend_mm_block_info), 然后再用 string 重用这个伪造后的 memory block, 如果写入的长度不足以覆盖 jmpbuf, 继续伪造 memory header 相关的操作, 直到能够覆盖 jmpbuf 为止.

6. 能够覆盖 jmpbuf 之后 ?

将 eip 设置为 zend_eval_string , 将 esp 设置为一个直接可控的 stack(比如说 jmpbuf 之后), 填充好 jmpbuf, 该混淆的寄存器继续混淆. 然后在这个可控的 stack 上设置好 zend_eval_string 的参数, zend_eval_string 的定义如下:

#!cZEND_API int zend_eval_string(char *str, zval *retval_ptr, char *string_name TSRMLS_DC)

最后触发一个 exception, 即可执行我们想要的代码.

7. PHP7 的变化 ?

php7 的 zval 格式有很大的变化, 通过字符串数据覆盖 zval 结构没法再做到读取任意地址数据了, 只能向后读取数据(drops 这篇文章的作者 libnex 说他有办法, 期待新文章).

#!cstruct _zval_struct {    zend_value        value;            /* value */    union {        struct {            ZEND_ENDIAN_LOHI_4(                zend_uchar    type,         /* active type */                zend_uchar    type_flags,                zend_uchar    const_flags,                zend_uchar    reserved)     /* call info for EX(This) */        } v;        uint32_t type_info;    } u1;    union {        uint32_t     var_flags;        uint32_t     next;                 /* hash collision chain */        uint32_t     cache_slot;           /* literal cache slot */        uint32_t     lineno;               /* line number (for ast nodes) */        uint32_t     num_args;             /* arguments number for EX(This) */        uint32_t     fe_pos;               /* foreach position */        uint32_t     fe_iter_idx;          /* foreach iterator index */    } u2;};

.

#!cstruct _zend_string {    zend_refcounted_h gc;    zend_ulong        h;                /* hash value */    size_t            len;    char              val[1];};

如果通过数据去覆盖 zval_struct , 只能通过修改 len 来实现向后读取.

总结 exploit 利用步骤

  1. 利用 part 2 介绍的方法可以泄漏 std_object_handlers 信息, 随便找一个数值较小的地址
  2. 利用 part 2 介绍的任意地址读取的方法向前读取数据, 直到出现 \x7FELF .
  3. 通过 strtab, symtab 可以泄漏 zend_eval_string , php_execute_script , executor_globals (作者图省事, 文章直接本地 readelf)
  4. 通过 excutor_globals 可以拿到 bailout 地址 (也就是 jmpbuf 地址)
  5. 通过 php_execute_script 获取到调用 setjmp 时的 eip
  6. 获取到了 setjmp caller 的 eip, 再获取到 jmpbuf 地址中 eip 混淆后的值, 通过 PTR_DEMANGLE 即可获得 POINTER_GUARD 的值.
  7. 通过反复释放重用内存, 直到能过覆盖 jmpbuf
  8. 将 zend_eval_string 的地址与之前的 POINTER_GUARD 进行 PTR_MANGLE 写入到 jmpbuf 的 eip 中.
  9. 将 esp 设置为一个我们可写的 stack 范围, 比如说 jmpbuf 之后的内存, 进行 PTR_MANGLE 之后写入到 jmpbuf 的 esp 中.
  10. 在刚刚能覆盖 jmpbuf 的内存块后面依次写入 返回地址, php 代码地址, php 函数名, php 结果返回地址, php 文件名, php 代码.
  11. 触发一个 exception.

如果还有疑惑的地方, 可以去看看作者的视频以及树人的 paper. 如果我补充的有不正确的地方, 请不吝赐教.

성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
PHP의 지속적인 사용 : 지구력의 이유PHP의 지속적인 사용 : 지구력의 이유Apr 19, 2025 am 12:23 AM

여전히 인기있는 것은 사용 편의성, 유연성 및 강력한 생태계입니다. 1) 사용 편의성과 간단한 구문은 초보자에게 첫 번째 선택입니다. 2) 웹 개발, HTTP 요청 및 데이터베이스와의 우수한 상호 작용과 밀접하게 통합되었습니다. 3) 거대한 생태계는 풍부한 도구와 라이브러리를 제공합니다. 4) 활성 커뮤니티와 오픈 소스 자연은 새로운 요구와 기술 동향에 맞게 조정됩니다.

PHP 및 Python : 유사점과 차이점을 탐구합니다PHP 및 Python : 유사점과 차이점을 탐구합니다Apr 19, 2025 am 12:21 AM

PHP와 Python은 웹 개발, 데이터 처리 및 자동화 작업에 널리 사용되는 고급 프로그래밍 언어입니다. 1.PHP는 종종 동적 웹 사이트 및 컨텐츠 관리 시스템을 구축하는 데 사용되며 Python은 종종 웹 프레임 워크 및 데이터 과학을 구축하는 데 사용됩니다. 2.PHP는 Echo를 사용하여 콘텐츠를 출력하고 Python은 인쇄를 사용합니다. 3. 객체 지향 프로그래밍을 지원하지만 구문과 키워드는 다릅니다. 4. PHP는 약한 유형 변환을 지원하는 반면, 파이썬은 더 엄격합니다. 5. PHP 성능 최적화에는 Opcache 및 비동기 프로그래밍 사용이 포함되며 Python은 Cprofile 및 비동기 프로그래밍을 사용합니다.

PHP와 Python : 다른 패러다임이 설명되었습니다PHP와 Python : 다른 패러다임이 설명되었습니다Apr 18, 2025 am 12:26 AM

PHP는 주로 절차 적 프로그래밍이지만 객체 지향 프로그래밍 (OOP)도 지원합니다. Python은 OOP, 기능 및 절차 프로그래밍을 포함한 다양한 패러다임을 지원합니다. PHP는 웹 개발에 적합하며 Python은 데이터 분석 및 기계 학습과 같은 다양한 응용 프로그램에 적합합니다.

PHP와 Python : 그들의 역사에 깊은 다이빙PHP와 Python : 그들의 역사에 깊은 다이빙Apr 18, 2025 am 12:25 AM

PHP는 1994 년에 시작되었으며 Rasmuslerdorf에 의해 개발되었습니다. 원래 웹 사이트 방문자를 추적하는 데 사용되었으며 점차 서버 측 스크립팅 언어로 진화했으며 웹 개발에 널리 사용되었습니다. Python은 1980 년대 후반 Guidovan Rossum에 의해 개발되었으며 1991 년에 처음 출시되었습니다. 코드 가독성과 단순성을 강조하며 과학 컴퓨팅, 데이터 분석 및 기타 분야에 적합합니다.

PHP와 Python 중에서 선택 : 가이드PHP와 Python 중에서 선택 : 가이드Apr 18, 2025 am 12:24 AM

PHP는 웹 개발 및 빠른 프로토 타이핑에 적합하며 Python은 데이터 과학 및 기계 학습에 적합합니다. 1.PHP는 간단한 구문과 함께 동적 웹 개발에 사용되며 빠른 개발에 적합합니다. 2. Python은 간결한 구문을 가지고 있으며 여러 분야에 적합하며 강력한 라이브러리 생태계가 있습니다.

PHP 및 프레임 워크 : 언어 현대화PHP 및 프레임 워크 : 언어 현대화Apr 18, 2025 am 12:14 AM

PHP는 현대화 프로세스에서 많은 웹 사이트 및 응용 프로그램을 지원하고 프레임 워크를 통해 개발 요구에 적응하기 때문에 여전히 중요합니다. 1.PHP7은 성능을 향상시키고 새로운 기능을 소개합니다. 2. Laravel, Symfony 및 Codeigniter와 같은 현대 프레임 워크는 개발을 단순화하고 코드 품질을 향상시킵니다. 3. 성능 최적화 및 모범 사례는 응용 프로그램 효율성을 더욱 향상시킵니다.

PHP의 영향 : 웹 개발 및 그 이상PHP의 영향 : 웹 개발 및 그 이상Apr 18, 2025 am 12:10 AM

phphassignificallyimpactedwebdevelopmentandextendsbeyondit

스칼라 유형, 반환 유형, 노조 유형 및 무효 유형을 포함한 PHP 유형의 힌트 작업은 어떻게 작동합니까?스칼라 유형, 반환 유형, 노조 유형 및 무효 유형을 포함한 PHP 유형의 힌트 작업은 어떻게 작동합니까?Apr 17, 2025 am 12:25 AM

PHP 유형은 코드 품질과 가독성을 향상시키기위한 프롬프트입니다. 1) 스칼라 유형 팁 : PHP7.0이므로 int, float 등과 같은 기능 매개 변수에 기본 데이터 유형을 지정할 수 있습니다. 2) 반환 유형 프롬프트 : 기능 반환 값 유형의 일관성을 확인하십시오. 3) Union 유형 프롬프트 : PHP8.0이므로 기능 매개 변수 또는 반환 값에 여러 유형을 지정할 수 있습니다. 4) Nullable 유형 프롬프트 : NULL 값을 포함하고 널 값을 반환 할 수있는 기능을 포함 할 수 있습니다.

See all articles

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

AI Hentai Generator

AI Hentai Generator

AI Hentai를 무료로 생성하십시오.

뜨거운 도구

SecList

SecList

SecLists는 최고의 보안 테스터의 동반자입니다. 보안 평가 시 자주 사용되는 다양한 유형의 목록을 한 곳에 모아 놓은 것입니다. SecLists는 보안 테스터에게 필요할 수 있는 모든 목록을 편리하게 제공하여 보안 테스트를 더욱 효율적이고 생산적으로 만드는 데 도움이 됩니다. 목록 유형에는 사용자 이름, 비밀번호, URL, 퍼징 페이로드, 민감한 데이터 패턴, 웹 셸 등이 포함됩니다. 테스터는 이 저장소를 새로운 테스트 시스템으로 간단히 가져올 수 있으며 필요한 모든 유형의 목록에 액세스할 수 있습니다.

에디트플러스 중국어 크랙 버전

에디트플러스 중국어 크랙 버전

작은 크기, 구문 강조, 코드 프롬프트 기능을 지원하지 않음

스튜디오 13.0.1 보내기

스튜디오 13.0.1 보내기

강력한 PHP 통합 개발 환경

SublimeText3 영어 버전

SublimeText3 영어 버전

권장 사항: Win 버전, 코드 프롬프트 지원!

PhpStorm 맥 버전

PhpStorm 맥 버전

최신(2018.2.1) 전문 PHP 통합 개발 도구