PHP7.0.0格式化字符串漏洞与EIP劫持分析

2016-10-17 08:15:00 作者:红黑联盟 分类 : 比特网

  PHP7.0.0的这个格式化字符串漏洞是15年12月在exploit-db上发现的。当初发现时,笔者还在北京东北方向的某信息安全公司上班,那时比较忙,并未能深入探究。最近几天无意间又看到了这个漏洞,发现该漏洞多了一个CVE编号:CVE-2015-8617,于是深入地看了看这个漏洞,在这里对该格式化字符串漏洞进行一些简要分析,并讨论一下利用该漏洞劫持EIP的潜在方法,供各位读者参考。

  1.引言

  在PHP中有两个常见的格式化字符串函数,分别是sppintf()和vsppintf(),它们分别对应sprintf()函数和vsprintf()函数,这两个函数的声明为:

  PHPAPI int spprintf( char **pbuf, size_t max_len, const char *format, ...);

  PHPAPI int vspprintf(char **pbuf, size_t max_len, const char *format, va_list ap);

  通过其函数声明可以看到,spprintf()接收可变数量的参数,而vspprintf()仅接收4个参数。

  虽然这两个函数的内部实现原理是类似的,但笔者不打算就此点进行深入讨论,如有感兴趣读者,可以看一看《程序员的自我修养》一书。关于格式化字符串漏洞的分析文章普遍集中于sprintf()函数,而在本文中则需要重点讨论一下vsprintf()函数,即着重讨论下PHP中的vspprintf()函数。

  2.漏洞分析

  本文所研究的vspprintf()函数在zend_throw_error()函数中,当触发漏洞时,zend_throw_error()函数由zend_throw_or_error()函数调用。zend_throw_or_error()函数不是很长,所以复制其代码如下:

  static void zend_throw_or_error(int fetch_type, zend_class_entry *exception_ce, const char *format, ...)

  { va_list va; char *message = NULL; va_start(va, format); zend_vspprintf(&message, 0, format, va); if (fetch_type & ZEND_FETCH_CLASS_EXCEPTION) { zend_throw_error(exception_ce, message); //vul_func //zend_throw_error(exception_ce, "%s", message); patched in the subsequent version } else { zend_error(E_ERROR, "%s", message); } efree(message); va_end(va); }

  在上述代码段中,触发漏洞的函数调用已用红色笔标明出,由于调用时少了一个参数导致触发了格式化字符串漏洞。该漏洞的补丁也用红色笔在代码中标明了。

  关于该格式化字符串漏洞,并没有很多需要分析说明的地方,下面开始分别从windows和linux两个环境中讨论利用该漏洞劫持EIP的方法。

  3.windows环境下分析

  为了减少在win7环境下的分析难度,笔者暂且把ASLR关掉。若计划实现稳定的EIP劫持,可能还需要通过其他手段获取一些模块基址,当然这PHP7.0.0格式化字符串漏洞本身也可以泄露一部分有用的内存数据

  在windows版本的PHP中,其漏洞函数位于php7ts.dll动态链接库中,构造php页面如下:

  $name="%n%n"; $name::doSomething(); ?>

  通过调试器启动PHP解析该php页面,执行到程序崩溃时,通过栈回溯,可以找到vspprintf()函数调用(该函数是导出函数,也可以直接在导出表中找到此函数),在该函数的函数头下断点,重新执行,找到即将触发漏洞的某次调用。此时,观察栈中的数据:

\

  上图中,栈顶是函数返回地址,即返回到zend_throw_error()函数中,接下来的是vspprintf()函数的四个参数。其中,0441E890即为va_list类型的参数。

  这里需要指出的是,如果是传统的spprintf()函数的格式化字符串溢出,则只需要不断地利用%x递增栈上参数数量,最后利用%n实现覆盖函数返回地址即可有效地实现劫持EIP。但是此处是vspprintf()函数的,只接受4个参数,所以如果打算继续劫持EIP,则需要研究一下va_list,va_list在不同环境下的定义略有不同,这里我们可以粗略地定义va_list类型如下:

  #define va_list void*

  即认为va_list是一个指向可变数量参数的指针。在vspprintf()函数中,对于%x的处理是直接取va_list指向的内容,如下图:

\

  其中,0441E890即为va_list的起始地址,通过图1的第四个参数可以观察到。对于第一个%x,则输出0565D3C0;对于第二个%x,则输出96E436E2;对于第三个%x,则输出0441E8C4,以此类推下去。

  在vspprintf()函数中,对于%n的处理则较为麻烦,它不会像%x那样直接依次地读写下去,而是取va_list指向的参数表的每个参数作为指针,进而覆盖该指针所指向的内容。结合图2,具体叙述如下:对于第一个%n,则覆盖0565D3C0所指向的内容,对于第二个%n,则覆盖96E436E2所指向的内容,此时PHP就崩溃了,因为该地址是无效的。

  此时,是无法直接覆盖函数的返回地址。为实现劫持EIP的目的,需要在栈上找一个二级指针。该二级指针取值第一次为保存函数返回地址变量的地址,取值两次为函数返回地址变量的值。但笔者在栈上并没有找到所需的二级指针,所以,笔者只能选择构造一个这样子的指针,其构造方法如下:

  1,首先在栈上选择一个合适位置,该位置存储内容指向栈的另一个位置,指向位置大于且接近该位置的地址。

  复制部分栈内容如下:

  0441E890 0565D3C0

  0441E894 96E436E2

  0441E898 0441E8C4

  正如上表所示,0441E8C4就是4字节对齐的,大于且接近0441E898,是一个非常合适的栈位置。

  2,通过上一步找到的合适位置,覆盖0441E8C4的内容,使其指向栈上保存函数返回地址的地址。

  在笔者调试时,将其覆盖为0441E82C,即当前函数返回到vspprintf()函数的返回地址:

\

  3,第一次覆盖之后,用%x继续在栈上滑行,直到0441E8C4的位置,此时将会第二次覆盖0441E82C的内容,使其指向我们需要跳转的位置,比方说跳转到04422222的位置。

  按照上述思路,其栈空间的内容大致如下:

  …

  0441E824 96E40112

  0441E828 96E43659

  0441E82C 04422222

  基于此,笔者尝试构造php页面如下:

  $name="%71428125x%x%n%x%x%x%x%x%x%x%x%x%14788x%n"; $name::doSomething(); ?>

  当PHP解析该页面的时候,首先输出2个%x后,遇到第一个%n,则会覆盖0441E8C4覆盖为0441E82C;继续跳过10个%x后,遇到第二个%n,则会覆盖0441E82C覆盖为04422222。

  其运行结果如下图所示:

\

  单步执行后,就会来到04422222的位置:

\

  Windows环境下的分析就到此位置,至于出现的几个常数:71428125和14788以及10个%x从何而来,相信读者自己也能想到。至于是否可以在栈上构造一些合适的数据,最后通过ROP实现EXP,这点也留给读者自己考虑分析一下吧。

  4.Linux环境下分析

  Linux环境下,同样先把ASLR关掉,用以减少我们的分析难度。与Windows环境下的分析略有不同,由于Linux环境下的栈基址比较高,如下图所示:

\

  声明一个如此之长的字符串,容易出现各种各样的问题,所以笔者只好放弃直接覆盖函数返回地址实现劫持EIP的方法。

  这里考虑另一种劫持EIP的方法,覆盖对象虚表的方法(一般情况下有三种常见的方法,在笔者之前的分析《kill.exe溢出漏洞分析与EXP讨论》中有提到,感兴趣的读者可以看一下)。构造合适的php页面,令PHP不崩溃,而是让其继续下去的话,就会发现PHP接下来将要调用_object_init_ex()函数,初始化异常对象。该初始化函数会进一步调用object_and_properties_init()函数,而在此函数中,会调用对象虚表中的函数,关键代码段如下:

  object_and_properties_init() { … mov ebx, [esp+0Ch+class_type] … mov eax, [ebx+0FCh] … call eax ; call [[esp+0Ch+class_type]+0FCh] … }

  考虑到此时存储在[esp+0Ch+class_type]+0FCh的值比较小,可以尝试利用此处的call eax实现劫持EIP。

  选择在第3章节描述的二次覆盖方法,可以构造栈空间如下:

  …

  08948F5C 08945D4C

  08948F60 08945D50

  08948F64 08955555

  基于以上讨论,笔者构造php页面如下:

  ini_set("memory_limit", "2G"); $name="%143953757x%n%x%x%x%x%x%x%x%x%x%x%50621x%n"; $name::doSomething(); ?>

  当PHP在解析该页时,第一次遇到%n将会覆盖8FFFBFCC位置的数据为08948F64;而第二次遇到%n时,将08948F4位置的数据覆盖为08955555。此后,程序会正常执行,直到call eax指令的位置:

\

  此时,PHP将跳转到我们指定的地址继续执行,在上图中为8955555地址。

  值得庆幸的是,在Linux环境中,并没有Windows环境的CFG保护。如果存在CFG保护,即有/GUARD:CF标记,将可能导致此种利用方式失败。

  Linux环境下的分析也就到此位置,至于出现的几个常数:143953757和50621以及11个%x从何而来,相信读者自己也能想到。至于是否可以实现有效的EXP,这点也留给读者自己考虑分析一下吧。

  5.小结

  本文简要地分析了PHP7.0.0格式化字符串漏洞,并在windows和linux两种不同的环境下,给出了运用该漏洞劫持EIP的方法。但需要指出的是,本文所有的分析都在禁用了ASLR的场景之下进行的,若打算实际利用该漏洞,还需要获取一些模块基址等其他有用信息

最近更新
科普

科普图集
从《中国互联网+指数报告(2018)》看数字经济

从《中国互联网+指数报告(2018)》看数字经济>>详情

“互联网+”的这些新变化,你知道吗?

“互联网+”的这些新变化,你知道吗?>>详情

邮件订阅

软件信息化周刊
比特软件信息化周刊提供以数据库、操作系统和管理软件为重点的全面软件信息化产业热点、应用方案推荐、实用技巧分享等。以最新的软件资讯,最新的软件技巧,最新的软件与服务业内动态来为IT用户找到软捷径。
商务办公周刊
比特商务周刊是一个及行业资讯、深度分析、企业导购等为一体的综合性周刊。其中,与中国计量科学研究院合力打造的比特实验室可以为商业用户提供最权威的采购指南。是企业用户不可缺少的智选周刊!
网络周刊
比特网络周刊向企业网管员以及网络技术和产品使用者提供关于网络产业动态、技术热点、组网、建网、网络管理、网络运维等最新技术和实用技巧,帮助网管答疑解惑,成为网管好帮手。
服务器周刊
比特服务器周刊作为比特网的重点频道之一,主要关注x86服务器,RISC架构服务器以及高性能计算机行业的产品及发展动态。通过最独到的编辑观点和业界动态分析,让您第一时间了解服务器行业的趋势。
存储周刊
比特存储周刊长期以来,为读者提供企业存储领域高质量的原创内容,及时、全面的资讯、技术、方案以及案例文章,力求成为业界领先的存储媒体。比特存储周刊始终致力于用户的企业信息化建设、存储业务、数据保护与容灾构建以及数据管理部署等方面服务。
安全周刊
比特安全周刊通过专业的信息安全内容建设,为企业级用户打造最具商业价值的信息沟通平台,并为安全厂商提供多层面、多维度的媒体宣传手段。与其他同类网站信息安全内容相比,比特安全周刊运作模式更加独立,对信息安全界的动态新闻更新更快。
新闻中心热点推荐
新闻中心以独特视角精选一周内最具影响力的行业重大事件或圈内精彩故事,为企业级用户打造重点突出,可读性强,商业价值高的信息共享平台;同时为互联网、IT业界及通信厂商提供一条精准快捷,渗透力强,覆盖面广的媒体传播途径。
云计算周刊
比特云计算周刊关注云计算产业热点技术应用与趋势发展,全方位报道云计算领域最新动态。为用户与企业架设起沟通交流平台。包括IaaS、PaaS、SaaS各种不同的服务类型以及相关的安全与管理内容介绍。
CIO俱乐部周刊
比特CIO俱乐部周刊以大量高端CIO沙龙或专题研讨会以及对明星CIO的深入采访为依托,汇聚中国500强CIO的集体智慧。旨为中国杰出的CIO提供一个良好的互融互通 、促进交流的平台,并持续提供丰富的资讯和服务,探讨信息化建设,推动中国信息化发展引领CIO未来职业发展。
IT专家网
IT专家新闻邮件长期以来,以定向、分众、整合的商业模式,为企业IT专业人士以及IT系统采购决策者提供高质量的原创内容,包括IT新闻、评论、专家答疑、技巧和白皮书。此外,IT专家网还为读者提供包括咨询、社区、论坛、线下会议、读者沙龙等多种服务。
X周刊
X周刊是一份IT人的技术娱乐周刊,给用户实时传递I最新T资讯、IT段子、技术技巧、畅销书籍,同时用户还能参与我们推荐的互动游戏,给广大的IT技术人士忙碌工作之余带来轻松休闲一刻。