
Análisis educativo en profundidad de los bundles de aplicaciones de macOS, los archivos plist y el comportamiento de los procesos launchd, con notas de seguridad ofensiva sobre el empaquetado de payloads como archivos .app y la evasión de la monitorización del árbol de procesos.
Transicionar a macOS desde Linux o Windows puede sentirse como caminar por una tierra nueva y desconocida. Dado que Linux es de código abierto y Windows está bien documentado y es muy popular (y macOS no es ninguna de las dos cosas, exactamente), macOS puede resultar desafiante en ocasiones. En esta entrada de blog, pretendo hablar sobre algunas de las primeras cosas que podrías notar en macOS: ¡Apps, Apps por todas partes!
Si vienes de un entorno Windows o Linux, el concepto de Apps puede parecer extraño. Todos sabemos que los hilos son "unidades de ejecución" y que los procesos son contenedores de hilos con su propio espacio de direcciones; ¿qué más hay que saber? Bueno, los procesos rara vez se despliegan en un único archivo. Tanto en Windows como en Linux hay muchas cosas que el código podría necesitar para funcionar; algunas de ellas son:
.dll, .so). Por ejemplo, la biblioteca de runtime de C (msvcr<version>.dll en Windows, libc-<version>.so) así como otras dependencias.PE, que tiene directorios; uno de ellos es el directorio de recursos (incluso algo documentado aquí) que puede contener recursos (imágenes, cadenas y otros). Los recursos también podrían cargarse dinámicamente desde el disco, por supuesto.PE (lee aquí) o en archivos de catálogo (es decir, externamente).xml, ini, json) y el Registro de Windows.Bueno, macOS pone un fuerte énfasis en los Application Bundles. La idea es empaquetar (casi) todo lo necesario para que el programa se ejecute en una estructura de directorios, incluyendo recursos, información de localización, etc. Por supuesto, no todo puede empaquetarse de forma ordenada (como la biblioteca de runtime de C, por ejemplo), pero eso sigue significando que las cosas están empaquetadas de forma ordenada: no hay necesidad de navegar por un enorme Registro o leer páginas de manual para ubicaciones oscuras de archivos de configuración. Los application bundles son simplemente directorios que terminan en .app, aunque la interfaz de usuario oculta la extensión .app (y el hecho de que es un directorio).
Desde la perspectiva de un atacante, esto es interesante, ya que un Application Bundle puede tener iconos arbitrarios y ocultar la extensión .app; la entrega de malware podría lograrse engañando a un usuario desprevenido para que haga clic en dicha app. Por ejemplo, piensa en un archivo Resume.app con un icono de PDF.
La estructura de directorios de un Application Bundle puede examinarse fácilmente, obviamente con la App Calculator integrada:
jbo@McJbo ~ % cd /System/Applications/Calculator.app
jbo@McJbo Calculator.app % ll
total 0
drwxr-xr-x 3 root wheel 96 Mar 17 21:34 .
drwxr-xr-x 43 root wheel 1376 Mar 17 21:34 ..
drwxr-xr-x 9 root wheel 288 Mar 17 21:34 Contents
jbo@McJbo Calculator.app % cd Contents
jbo@McJbo Contents % ll
total 16
drwxr-xr-x 9 root wheel 288 Mar 17 21:34 .
drwxr-xr-x 3 root wheel 96 Mar 17 21:34 ..
-rw-r--r-- 1 root wheel 2147 Mar 17 21:34 Info.plist
drwxr-xr-x 3 root wheel 96 Mar 17 21:34 MacOS
-rw-r--r-- 204 root wheel 8 Mar 17 21:34 PkgInfo
drwxr-xr-x 4 root wheel 128 Mar 17 21:34 PlugIns
drwxr-xr-x 54 root wheel 1728 Mar 17 21:34 Resources
drwxr-xr-x 3 root wheel 96 Mar 17 21:34 _CodeSignature
-rw-r--r-- 1 root wheel 461 Mar 17 21:34 version.plist
jbo@McJbo Contents % cd MacOS
jbo@McJbo MacOS % ll
total 344
drwxr-xr-x 3 root wheel 96 Mar 17 21:34 .
drwxr-xr-x 9 root wheel 288 Mar 17 21:34 ..
-rwxr-xr-x 1 root wheel 540912 Mar 17 21:34 Calculator
jbo@McJbo MacOS %
Como puedes ver, Calculator.app es un directorio. Dentro hay un único elemento: otro directorio llamado Contents.
Bajo Contents hay múltiples elementos:
Info.plist - contiene metadatos de la App. Más sobre esto más adelante.MacOS - contiene el ejecutable principal de la App (como se puede ver en el tercer listado de directorio).PkgInfo - no obligatorio. Un archivo binario que contiene información del paquete.PlugIns - no obligatorio. Un directorio que podría contener plugins para la App. Calculator tiene dos: uno para "Básica y científica" y otro para "Hexadecimal" (realmente no sé por qué hicieron esa separación, ni me importa).Resources - no obligatorio. Como su nombre sugiere, contiene recursos. Puedes encontrar varios elementos allí, incluyendo un archivo .icns con iconos, así como directorios con el sufijo .lprroj que están relacionados con la localización._CodeSignature - no obligatorio. Como su nombre sugiere - contiene información de firma de código.version.plist - no obligatorio, contiene información de versión.Ten en cuenta que hay muy pocos elementos que sean oficialmente requeridos. De hecho, ¡podemos crear nuestra primera App sin siquiera compilar nada!
Pero primero debemos hablar sobre ese archivo Info.plist.
Cuanto más miras macOS, más encontrarás esos extraños archivos. No son más que archivos de configuración glorificados.
Siempre tendrán una extensión .plist, que es solo una forma abreviada de llamar a su nombre formal: archivos Property list.
Desafortunadamente, hay 3 formatos diferentes de plist mantenidos por Apple:
xml, legible por humanos.json, no muy utilizado.bplist como magic number.Afortunadamente, hay una utilidad llamada plutil que soporta todos los formatos. Para mostrar un archivo plist, simplemente usa plutil -p. Por ejemplo:
jbo@McJbo Contents % plutil -p Info.plist | head -n 20
{
"BuildMachineOSBuild" => "22A380007"
"CFBundleDevelopmentRegion" => "English"
"CFBundleExecutable" => "Calculator"
"CFBundleGetInfoString" => "10.14, Copyright © 2000-2018, Apple Inc."
"CFBundleHelpBookFolder" => "Calculator.help"
"CFBundleHelpBookName" => "com.apple.Calculator.help"
"CFBundleIconFile" => "AppIcon"
"CFBundleIconName" => "AppIcon"
"CFBundleIdentifier" => "com.apple.calculator"
"CFBundleInfoDictionaryVersion" => "6.0"
"CFBundleName" => "Calculator"
"CFBundlePackageType" => "APPL"
"CFBundleShortVersionString" => "10.16"
"CFBundleSignature" => "????"
"CFBundleSupportedPlatforms" => [
0 => "MacOSX"
]
"CFBundleVersion" => "223"
"CTIgnoreUserFonts" => 1
jbo@McJbo Contents %
También hay funcionalidades de conversión integradas en plutil; no las demostraremos por ahora.
Apple documenta varios requisitos en el Info.plist de una App, pero hay muy pocos campos que sean realmente obligatorios. Aquí hay algunos campos interesantes:
CFBundleExecutable - el nombre del ejecutable principal, que se espera que esté en el directorio MacOS.CFBundleIconFile - el nombre del archivo de icono. No obligatorio.CFBundleIdentifier - un identificador para el App Bundle. Apple recomienda usar una notación DNS inversa (por ejemplo, com.apple.calculator).CFBundleName - el nombre del bundle.Con eso en mente, podemos crear nuestra primera y asombrosa app, ¡sin siquiera programar! Echa un vistazo:
#!/bin/zsh
# Create Bundle structure
mkdir -p ./MyApp.app/Contents/MacOS
# Create main executable file - a shell script in our case
cat <<EOF > ./MyApp.app/Contents/MacOS/MyApp
#!/bin/zsh
osascript -e 'tell app "Finder" to display dialog "Hello from MyApp!"'
EOF
chmod +x ./MyApp.app/Contents/MacOS/MyApp
# Create the Info.plist file
cat <<EOF > ./MyApp.app/Contents/Info.plist
<?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>CFBundleExecutable</key>
<string>MyApp</string>
<key>CFBundleIdentifier</key>
<string>com.myapp</string>
<key>CFBundleName</key>
<string>MyApp</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
</dict>
</plist>
EOF
Esto creará una nueva app llamada MyApp: hacer clic en ella simplemente ejecutará el script zsh (llamado MyApp).
Ten en cuenta que usa osascript, que es un intérprete de AppleScript y toda una caja de pandora, pero solo mostrará un diálogo que dice Hello from MyApp!. Obviamente, podrías escribir código arbitrario en ese archivo de shell zsh. Las versiones más nuevas de macOS podrían mostrar un aviso pidiendo permiso para que zsh llame a osascript; hablaremos de por qué sucede en un próximo post, pero ten en cuenta que no preguntará después de una primera aprobación.
Lanzar una App significa que aún se crea un proceso; obviamente, el apuntado por CFBundleExecutable. ¿Bajo qué proceso se ejecuta? Veamos:
jbo@McJbo ~ % open -a Calculator
jbo@McJbo ~ % ps -A -j | grep Calculator | grep -v grep
jbo 12067 1 12067 0 1 S ?? 0:00.41 /System/Applications/Calculator.app/Contents/MacOS/Calculator
jbo@McJbo ~ %
El comando open es equivalente a hacer doble clic en la App Calculator; es bastante fascinante y hablaremos de ello pronto.
Ahora que Calculator está en ejecución, usamos ps para mostrar los procesos en ejecución. Como se esperaba, /System/Applications/Calculator.app/Contents/MacOS/Calculator es el proceso que se ejecuta y tiene un PID de 12067. Sin embargo, ¡su ID de proceso padre es 1!
El ID de proceso 1 en macOS es /sbin/launchd. Es el "administrador de daemons/agentes a nivel de sistema y por usuario". Puedes imaginarlo como services.exe (si vienes de un entorno Windows) o systemd (si estás familiarizado con Linux). Además de gestionar servicios (que en macOS se llaman Launch Agents y Launch Daemons), también es el padre de todas las Applications, razón por la cual obtener un árbol de procesos significativo en macOS es un desafío.
Curiosamente, aún podrías ejecutar la App Calculator solo como un proceso invocando directamente /System/Applications/Calculator.app/Contents/MacOS/Calculator, pero no será hijo de launchd:
jbo@McJbo ~ % /System/Applications/Calculator.app/Contents/MacOS/Calculator &
[1] 12502
jbo@McJbo ~ % 2023-04-04 15:54:22.987 Calculator[12502:1297087] XType: XTFontStaticRegistry is enabled by Info.plist.
jbo@McJbo ~ % ps -A -j | grep Calculator | grep -v grep
jbo 12502 951 12502 0 1 SN s000 0:00.31 /System/Applications/Calculator.app/Contents/MacOS/Calculator
jbo@McJbo ~ % echo $$
951
jbo@McJbo ~ %
Efectivamente, Calculator se ejecuta sin problemas, pero ahora es el proceso hijo de nuestra terminal.
Una cosa a tener en cuenta en el comportamiento que hemos visto es que los atacantes podrían usarlo para diferentes propósitos. Por ejemplo, los atacantes pueden salirse fácilmente del árbol de procesos para evadir herramientas de seguridad, así como abusar de vulnerabilidades lógicas (lee mi informe sobre la vulnerabilidad de escape del sandbox de macOS si tienes tiempo).
Hay otras responsabilidades interesantes de launchd (lee sobre LaunchAgents y LaunchDaemons), pero no hablaremos de ellas por ahora.
Aquí te he mostrado un tipo de bundle, pero hay muchos más (no es una lista completa):
.app - ya vimos este; son Application Bundles que son contenedores para Apps..framework - contiene Frameworks, que son bundles cargables. Sí, en macOS puedes llamar a dlopen sobre un archivo cargable (.dylib) o cargar un bundle framework completo (con recursos, código, etc.)..kext - contiene kernel extensions, que son bundles cargables, pero para el kernel de macOS. En versiones recientes del sistema, Apple realmente se esfuerza mucho por reducir el número de extensiones de kernel..plugin - como su nombre sugiere, un contenedor para plugins.Esta es la primera de una serie de publicaciones breves destinadas a ayudar a la gente a hacer la transición hacia la investigación en macOS.
Lo primero que noté, desde una perspectiva de seguridad ofensiva, fue lo fácil que es empaquetar payloads en una bonita estructura de app.
Sin embargo, las cosas no son tan simples: en las próximas publicaciones descubriremos que lograr ejecución de código no es tan trivial debido a las muchas características de seguridad de macOS.
¡Mantente atento!
Jonathan Bar Or (https://jonathanbaror.com)