作者:SungLin@知道创宇404实验室
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