MASTG-TEST-0071: Teste de UIActivity Sharing
Visão Geral¶
Análise Estática¶
Envio de Itens¶
Ao testar o compartilhamento via UIActivity, você deve prestar atenção especial a:
- os dados (itens) sendo compartilhados,
- as atividades personalizadas,
- os tipos de atividade excluídos.
O compartilhamento de dados via UIActivity funciona criando um UIActivityViewController e passando a ele os itens desejados (URLs, texto, uma imagem) em init(activityItems: applicationActivities:).
Como mencionamos anteriormente, é possível excluir alguns dos mecanismos de compartilhamento por meio da propriedade excludedActivityTypes do controlador. É altamente recomendável realizar os testes usando as versões mais recentes do iOS, pois o número de tipos de atividade que podem ser excluídos pode aumentar. Os desenvolvedores devem estar cientes disso e excluir explicitamente aqueles que não são apropriados para os dados do aplicativo. Alguns tipos de atividade podem nem mesmo estar documentados, como "Create Watch Face".
Se você tiver o código-fonte, deve analisar o UIActivityViewController:
- Inspecione as atividades passadas para o método
init(activityItems:applicationActivities:). - Verifique se ele define atividades personalizadas (também passadas para o método anterior).
- Confirme os
excludedActivityTypes, se houver.
Se você tiver apenas o aplicativo compilado/instalado, tente pesquisar o método e a propriedade anteriores, por exemplo, usando rabin2:
$ rabin2 -zq Telegram\ X.app/Telegram\ X | grep -i activityItems
0x1000df034 45 44 initWithActivityItems:applicationActivities:
Recebimento de Itens¶
Ao receber itens, você deve verificar:
- se o aplicativo declara tipos de documento personalizados analisando as UTIs Exportadas/Importadas (guia "Info" do projeto no Xcode). A lista de todas as UTIs (Identificadores de Tipo Uniforme) declaradas pelo sistema pode ser encontrada na Documentação de Desenvolvedor da Apple arquivada.
- se o aplicativo especifica tipos de documento que pode abrir analisando os Tipos de Documento (guia "Info" do projeto no Xcode). Se presentes, eles consistem em um nome e uma ou mais UTIs que representam o tipo de dados (por exemplo, "public.png" para arquivos PNG). O iOS usa isso para determinar se o aplicativo é elegível para abrir um determinado documento (especificar UTIs Exportadas/Importadas não é suficiente).
- se o aplicativo verifica adequadamente os dados recebidos analisando a implementação de
application:openURL:options:(ou sua versão obsoletaUIApplicationDelegate application:openURL:sourceApplication:annotation:) no app delegate.
Se não tiver o código-fonte, você ainda pode analisar o arquivo Info.plist e pesquisar por:
UTExportedTypeDeclarations/UTImportedTypeDeclarationsse o aplicativo declara tipos de documento personalizados exportados/importados.CFBundleDocumentTypespara ver se o aplicativo especifica tipos de documento que pode abrir.
Uma explicação muito completa sobre o uso dessas chaves pode ser encontrada no Stackoverflow.
Vejamos um exemplo do mundo real. Tomaremos um aplicativo de Gerenciador de Arquivos e analisaremos essas chaves. Usamos objection aqui para ler o arquivo Info.plist.
objection --gadget SomeFileManager run ios plist cat Info.plist
Observe que isso é o mesmo que se recuperássemos o IPA do telefone ou acessássemos via, por exemplo, SSH e navegássemos até a pasta correspondente no IPA / sandbox do aplicativo. No entanto, com o objection estamos a apenas um comando de nosso objetivo, e isso ainda pode ser considerado análise estática.
A primeira coisa que notamos é que o aplicativo não declara nenhum tipo de documento personalizado importado, mas encontramos alguns exportados:
UTExportedTypeDeclarations = (
{
UTTypeConformsTo = (
"public.data"
);
UTTypeDescription = "SomeFileManager Files";
UTTypeIdentifier = "com.some.filemanager.custom";
UTTypeTagSpecification = {
"public.filename-extension" = (
ipa,
deb,
zip,
rar,
tar,
gz,
...
key,
pem,
p12,
cer
);
};
}
);
O aplicativo também declara os tipos de documento que abre, pois podemos encontrar a chave CFBundleDocumentTypes:
CFBundleDocumentTypes = (
{
...
CFBundleTypeName = "SomeFileManager Files";
LSItemContentTypes = (
"public.content",
"public.data",
"public.archive",
"public.item",
"public.database",
"public.calendar-event",
...
);
}
);
Podemos ver que este Gerenciador de Arquivos tentará abrir qualquer coisa que se conforme a qualquer uma das UTIs listadas em LSItemContentTypes e está pronto para abrir arquivos com as extensões listadas em UTTypeTagSpecification/"public.filename-extension". Por favor, tome nota disso, pois será útil se você quiser procurar por vulnerabilidades ao lidar com os diferentes tipos de arquivos ao realizar a análise dinâmica.
Análise Dinâmica¶
Envio de Itens¶
Há três coisas principais que você pode facilmente inspecionar realizando instrumentação dinâmica:
- Os
activityItems: um array dos itens sendo compartilhados. Eles podem ser de tipos diferentes, por exemplo, uma string e uma imagem para serem compartilhados via um aplicativo de mensagens. - Os
applicationActivities: um array de objetosUIActivityrepresentando os serviços personalizados do aplicativo. - Os
excludedActivityTypes: um array dos Tipos de Atividade que não são suportados, por exemplo,postToFacebook.
Para alcançar isso, você pode fazer duas coisas:
- Enganchar o método que vimos na análise estática (
init(activityItems: applicationActivities:)) para obter osactivityItemseapplicationActivities. - Descobrir as atividades excluídas enganchando a propriedade
excludedActivityTypes.
Vejamos um exemplo usando o Telegram para compartilhar uma imagem e um arquivo de texto. Primeiro prepare os hooks, usaremos o REPL do Frida e escreveremos um script para isso:
Interceptor.attach(
ObjC.classes.
UIActivityViewController['- initWithActivityItems:applicationActivities:'].implementation, {
onEnter: function (args) {
printHeader(args)
this.initWithActivityItems = ObjC.Object(args[2]);
this.applicationActivities = ObjC.Object(args[3]);
console.log("initWithActivityItems: " + this.initWithActivityItems);
console.log("applicationActivities: " + this.applicationActivities);
},
onLeave: function (retval) {
printRet(retval);
}
});
Interceptor.attach(
ObjC.classes.UIActivityViewController['- excludedActivityTypes'].implementation, {
onEnter: function (args) {
printHeader(args)
},
onLeave: function (retval) {
printRet(retval);
}
});
function printHeader(args) {
console.log(Memory.readUtf8String(args[1]) + " @ " + args[1])
};
function printRet(retval) {
console.log('RET @ ' + retval + ': ' );
try {
console.log(new ObjC.Object(retval).toString());
} catch (e) {
console.log(retval.toString());
}
};
Você pode armazenar isso como um arquivo JavaScript, por exemplo, inspect_send_activity_data.js e carregá-lo assim:
frida -U Telegram -l inspect_send_activity_data.js
Agora observe a saída quando você primeiro compartilha uma imagem:
[*] initWithActivityItems:applicationActivities: @ 0x18c130c07
initWithActivityItems: (
"<UIImage: 0x1c4aa0b40> size {571, 264} orientation 0 scale 1.000000"
)
applicationActivities: nil
RET @ 0x13cb2b800:
<UIActivityViewController: 0x13cb2b800>
[*] excludedActivityTypes @ 0x18c0f8429
RET @ 0x0:
nil
e então um arquivo de texto:
[*] initWithActivityItems:applicationActivities: @ 0x18c130c07
initWithActivityItems: (
"<QLActivityItemProvider: 0x1c4a30140>",
"<UIPrintInfo: 0x1c0699a50>"
)
applicationActivities: (
)
RET @ 0x13c4bdc00:
<_UIDICActivityViewController: 0x13c4bdc00>
[*] excludedActivityTypes @ 0x18c0f8429
RET @ 0x1c001b1d0:
(
"com.apple.UIKit.activity.MarkupAsPDF"
)
Você pode ver que:
- Para a imagem, o item de atividade é um
UIImagee não há atividades excluídas. - Para o arquivo de texto, há dois itens de atividade diferentes e
com.apple.UIKit.activity.MarkupAsPDFé excluído.
No exemplo anterior, não havia applicationActivities personalizados e apenas uma atividade excluída. No entanto, para ilustrar melhor o que você pode esperar de outros aplicativos, compartilhamos uma imagem usando outro aplicativo. Aqui você pode ver vários application activities e excluded activities (a saída foi editada para ocultar o nome do aplicativo de origem):
[*] initWithActivityItems:applicationActivities: @ 0x18c130c07
initWithActivityItems: (
"<SomeActivityItemProvider: 0x1c04bd580>"
)
applicationActivities: (
"<SomeActionItemActivityAdapter: 0x141de83b0>",
"<SomeActionItemActivityAdapter: 0x147971cf0>",
"<SomeOpenInSafariActivity: 0x1479f0030>",
"<SomeOpenInChromeActivity: 0x1c0c8a500>"
)
RET @ 0x142138a00:
<SomeActivityViewController: 0x142138a00>
[*] excludedActivityTypes @ 0x18c0f8429
RET @ 0x14797c3e0:
(
"com.apple.UIKit.activity.Print",
"com.apple.UIKit.activity.AssignToContact",
"com.apple.UIKit.activity.SaveToCameraRoll",
"com.apple.UIKit.activity.CopyToPasteboard",
)
Recebimento de Itens¶
Após realizar a análise estática, você saberá os tipos de documento que o aplicativo pode abrir e se ele declara algum tipo de documento personalizado e (parte dos) métodos envolvidos. Você pode usar isso agora para testar a parte de recebimento:
- Compartilhe um arquivo com o aplicativo a partir de outro aplicativo ou envie-o via AirDrop ou e-mail. Escolha o arquivo para que ele dispare o diálogo "Abrir com..." (ou seja, não há um aplicativo padrão que abrirá o arquivo, um PDF, por exemplo).
- Enganche
application:openURL:options:e quaisquer outros métodos identificados em uma análise estática anterior. - Observe o comportamento do aplicativo.
- Além disso, você pode enviar arquivos malformados específicos e/ou usar uma técnica de fuzzing.
Para ilustrar isso com um exemplo, escolhemos o mesmo aplicativo de gerenciador de arquivos do mundo real da seção de análise estática e seguimos estas etapas:
- Envie um arquivo PDF de outro dispositivo Apple (por exemplo, um MacBook) via AirDrop.
- Aguarde o pop-up do AirDrop aparecer e clique em Aceitar.
-
Como não há um aplicativo padrão que abrirá o arquivo, ele muda para o pop-up Abrir com.... Lá, podemos selecionar o aplicativo que abrirá nosso arquivo. A próxima captura de tela mostra isso (modificamos o nome de exibição usando Frida para ocultar o nome real do aplicativo):

