
Mergulho educacional aprofundado em app bundles do macOS, arquivos plist e comportamento do processo launchd, com notas de segurança ofensiva sobre empacotar payloads como arquivos .app e evadir o monitoramento da árvore de processos.
Fazer a transição do Linux ou Windows para o macOS pode parecer andar por uma terra estranha e desconhecida. Como o Linux é open-source e o Windows é bem documentado e muito popular (e o macOS não é exatamente nenhum dos dois), o macOS pode ser desafiador às vezes. Neste post, pretendo discutir algumas das primeiras coisas que você pode notar no macOS - Apps, Apps em todos os lugares!
Para quem vem de um ambiente Windows ou Linux, o conceito de Apps pode parecer estranho. Todos sabemos que threads são "unidades de execução" e que processos são contêineres para threads com seu próprio espaço de endereçamento -- o que há mais além disso? Bem, processos raramente são distribuídos em arquivos únicos. Tanto no Windows quanto no Linux, há muitas coisas das quais o código pode precisar para funcionar. Algumas delas são:
.dll, .so). Por exemplo, a biblioteca de runtime C (msvcr<version>.dll no Windows, libc-<version>.so) e outras dependências.PE, que possui diretórios - um deles é o diretório de recursos (até certo ponto documentado aqui) que pode conter recursos (imagens, strings e outros). Recursos também podem ser carregados do disco dinamicamente, é claro.PE (leia aqui) ou em arquivos de catálogo (ou seja, externamente).xml, ini, json) e o Registro do Windows.Bem, o macOS dá grande ênfase a Application Bundles. A ideia é empacotar (quase) tudo o que é necessário para o programa rodar em uma estrutura de diretórios - incluindo recursos, informações de localização, etc. Claro, nem tudo pode ser empacotado de forma organizada (como a biblioteca de runtime C, por exemplo) - mas ainda assim significa que as coisas ficam empacotadas de forma organizada - sem necessidade de navegar em um Registro enorme ou ler páginas de manual para localizações obscuras de arquivos de configuração. Application bundles são apenas diretórios que terminam com .app - embora a interface oculte a extensão .app (e o fato de ser um diretório).
Da perspectiva de um atacante, isso é interessante - já que um Application Bundle pode ter ícones arbitrários e esconde a extensão .app - a entrega de malware pode ser alcançada enganando um usuário desprevenido para clicar em tal app. Por exemplo, pense em um arquivo Resume.app com um ícone de PDF.
A estrutura de diretórios de um Application Bundle pode ser facilmente examinada, obviamente pela App Calculator embutida:
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 você pode ver, Calculator.app é um diretório. Dentro dele há um único item - outro diretório chamado Contents.
Dentro de Contents há vários itens:
Info.plist - contém metadados sobre a App. Mais sobre isso depois.MacOS - contém o executável principal da App (como pode ser visto na terceira listagem de diretórios).PkgInfo - não obrigatório. Um arquivo binário que contém informações do pacote.PlugIns - não obrigatório. Um diretório que pode conter plugins para a App. A Calculator tem dois - um para "Basic and Scientific" e outro para "Hexadecimal" (eu realmente não sei por que fizeram essa separação, nem me importo).Resources - não obrigatório. Como o nome sugere, contém recursos. Você pode encontrar vários itens lá, incluindo um arquivo .icns com ícones, bem como diretórios com o sufixo .lprroj relacionados à localização._CodeSignature - não obrigatório. Como o nome sugere - contém informações de assinatura de código.version.plist - não obrigatório, contém informações de versão.Observe que são muito poucos os itens oficialmente necessários. Na verdade, podemos criar nossa primeira App sem nem compilar nada!
Mas primeiro precisamos discutir o arquivo Info.plist.
Quanto mais você olha para o macOS, mais encontra esses arquivos estranhos. Eles nada mais são do que arquivos de configuração glorificados.
Eles sempre terão a extensão .plist, que é apenas uma forma abreviada de se referir ao seu nome formal: arquivos Property list.
Infelizmente, existem 3 formatos plist diferentes mantidos pela Apple:
xml, legível por humanos.json, não muito usado.bplist como magic number.Felizmente, existe um utilitário chamado plutil que suporta todos os formatos. Para exibir um arquivo plist, basta usar plutil -p. Por exemplo:
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 %
Há também funcionalidades de conversão embutidas no plutil - não vamos demonstrá-las agora.
A Apple documenta vários requisitos no Info.plist de uma App, mas são muito poucos os campos realmente obrigatórios. Aqui estão alguns campos interessantes:
CFBundleExecutable - o nome do executável principal, esperado no diretório MacOS.CFBundleIconFile - o nome do arquivo de ícone. Não obrigatório.CFBundleIdentifier - um identificador para o App Bundle. A Apple recomenda usar a notação DNS reversa (ex.: com.apple.calculator).CFBundleName - o nome do bundle.Com isso em mente, podemos criar nossa primeira app incrível, sem nem programar! Dê uma olhada:
#!/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
Isso criará uma nova app chamada MyApp - clicar nela simplesmente executará o script zsh (chamado MyApp).
Note que ela usa osascript, que é um interpretador de AppleScript e uma verdadeira caixa de Pandora, mas apenas mostrará um diálogo que diz Hello from MyApp!. Você poderia escrever código arbitrário nesse arquivo de shell zsh, obviamente. Versões mais novas do macOS podem exibir um prompt pedindo para permitir que o zsh chame o osascript - vamos discutir por que isso acontece em um post futuro, mas note que não perguntará após a primeira aprovação.
Abrir uma App significa que um processo ainda é criado - obviamente aquele apontado por CFBundleExecutable. Sob qual processo ele roda? Vejamos:
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 ~ %
O comando open é equivalente a dar um duplo clique na App Calculator - é bastante fascinante e vamos discuti-lo em breve.
Agora que a Calculator está rodando, usamos ps para mostrar os processos em execução. Como esperado, /System/Applications/Calculator.app/Contents/MacOS/Calculator é o processo que roda, e ele tem um PID de 12067. No entanto, seu ID de processo pai é 1!
O ID de processo 1 no macOS é /sbin/launchd. Ele é o "gerente de daemons/agentes por usuário e em todo o sistema". Você pode imaginá-lo como services.exe (se veio de um ambiente Windows) ou systemd (se está familiarizado com Linux). Além de gerenciar serviços (que são chamados de Launch Agents e Launch Daemons no macOS), ele também é o pai de todas as Applications, o que é uma das razões pelas quais obter uma árvore de processos significativa no macOS é desafiador.
Curiosamente, você ainda pode rodar a App Calculator apenas como um processo invocando /System/Applications/Calculator.app/Contents/MacOS/Calculator diretamente, mas ela não será filha do 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 ~ %
De fato, a Calculator roda sem problemas, mas agora é um processo filho do nosso terminal.
Uma coisa a notar no comportamento que vimos é que atacantes podem usá-lo para fins diferentes. Por exemplo, atacantes podem facilmente sair da árvore de processos para evadir ferramentas de segurança, além de abusar de vulnerabilidades de lógica (leia meu artigo sobre a vulnerabilidade de escape do Sandbox do macOS se tiver tempo).
Há outras responsabilidades interessantes para o launchd (leia sobre LaunchAgents e LaunchDaemons), mas não vamos discuti-las por enquanto.
Aqui mostrei um tipo de bundle, mas há muitos outros (não é uma lista completa):
.app - já vimos este; são Application Bundles que são contêineres para Apps..framework - contém Frameworks, que são bundles carregáveis. Sim, no macOS você pode chamar dlopen em um arquivo carregável (.dylib) ou pode carregar um bundle de framework inteiro (com recursos, código, etc.)..kext - contém kernel extensions, que são bundles carregáveis, mas para o kernel do macOS. Em versões recentes do sistema operacional, a Apple realmente se esforça para reduzir o número de extensões de kernel..plugin - como o nome sugere, um contêiner para plugins.Este é o primeiro de uma série de posts curtos com o objetivo de ajudar as pessoas a fazer a transição para a pesquisa no macOS.
A primeira coisa que notei, do ponto de vista da segurança ofensiva, foi como é fácil empacotar payloads em uma estrutura de app agradável.
As coisas não são tão simples, porém - nos próximos posts descobriremos que obter execução de código não é tão trivial devido aos muitos recursos de segurança do macOS.
Fique ligado!
Jonathan Bar Or (https://jonathanbaror.com)