王宇:大家好,這是我們Pwn2Own的一個分享,可能今天的議題和ZDI的同事們會有很多重復的地方,我可能會更多的描述一些漏洞細節的內容。
其實我也是在比賽結束之后,第一時間去尋找漏洞的內容,尋找出了什么問題,今天算是對于我之前分析的一個總結。今年的Pwn2Own內核提權一共是五個,我排了一下順序,前兩個是我打算分析尹亮同學的一個內核的提權,一個信息泄漏,還有兩個來自360的提權。
第一個漏洞,出問題的函數我命名成不一致型的漏洞。我們先看看這個補丁的細節,左邊是補丁之前的內容,第一點是補丁刪除類型的,這個不是太多見,因為一般我們見到的補丁都是添加一部分的邏輯去修正之前的那些問題,而純粹的刪除去面對問題這樣的幾率不是很多。
我們發現在有問題的代碼里面,某一個結構,RCX+40偏移的位置做了一個判斷,基于這個判斷去做了一些Return值的一些選擇,可以Return0或者是1。給我們一定是兩個問題,第一個問題是RCX只是一個結構,RCX+40是一個什么問題?
這個漏洞本身,為什么刪除這個代碼這個漏洞就不存在了?隨著往后的研究,我們發現根據這個漏洞相關的結構叫做PFF,PFF是自己比較高層的一個結構。我們知道這個結構叫PFF之后,下一步一定是去找到它的結構的定義。
上哪里去找這些結構的定義呢?最左邊的是來自于比較靠譜的一份結構,這個結構偏移量是基于32位的,所以給出的注釋都是基于32位偏移。右邊是不太合法的,是來自于微軟泄漏的一個樣本,在座的各位可能是微軟的前雇員,所以我們很多就不展開怎么去弄這些東西。
但是實際上這個代碼的參考價值不是太大,因為這個代碼的時間非常久遠。但是我們可以看到這么幾樣東西,我們可以看到有一樣東西是CR Funt,還有我用紅色和藍色標出來的東西,藍色對應的就是OX+40便宜的地址,紅色的是另外三個偏移。
繼續看這個結構的生命周期,因為我們看到一個數據結構之后一定是有它的生命周期的,我們要知道是什么時候重建,什么時候被銷毀,以及什么時候再利用。下一步我一定要對OX+40這個偏移下一個斷點,看一下什么情況下會被引用。
反過來我們能看到,首先可以看出這個地方一定是一個相關的東西。能看得出來,這兩個都是跟這個Funt相關,所以能看出剛才那張圖里面,+40偏移這個C一定是代表的Funt,所以也能印證我這個結構偏移角應該是對的。也就是說,這個是操作PFF結構的這么一樣東西。
再往后看,下一步我一定是關心這個結構什么時候會銷毀,這是關系到一個結構完整生命周期的問題。我們找到了一個Hyper,它會去做一些判斷,對這個結構做銷毀。怎么銷毀呢?我銷毀不能說是莫名其妙的就銷毀掉了,一定是做了一些判斷。
我可以看到它做了哪些判斷,它判斷了加某一個結構,這個結構是PFF,他判斷了+38偏移,判斷了+3C偏移,判斷了+A0偏移,下面就可以被銷毀。反過來再回頭看第一頁圖上+40偏移,這個偏移就很尷尬。也就是說我定了一個規則,銷毀的時候并沒有按照這些規則,
銷毀的時候判斷的是38、3C和A0,另外一個地方判斷的是40偏移,這兩個地方就存在著不一致性的問題。也就是說我定了一套標準,所有人都應該遵守,而不應該是我判斷了這幾個,我就認為可以被應用,而另外是另外的東西,這是存在不一致性的東西。
我們再往后看剛才出問題的那個函數,如果他Return是True會執行這樣一段代碼。但是如果是我用灰色標出來的代碼是會被跳過的。跳過的結果是,這個字體在一個全局的變量,在全局的結構中并沒有被擦除,它的擦除動作是用下一列表覆蓋掉,把自己抹掉。
也就是說如果這段代碼跳過了這樣一個東西的話,這個結構的指征會永遠的存在Private全局的結構當中。再回過頭來我們就能看到,最上面的是內核去引用了這樣一個全局變量,這個全局變量的某一個偏移存了一個指征,結尾是3:50。我們去看的話,其實是一個被Free掉的內存。如果有人再去基于這個全局的結構引用的話,這就是一個標準的漏洞。
懂行的人下一個問題一定是問,這個內存在什么樣的堆上,這個堆的大小是多少,這個Pool的Side是多少。懂行人看到這一頁圖一定是看到這兩個位置,它的大小是0X140,這兩個問題就決定了我在什么樣的堆上,分配多大的內存去占位這個結構,然后再往后的問題是我選取什么樣的結構,
基于這個結構用什么樣的方法可以做到Read,用什么樣的方法可以做到Write,把這樣一個問題轉化成一個內核的問題。所以我們可以看到,真正的利用過程,我后來選的對于內存的占用。
占用完之后,把某一個偏移量,就是已經被Free的內存,在系統有一個VQ的函數,這個函數是一個挺倒霉的家伙,它會用剛才被Free的Pool。這個時候他會對我提供數據的某一個地址做一個寫操作,只要把某些敏感的地址填進去,系統就可以去掉。
既然有這么靠譜的,下一步一定是提權,沒有任何問題。訪問一個地址,立馬就可以彈出來,就是這個原因,因為這個漏洞本身沒有太多的不確定性。
反過來,這個問題只是一個惡意地址寫的問題,懂行的人一定會問下一個問題,就是我這個地址寫哪兒了,內核有內核的計劃,我把什么樣的地址寫到里面,這就引出了下一個漏洞。
有一個內核的信息泄漏的問題,這個漏洞也是來自于電腦管家的尹亮同學,它的編號是20160175,出問題的函數是這個函數,NtGdiGetEmBUFI。這個歷程是一樣的,左邊是有問題的函數,右邊可以看出加了一個邏輯,老的代碼是從某一個地方的RAS取出來一個值,把這個值填到20里面去。
因為是信息泄漏是我們這個漏洞的背景,所以一定RAS里面填的是一個內核地址。這個內核地址被寫入一個用戶的數據里面去,所以把一個內核地址泄漏出來了。現在是把一個內核地址取出來之后,又把RAS里面的值,8C偏移的那個值取出來,把這個值放進來。
多半來講,我們對于這個內核這么多年的了解,各位的了解來講,多半都是把一個原本的地址現在換成了一個用戶態的指征,這個補丁其實修復的很簡單。
我們可以看一下,這里是出問題的,用的是64位的。所以可以看到,這一條目移到24的時候,我們可以看一下參數,24是一個Buffer。Kernel會填進去PGFF,大小是0X140的一個Pool,他把這個Pool的基地值返回,這個Pool在剛才的第一個漏洞當中是非常重要的,
這個Pool就會發生Free,這個函數又會非常漂亮的把出問題的Pool值返回,就像神配合一樣,把出問題的Pool的值返給用戶態,用戶態又知道哪里做這個Free,這兩個合作可以說是天衣無縫的。這就是尹亮同學這一套提權利用的細節。
再往后可以看到補丁是怎么修復的。補丁還是從這個基地值取出了這個Pool的值,Pool+8C的時候,取出了一個0700B4的東西,這個東西是放棄了對應的Handle,這個時候把Handle回填,新版的補丁后的內容,把一個內核堆上結構對應的Handle回了回去,這個回是沒問題的。
再反過來看這個源代碼,大家應該有點耳聞,是一個民間自發寫的一個微軟SP版本源代碼公開的東西。所以可以看到,它在定義這個PFF結構的時候,32位加偏移的時候顯示On No,就是對應的這個結構的Handle。所以大家感興趣,想對開源社區做一些貢獻的話,可以把這個改成Handle之類的東西。
剛才講到這個泄漏,實際上我覺得這個話題倒是,泄漏無處不在。我記得我在去年GeekPwn公開課的時候舉過這個例子,這個函數我們可以看到那個地址泄漏出一個內核的指征,也就是說,它會把整個的內核指征直接泄漏到這個當中去。
我截圖的這個時候是10月份的,11月的補丁是昨天的。我試了一下11月的補丁,這個問題依然是存在的。但是這一類的問題在當中會非常多,多到以至于這個東西沒有任何的價值。非常多的這些從何而來呢?
我寫過一個小工具,這個工具我相信在座的各位寫用用的時候應該都會寫過,可以到每一個內核的對象,他們的Type、Basic Address。最夸張的是堆加密的頭,我們知道在Windows10等等一些系統上面,微軟為了防止別人猜到他們堆頭的信息,他們進行了加密,加密有一個總則,
這個泄漏甚至把這個堆頭加密的總則泄漏出來了,我可以反推出來,也就是說內核已經泄漏了足夠多的信息了,根本不在乎這一兩個泄漏堆地址,泄漏函數的基地址等等。所以我覺得就是,如果微軟想繼續提高系統的安全性,這種機制至少是應該被限制的。不可以讓用戶這么輕易的得到內核的布局,內核的地址信息,對象的地址等等。
這個也是選取的一個圖形的東西,是DXG模塊的一個組件,在圖形顯示的時候,會調用到這個組件做一些渲染。這個漏洞其實是這五個漏洞里面最低級的一個,至少我們評估程序員犯錯誤的時候,這個犯錯誤的水平是最低級的。
我們可以看到有一個函數,它要做Momery Copy,我們要知道地址是哪里,源是哪里,最關鍵的問題是拷貝多大。源是我們應該控制的,我們提交多大這個東西至少是應該有限制的,因為我不可能提交1萬字節你就給我拷貝1萬字節,如果能夠把1萬字節分配出去也沒有問題。
但是實際上他可以不做任何的檢查,就給我往任何一個地方做拷貝。這個漏洞實際上不太高級,犯錯誤的程序員應該試著檢討一下自己。作為檢查,我們可以看到這個新的補丁至少是對長度做了一些判斷,在長度小于0X437的時候才會去做一些相關的拷貝操作。
我這里截了當時我做分析時候的一些截圖,就是我可以控制,這個是一個非常巨大的指證,他會做一些相關的計算。比如說有惡意操作,有一些+334的操作,加完之后,我們可以看到這個Memory Copy的分別是一個內核的原地址,一個是內河的目的地址。
我們可以看到這個是基于做運算而做出來的,這個非常大,它計算出來的結果一定是導致一個堆。
如果大家好奇剛才的那段操作是怎么做出來的,剛才是如何到DA、DB、F22E,怎么到這個值的時候,可以看到它的計算其實是基于它的數據結構的。這些結構在Windows、WDK10,在這些位置都可以找到這些結構的基本定義。
所以我相信在座的各位如果去看皮特最新的演講上,他們也有詳細的介紹,所以我就不用多講了。但是我引用了他們一頁幻燈片,是取自他們的研究成果。這張幻燈片我一看就笑了,原因是因為我們遇到過同樣的問題,這里拋出來的一個問題是說,他們做了很多實驗,不太好去寫這個東西。
原因是因為目標地址剛才是長度、源和目標,目標地址是一個Slist,這是一個單元,是一個單鏈表片出來的結構,每一個結構指向某一個地方。如果我越界的話,一定是越到下一個結構上去。那么我非常希望有人用我越界寫的這個內容才可以,因為這個時候才能繼續往下走。
我們發現一個問題,就是我很難找到第二個并發的人,很多情況下我們在調試的時候,我們發現有很多非常大的,很難有人跟你去做并發,他們遇到什么問題很難做并發。所以提出來的問題就是說,這真的是一個有人可以去做第二次POP的操作嗎?
理論上如果不是一個并發的話,根本沒有必要所謂的SList去做,這一點可以看出來Keenlab的同學想法是非常正確的。我做過一個事情,就是寫一些腳本,跑一些圖形密集的程序去抓,所以他們的當空接龍游戲,完成了這個并發,通過學習它的行為,我們就能知道我們應該如何寫這個代碼。
最后用了多線程的方法調用某些函數,去訪問一個被你溢出的Pool。我相信在座的各位只要是去嘗試寫過這樣的東西,反過來,其實這個漏洞的利用還有一些問題,就是假設我們在座的所有的各位是一個班級的,每個人都有一個學號。
如果溢出是按順序做溢出的,我取學1號,那個相鄰的人不一定是2號,2號可能在那個位置,所以這個時候整個內存的寫入控制其實是需要做一些精準的調整的,這個就看寫漏洞的人本事了,想把一個漏洞寫得標準沒有那么容易,在這個里面需要做一些優化。
總體來說剛才那個漏洞還是一個標準的內核堆的溢出。還有一個問題是來自360的,出問題的函數是在GreRestoreDC,我認為這是一個邏輯,這個邏輯的后果導致的。左邊是補丁之前的,右邊是補丁之后的,相對來講不太那么好看,因為這個補丁之后的邏輯變得很復雜。
但是實際上一句話就能說明白,而且是一個非常低級的問題,但是這個低級的問題很難被發現,原因是和這個漏洞相關的一共是兩個結構,第一個結構是一個外層結構,它的名字是DC,DC內部有一個結構,它的名字叫Service。
出問題的時候,我們在訪問DC的Surface對象的時候,發現我們的Surface已經被Free掉了。反過來作為漏洞分析的人員,一定要緊盯Surface這個結構。我對Surface這個結構做了一些斷點的監控,我們可以看到,這個結尾是BAC0的地址是一個Surface的。
它的+C偏移,64位的一個+C偏移的地方我們可以看到被增加的地方,還可以看到一個被減少的變量,就是有人加,有人減。再往后跟蹤,我可以發現在這個時候做了加操作,再往后做了一個減操作。
最后一個關鍵的,我們發現又做了一個減操作。也就是說操作理論上一般都是成對的,有加有減,函數入口加,函數出口減。為什么在一個RestoreDC里面連續兩次減?這是一個值得深挖的問題。
寫Windows圖形相關程序的人大家知道,這個概率非常大,我們可以看出,甚至它處于一個模塊,也就是說Base模塊是把最強的東西取出來放到里面,這個里面都是很基礎的東西。
我們就能看到,同樣的RestoreDC會調用一次減少,RestoreDC調用了國內的之后又會做一次減小,這是我們要找的原因,就是為什么會連續做兩次。我們看到左邊的代碼,左邊的代碼我們可以發現做一個判斷,如果怎么樣,做一次解碼操作。
緊接下來又是一個什么東西,做一個調用,這個變量的內部又會做一次減法操作。反過來我們就可以問自己一個問題,如果我有一個條件能同時激活這兩條的話怎么辦?是不是這個程序就會連續被減了兩次?答案是肯定的。
不會讓一個對象連續被減兩次,所以這個補丁非常簡單。反過來就可以看出,當時寫程序的人其實腦子也沒有想明白,他并沒有想清楚這幾條并發的邏輯分支應該是可被同時執行調用的?還是說每一個執行序列只能選中一條,所以這個可能是因為前期的復雜性導致的。
攻擊代碼其實就是嘗試同時激活這兩個分支,讓這兩個分支同時被調用,這個就是這個漏洞本身的問題。因為它同時被做了Restore兩次減法,所以會提前結束。下一步一定是他的對象的字節是370個,它來自于Surface這個對象的分配。
這個時候作為內存占用,我們可以找相同堆上的大小,可以擴展到370個字節的對象做占用,BitMap可以做這樣的應用。
最后一個漏洞也還比較有意思,是來自于這個函數的Use After Free。大家可能比較好奇,有的時候是以FFF,有的是以XXX,有的是ZZZ。XXX是表示這個函數會做一個內核的同步調用,
ZZZ表示這個函數會做一個異步調用,實際上他們是給程序員的,表示你看到這個函數,就知道它可能會發出一個請求,然后發到一個用戶里面去。看到這個名字,多半都會有一個意識就是Call Back,就是說我這個函數可以發出一個Call Back。
Call Back之前和之后是要做一個很嚴格的檢查,我不知道各位有沒有概念,實際上我們可以這樣想,如果在座的各位是高權限的,他也不能代表所有人,他也要別人幫他做一些事情,他會把控制權交給別人去完成。
反過來如果我把控制權交給別人完成,回來的時候,我是不是要對這個回來的結果做一個完整詳細的測試呢?就是有的時候你并不能把東西交給別人回來不檢查,這是不可以的。以這種方法,我們能夠找到一批的漏洞。
比如說今天上午ADI說的那個問題,源自于模塊之間的互相調用,導致在回來的時候出問題。Use After Free Call Back是回來再做檢測,而檢測不過關出了問題。再往后我們想,我不可能把所有的訪問都做了,我意識到在外面做一些事情。所以這個時候互相的信任和調用是不可避免的,但是你們要相信這種信任。
這個例子就是一個標準的Use After Free Call Back,干了一件事情,這個時候這個Handle就已經沒有意義了,是一個毫無意義的。但是內核拿到這個Handle在用,補丁是拿到這個Handle之后,對Handle做了有效性的驗證,然后加了應用,最后才把這個東西傳到內核里面去做二次調用,這是兩邊根源的差異。
我們可以看到有一個內核的函數,發出了一個調用,這個調用會通過這個接口反饋回來,這個就是一個交互的過程。關鍵的問題就在于出去之后再回來,我們可以看到最那邊的數字,11512這是一個Handle,這個Handle我們通過查重表,查內核的一個表,查出來它對應的這個內核的對象是41930。
這個對象我們可以驗證一步,因為訪問這個對象的第一個域,它可以反向指向這個對象的Handle值。我們可以看出,這個對象是有效的,是指向某個地址,這個地址是這個對象的值。
我們看到緊接下來,這個15012這個地方,立馬會變成一個被Free的內容,所以說這個對象已經被Free掉了,這種東西是一個非常傳統的,很古老的一個方式。我們應該怎么做?
作為漏洞的利用,我們如果去對比這個漏洞和第一個漏洞,雖然大家都是Use After Free,但是第一個Use After Free大家爭搶的是某一個地址,我們一定要把那個被Free的地址調用出來。這個地方是用聚敏,我們一定是搶這個聚敏值,我們看哪一個Object可以把剛才那個Handle找出來,這個就是利用上的一些差異,但是實際上理論上是一樣的。
這樣做的結果是,我們可以看到這個地方就在索引,也就是說,這個聚敏值已經被Close,又重新出來之后,這個地方還緩沖了這個聚敏,但是實際上這個聚敏已經對應不上了。我們可以看到,如果我們沒有去做內存占隊的話,那邊表示這個地址也是一個非法的。
我這個時候用的是ACCER這種結構,結果是系統要幫我到每一個地址,這個時候就能達到這個結果。為什么這兩個漏洞不需要信息泄漏就能夠達到利用呢?因為利用的方法基本上一個漏洞就可以達到兩種方法同時,就是讀和寫都能知道要改哪兒,改什么,都能同時去解決,一個漏洞可以解決所有的東西。
剛才漏洞講完了,就是今年所有Pwn2Own的一些細節。他們還不是利用起來寫的比較復雜一點的,我曾經挑戰過CVE-2015-0057的漏洞,這是由以色列一個人發現的,他發了一個Blog,我當然看到這個漏洞,我發現我控制內存的時候,我對這個對象進行讀寫的時候,我并不能完全的操控這個內存。
也就是說,藍色的部分是我可以控制到的,而紅色的部分是我不能控制到的。其實用這種對象去做內存的讀寫的時候,我的能力總是受到限制,這個讀寫能力都是殘缺的。怎么去解決這個問題?
我當時用了兩個對象去交錯的讀寫,能看到這兩張圖的差異,第一張圖是我在狀態一時刻的讀寫,第二張圖是我在狀態二時刻的讀寫。
通過這個對象的錯位,我能完整的去寫一個32字節的地址。所以到后來對象的布局變得比較夸張,但是最后的結果是我可以在64位上通過交叉去表現所有內存的寫入。我本來有一個Demo,如果大家愿意看的話可以看今年的Black Hat上我的演講,上面有一個演示。
最后提兩個,一個是今年的,一個是去年的,分別是2546和0167,我們有兩個Blog。每個出問題的地方都是xxx的Use After Free Call Back里面。我想說一下它的細節,這個漏洞,包括2546,我們發現它的作者都是來自于同一個人,我們對后臺對這個人做過監控。
這是當時我們抓到的他兩個樣本,這兩個樣本在Web上是沒有的,當時的演示Demo,我可以說一下大家的關注點。實際上在我們追蹤這個對象的時候,用戶可以去創建一個對象,但是我們去分析發現,Kernel也會去調用,他也會在Kernel創建一個對象。
這個時候就有幾個問題出現了,我們可不可以把這個內核自己創建的對象的窗口去抓走?我們可不可以把內核創建對象的窗口對應的地址抓走?所以如果我們基于內核對象去偏離核對象,這個時候系統的檢查往往是有的,這幾個漏洞都是從這個角度去挖的。
最后是一些總結,我覺得今年我們看到都是來自于中國人的,還是很長志氣的,確實這個能力,吳老師團隊的能力,包括像360的能力是相當不錯的,這一點是我們覺得欣慰的。
我覺得能力這個東西是可以從一個領域到另一個領域的,可以發現吳老師的團隊做到內核,做到汽車,實際上他們的方法論是一樣的,也就是說找到了比較好的調試的方法,你看所有的東西,其實道理是相通的。
最后一個是明年的Pwn2Own,可能要作為漏洞的利用的話要找新的突破口,我覺得在座的各位可以借鑒一下剛才幾位演講者的奇妙的想法,謝謝大家!