<span id="7ztzv"></span>
<sub id="7ztzv"></sub>

<span id="7ztzv"></span><form id="7ztzv"></form>

<span id="7ztzv"></span>

        <address id="7ztzv"></address>

            原文地址:http://drops.wooyun.org/papers/4486

            0x00 背景


            這是發表在Project Zero中的一篇文章,講述了CVE-2014-9295 ntpd漏洞的發掘與利用

            原文鏈接如下:http://googleprojectzero.blogspot.com/2015/01/finding-and-exploiting-ntpd.html

            作者Stephen R?ttger, Time Lord。

            前言:Stephen的這篇文章是Project Zero的第一篇客座文章。我們將不時的推出頂級安全研究的客座文章。這篇文章里,這些漏洞的遠程利用性質以及導致遠程代碼執行的錯誤鏈和特性讓我們印象深刻。你可能已經看到最近的ntpd漏洞披露,這篇文章由發現這一漏洞的研究者來講述這一故事。 Chris Evans

            0x01 簡介


            幾個月前,我決定開始做fuzzing。我選擇了網絡時間協議(Network Time Protocol,NTP)的參考實現ntpd作為我的第一個目標,因為我有NTP的一些背景知識,同時這一協議似乎很簡單,可以很好用來學習fuzzing。此外,ntpd可用于許多平臺,已經被廣泛使用, 是OS X默認安裝的一部分。

            當查看源代碼以更好的了解該協議時,我注意到它的處理比我預想的要復雜得多。除了時間同步數據包,ntpd支持對稱和非對稱(Autokey)認證和用于查詢守護進程統計數據或進行配置更改的私有控制模式包(如果我沒有記錯,這是ntpdc和ntpq協議中分別講到的)。我很快就在處理Autokey協議消息的代碼中找到了一個bug,因此決定深入挖掘,并對其他部分進行人工代碼審查。最終找到了CVE-2014-9295的漏洞并寫了我的第一個OS X漏洞利用,下面我將對其進行詳細的描述。

            長話短說,在常規配置下,本地網絡上的攻擊者通過偽造::1源的IPv6數據包就能觸發一個全局的緩沖區溢出。如果你的ntpd還沒有打補丁,那么在你的配置文件中為每一個限制行(restrict line)添加nomodify 或noquery,即使它是localhost。

            這就足夠了,讓我們跳到細節

            0x02 漏洞


            最嚴重的是一個緩沖區溢出漏洞,它存在于處理控制數據包的代碼中,在OS X Mavericks上可以成功利用。如果控制模式的響應數據超過了用于存儲它們的緩沖區的大小,將會被拆分,它的實現代碼如下:

            #!c
            static void
            ctl_putdata(
            const char *dp,
              unsigned int dlen,
              int bin   /* set to 1 when data is binary */
            )
            {
            //[...]
            ?
            /*
             * Save room for trailing junk
             */
               if (dlen + overhead + datapt > dataend) {
                 /*
              * Not enough room in this one, flush it out.
              */
                ctl_flushpkt(CTL_MORE);
               }
               memmove((char *)datapt, dp, (unsigned)dlen);
               datapt += dlen;
               datalinelen += dlen;
            }
            

            正如你所看到的,如果剩余緩沖區空間不能容納要寫入的數據,將被調用,它會發送當前數據包,重置datapt的值,使其指向緩沖區的起始位置。然而任何情況下memmove都會被調用,如果dlen大于datapt緩沖區的大小,將發生緩沖區溢出。需要注意的是,溢出發生在一個全局緩沖區中,在這種情況下stack cookies不會起作用。因此,讓我們看看是否能夠找到一個代碼路徑來觸發此漏洞。

            在大多數的調用中,將要寫入的數據來自一個固定大小緩沖區并且小于輸出緩存區,因而不會發生溢出。

            負責處理由特權客戶端發送的ntp.conf風格的遠程配置的函數< configure>將調用將任意的錯誤信息返回給客戶端。通過發送具有大量錯誤的配置,錯誤消息字符串將超過緩沖區的大小。

            然而,事實是所寫入的數據被限制在一組固定的錯誤消息,因此漏洞利用很困難。

            一個更好的覆蓋位置可以在中找到。 NTP守護進程保存了一組名值對的變量列表,可以通過配置進行設置,也可以通過控制模式包來讀回。如果大于輸出緩沖區的變量被讀回,它將溢出和破壞緩沖區之后的數據。

            0x03 設置變量


            那么我們怎樣才能設置變量?如前面提到的,有一個控制模式包,通過它我們可以發送配置命令給ntpd,并由此設定我們想要的任何變量。但是,這顯然是一個特權操作,被兩種機制所保護:

            1. 在ntp.conf中可以基于源IP對訪問私有控制模式的查詢進行限制。默認安裝中,通常禁止除127.0.0.1和::1這些查詢之外的源IP。Ubuntu,Debian和OS X都是這樣做的。

            2. 必須在ntp.conf中指定共享密鑰,用于對報文進行MAC(Media Access Control)認證,在默認安裝中這也沒有被設置。

            如果在同一個網絡上,繞過第一個其實并不難。大家都知道,IP地址是可以偽造。但是,我們能否欺騙本地主機的地址呢?OS X和Linux內核在這種情況下的行為很相似。任何源IP為127.0.0.1的數據包到達外部接口后將立即被丟棄。但是,如果我們使用IPv6,實際上可以偽造::1,并發送控制模式包到守護進程(一些Linux發行版已經制定了防火墻規則來防止這一點,例如Red Hat)。因此,如果我們是在同一個本地網絡上,我們可以發送欺騙性的數據包到目標的本地鏈路地址,并繞過IP限制。但是,怎樣滿足需求2呢?這個聽起來很困難:如果沒有指定密鑰,你怎么能有一個有效的MAC?

            0x04 對關鍵問題進行探尋


            讓我們首先來討論一些背景知識。通過的ntp.conf可以指定多個密鑰并給它們分配id。這些密鑰的id可以分配給不同的角色,例如,一個requestkey可用于驗證私有模式包和一個controlkey用于控制模式包。我們需要一個controlkey發送我們的配置請求,但實際上一個requestkey就足夠了,因為私有模式包的存在,將設置controlkey id為特定的值。

            另一個由Neel Mehta發現的bug也發揮了的作用。讓我們來看看,如果在配置中沒有指定requestkey,ntpd將做些什么:

            #!c
            /* if doesn't exist, make up one at random */
            if (authhavekey(req_keyid)) {
              //[...]
            } else {
              unsigned int rankey;
            ?
              rankey = ntp_random();
              req_keytype = NID_md5;
              req_hashlen = 16;
              MD5auth_setkey(req_keyid, req_keytype,
             ??(u_char *)&rankey, sizeof(rankey));
              authtrust(req_keyid, 1);
            }
            

            沒錯,如果沒有指定密鑰,將會產生隨機的31位密鑰,這意味著我們可以窮舉發送2^31個包到存在漏洞的守護進程,每一個包包含68字節的有效載荷。別急,還有更精彩的!隨機密鑰由一個定制的隨機數發生器通過32位隨機種子來創建,我們可以通過標準時間同步請求來得到這個發生器的輸出。我們通過向守護進程查詢時間得到的時間戳是生成器生成的一個隨機值,每個查詢使我們能夠恢復輸出的約12個bit位,我們可以用它來離線暴力破解隨機種子。然而,這一簡單暴力方法的可行性高度依賴于ntpd的正常運行時間,因為已創建的隨機值的數量將增加搜索空間的。為了反映時間復雜度,在我的筆記本電腦上,我的單核實現方式需要消耗幾個小時,即使我限制了搜索空間為前1024個隨機值,但是你可以使用更多的核或者盡可能預先計算同時建立查找表。

            現在,我們已經能在標準配置機器上遠程觸發全局緩沖區溢出。

            0x05 溢出


            現在,我們有密鑰,就可以發送配置命令和寫入任意變量。當從守護進程讀回它們時,你可以指定你感興趣的變量。ntpd會遍歷它們,通過函數把它們(用逗號分隔)寫到全局緩沖區中,最終通過將它們發送出來。在這個溢出上還是有一些限制,使開采特別困難。

            1. 我們不能寫0x00,0x22符號(“)和0xFF的。

            2. 一些數據將追加在我們覆蓋的數據之后。例如,“,”出現在兩個可變之間以及最后flush時將寫入“\ r\ n”。

            如何從這里繼續取決于你的目標是哪個OS /版本/架構,因這將導致保護機制和全局數據結構的內存布局有所不同。舉幾個例子:

            在x64上,由于無法寫入空字節使我們無法完全覆蓋指針,因為最重要的字節是空字節。因為“\ r\ n”是附加到我們的數據之后,這就限制了指針的部分覆蓋的方法。然而,在x86上,這不是一個問題。

            至少在Debian,針對ntpd的一些編譯時的保護未啟用。即可執行文件不是地址無關的,全局偏移表(GOT)是運行時可寫的。

            在OS X Mavericks,指向緩沖區當前位置的變量datapt在緩沖區之后,而在Debian和Ubuntu中,datapt指針在緩沖區之前,因此不能被覆蓋。

            我選擇在64位OS Mavericks上嘗試。由于我之前并沒有有OS X的經驗,如果我犯了明顯錯誤的或使用了錯誤的術語,請原諒我:)。

            環境是這樣的:

            1. 二進制文件,棧,堆和共享庫分別包含16位的隨機部分。

            2. 共享庫的地址是在啟動時隨機確定的。

            3. 崩潰后,ntpd自動重啟延遲大約10秒。

            4. ntpd編譯時采用stack cookies(由于我們溢出一個全局緩沖區,這并不產生影響)。

            5. 全局偏移表(GOT)在運行時是可寫的。

            為了達到一個穩定的利用,我們將不得不繞過ASLR,所以讓我們泄露一些指針。因為datapt變量的原因,這實際上很容易,你可能還記得它指向當前寫入位置,位于緩沖區之后:

            enter image description here

            我們只需要覆蓋datapt變量的兩個最低有效字節,然后ntpd會誤判長度,將緩沖區之后的數據發送給你,這將泄漏一個指向ntpd二進制文件的指針以及一個堆指針。在此之后,datapt變量被重置為指向緩沖區的開始。

            需要注意的是,通常為“\ r\ n”會得到追加到我們的數據之后并破壞了部分指針覆蓋。但由于我們改寫了寫指針本身,相應的換行序列將被寫入到新的目的地址。

            用同樣的伎倆,我們可以把錯誤變成稍受限制的write-what-where:通過部分覆蓋datapt變量,使其指向你想要寫的地方(減去幾個字節,為分隔符騰出空間),然后通過第二ntpd變量寫任意數據。同樣的,第一個寫數據時,垃圾數據被附加到我們的數據是不會引發問題的,因為它會被寫入到新的位置,不會破話指針。需要注意的是,我們只能在緩沖區的前面寫任意數據,因為較高的地址將觸發flush并重置datapt(寫入分隔符之后,這仍然可被用來破壞一個長度字段)。

            不幸的是,所附加的字節仍然會導致一個問題。如果我們試圖做指針的部分覆蓋,“\ r\ n”序列總是在指針使用之前就破話了它。然而,幾乎總是如此,我花了很長時間來搞清楚GOT,它實際上可寫的,并且在我們的覆蓋被加入的“\ r\n”破壞之前可以使用兩次。在寫一個變量和flush包之間,被調用。這意味著,如果我們通過部分覆蓋重寫這兩個函數中的任何一個的GOT項,指針將會在破壞之前被使用,我們就能控制rip。

            0x06 再一次信息泄露


            只要找到二進制中的一個小片段,并跳轉到它,對嗎?不幸的是,這是行不通的。要知道為什么,讓我們來看看來自二進制文件和libsystem_c中的一對地址:

            0x000000010641c000 /usr/sbin/ntpd
            0x00007fff88791000 /usr/lib/system/libsystem_c.dylib
            

            系統庫的地址包含兩個空字節作為他們的最高有效字節,而二進制地址開始有三個空字節。因此,如果我們通過二進制文件中的地址來覆蓋的GOT項,仍然會有從庫地址中留下的0x7F字節(記住:我們不能寫NUL字節)。

            為了獲得一個系統庫地址,我們可以嘗試修改我們的覆蓋來得到一個更好的泄漏,例如覆蓋一些長度字段。但有一個懶惰的辦法,基于OS X Mavericks上ASLR的弱點。

            最常見的庫加載在split library region(“man vmmap”中這樣稱呼),由系統中的所有進程共享。這個區域的加載地址在引導期間進行隨機化。這意味著庫地址保持不變,即使一個程序重新啟動或者根本不使用庫也會在地址空間中加載這些庫,因此可以用于構造ROP。這和ntpd崩潰時自動重新啟動可以用來按字節暴力猜解(libsystem_c)或(libsystem_malloc)的地址。

            如果你重啟系統幾次,你可以觀察到split library region的加載地址的形式總是0x00007fff8XXXX000,包含16位的隨機部分或在我們的情況下是17位,因為該區域可延伸至0x00007fff9XXXX000。讓我們用之前例子里的libsystem_c地址:0x00007fff88791000。我們知道,<strlen的>位于偏移0x1720處,因此0x00007fff88792720是我們試圖暴力破解的地址。

            我們首先暴力破解第二個最低有效字節的高4位。我們用0x0720覆蓋的的GOT條目,導致新條目0x00007fff88790720。由于我們沒有得到正確的地址,ntpd會崩潰,不會發送任何回復給我們。在這種情況下,我們增加地址為0x1720并再次嘗試。如果ntpd給我們發送了回復,這將發生在0x2720,說明我們找到了正確的字節,并繼續下一個(0x012720)。

            通過這種方式,在最壞的情況下,通過304次嘗試(4位+8位+5位)可以得到libsystem_c地址。 OS X將大約每隔10秒重新啟動ntpd,但是對于每一次嘗試你需要重新暴力破解密鑰,所以請使用超級計算機。此外,如果你運氣不好,你會遇到無限循環必須手動殺死ntpd。

            0x07 任意代碼執行


            如果不是ntpd在沙盒中運行這一事實,我們現在就可以結束了。我們只需要用 的地址覆蓋的GOT項就能執行任意命令,因為它會使用用戶控制的字符串來調用該函數。但是通過這種方式你只是在/var/log/system.log中得到下面一行:

            sandboxd[405] ([41]): ntpd(41) deny process-exec /bin/sh
            

            相反,我們需要找到一個gadget來控制堆棧指針使其指向一個ROP鏈。這通常需要一個stack pivot來做到這一點,但我們控制的堆棧上的數據是有限的。

            enter image description here

            在棧上,我們控制了3個地點的數據,我們可以填充任意的指針,沒有任何限制。除此之外,我們完全控制二進制程序中已知地址上的全局緩沖區的中的內容,如果我們能將堆棧指針(RSP)指向該緩沖區,我們可以執行任意ROP鏈。

            由于我們通過覆蓋GOT來利用該漏洞,只能控制指令指針一次,即我們不能鏈接多個調用。因此,我們的第一個gadget需要將堆棧指針增加0x80,0x90或0xb8,以便它在返回時用到我們可控的一個地址上的地址值,同時做一些有用的事。幸運的是,我在libsystem_c.dylib中發現了下面的gadget:

            add rsp, 0x88
            pop rbx
            pop r12
            pop r13
            pop r14
            pop r15
            pop rbp
            ret
            

            此gadget返回到我們在RSP+0xb8處的地址,同時加載RSP+0x90處的值到R12中。由于我們現在控制了一個寄存器,我們可以通過call qword [reg+n]鏈接gadgets,其中寄存器指向我們控制的全局緩沖區。例如,第二個gadget看起來是這樣的:

            mov rdi, r12
            mov rsi, r14
            mov rdx, r13
            call qword [r12+0x10]
            

            通過這樣的一些gadget,我們控制RSI,將其加載到RSP:

            push rsi
            pop rsp
            xor eax, eax
            pop rbp
            ret
            

            有了這些,我們就大功告成了。這將崩潰在一條ret指令,此時RSP指向用戶控制的數據,執行任意代碼就很簡單了。由于我們控制了堆棧,就可以建立一個ROP鏈,加載和執行shellcode,并從那里嘗試通過攻擊內核或IPC通道來突破沙箱。但是,這留給讀者作為練習:)。

            0x08 利用摘要


            1. 發送一堆常規時間同步請求來泄漏隨機值。

            2. 通過暴力破解得到種子并計算requestkey(它的key為id 65535)。

            3. 通過requestkey來簽名源IP地址偽裝為::1的私有模式數據包,并發送到服務器,將controlkey id設置為65535。

            4. 發送配置變更來解除對我們的IP地址的所有限制。

            5. 加入我們的IP來獲取異步通知(我們不得不這樣做,因為后面我們會覆蓋一個標志,它控制著響應是直接或是異步發送)。

            6. 通過設置一個長變量并讀回該變量來觸發這個溢出并泄漏二進制程序基址。

            7. 再次使用溢出實現write-what-where,并按字節暴力破解的地址。

            8. 準備堆棧和全局緩沖區中的數據。

            9. 調用gadgets來控制rsp并執行一個ROP鏈。

            0x09 緩解


            如果你的ntpd還沒有修補,這些bugs可以通過修改你的ntp.conf來有效地防護。有漏洞的函數被用于處理控制模式數據包,這完全可以通過在每一個被限制的配置行中添加“noquery”來阻止。如前所述,重要的是對于localhost也要加上“noquery”,這是由于基于IP的訪問限制往往可以通過欺騙來繞過。但是請注意,這將阻止ntpq正常工作,你將無法再查詢相應的信息和其他統計信息。

            例如,如果你的配置包括多個“限制”行:

            restrict default kod nomodify notrap nopeer noquery
            
            restrict -6 default kod nomodify notrap nopeer noquery
            
            restrict 127.0.0.1
            
            restrict -6 ::1
            

            確保“noquery”被包括在所有這些中:

            restrict default kod nomodify notrap nopeer noquery
            
            restrict -6 default kod nomodify notrap nopeer noquery
            
            restrict 127.0.0.1 noquery
            
            restrict -6 ::1 noquery
            

            <span id="7ztzv"></span>
            <sub id="7ztzv"></sub>

            <span id="7ztzv"></span><form id="7ztzv"></form>

            <span id="7ztzv"></span>

                  <address id="7ztzv"></address>

                      亚洲欧美在线