這篇文章講述了我對許多短信平臺呼叫狀態機的調查,包括Signal、JioChat、Mocha、Google Duo和Facebook Messenger,調查過程中發現了5個漏洞,這些漏洞可以讓呼叫方設備強制被叫方設備傳輸音頻或視頻數據,我試圖分析這其中的原因。
2019年1月29日,在FaceTime組中發現了一個嚴重的漏洞,攻擊者可以通過該漏洞呼叫目標,不需要進行用戶交互就可以強行連接呼叫,從而允許攻擊者在目標不知情或不同意的情況下監聽目標的周圍環境。該bug的影響和機制上都很出色。在沒有獲得代碼執行情況下,漏洞能夠強制目標設備向攻擊者設備傳輸音頻是不尋常的、可能前所未有的影響。此外,該漏洞是FaceTime調用狀態機中的一個邏輯bug,只需使用設備的用戶界面即可執行。雖然這個bug很快就被修復,但我從未在任何平臺上考慮過由于調用狀態機中的邏輯bug發生如此嚴重且容易實現的漏洞攻擊場景,這一事實使我懷疑其他狀態機是否也存在類似的漏洞。這篇文章描述了我對許多短信平臺呼叫狀態機的調查,包括Signal、JioChat、Mocha、Google Duo和Facebook Messenger。 WebRTC和狀態機 大多數視頻會議應用都是使用WebRTC實現的,這些在過去幾篇博文中已經討論過。WebRTC是通過在對等方之間會話描述協議(SDP)中的呼叫設置信息來創建連接的,這個過程被稱為信令。信令不是由WebRTC實現的,WebRTC允許對等體以任何可用安全通信消息交換SDP,通常web applications采用WebSockets,以及消息傳遞應用程序的安全消息傳遞。 可以由WebRTC對等體進行交換的有幾種類型的SDP。在一個典型的連接中,呼叫者首先發送一個SDP提議,然后被叫者用SDP應答來響應。這些消息包含了傳輸和接收媒體所需的大部分信息,包括編解碼器支持、加密密鑰等。在交換報價/應答之后,對等方可以向其他對等方發送SDP候選者。候選者是兩個對等方可以用來相互連接的潛在網絡路徑,SDP候選者包含IP地址和TURN服務器等信息。對等方通常向一個對等方發送多個候選者,候選者可以在連接過程中的任何時間發送。 WebRTC連接維護著一個與是否已經收到并處理了報價或應答相關的內部狀態,然而, 使用WebRTC的應用程序通常必須維護自己的狀態機來管理應用程序的用戶狀態。用戶狀態如何映射到WebRTC狀態是由WebRTC集成商做出的設計選擇,這對安全和性能都有影響。例如,有些應用在被叫用戶與應用交互接聽電話之前,不會交換任何SDP,同時,有些應用建立了對等連接,在被叫用戶還沒有接到呼叫通知之前,就開始從呼叫者向被叫用戶發送音頻和視頻。 無論設計如何,從輸入設備傳輸音頻或視頻都必須由應用程序代碼使用WebRTC直接啟用。這通常是通過一個叫做軌道的功能來實現的。每個輸入設備都被認為是一個 "軌道",每個特定的軌道必須在傳輸音頻或視頻之前通過調用addTrack(或等效語言)添加到特定的對等連接中。音軌也可以被禁用,對于實現靜音和相機關閉功能非常有用。每個軌道還有一個RTPSender屬性,可以用來微調傳輸的屬性,也可以用來禁用音頻或視頻傳輸。 從理論上講,在音頻或視頻傳輸之前確保被叫方同意是一個相當簡單的問題,即等待用戶接受呼叫后再向對等連接添加任何軌道。然而,當我查看實際的應用程序時,它們以許多不同的方式啟用傳輸。其中大多數導致了漏洞,使得呼叫在沒有被叫者的互動情況下被連接。 Signal Messenger 我在2019年9月看了Signal,當時該應用的呼叫設置與WebRTC文檔中推薦的非常相似。
建立對等連接,然后當被叫方通過與用戶界面交互接受呼叫時,將被叫方的音軌添加到連接中。然后通過對等連接向被叫方發送消息,告訴被叫方也進入連接狀態并添加音軌。 不幸的是,應用程序沒有檢查接收連接消息的設備是主叫設備,所以可以從主叫設備向被叫設備發送連接消息。這就導致了音頻通話的連接使主叫方聽到了被叫方的周圍環境。我通過修改Signal的開源代碼來發送消息并重新編譯攻擊客戶端來測試這個bug。 這個漏洞在2019年9月在客戶端被修復,此后,Signal的信令代碼被使用更保守的狀態機的ringrtc項目取代 該bug純屬Signal的代碼,并非由于對WebRTC功能的誤解。狀態機設計在很大程度上有效,需要用戶同意才能傳輸音頻,但未執行特定檢查 JioChat 與Mocha 2020年7月,我在偶然發現在JioChat和Mocha messengers中兩個非常相似的漏洞。他們都有一個類似的信令設計,這是服務器介導的。
通過服務器交換邀約和回答,然后呼叫者和被叫者都將他們的候選人發送到服務器。然后服務器將它們存儲起來,直到被叫方與其設備交互并接受呼叫。然后建立對等連接,當WebRTC進入內部連接狀態時,就會添加軌道,造成音頻和視頻的傳輸。 這種設計有一個根本性的問題,因為可以選擇將候選人包含在SDP提議或回答中。在這種情況下,對等連接將立即開始,因為在這個設計中,唯一阻止連接的是缺乏候選人,這將反過來導致輸入設備的傳輸。我通過使用Frida將候選者添加到每個應用程序創建的提議中進行測試。能夠導致JioChat在未經用戶同意的情況下發送音頻,以及Mocha發送音頻和視頻。這兩個漏洞在提交后不久就通過過濾服務器上的SDP進行了修復。 這些問題是由于對WebRTC的工作原理的誤解,再加上試圖通過不尋常的信令設計來提高WebRTC的性能所致。通常情況下,WebRTC集成商必須決定是否要等到被叫方接聽電話后再建立點對點連接。提前設置連接可以提高性能,避免用戶在接聽電話時需要等待,但也大大增加了WebRTC的遠程攻擊面。這些應用試圖通過這種設計來提高性能,不需要付出安全成本,但沒有考慮到WebRTC可以啟動對等連接的所有方式。 一般來說,集成商在任何不添加或啟用軌道的WebRTC功能上控制音頻或視頻傳輸不是一個好主意。首先,許多WebRTC功能是復雜的,所以很容易犯錯,允許音頻或視頻被傳輸。另外,如果門控功能不是常用的安全功能,那么將來可能會進行不良測試或改變。 Duo 我在2020年9月看了Google Duo。Duo的信令方法與很多信令不同,因為它支持一個功能,即在接聽之前,被叫方可以預覽來電的視頻。所以在接聽電話之前需要設置一個單向的視頻流。
上圖顯示了單向視頻流的設置。虛線代表使用Java執行器進行的異步調用。從被叫方到呼叫方的傳輸缺失是通過兩個方法來實現的。首先,SDP要約中包含了視頻的屬性a=sendonly,這使得視頻只能朝一個方向傳輸。另外,當被叫方收到呼叫方的offer時,它將視頻軌道添加到對等連接中,但隨后使用軌道的RTPSender屬性將其禁用(音頻軌道在用戶接受呼叫之前不會被添加或啟用)。 這兩種方法都不能有效地阻止視頻從被叫方傳輸到呼叫方。SDP屬性很容易解決,因為調用者向被叫者提供SDP,所以很容易被改變。除了異步設計外,一處理完offer就停用視頻軌跡應該是可以的。正常情況下,setLocalDescription方法(處理SDP offer)會調用callbackonSetSuccess,回調結束后再設置對等連接。但是,如果回調再進行一次異步調用,那么在連接建立之前,onSetSuccess完成的保證不再成立,因為setLocalDescription方法只等待onSetSuccess線程完成。這就造成了禁用視頻和建立連接之間的競爭,所以在某些情況下,被叫者可以在禁用傳輸之前向調用者傳輸幾個視頻幀。 我通過使用Frida來改變被叫方發送的SDP來測試,然后我嘗試了很多方法來贏得比賽。結果發現贏得比賽相當困難,我花了大概兩周的時間,試圖找出如何減緩視頻禁用呼叫的速度,以便給連接時間建立起來。最后,我發送了多個offer,并在offer中添加候選人減少連接時間,因為網絡連接已經建立。然后,我通過對等連接的數據通道發送了許多需要長時間處理的消息,以減緩視頻軌道的禁用。數據消息的處理與禁用視頻軌線程隊列上是一樣的,所以發送數據消息就把禁用視頻所需要的隊列和許多其他條目一起填滿了,延遲了視頻軌被禁用的時間。 這個bug在2020年12月被修復,刪除了onSetSuccess中的異步調用。雖然Duo總體上設計的信令能有效防止視頻從被叫方傳輸到呼叫方,但異步實現設計引入了問題。異步信令的實現在移動應用上越來越常見,因為有很多不可預知的情況下,WebRTC需要在網絡或對等體上進行等待,將函數調用分離到不同的線程中,意味著一次調用的延遲不會影響到不相關的功能。然而異步調用使得對狀態機在所有情況下的表現進行建模變得更加困難,因此在WebRTC信令中加入異步調用是非常重要的。在本例中,禁用視頻軌道的異步調用在性能方面沒有增加任何東西,因為禁用軌道的任何調用都沒有理由阻塞,onSetSuccess已經在自己的線程中運行,可以讓位于更高優先級的線程。平衡異步調用的風險和收益是很重要的,不要濫竽充數。 Facebook Messenger 我在2020年10月研究了Facebook Messenger。這是一個相當具有挑戰性的目標,需要大量的反向工程。退一步講,WebRTC在幾種編程語言中都有綁定去允許它集成到使用該語言的應用程序中。大多數集成WebRTC的Android應用都使用Java綁定。這使得研究信令狀態機變得相當直接,因為重要的Java函數,如setLocalDescription(處理報價和應答)、addRemoteIceCandidate(處理候選者)和addTrack(將軌道添加到連接中)可以在Frida中掛起,并記錄下來進行分析,可以直接使用這些調用來改變攻擊者設備的行為。 Facebook Messenger并沒有使用Java綁定來集成WebRTC,而是使用C++綁定。此外,它靜態地將WebRTC鏈接到一個更大的庫(librtcR20.so,很可能就是本文提到的rsys庫),所以調用綁定的符號會被剝離,使其難以掛接。此外,Facebook Messenger在傳輸SDP之前會將SDP序列化成另一種格式,很難通過監控流量來確定信令的工作情況。 我最終意識到要想弄清楚Facebook Messenger信令的工作原理,唯一合理的方法就是弄清楚它的網絡協議。值得慶幸的是,Facebook已經公開表示他們使用的是fbthrift,是thrift的一個分支。我把librtcR20.so庫加載到IDA中,看看能不能找到它調用到thrift庫的地方,但雖然有一些調用,但看起來代碼大部分是靜態鏈接的。最后我想明白是因為thrift每實現一個協議都會生成序列化代碼,所以大部分序列化和反序列化代碼最后都會和協議處理代碼一起編譯。所以我決定編譯fbthrift,做一個示例序列化器,并在IDA中查看它,這樣我就可以對編譯后的fbthrift序列化器有一個印象。我注意到,在序列化過程中,對象的成員是通過調用一個叫做writeFieldBegin的方法來序列化的。當這個方法被調用時,字段名是必需的,盡管它通常不包含在序列化輸出中。所以我在librtcR20中尋找了一個頻繁調用的函數,該函數使用不同的字符串參數,似乎是合理的字段名。符合這個標準的函數并不多,所以我能夠確定writeFieldBegin。
此時,我可以發現很多地方的對象都是序列化的,需要確定哪一個是用來設置WebRTC調用的消息。 早些時候,我注意到庫中有一個名為P2PCall::OnP2PMessageFromPeer的方法(注意,這個方法的符號是被剝離的,但當它被調用時,方法名會被記錄下來)。這似乎是一個可能會處理反序列化消息的地方。搜索字符串 "P2PMessage",我發現了一個名為P2PMessageRequest的類型的序列化代碼。我認為這就是創建調用設置消息的地方。 Thrift序列化代碼是根據Thrift定義文件中的類定義生成的。根據傳遞給writeFieldBegin的字段名和類型,可以慢慢地對這個類型的完整thrift定義進行逆向工程。這是一項繁瑣的工作,因為定義相當長,而且代碼被混淆了,使得寄存器的使用不一致,所以我不相信任何自動化的方法都是準確的。 以下是序列化代碼的示例。
需要注意的是它從一個Extmap類型的對象中寫入了兩個字段。第一個字段名為id,是必填字段。寫代碼的函數如下。
寫的字段標識符為1,字段類型為8,翻譯成i32(32位整數)。第二個字段是可選字段,寫它的寄存器在下面的代碼中設置。
將字段名設置為uri,字段標識符設置為2,字段類型設置為8(也是i32)。所有這些,這段代碼可以用下面的thrift定義來表示。
struct Extmap{ 1: i32 id 2: optional i32 uri} 在對P2PMessageRequest類型的每個字段進行類似的逆向工程后,我有了一個完整的thrift定義,可以在這里找到。 我用這個thrift定義做了兩件事。首先,我用它來確定C++中P2PMessageRequest類型的布局。這是極有價值的,因為它允許我將結構定義加載到IDA中,并正確命名每一個字段。這讓我更容易理解P2PCall::OnP2PMessageFromPeer中如何處理傳入消息。fbthrift可以直接從thrift定義中生成C++頭文件,但這些文件非常長,包含了很多不必要的定義,無法被IDA處理。所以我最后把生成的源碼編譯后加載到IDA中,然后導出結構定義,導入到另一個已經加載了librtcR20.so的IDA實例中。在我的編譯中,有幾個字段的大小與Facebook的不同,但很接近,可以通過一些修改讓它工作。 下圖是一個在IDA中反編譯并導入了thrift定義的代碼例子,讓大家了解一下它對消息對象的處理有多容易。
我還能夠解碼并生成通過網絡發送的消息。為了做到這一點,我從Python中的thrift定義中生成了序列化代碼,因為thrift支持多種語言的代碼生成。然后,當使用Frida Python在Facebook Messenger中掛鉤函數時,我能夠導入這些代碼 然后我需要找到處理傳入的P2PMessageRequest消息的代碼。因為這些消息是由本地代碼處理的,而大多數Facebook消息是由Java代碼處理的,所以我找了一個有合適名字的本地調用,我找到了com.facebook.webrtc.WebrtcEngine.onThriftMessageFromPeer。并把這個方法和Frida掛上鉤,在生成的反序列器中輸入它的字節數組參數,它就能對傳入的消息進行解碼。 我發現了一個類似的方法,用來發送thrift消息,sendThriftToPeer(這個方法的類名被混淆了,在每個版本的Facebook Messenger中都會改變,但可以通過grepping應用程序的smali找到它)。我將這個方法與Frida連接起來,并在生成的反序列化器中輸入它的字節數組參數,它對傳入的消息進行解碼。 現下我能夠理解Facebook Messenger的信令狀態機。取決于用戶在哪里登錄到Facebook Messenger可以有兩種不同的方式可以發生信令。如果用戶在多個設備或瀏覽器上登錄,那么在被叫人與他們的設備交互之前,幾乎不會發生什么。報價、應答和候選人被交換,但它們被被叫者設備存儲,直到被叫者用戶接聽電話才會被處理。因為否則Facebook Messenger不知道要連接到什么設備。 如果被叫人只在一個設備上登錄,狀態機就比較有意思了。
在這種情況下,Facebook Messenger在收到提議時立即啟用跟蹤,但改變了提議,使所有傳出的流都不活躍。然后,它用用戶與設備交互時它們處于活躍狀態的提議來替換。 我擔心可能會有繞過改變報價的方法,但我看了一下這是如何做到的,雖然我一般不建議使用除了添加或禁用軌道以外的任何東西來禁用輸入設備傳輸,但這是相當強大的。在SDP解碼成一個內部的WebRTC對象后,報價就被改變了,而且直接對這個對象進行修改,這就消除了解析錯誤的可能性。 在研究如何處理接收的消息時,我注意到除了報價、回答和候選人之外的許多消息類型在電話被接聽之前就被處理了。有一種類型很突出,叫做SdpUpdate。當收到SdpUpdate消息時,通過調用setLocalDescription更新本地的offer或應答。 這個消息類型發送到上面的狀態機時并沒有任何作用,因為它已經在存儲SDP并等待調用setLocalDescription。但在用戶登錄兩個設備的情況下,它導致了setLocalDescription被調用,并啟動了音頻連接。 目前還不清楚SdpUpdate消息類型在Facebook Messenger中的用途。在我的測試設備上嘗試了許多場景,包括網絡切換,但無法在正常使用中生成一個。無論如何,很明顯在接聽電話之前,并沒有打算讓這種消息類型被接收。這與上面描述的Signal bug類似,它與應用程序使用WebRTC無關,而是由于在處理輸入時遺漏了一個檢查,會導致狀態轉換。 T該漏洞已于2020年11月通過服務器變更修復,防止在呼叫連接之前發送該消息類型。 其他應用 我還查看了其他一些應用程序,它們的狀態機沒有發現問題。我在2020年8月查看了Telegram,就在視頻會議被添加到應用程序之后。沒有發現任何問題主要是因為該應用在被叫人接聽電話之前不會交換報價、應答或候選人。我在2020年11月研究了Viber,沒有發現他們的狀態機有任何問題,盡管逆向工程應用程序的挑戰使這個分析不像我研究的其他應用程序那么嚴格。 探討 我調查的大多數呼叫狀態機都存在邏輯漏洞,允許音頻或視頻內容在未經被叫方同意的情況下從被叫方傳輸給呼叫方。這顯然是在保護WebRTC應用安全時經常被忽視的一個領域。 大多數錯誤似乎不是由于開發人員對WebRTC功能的誤解造成的。相反,它們是由于狀態機的實現方式出現了錯誤。也就是說,對這些類型的問題缺乏認識可能是一個因素。很少有WebRTC文檔或教程明確討論從用戶的設備上傳輸音頻或視頻時需要得到用戶的同意。 許多狀態機在如何處理調用設置方面都有不必要的復雜性,這也是一個因素。不必要的線程處理、對模糊特性的依賴以及大量的狀態和輸入類型增加了在信號狀態機中發生此類漏洞的可能性。 此外,值得注意的是,我沒有研究這些應用程序的任何群組呼叫功能,所有報告的漏洞都是在對等呼叫中發現的。這是今后工作的一個領域,可能會發現更多的問題。 結論 我調查了7個視頻會議應用程序的信令狀態機,發現了5個漏洞,這些漏洞可以讓呼叫方設備強制被叫方設備傳輸音頻或視頻數據。這些漏洞后來都被修復了。目前還不清楚為什么這是一個如此普遍的問題,但缺乏對這類漏洞的認識以及信令狀態機不必要的復雜性可能是一個因素。信令狀態機是視頻會議應用中一個令人關注且未被充分調查的攻擊面,隨著進一步的研究,很可能會發現更多的問題。
責任編輯:lq
-
音頻
+關注
關注
29文章
2884瀏覽量
81668 -
SDP
+關注
關注
0文章
35瀏覽量
13180 -
WebRTC
+關注
關注
0文章
57瀏覽量
11264
原文標題:對于短信平臺呼叫狀態機的調查
文章出處:【微信號:livevideostack,微信公眾號:LiveVideoStack】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論