Análise estática no iOS: binário, Info.plist e entitlements
As mesmas perguntas do Android, do lado do IPA: proteções do binário, App Transport Security, entitlements e o que não deveria estar dentro do pacote.
Série · Parte 9 de 11 Segurança Mobile
- 01 O mapa da segurança mobile: OWASP MASVS, MASWE e MASTG 6 min
- 02 Arquitetura do Android: do kernel ao app 10 min
- 03 Arquitetura do iOS: do boot ao Secure Enclave 7 min
- 04 Análise estática no Android – parte 1: abrindo o APK 6 min
- 05 Análise estática no Android – parte 2: segredos no binário 4 min
- 06 Análise estática no Android – parte 3: manifesto e superfície de ataque 5 min
- 07 Análise estática no Android – parte 4: TLS, pinning e armazenamento 5 min
- 08 Scanners automáticos: o que eles pegam e o que passa batido 4 min
- 09 Análise estática no iOS: binário, Info.plist e entitlements 5 min
- 10 Análise dinâmica: o app rodando sob instrumentação 5 min
- 11 Do achado ao relatório: confirmar, classificar e registrar 4 min
Neste artigo7 seções
O Android tem o APK; o iOS tem o IPA, também um arquivo ZIP, só que com uma estrutura interna diferente e um conjunto próprio de proteções para checar. Boa parte das perguntas é parecida com as que os artigos anteriores desta série já fizeram para Android, mas as respostas vêm de ferramentas e formatos específicos da Apple.
Abrindo o pacote
Dentro de um IPA descompactado, o app em si fica numa pasta Payload/<Nome>.app/. O binário executável, que tem o mesmo nome definido na chave CFBundleExecutable do Info.plist, é o alvo principal da análise estática: é nele que ficam compiladas as strings, a lógica nativa e, como visto no artigo sobre segredos no binário, as variáveis de ambiente embutidas no build.
mkdir -p pentest-ios && cd pentest-ios
unzip -q -o example.ipa -d ipa-out
A=$(ls -d ipa-out/Payload/*.app)
B="${A}/$(/usr/libexec/PlistBuddy -c 'Print CFBundleExecutable' "${A}/Info.plist")"
As duas variáveis, A (o caminho do .app) e B (o caminho do binário dentro dele), são usadas nos comandos a seguir.
Proteções do binário
O macOS já vem com um conjunto de ferramentas de linha de comando, parte do Xcode, que permitem inspecionar essas proteções sem precisar de nada além do próprio sistema:
| Proteção | Ferramenta | O que verificar |
|---|---|---|
| PIE (Position Independent Executable) | otool -hv |
Precisa estar presente. Sem PIE, o endereço de carregamento do binário é previsível, o que facilita explorar vulnerabilidades de memória |
| Stack canary | otool -Iv (procurando ___stack_chk_guard) |
Detecta estouro de pilha (buffer overflow) antes que ele corrompa o fluxo de execução |
| ARC (Automatic Reference Counting) | otool -Iv (procurando _objc_release) |
Gerenciamento automático de memória em Objective-C, reduz uma classe inteira de bugs de memória |
Criptografia do binário (cryptid) |
otool -Vl (seção LC_ENCRYPT) |
Em um IPA baixado da App Store, o valor esperado é 1 (binário cifrado pela Apple). Builds locais ou de teste normalmente saem com cryptid 0, o que é esperado nesse contexto e não é, sozinho, uma falha |
otool -hv "${B}" | grep PIE
otool -Iv "${B}" | grep -w ___stack_chk_guard
otool -Iv "${B}" | grep -w _objc_release
otool -arch all -Vl "${B}" | grep -A4 LC_ENCRYPT
Essas quatro proteções existem no nível do compilador e do sistema operacional, então a ausência de qualquer uma delas num app atual costuma indicar uma configuração de build incomum, mais do que uma escolha deliberada.
Funções criptográficas e de manipulação de memória fracas
O mesmo otool ajuda a localizar o uso de funções historicamente associadas a vulnerabilidades: algoritmos de hash considerados fracos para uso em segurança, como MD5 e SHA-1, geradores de número pseudoaleatório não criptográficos (rand, random) usados para gerar algo que precisaria ser imprevisível (como um token), e funções clássicas de manipulação de string em C sem checagem de limite, como strcpy, sprintf e gets, historicamente associadas a estouros de buffer.
for f in _CC_MD5 _CC_SHA1 _random _srand _rand _gets _strcpy _sprintf _vsprintf _malloc; do
c=$(otool -Iv "${B}" | grep -cw "$f"); [ "$c" != 0 ] && echo "$f $c"
done
A simples presença de uma dessas funções no binário raramente é, por si só, uma vulnerabilidade: a maior parte vem embutida em bibliotecas de terceiros, não no código do app. O passo seguinte é sempre confirmar se o uso é do código próprio do app (por exemplo, hashear uma senha com MD5) ou de uma dependência que a usa para outra finalidade.
Info.plist
O Info.plist é o arquivo de configuração central de um app iOS, e vale uma leitura dedicada depois de convertido para JSON com a ferramenta plutil, também nativa do macOS:
plutil -convert json -o info.json "${A}/Info.plist"
import json
d = json.load(open('info.json'))
print('ATS:', json.dumps(d.get('NSAppTransportSecurity')))
print('URL schemes:', [s for t in d.get('CFBundleURLTypes', []) for s in t.get('CFBundleURLSchemes', [])])
for k in d:
if any(x in k.upper() for x in ('SECRET', 'KEY', 'TOKEN', 'CLIENT', 'API', 'PASS')):
print('chave suspeita:', k)
Dois pontos de atenção recorrentes:
- App Transport Security (ATS): a chave
NSAppTransportSecuritycontrola as regras de conexão de rede do app. A chaveNSAllowsArbitraryLoads: truedesliga essa proteção por completo, permitindo conexões HTTP sem criptografia para qualquer domínio. Mesmo quando essa chave está ausente, vale checar se existem exceções por domínio específico (NSExceptionDomains) além do que seria esperado para ambientes de teste local. - Esquemas de URL customizados: declarados em
CFBundleURLTypes, definem como outros apps (ou um navegador) conseguem abrir o app por um link comomeuapp://. Como no Android, todo parâmetro recebido por esse caminho é entrada não confiável.
Entitlements
Entitlements são permissões especiais concedidas ao app pela Apple, assinadas junto com o binário:
codesign -d --entitlements :- "${A}" | plutil -p -
Alguns merecem checagem redobrada num app de produção:
| Entitlement | Esperado em produção |
|---|---|
get-task-allow |
false. Com true, qualquer debugger pode anexar ao processo do app, incluindo num aparelho sem jailbreak |
aps-environment |
production, para que as notificações push usem o ambiente correto da Apple |
com.apple.developer.associated-domains |
Só os domínios realmente controlados pelo dono do app. Um domínio associado permite que esse domínio abra o app diretamente (Universal Links) e participe de recursos como AutoFill de senha |
Arquivos soltos dentro do .app
Por fim, vale uma varredura pela pasta do app em busca de arquivos que não deveriam estar ali: variáveis de ambiente (.env), certificados privados (.p12, .pem), código-fonte TypeScript não compilado ou sourcemaps (.ts, .map):
find "${A}" -maxdepth 2 -type f \( -iname '*.env*' -o -iname '*.json' -o -iname '*.p12' -o -iname '*.pem' -o -iname '*.ts' -o -iname '*.map' \)
Em apps React Native, o arquivo modules.json, quando presente, lista as dependências JavaScript do projeto com suas versões, informação que, embora não seja um segredo, ajuda quem procura uma vulnerabilidade conhecida (CVE) numa dessas dependências. Já um arquivo como GoogleService-Info.plist, do Firebase, é esperado e normalmente não representa risco por si só: ele contém identificadores de projeto, não segredos.
Por fim, as mesmas buscas do bundle JavaScript, descritas no artigo sobre abrir um APK, valem para o bundle do iOS, que fica em ${A}/main.jsbundle, e o strings do binário principal continua sendo um atalho rápido para achar algo óbvio:
strings "${B}" | grep -iE 'secret|client_id|api_key|https://' | sort -u | head -50
O que falta: o app em execução
Tudo isso, de novo, é análise estática. Ela identifica proteções presentes ou ausentes e configurações de risco, mas não confirma como o app se comporta de fato quando está rodando, conectado à rede e manipulando dados reais. É esse o assunto do próximo artigo: a análise dinâmica, com o app instrumentado por ferramentas que observam e alteram seu comportamento em tempo real.






