時(shí)間:2023-06-29 04:51:02 | 來(lái)源:網(wǎng)站運(yùn)營(yíng)
時(shí)間:2023-06-29 04:51:02 來(lái)源:網(wǎng)站運(yùn)營(yíng)
利用一個(gè)堆溢出漏洞實(shí)現(xiàn)VMware虛擬機(jī)逃逸:[作者:李小龍(acez),中文翻譯:kelwin]void Backdoor_InOut(Backdoor_proto *myBp) // IN/OUT { uint64 dummy; __asm__ __volatile__(#ifdef __APPLE__ /* * Save %rbx on the stack because the Mac OS GCC doesn't want us to * clobber it - it erroneously thinks %rbx is the PIC register. * (Radar bug 7304232) */ "pushq %%rbx" "/n/t"#endif "pushq %%rax" "/n/t" "movq 40(%%rax), %%rdi" "/n/t" "movq 32(%%rax), %%rsi" "/n/t" "movq 24(%%rax), %%rdx" "/n/t" "movq 16(%%rax), %%rcx" "/n/t" "movq 8(%%rax), %%rbx" "/n/t" "movq (%%rax), %%rax" "/n/t" "inl %%dx, %%eax" "/n/t" /* NB: There is no inq instruction */ "xchgq %%rax, (%%rsp)" "/n/t" "movq %%rdi, 40(%%rax)" "/n/t" "movq %%rsi, 32(%%rax)" "/n/t" "movq %%rdx, 24(%%rax)" "/n/t" "movq %%rcx, 16(%%rax)" "/n/t" "movq %%rbx, 8(%%rax)" "/n/t" "popq (%%rax)" "/n/t"#ifdef __APPLE__ "popq %%rbx" "/n/t"#endif : "=a" (dummy) : "0" (myBp) /* * vmware can modify the whole VM state without the compiler knowing * it. So far it does not modify EFLAGS. --hpreg */ :#ifndef __APPLE__ /* %rbx is unchanged at the end of the function on Mac OS. */ "rbx",#endif "rcx", "rdx", "rsi", "rdi", "memory" );}
上面的代碼中出現(xiàn)了一個(gè)很奇怪的指令inl。在通常環(huán)境下(例如Linux下默認(rèn)的I/O權(quán)限設(shè)置),用戶態(tài)程序是無(wú)法執(zhí)行I/O指令的,因?yàn)檫@條指令只會(huì)讓用戶態(tài)程序出錯(cuò)并產(chǎn)生崩潰。而此處這條指令產(chǎn)生的權(quán)限錯(cuò)誤會(huì)被host上的hypervisor捕捉,從而實(shí)現(xiàn)通信。Backdoor所引入的這種從guest上的用戶態(tài)程序直接和host通信的能力,帶來(lái)了一個(gè)有趣的攻擊面,這個(gè)攻擊面正好滿足Pwn2Own的要求:“在這個(gè)類型(指虛擬機(jī)逃逸這一類挑戰(zhàn))中,攻擊必須從guest的非管理員帳號(hào)發(fā)起,并實(shí)現(xiàn)在host操作系統(tǒng)中執(zhí)行任意代碼”。guest將0x564D5868存入$eax,I/O端口號(hào)0x5658或0x5659存儲(chǔ)在$dx中,分別對(duì)應(yīng)低帶寬和高帶寬通信。其它寄存器被用于傳遞參數(shù),例如$ecx的低16位被用來(lái)存儲(chǔ)命令號(hào)。對(duì)于RPCI通信,命令號(hào)會(huì)被設(shè)為BDOOR_CMD_MESSAGE(=30)。文件lib/include/backdoor_def.h中包含了一些支持的backdoor命令列表。host捕捉到錯(cuò)誤后,會(huì)讀取命令號(hào)并分發(fā)至相應(yīng)的處理函數(shù)。此處我省略了很多細(xì)節(jié),如果你有興趣可以閱讀相關(guān)源碼。static Bool DnDCPMsgV4IsPacketValid(const uint8 *packet, size_t packetSize){ DnDCPMsgHdrV4 *msgHdr = NULL; ASSERT(packet); if (packetSize < DND_CP_MSG_HEADERSIZE_V4) { return FALSE; } msgHdr = (DnDCPMsgHdrV4 *)packet; /* Payload size is not valid. */ if (msgHdr->payloadSize > DND_CP_PACKET_MAX_PAYLOAD_SIZE_V4) { return FALSE; } /* Binary size is not valid. */ if (msgHdr->binarySize > DND_CP_MSG_MAX_BINARY_SIZE_V4) { return FALSE; } /* Payload size is more than binary size. */ if (msgHdr->payloadOffset + msgHdr->payloadSize > msgHdr->binarySize) { // [1] return FALSE; } return TRUE;}Bool DnDCPMsgV4_UnserializeMultiple(DnDCPMsgV4 *msg, const uint8 *packet, size_t packetSize){ DnDCPMsgHdrV4 *msgHdr = NULL; ASSERT(msg); ASSERT(packet); if (!DnDCPMsgV4IsPacketValid(packet, packetSize)) { return FALSE; } msgHdr = (DnDCPMsgHdrV4 *)packet; /* * For each session, there is at most 1 big message. If the received * sessionId is different with buffered one, the received packet is for * another another new message. Destroy old buffered message. */ if (msg->binary && msg->hdr.sessionId != msgHdr->sessionId) { DnDCPMsgV4_Destroy(msg); } /* Offset should be 0 for new message. */ if (NULL == msg->binary && msgHdr->payloadOffset != 0) { return FALSE; } /* For existing buffered message, the payload offset should match. */ if (msg->binary && msg->hdr.sessionId == msgHdr->sessionId && msg->hdr.payloadOffset != msgHdr->payloadOffset) { return FALSE; } if (NULL == msg->binary) { memcpy(msg, msgHdr, DND_CP_MSG_HEADERSIZE_V4); msg->binary = Util_SafeMalloc(msg->hdr.binarySize); } /* msg->hdr.payloadOffset is used as received binary size. */ memcpy(msg->binary + msg->hdr.payloadOffset, packet + DND_CP_MSG_HEADERSIZE_V4, msgHdr->payloadSize); // [2] msg->hdr.payloadOffset += msgHdr->payloadSize; return TRUE;}
對(duì)于Version 4的DnD/CP功能,當(dāng)guest發(fā)送分片DnD/CP命令數(shù)據(jù)包時(shí),host會(huì)調(diào)用上面的函數(shù)來(lái)重組guest發(fā)送的DnD/CP消息。接收的第一個(gè)包必須滿足payloadOffset為0,binarySize代表堆上分配的buffer長(zhǎng)度。[1]處的檢查比較了包頭中的binarySize,用來(lái)確保payloadOffset和payloadSize不會(huì)越界。在[2]處,數(shù)據(jù)會(huì)被拷入分配的buffer中。但是[1]處的檢查存在問(wèn)題,它只對(duì)接收的第一個(gè)包有效,對(duì)于后續(xù)的數(shù)據(jù)包,這個(gè)檢查是無(wú)效的,因?yàn)榇a預(yù)期包頭中的binarySize和分片流中的第一個(gè)包相同,但實(shí)際上你可以在后續(xù)的包中指定更大的binarySize來(lái)滿足檢查,并觸發(fā)堆溢出。packet 1{ ... binarySize = 0x100 payloadOffset = 0 payloadSize = 0x50 sessionId = 0x41414141 ... #...0x50 bytes...#}packet 2{ ... binarySize = 0x1000 payloadOffset = 0x50 payloadSize = 0x100 sessionId = 0x41414141 ... #...0x100 bytes...#}
有了以上的知識(shí),我決定看看Version 3中的DnD/CP功能中是不是也存在類似的問(wèn)題。令人驚訝的是,幾乎相同的漏洞存在于Version 3的代碼中(這個(gè)漏洞最初通過(guò)逆向分析來(lái)發(fā)現(xiàn),但是我們后來(lái)意識(shí)到v3的代碼也在open-vm-tools的git倉(cāng)庫(kù)中):Bool DnD_TransportBufAppendPacket(DnDTransportBuffer *buf, // IN/OUT DnDTransportPacketHeader *packet, // IN size_t packetSize) // IN{ ASSERT(buf); ASSERT(packetSize == (packet->payloadSize + DND_TRANSPORT_PACKET_HEADER_SIZE) && packetSize <= DND_MAX_TRANSPORT_PACKET_SIZE && (packet->payloadSize + packet->offset) <= packet->totalSize && packet->totalSize <= DNDMSG_MAX_ARGSZ); if (packetSize != (packet->payloadSize + DND_TRANSPORT_PACKET_HEADER_SIZE) || packetSize > DND_MAX_TRANSPORT_PACKET_SIZE || (packet->payloadSize + packet->offset) > packet->totalSize || //[1] packet->totalSize > DNDMSG_MAX_ARGSZ) { goto error; } /* * If seqNum does not match, it means either this is the first packet, or there * is a timeout in another side. Reset the buffer in all cases. */ if (buf->seqNum != packet->seqNum) { DnD_TransportBufReset(buf); } if (!buf->buffer) { ASSERT(!packet->offset); if (packet->offset) { goto error; } buf->buffer = Util_SafeMalloc(packet->totalSize); buf->totalSize = packet->totalSize; buf->seqNum = packet->seqNum; buf->offset = 0; } if (buf->offset != packet->offset) { goto error; } memcpy(buf->buffer + buf->offset, packet->payload, packet->payloadSize); buf->offset += packet->payloadSize; return TRUE;error: DnD_TransportBufReset(buf); return FALSE;}
Version 3的DnD/CP在分片重組時(shí),上面的函數(shù)會(huì)被調(diào)用。此處我們可以在[1]處看到與之前相同的情形,代碼依然假設(shè)后續(xù)分片中的totalSize會(huì)和第一個(gè)分片一致。因此這個(gè)漏洞可以用和之前相同的方法觸發(fā):packet 1{ ... totalSize = 0x100 payloadOffset = 0 payloadSize = 0x50 seqNum = 0x41414141 ... #...0x50 bytes...#}packet 2{ ... totalSize = 0x1000 payloadOffset = 0x50 payloadSize = 0x100 seqNum = 0x41414141 ... #...0x100 bytes...#}
在Pwn2Own這樣的比賽中,這個(gè)漏洞是很弱的,因?yàn)樗皇鞘艿街奥┒吹膯l(fā),而且甚至可以說(shuō)是同一個(gè)。因此,這樣的漏洞在賽前被修補(bǔ)并不驚訝(好吧,也許我們并不希望這個(gè)漏洞在比賽前一天被修復(fù))。對(duì)應(yīng)的VMware安全公告在這里。受到這個(gè)漏洞影響的VMWare Workstation Pro最新版本是12.5.3。tools.capability.dnd_version 3 tools.capability.copypaste_version 3 vmx.capability.dnd_version vmx.capability.copypaste_version
前兩行消息分別設(shè)置了DnD和Copy/Paste的版本,后續(xù)兩行用來(lái)查詢版本,這是必須的,因?yàn)橹挥胁樵儼姹静艜?huì)真正觸發(fā)版本切換。RPCI命令vmx.capability.dnd_version會(huì)檢查DnD/CP協(xié)議的版本是否已被修改,如果是,就會(huì)創(chuàng)建一個(gè)對(duì)應(yīng)版本的C++對(duì)象。對(duì)于version 3,2個(gè)大小為0xA8的C++對(duì)象會(huì)被創(chuàng)建,一個(gè)用于DnD命令,另一個(gè)用于Copy/Paste命令。info-set guestinfo.KEY VALUE info-get guestinfo.KEY
VALUE是一個(gè)字符串,字符串的長(zhǎng)度可以控制堆上buffer的分配長(zhǎng)度,而且我們可以分配任意多的字符串。但是如何用這些字符串來(lái)泄露數(shù)據(jù)呢?我們可以通過(guò)溢出來(lái)覆蓋結(jié)尾的null字節(jié),讓字符串連接上相鄰的內(nèi)存塊。如果我們能夠在發(fā)生溢出的內(nèi)存塊和DnD或CP對(duì)象之間分配一個(gè)字符串,那么我們就能泄露對(duì)象的vtable地址,從而我們就可以知道vmware-vmx的地址。盡管Windows的LFH堆分配存在隨機(jī)化,但我們能夠分配任意多的字符串,因此可以增加實(shí)現(xiàn)上述堆布局的可能性,但是我們?nèi)匀粺o(wú)法控制溢出buffer后面分配的是DnD還是CP對(duì)象。經(jīng)過(guò)我們的測(cè)試,通過(guò)調(diào)整一些參數(shù),例如分配和釋放不同數(shù)量的字符串,我們可以實(shí)現(xiàn)60%到80%的成功率。DnD_CopyPaste_RpcV3{ void * vtable; ... uint64_t ifacetype; RpcUtil{ void * vtable; RpcBase * mRpc; DnDTransportBuffer{ uint64_t seqNum; uint8_t * buffer; uint64_t totalSize; uint64_t offset; ... } ... }}RpcBase{ void * vtable; ...}
我們?cè)诖耸÷粤私Y(jié)構(gòu)中很多與本文無(wú)關(guān)的屬性。對(duì)象中有個(gè)指針指向另一個(gè)C++對(duì)象RpcBase,如果我們能用一個(gè)可控?cái)?shù)據(jù)的指針的指針覆蓋mRpc這個(gè)域,那我們就控制了RpcBase的vtable。對(duì)此我們可以繼續(xù)使用unity.window.contents.start命令來(lái)來(lái)控制mRpc,該命令的另一個(gè)參數(shù)是imgsize,這個(gè)參數(shù)代表分配的圖像buffer的大小。這個(gè)buffer分配出來(lái)后,它的地址會(huì)存在vmware-vmx的固定偏移處。我們可以使用命令unity.window.contents.chunk來(lái)填充buffer的內(nèi)容。步驟如下:關(guān)鍵詞:實(shí)現(xiàn),虛擬,漏洞,利用,逃逸
客戶&案例
營(yíng)銷資訊
關(guān)于我們
客戶&案例
營(yíng)銷資訊
關(guān)于我們
微信公眾號(hào)
版權(quán)所有? 億企邦 1997-2025 保留一切法律許可權(quán)利。