使用WebRTC开发Android Messenger:第2部分
這是一個由三部分組成的系列文章,內容涉及:利用WebRTC中的BUG和利用Messenger應用程序。本系列文章重點闡述了當應用程序不能應用于WebRTC補丁程序以及通信和安全問題通知中斷時可能出問題的方面。
文 /?Natalie Silvanovich
原文鏈接:
https://googleprojectzero.blogspot.com/2020/08/exploiting-android-messengers-part-2.html
?
Part 2: A Better Bug
在使用WebRTC開發Android Messenger:第1部分中,我探討了是否有可能在RTP處理中使用兩個內存損壞bug來利用WebRTC。當我成功移動指令指針時,我無法破解ASLR,因此我決定尋找更適合此目的的漏洞。
?
usrsctp
我首先瀏覽了過去提交的WebRTC bugs,以查看是否有可能破壞ASLR。 即使很早就修復了bug,它也表明可能在何處發現了類似的漏洞。這樣的bug是CVE-2020-6831,該漏洞在usrsctp中的越界讀取。
usrsctp是WebRTC使用的流控制傳輸協議(SCTP)的實現。使用WebRTC的應用程序可以打開數據通道,該通道允許將文本或二進制數據從對等方傳輸。數據通道通常用于允許在視頻通話期間交換文本消息,或在發生某些事件時告訴對等方,例如另一個對等方禁用其攝像頭。? SCTP是數據通道的基礎協議。在WebRTC中,SCTP類似于RTP,其中RTP用于音頻和視頻內容,SCTP用于數據。
我花了一些時間檢查usrsctp代碼中的漏洞。 我最終找到了CVE-2020-6831,這是從usrsctp中的堆棧緩沖區溢出。該bug使攻擊者可以完全控制溢出的大小和內容。 Samuel Gro?建議,這個bug可以用來破壞ASLR,方法是覆蓋堆棧cookie,然后一次覆蓋一個字節的返回地址,并根據應用程序是否崩潰來檢測值是否正確。不幸的是,事實證明,此bug無法通過WebRTC訪問,因為它需要客戶端套接字連接到偵聽套接字,而在WebRTC中,兩個套接字都是客戶端套接字。
我一直在尋找,最終找到了CVE-2020-6514。 這是WebRTC如何與usrsctp交互的一個非常不尋常的bug。? usrsctp支持自定義傳輸,在這種情況下,集成商需要為每個連接提供一對無效指針,以提供源地址和目標地址。 這些指針的未取消引用的值隨后被usrsctp用作地址,這意味著該值包含在某些數據包中。 在WebRTC中,地址指針設置為WebRTC使用的SctpTransport實例的地址。 結果是在每個SCTP連接期間,此對象在內存中的位置將發送到遠程對等方。從技術上講,這是WebRTC中的bug,盡管usrsctp的設計也有缺陷,因為對自定義地址使用void*類型會強烈鼓勵集成器使用該值的指針,盡管這是不安全的。
我希望此bug足以破解ASLR,但事實并非如此。對于漏洞利用,我需要一個已加載庫的位置以及堆的位置,因此我在Android設備上進行了一系列測試,以查看這些位置之間是否存在任何關聯,結果是沒有任何關聯。堆指針的位置不足以確定加載的庫的位置。
?
我一直在尋找,我注意到usrsctp處理ASCONF塊的方式中存在一個漏洞,這些塊用于管理動態IP地址。該錯誤的來源如下:
if (param_length > sizeof(aparam_buf)) { SCTPDBG(SCTP_DEBUG_ASCONF1, "handle_asconf: param length (%u) larger than buffer size!\n", param_length); sctp_m_freem(m_ack); return; } if (param_length <= sizeof(struct sctp_paramhdr)) { SCTPDBG(SCTP_DEBUG_ASCONF1, "handle_asconf: param length (%u) too short\n", param_length); sctp_m_freem(m_ack); } |
請注意,對sctp_m_freem的第二次調用缺少了一個返回值,因此m_ack變量可以在釋放后使用。發現此漏洞后,我注意到該bug已在usrsctp和WebRTC的較新版本中進行了修補。后來我得知,另一位Google員工Mark Wodrich在2019年9月19日將其報告為usrsctp中的Bug 376。
?
Revealing Memory with Bug376
在分析一個“后用”bug時,兩個重要的問題是釋放了什么,以及如何使用它。在Bug 376中,釋放的對象是一個mbuf結構,一種用于存儲入站和出站數據包內容的類型。
mbuf結構從一個子結構m_hdr開始,它的定義如下:
?
struct m_hdr { struct mbuf *mh_next; /* next buffer in chain */ struct mbuf *mh_nextpkt; /* next chain in queue/record */ caddr_t ?mh_data; /* location of data */ int ?mh_len; /* amount of data in this mbuf */ int ?mh_flags; /* flags; see below */ short ?mh_type; /* type of data in this mbuf */ uint8_t? ? ? ? ? pad[M_HDR_PAD];/* word align? ? ? ? ? ? ? ? ? */ } |
那么,這個結構是如何使用的呢?
查看ASCONF處理的其余部分,它最終被添加到一個出站包隊列中,以確認發送的包。
TAILQ_INSERT_TAIL(&stcb->asoc.asconf_ack_sent, ack, next); |
這使得如果將釋放的m_buf結構替換為帶有指向內存連續指針的結構(例如,CVE-2020-6514顯示的SctpTransport指針)的結構,則該錯誤很可能被用于顯示遠程對等機的內存。
?
我試圖通過發送與m_buf結構大小相同的RTP包來實現這一點。有一個很好的訣竅可以讓大量特定大小的分配在WebRTC中無法釋放。視頻包在被組合成幀之前被存儲在一個列表中,因此,如果一個幀的末尾從未被發送,它們將被永久存儲,只要沒有達到最大數量的包。不幸的是,這導致了一個意想不到的問題。WebRTC使用的OpenSSL碰巧有一些堆分配,其大小與m_buf結構的大小相同,如果它們恰好被分配到釋放的m_buf結構的位置,它們將被寫入m_buf send進程中,這出于某種原因將導致OpenSSL中的不可恢復狀態。應用程序沒有崩潰,它只會陷入某種循環中,拒絕接受更多的連接。
所以我決定在usrsctp中分配內存來代替m_buf結構會更好。SCTP允許將包含任意數量的塊的數據包發送到主機,并且在大多數情況下,它們被當作一個數據包序列來處理。更好的是,在當前數據包中的所有塊都被處理之前,添加了釋放的m_buf結構的出站數據包隊列不會發送任何數據包。這意味著應該可以發送一個包,其中包含一個觸發該錯誤的塊,然后發送一個塊,該塊將釋放的內存設置為所需的值,然后將其發送回攻擊者。由于在釋放m_buf結構和安全地重新分配內存之間不需要發生網絡通信,因此避免了OpenSSL的問題。
不幸的是,在usrsctp中對malloc的調用很少,其大小可以由傳入流量控制,并且沒有一個允許指定整個包內容。我能找到的最好的方法是處理數據流重置塊。代碼如下,為清楚起見刪除了一些部分。
if (asoc->str_reset_seq_in == seq) { len = ntohs(req->ph.param_length); number_entries = ((len - sizeof(struct sctp_stream_reset_out_request)) / sizeof(uint16_t)); tsn = ntohl(req->send_reset_at_tsn); asoc->last_reset_action[1] = asoc->last_reset_action[0]; if (...) { ... } else if (SCTP_TSN_GE(asoc->cumulative_tsn, tsn)) { /* we can do it now */ ... } else { /* ?* we must queue it up and thus wait for the TSN's ?* to arrive that are at or before tsn ?*/ struct sctp_stream_reset_list *liste; int siz; siz = sizeof(struct sctp_stream_reset_list) + (number_entries * sizeof(uint16_t)); SCTP_MALLOC(liste, struct sctp_stream_reset_list *,? siz, SCTP_M_STRESET); if (liste == NULL) { /* gak out of memory */ asoc->last_reset_action[0] = SCTP_STREAM_RESET_RESULT_DENIED; sctp_add_stream_reset_result(chk, seq, asoc->last_reset_action[0]); return; } liste->seq = seq; liste->tsn = tsn; liste->number_entries = number_entries; memcpy(&liste->list_of_streams, req->list_of_streams, number_entries * sizeof(uint16_t)); TAILQ_INSERT_TAIL(&asoc->resetHead, liste, next_resp); |
此代碼分配了liste結構,該結構可用于替換釋放的mbuf結構。它有一個非常幸運的功能,那就是與mbuf結構的mh_next屬性對齊的next_resp屬性恰好是mbuf類型。如果這是另一種類型,則會導致問題,因為usrsctp在發送數據包之前會遍歷整個mbuf鏈。
?
一個不太幸運的特性是,與mbuf結構的mh_data屬性一致的屬性恰好是當前重置序列號和傳輸序列號(TSN)。在這種方法中,兩者都要經過多次檢查。重置序列號需要完全等于初始化連接時設置的序列號(在INIT或COOKIE_ECHO塊中),還需要等于SctpTransport指針的低位四個字節。可以通過發送COOKIE_ECHO塊來通過此檢查,該塊在觸發錯誤之前將重置序列號設置為所需的值。
更具挑戰性的是在TSN上執行的檢查。它與累積TSN進行比較,后者最初被設置為與重置序列號相同的值。實際執行的比較是一個“序列號大于”,它確定一個值是在另一個值之前還是在后面,假設序列號在所有位都被設置時滾動到零。例如,如果當前序列號為0xFFFFFFFF,則值2將通過“序列號大于”檢查,但值0xFFFFFFFE和0x80000001將失敗。從傳入數據包中讀出的TSN必須是SctpTransport指針的前四個字節,而累積的TSN必須是該指針的后四個字節,因為它與重置序列號的值相同。所以這實際上是指針的兩半部分之間的比較。TSN是一個很小的數字,小于0x80,因為它是指針的頂部,所以每當指針的第31位未設置時,此比較將大致返回true,并在設置指針時大致返回所需的false結果。
指針的第31位是由ASLR隨機確定的,以及SctpTransport實例在堆上分配的位置,這意味著它被設置為大約50%的時間。通常情況下,我可以接受50%有效的漏洞攻擊,因為這意味著它很可能只需嘗試幾次就可以成功,但在這種情況下,這不是真的,因為它在同一個ASLR布局上會有一次又一次失敗的傾向。ASLR布局是在Android設備啟動時確定的,并且在重新啟動之前不會再次更改。所以我需要一種方法在重置序列號被設置之后改變累積的TSN。
?
事實證明,使用FWD_TSN塊類型是可行的,該類型允許一個對等方請求另一個對等方將其累積的TSN最多向前移動4096字節。通過重復發送此塊類型,可以將累積的TSN向前移動足夠多的位,以使第31位翻轉。這需要相當多的數據塊,但是將這些數據塊組合成更少的數據包并盡可能快地發送出去,它可以在幾秒鐘內翻轉過來。
總而言之,這個bug可以用來讓目標設備發回SctpTransport實例的內存,該實例包含指向類的vtable的指針,最后給出WebRTC庫的位置并破壞ASLR。
仔細想想,我不認為WebRTC庫是我的漏洞利用的最佳庫,因為WebRTC集成器將它靜態地與其他庫鏈接起來并使用各種工具鏈是很正常的。更容易知道libc的位置,libc來自Android系統,變化較小。所以我添加了這個bug的第二個用法,從全局偏移表讀取malloc的位置,這是從已經讀取的SctpTransport vtable的固定偏移量。這允許計算libc的位置。
?
Moving the InstructionPointer (Again)
在使用WebRTC開發Android Messenger:第1部分中,我弄清楚了如何使用RTP內存損壞錯誤來移動指令指針,但是在提交CVE-2020-6514之后,Jann Horn建議也可以使用該bug來移動指令指針。當WebRTC使用SctpTransport指針作為地址時,它不僅使用它來標識連接,而且實際上也使用它將指針強制轉換為SctpTransport類,并在發送從usrsctp接收的出站數據包時對其進行虛擬調用。
?
同時,usrsctp通常根據數據包中的標識符確定出站數據包的地址,但是唯獨在一種情況下,它需要從數據包本身提取地址:在處理COOKIE_ECHO塊時。通常,不可能將不可信的指針放在這種塊類型中,因為通常會從傳入的數據包中回顯它們,并且需要對其進行簽名。但是,Jann注意到簽名密鑰的隨機數生成非常弱。初始化usrsctp時,將調用以下代碼。
?
srandom(getpid());? |
然后通過調用rand為隨機數生成器提供種子。
啟動SCTP連接時發送的INIT塊包含用于身份驗證的隨機生成的密鑰,該密鑰由用于密鑰的同一隨機數生成器生成。我編寫了一個腳本,根據這個密鑰確定遠程PID的值,方法是對0到70000之間的每個數字調用srand,并查看哪個會導致隨機數生成器生成相同的身份驗證密鑰。然后就可以推斷出密鑰的值。
?
現在,此密鑰允許攻擊設備發送包含任何內容的COOKIE_ECHO塊,包括將地址更改為自定義指針。這允許移動指令指針,因為下一次發送出站數據包時,將對提供的任何地址進行虛擬調用,當對等方用COOKIE_ACK響應時,將立即進行虛擬調用。在上面的部分中,我還討論了如何使用COOKIE_ECHO包來更改重置序列號,同時還討論了如何實際發送它們。它是用同樣的方法。
我現在有兩種可能的方法來設置利用漏洞的指令指針。我選擇繼續使用這個,因為它使用usrsctp,這也是打破ASLR所必需的,而RTP-one使用了一個不同的特性。我覺得減少需要啟用的特性的數量可以增加它所使用的應用程序的數量,因為有時應用程序會禁用特定的WebRTC功能。
?
Putting it All Together
具有利用漏洞所需的所有必要功能后,我需要將它們全部整合在一起。我的一般策略是在已知位置的堆上創建一個假對象,然后對該對象進行虛擬調用。假對象將在同一個緩沖區中有一個假vtable,它將指向system,后者將運行shell命令。
缺少的一環是如何在已知位置填充堆內存。一種可能是使用RTP來分配與SctpTransport對象大小相同的內存,希望它在對象后面的地址或可預測的位置分配。我試過這個方法,大概50%的時間都有效,但考慮到我有辦法讀懂記憶,我想我可以做得更好。
我注意到SctpTransport類包含一個CopyOnWriteBuffer對象,名為partial_incoming_message_,它有時用于存儲傳入的SCTP數據。如果rtcp支持不完整的數據包,那么這些數據包將通過不完整的scp。這些存儲在部分“傳入”消息對象中,直到接收到數據包的其余部分。所以我想如果我通過SCTP把假對象的數據發送到目標設備,它最終會填充這個緩沖區,我可以讀取地址。(請注意,這實際上需要兩次讀取,因為在CopyOnWriteBuffer對象與其支持數據之間存在兩級間接尋址。)
我試過了,效果很好,但還有另一個問題。為了用一個假vtable創建一個假對象,這個假對象需要引用它自己,但是這個方法只允許我知道內存被寫入后的位置,并且不能更改。我仔細看了一下這個功能是如何工作的。設置緩沖區的代碼如下。
transport->partial_incoming_message_.AppendData( ??????????reinterpret_cast<uint8_t*>(data), length); ??????????... if (!(flags & MSG_EOR) && (transport->partial_incoming_message_.size() < kSctpSendBufferSize)) { ????????return 1; ??????} ... transport->invoker_.AsyncInvoke<void>( RTC_FROM_HERE, transport->network_thread_, rtc::Bind(&SctpTransport::OnInboundPacketFromSctpToTransport, transport, transport->partial_incoming_message_, params, flags)); transport->partial_incoming_message_.Clear(); |
這里發生的情況是,傳入的數據總是立即附加到partial_incoming_message_緩沖區中,然后,如果它是不完整的片段,則函數將返回。否則,它將使線程排隊以處理數據,然后清除緩沖區。
考慮到可能尚未完成的排隊線程仍然需要數據,我開始懷疑清除的工作原理。事實證明,CopyOnWriteBuffer類會保留對數據的引用,并且僅在剩余零個引用的情況下才將其刪除。否則,它將減少引用計數并為緩沖區分配當前大小的新數據。這意味著可以在寫入數據之前讀取_incoming_message_緩沖區的位置,因為它實際上是在清除期間分配的。只要由AppendData寫入的數據更短或與已清除的最大大小相同,該內存就不會被重新分配。
這允許我在一個已知的位置創建一個堆緩沖區并填充它。最后一步是找出要填充的內容。我首先用序列號填充它,然后使用它崩潰的地址來計算要更改的內存。在使用crash locations創建假vtable之后,我最終在一個到X8的分支上發生了崩潰,唯一的另一個可控寄存器是X21。X0當然被設置為假vtable的位置,因為這次崩潰是由于一個虛擬調用造成的,X1和X23也是如此。
?
令人驚訝的是,libc有一個完美的工具來應對這種情況。
?
do_nftw(char const*,int (*) …) + 0x138 LDR ? ? ? ? ? ? X0, [X23,#0x30] LDR ? ? ? ? ? ? X1, [X23,#0x70] BLR ? ? ? ? ? ? X21 |
將X23中加載的值設置為system,并將一個字符串參數復制到偽虛函數表的偏移0x30處,從而導致系統被該參數調用!
?
?為了快速概述,以下是利用該漏洞所需的步驟,依次為:
?
1. 根據INIT塊中的密鑰確定PID,然后確定秘密密鑰
2. 從SctpTransport對象讀取vtable
3. 從全局偏移量表中讀取malloc的位置
4. 用所需大小的數據填充partial_incoming_message_緩沖區
5. 清除了partial_incoming_message_緩沖區,因此分配了新緩沖區
6. 從SctpTransport對象讀取partial_incoming_message_緩沖區的地址
7. 從緩沖區結構中讀取partial_incoming_message_后備緩沖區的地址
8. 基于malloc的位置,partial_incoming_message_緩沖區填充有漏洞利用數據
9. 觸發該漏洞,先虛擬調用小工具,然后再調用系統
?
現在我發現了一個漏洞,它在WebRTC的Android應用程序示例中起作用。
LiveVideoStackCon 2020?北京
2020年10月31日-11月1日
點擊【閱讀原文】了解更多詳細信息
總結
以上是生活随笔為你收集整理的使用WebRTC开发Android Messenger:第2部分的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用WebRTC开发Android Me
- 下一篇: 当一百万名记者都嚷嚷着“Facebook