-
Após selecionar SomeFileManager, podemos ver o seguinte:
(0x1c4077000) -[AppDelegate application:openURL:options:] application: <UIApplication: 0x101c00950> openURL: file:///var/mobile/Library/Application%20Support /Containers/com.some.filemanager/Documents/Inbox/OWASP_MASVS.pdf options: { UIApplicationOpenURLOptionsAnnotationKey = { LSMoveDocumentOnOpen = 1; }; UIApplicationOpenURLOptionsOpenInPlaceKey = 0; UIApplicationOpenURLOptionsSourceApplicationKey = "com.apple.sharingd"; "_UIApplicationOpenURLOptionsSourceProcessHandleKey" = "<FBSProcessHandle: 0x1c3a63140; sharingd:605; valid: YES>"; } 0x18c7930d8 UIKit!__58-[UIApplication _applicationOpenURLAction:payload:origin:]_block_invoke ... 0x1857cdc34 FrontBoardServices!-[FBSSerialQueue _performNextFromRunLoopSource] RET: 0x1
Como você pode ver, o aplicativo de envio é com.apple.sharingd e o esquema da URL é file://. Observe que, uma vez que selecionamos o aplicativo que deve abrir o arquivo, o sistema já moveu o arquivo para o destino correspondente, ou seja, para a Inbox do aplicativo. Os aplicativos são então responsáveis por excluir os arquivos dentro de suas Inboxes. Este aplicativo, por exemplo, move o arquivo para /var/mobile/Documents/ e o remove da Inbox.
(0x1c002c760) -[XXFileManager moveItemAtPath:toPath:error:]
moveItemAtPath: /var/mobile/Library/Application Support/Containers
/com.some.filemanager/Documents/Inbox/OWASP_MASVS.pdf
toPath: /var/mobile/Documents/OWASP_MASVS (1).pdf
error: 0x16f095bf8
0x100f24e90 SomeFileManager!-[AppDelegate __handleOpenURL:]
0x100f25198 SomeFileManager!-[AppDelegate application:openURL:options:]
0x18c7930d8 UIKit!__58-[UIApplication _applicationOpenURLAction:payload:origin:]_block_invoke
...
0x1857cd9f4 FrontBoardServices!__FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__
RET: 0x1
Se você observar o stack trace, pode ver como application:openURL:options: chamou __handleOpenURL:, que chamou moveItemAtPath:toPath:error:. Note que agora temos essas informações sem ter o código-fonte do aplicativo alvo. A primeira coisa que tivemos que fazer estava clara: enganchar application:openURL:options:. Quanto ao resto, tivemos que pensar um pouco e criar métodos que poderíamos começar a rastrear e que estão relacionados ao gerenciador de arquivos, por exemplo, todos os métodos contendo as strings "copy", "move", "remove", etc., até encontrarmos o que estava sendo chamado: moveItemAtPath:toPath:error:.
Uma última coisa que vale a pena notar aqui é que essa maneira de lidar com arquivos recebidos é a mesma para esquemas de URL personalizados. Consulte Teste de Custom URL Schemes para obter mais informações.