作者:SungLin@知道創宇404實驗室
時間:2019年10月23日

0x00

新加坡安全研究員Awakened在他的博客中發布了這篇[0]對whatsapp的分析與利用的文章,其工具地址是[1],并且演示了rce的過程[2],只要結合瀏覽器或者其他應用的信息泄露漏洞就可以直接在現實中遠程利用,并且Awakened在博客中也提到了:

1、攻擊者通過任何渠道將GIF文件發送給用戶其中之一可以是通過WhatsApp作為文檔(例如,按“Gallery”按鈕并選擇“Document”以發送損壞的GIF)

如果攻擊者在用戶(即朋友)的聯系人列表中,則損壞的GIF會自動下載,而無需任何用戶交互。

2、用戶想將媒體文件發送給他/她的任何WhatsApp朋友。因此,用戶按下“Gallery”按鈕并打開WhatsApp Gallery以選擇要發送給他的朋友的媒體文件。請注意,用戶不必發送任何內容,因為僅打開WhatsApp Gallery就會觸發該錯誤。按下WhatsApp Gallery后無需額外觸摸。

3、由于WhatsApp會顯示每個媒體(包括收到的GIF文件)的預覽,因此將觸發double-free錯誤和我們的RCE利用。

此漏洞將會影響WhatsApp版本2.19.244之前的版本,并且是Android 8.1和9.0的版本。

我們來具體分析調試下這個漏洞。

0x01

首先呢,當WhatsApp用戶在WhatsApp中打開“Gallery”視圖以發送媒體文件時,WhatsApp會使用一個本機庫解析該庫,libpl_droidsonroids_gif.so以生成GIF文件的預覽。libpl_droidsonroids_gif.so是一個開放源代碼庫,其源代碼位于[3],新版本的已經修改了decoding函數,為了防止二次釋放,在檢測到傳入gif幀大小為0的情況下就釋放info->rasterBits指針,并且返回了:

而有漏洞的版本是如何釋放兩次的,并且還能利用,下面來調試跟蹤下。

0x02

Whatsapp在解析gif圖像時會調用Java_pl_droidsonroids_gif_GifInfoHandle_openFile進行第一次初始化,將會打開gif文件,并創建大小為0xa8的GifInfo結構體,然后進行初始化。

之后將會調用Java_pl_droidsonroids_gif_GifInfoHandle_renderFrame對gif圖像進行解析。

關鍵的地方是調用了函數DDGifSlurp(GifInfo *info, bool decode, bool exitAfterFrame)并且傳入decode的值為true,在未打補丁的情況下,我們可以如Awakened所說的,構造三個幀,連續兩個幀的gifFilePtr->Image.Width或者gifFilePtr->Image.Height為0,可以導致reallocarray調用reallo調用free釋放所指向的地址,造成double-free:

然后android中free兩次大小為0xa8內存后,下一次申請同樣大小為0xa8內存時將會分配到同一個地址,然而在whatsapp中,點擊gallery后,將會對一個gif顯示兩個Layout布局,將會對一張gif打開并解析兩次,如下所示:

所以當第二次解析的時候,構造的幀大小為0xa8與GifInfo結構體大小是一致的,在解析時候將會覆蓋GifInfo結構體所在的內存。

0x03

大概是這樣,和博客那個流程大概一致:

第一次解析:

申請0xa8大小內存存儲數據

第一次free

第二次free

..

.. 第二次解析:

申請0xa8大小內存存儲info

申請0xa8大小內存存儲gif數據->覆蓋info

Free

Free

..

..

最后跳轉info->rewindFunction(info)

X8寄存器滑到滑塊指令

滑塊執行我們的代碼

0x04

制作的gif頭部如下:

解析的時候首先調用Java_pl_droidsonroids_gif_GifInfoHandle_openFile創建一個GifInfo結構體,如下所示:

我們使用提供的工具生成所需要的gif,所以說newRasterSize = gifFilePtr->Image.Width * gifFilePtr->Image.Height==0xa8,第一幀將會分配0xa8大小數據

第一幀頭部如下:

接下來解析到free所需要的幀如下,gifFilePtr->Image.Width為0,gifFilePtr->Image.Height為0xf1c,所以newRasterSize的大小將會為0,reallocarray(info->rasterBits, newRasterSize, sizeof(GifPixelType))的調用將會free指向的info->rasterBits

連續兩次的free掉大小為x0寄存器指向的0x6FDE75C580地址,大小為0xa8,而x19寄存器指向的0x6FDE75C4C0,x19寄存器指向的就是Info結構體指針

第一次解析完后info結構體數據如下,info->rasterBits指針指向了0x6FDE75C580,而這里就是我們第一幀數據所在,大小為0xa8:

經過reallocarray后將會調用DGifGetLine解碼LZW編碼并拷貝到分配內存:

第一幀數據如下,info->rasterBits = 0x6FDE75C580

在經過double-free掉0xa8大小內存后,第二次解析中,首先創建一個大小為0xa8的info結構體,之后將會調用DDGifSlurp解碼gif,并為gif分配0xa8大小的內存,因為android的兩次釋放會導致兩次分配同一大小內存指向同一地址特殊性,所以x0和x19都指向了0x6FDE75C580,x0是gif數據,x19是info結構體:

此時結構體指向0x6FDE75C580

之后經過DGifGetLine拷貝數據后,我們gif的第一幀數據將會覆蓋掉0x6FDE75C580,最后運行到函數末尾,調用info->rewindFunction(info)

此時運行到了info->rewindFunction(info),x19寄存器保存著我們覆蓋了的info指針,

此時x8寄存器指向了我們需要的指令,在libhwui中:

此時我們來分析下如何構造的數據,在我的本機上泄露了倆個地址,0x707d540804和0x707f3f11d8,如上所示,運行到info->rewindFunction(info)后,x19存儲了我們覆蓋的數據大小為0xa8,匯編代碼如下:

LDR    X8,[X19,#0X80]
MOV   X0,X19
BLR    X8

所以我們需要泄露的第一個地址要放在X19+0X80處為0x707d540804,而0x707d540804的指令如下,所以以如下指令作為跳板執行我們的代碼:

LDR    X8,[X19,#0X18]
ADD   X0,X19,#20
BLR    X8

所以剛好我們x19+0x18放的是執行libc的system函數的地址0x707f3f11d8,而x19+20是我們執行的代碼所在位置:

提供的測試小工具中,我們將會遍歷lib庫中的指令直到找到我們所需滑板指令的地址:

還有libc中的system地址,將這兩個地址寫入gif

跳轉到libhwui后,此地址指令剛好和我們構造的數據吻合

X8寄存器指向了libc的system調用

X0寄存器指向我們將要運行的代碼:

0x05

參考鏈接如下:

[0] https://awakened1712.github.io/hacking/hacking-whatsapp-gif-rce
[1] https://github.com/awakened1712/CVE-2019-11932
[2] https://drive.google.com/file/d/1T-v5XG8yQuiPojeMpOAG6UGr2TYpocIj/view
[3] https://github.com/koral--/android-gif-drawable/releases


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.jmbmsq.com/1061/