
SnatchBox (CVE-2020-27935) es una vulnerabilidad y exploit de escape de sandbox que afecta a macOS hasta la versión 10.15.x
SnatchBox (CVE-2020-27935) es una vulnerabilidad de escape del sandbox que afecta a macOS hasta la versión 10.15, así como a las primeras versiones beta de macOS 11.0. El impacto más significativo de SnatchBox es que permite a un editor malicioso escapar del sandbox obligatorio de la App Store de macOS y obtener acceso completo a todos los archivos del usuario, rompiendo el modelo de seguridad de la App Store en macOS.
El hecho de que en macOS, a diferencia de iOS por ejemplo, una tarea de espacio de usuario se coloque voluntariamente en un sandbox es vulnerable por diseño. Dado que se trata de una tarea sobre la cual un autor potencialmente malicioso tiene control casi total de sus asignaciones de memoria y contenidos, y el código que se ejecuta antes de la inicialización del sandbox (por ejemplo, el propio dyld, o el runtime de Objective-C) analiza el contenido de este binario potencialmente malicioso, se pueden usar datos cuidadosamente elaborados para obtener ejecución de código antes de la inicialización del sandbox. Si el proceso nunca ejecutara ningún código, incluido el código de dyld, antes de ser forzado a un sandbox, esto no habría sido un problema, ya que la ejecución temprana de código no habría proporcionado ninguna ventaja para el ataque en ese caso. Es conceptualmente similar al bypass del sandbox de Saagar Jha, excepto que evita las mitigaciones recién introducidas y las validaciones de la App Store.
Antes de macOS 10.15, la explotación de este error es bastante simple. Se crearía un binario que contenga una categoría de Objective-C para una clase que se usa antes de la inicialización del sandbox (como OS_xpc_object) y se sobrescribiría un método (preferiblemente uno heredado para evitar advertencias en tiempo de ejecución) que se usa antes de la inicialización del sandbox (como , que se llama implícitamente al primer acceso a una clase). Debido a que las categorías se cargan antes de la inicialización del sandbox, y el primer acceso a (u otras clases víctimas adecuadas) se realiza después de cargar las categorías y antes de la inicialización del sandbox, el método proporcionado por el atacante (u otro método víctima adecuado) se llamará antes de la inicialización del sandbox, lo que permite acceder a datos fuera del contenedor, por ejemplo. Alternativamente, el atacante puede reemplazar la referencia a (que inicializa el sandbox) con una función similar a nop para (potencialmente de manera condicional) deshabilitar el sandbox incluso después de reanudar la ejecución.
+initializeOS_xpc_object+initialize_libsecinit_initializerEl runtime de Objective-C utilizado en macOS 10.15 no es vulnerable a la técnica de explotación detallada previamente, porque las categorías no se cargan antes de que se establezca didCallDyldNotifyRegister, lo que hace que nuestro método +initialize se llame solo después de que ocurra la inicialización del sandbox.
Sin embargo, map_images aún se llama en nuestro binario, lo que permite alterar los datos del runtime de maneras no deseadas que nos permitirían ejecutar código antes de la inicialización del sandbox. La explotación completa y comentada está en main.c, pero repasaré los detalles básicos aquí. Creamos una estructura de clase de Objective-C cuyo puntero data apunta a una ubicación en libxbc.dylib. Esa ubicación debe elegirse de manera que flags tenga el bit 31 (RW_REALIZED) establecido, para que el runtime no intente realizar esta clase inválida y se bloquee, y firstSubclass debe compartir su dirección con la isa de una clase que queremos sobrescribir. Otra clase (meta) heredará de esta clase inválida y proporcionará su propio método +initialize. Agregamos la subclase a __objc_nlclslist para que el runtime realice esta clase.
Cuando el runtime realiza nuestra subclase, lo que sucede antes de la inicialización del sandbox, llamará a addSubclass en nuestra superclase inválida y subclase, lo que reemplazará la isa de la víctima con un puntero a nuestra subclase, reemplazando efectivamente todos sus métodos con nuestro +initialize. Cuando se llame a nuestro método +initialize, que será antes de la inicialización del sandbox si elegimos una clase víctima adecuada, podemos reemplazar nuevamente las referencias a _libsecinit_initializer con nops (de manera condicional o no), y corregir los cambios en el runtime que hayamos hecho para reanudar la ejecución sin bloquearse más adelante.
La demostración proporcionada se puede compilar ejecutando make, creando un archivo en ~/Documents/SecretDocument.txt, y ejecutando SnatchBox.app/Contents/MacOS/SnatchBox desde la terminal (Se crea un bundle porque es requerido por com.apple.security.app-sandbox, pero sigue siendo un programa de línea de comandos). El binario compilado está firmado con com.apple.security.app-sandbox lo que normalmente impediría el acceso a ~/Documents/SecretDocument.txt (ya que no está en nuestro contenedor), pero podrá leer sus datos de todos modos. Debido a cambios en la estructura del runtime, esta demostración no funcionará en macOS 10.14 y versiones anteriores sin modificar, pero funcionará en 10.15 y 11.0 (Probado: 10.15.4, 10.15.7 y 11.0 Beta (20A5354i)). Las dos técnicas de explotación se pueden combinar para apuntar a ambas versiones del runtime, pero no se proporciona dicha demostración.
Example run:
CatalinaVM:SnatchBox lior$ make
mkdir -p SnatchBox.app/Contents/MacOS/
clang -O3 -Wall -framework Foundation main.m -o SnatchBox.app/Contents/MacOS/SnatchBox
cp Info.plist SnatchBox.app/Contents/
codesign --force --sign - SnatchBox.app --entitlements ent.xml
CatalinaVM:SnatchBox lior$ echo "Quack"> ~/Documents/SecretDocument.txt
CatalinaVM:SnatchBox lior$ codesign -d --entitlements :- SnatchBox.app/Contents/MacOS/SnatchBox
Executable=/Volumes/SharedFolders/Home/Projects/SnatchBox/SnatchBox.app/Contents/MacOS/SnatchBox
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
</dict>
</plist>
CatalinaVM:SnatchBox lior$ SnatchBox.app/Contents/MacOS/SnatchBox
Found libsecinit_initializer at 0x7fff72309124
Found libSystem.B.dylib at 0x7fff6f0de000
Found __DATA at 0x7fff984eeca0
Replacing libsecinit_initializer reference at 0x7fff984eed48 with a nop
2020-12-18 16:31:48.196 SnatchBox[804:8043] Attempting to read protected file: /Users/lior/Documents/SecretDocument.txt
2020-12-18 16:31:48.197 SnatchBox[804:8043] Escaped sandbox! The contents are: <51756163 6b0a>
Como se mencionó anteriormente, esto permite crear una aplicación de la App Store de macOS que no se ejecuta en un sandbox a pesar de que la política de la App Store lo exige. La vulnerabilidad también se puede usar en un framework, que puede ser utilizado por aplicaciones de la App Store que de otro modo serían legítimas. Por último, incluso se puede combinar con algo similar a "Xcode Ghost" para inyectar masivamente código malicioso que se ejecuta fuera del sandbox en aplicaciones de la App Store.
Apple corrigió el exploit durante la etapa de pruebas beta de macOS 11.0 agregando una llamada a malloc_size en realizeClassWithoutSwift. Esto confirma que si una clase está marcada como realizada (RW_REALIZED, como nuestra clase falsa), efectivamente tiene un puntero de datos válido, asignado con malloc, con el tamaño correcto (0x20 bytes). Si este no es el caso, el runtime abortará con un mensaje similar a realized class 0x100002078 has corrupt data pointer 0x7fff88c00948. La corrección también se aplicó a iOS, iPadOS, tvOS y watchOS; incluso si no se ven directamente afectados.