MASTG-TEST-0075: Teste de Custom URL Schemes
Visão Geral¶
Análise Estática¶
Existem algumas coisas que podemos fazer usando análise estática. Nas próximas seções veremos o seguinte:
- Teste de registro de esquemas de URL personalizados
- Teste de registro de esquemas de consulta de aplicativos
- Teste de manipulação e validação de URL
- Teste de solicitações de URL para outros aplicativos
- Teste de métodos obsoletos
Teste de Registro de Esquemas de URL Personalizados¶
O primeiro passo para testar esquemas de URL personalizados é descobrir se um aplicativo registra algum manipulador de protocolo.
Se você tem o código-fonte original e quer visualizar os manipuladores de protocolo registrados, basta abrir o projeto no Xcode, ir até a aba Info e abrir a seção URL Types como apresentado na captura de tela abaixo:

Também no Xcode você pode encontrar isso pesquisando pela chave CFBundleURLTypes no arquivo Info.plist do aplicativo (exemplo de iGoat-Swift):
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>com.iGoat.myCompany</string>
<key>CFBundleURLSchemes</key>
<array>
<string>iGoat</string>
</array>
</dict>
</array>
Em um aplicativo compilado (ou IPA), os manipuladores de protocolo registrados são encontrados no arquivo Info.plist na pasta raiz do pacote do aplicativo. Abra-o e procure pela chave CFBundleURLSchemes, se presente, ela deve conter um array de strings (exemplo de iGoat-Swift):
grep -A 5 -nri urlsch Info.plist
Info.plist:45: <key>CFBundleURLSchemes</key>
Info.plist-46- <array>
Info.plist-47- <string>iGoat</string>
Info.plist-48- </array>
Uma vez que o esquema de URL é registrado, outros aplicativos podem abrir o aplicativo que registrou o esquema e passar parâmetros criando URLs formatadas apropriadamente e abrindo-as com o método UIApplication openURL:options:completionHandler:.
Nota do App Programming Guide for iOS:
Se mais de um aplicativo de terceiros registrar para manipular o mesmo esquema de URL, atualmente não há um processo para determinar qual aplicativo receberá esse esquema.
Isso pode levar a um ataque de sequestro de esquema de URL (veja a página 136 em [#thiel2]).
Teste de Registro de Esquemas de Consulta de Aplicativos¶
Antes de chamar o método openURL:options:completionHandler:, os aplicativos podem chamar canOpenURL: para verificar se o aplicativo de destino está disponível. No entanto, como este método estava sendo usado por aplicativos maliciosos como uma forma de enumerar aplicativos instalados, a partir do iOS 9.0 os esquemas de URL passados para ele também devem ser declarados adicionando a chave LSApplicationQueriesSchemes ao arquivo Info.plist do aplicativo e um array de até 50 esquemas de URL.
<key>LSApplicationQueriesSchemes</key>
<array>
<string>url_scheme1</string>
<string>url_scheme2</string>
</array>
canOpenURL sempre retornará NO para esquemas não declarados, independentemente de um aplicativo apropriado estar instalado ou não. No entanto, esta restrição aplica-se apenas a canOpenURL.
O método openURL:options:completionHandler: ainda abrirá qualquer esquema de URL, mesmo que o array LSApplicationQueriesSchemes tenha sido declarado, e retornará YES / NO dependendo do resultado.
Como exemplo, o Telegram declara em seu Info.plist estes Esquemas de Consulta, entre outros:
<key>LSApplicationQueriesSchemes</key>
<array>
<string>dbapi-3</string>
<string>instagram</string>
<string>googledrive</string>
<string>comgooglemaps-x-callback</string>
<string>foursquare</string>
<string>here-location</string>
<string>yandexmaps</string>
<string>yandexnavi</string>
<string>comgooglemaps</string>
<string>youtube</string>
<string>twitter</string>
...
Teste de Manipulação e Validação de URL¶
Para determinar como um caminho de URL é construído e validado, se você tem o código-fonte original, pode pesquisar pelos seguintes métodos:
- Método
application:didFinishLaunchingWithOptions:ouapplication:will-FinishLaunchingWithOptions:: verifique como a decisão é tomada e como as informações sobre a URL são recuperadas. application:openURL:options:: verifique como o recurso está sendo aberto, ou seja, como os dados estão sendo analisados, verifique as opções, especialmente se o acesso pelo aplicativo chamador (sourceApplication) deve ser permitido ou negado. O aplicativo também pode precisar de permissão do usuário ao usar o esquema de URL personalizado.
No Telegram você encontrará quatro métodos diferentes sendo usados:
func application(_ application: UIApplication, open url: URL, sourceApplication: String?) -> Bool {
self.openUrl(url: url)
return true
}
func application(_ application: UIApplication, open url: URL, sourceApplication: String?,
annotation: Any) -> Bool {
self.openUrl(url: url)
return true
}
func application(_ app: UIApplication, open url: URL,
options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
self.openUrl(url: url)
return true
}
func application(_ application: UIApplication, handleOpen url: URL) -> Bool {
self.openUrl(url: url)
return true
}
Podemos observar algumas coisas aqui:
- O aplicativo também implementa métodos obsoletos como
application:handleOpenURL:eapplication:openURL:sourceApplication:annotation:. - A aplicação de origem não está sendo verificada em nenhum desses métodos.
- Todos eles chamam um método privado
openUrl. Você pode inspecioná-lo para saber mais sobre como a solicitação de URL é manipulada.
Teste de Solicitações de URL para Outros Aplicativos¶
O método openURL:options:completionHandler: e o método obsoleto openURL: de UIApplication são responsáveis por abrir URLs (ou seja, para enviar solicitações / fazer consultas a outros aplicativos) que podem ser locais para o aplicativo atual ou podem ser fornecidos por um aplicativo diferente. Se você tem o código-fonte original, pode pesquisar diretamente por usos desses métodos.
Além disso, se você estiver interessado em saber se o aplicativo está consultando serviços ou aplicativos específicos, e se o aplicativo é bem conhecido, você também pode pesquisar por esquemas de URL comuns online e incluí-los em suas pesquisas. Por exemplo, uma rápida pesquisa no Google revela:
Apple Music - music:// ou musics:// ou audio-player-event://
Calendar - calshow:// ou x-apple-calevent://
Contacts - contacts://
Diagnostics - diagnostics:// ou diags://
GarageBand - garageband://
iBooks - ibooks:// ou itms-books:// ou itms-bookss://
Mail - message:// ou mailto://emailaddress
Messages - sms://phonenumber
Notes - mobilenotes://
...
Pesquisamos por este método no código-fonte do Telegram, desta vez sem usar o Xcode, apenas com egrep:
$ egrep -nr "open.*options.*completionHandler" ./Telegram-iOS/
./AppDelegate.swift:552: return UIApplication.shared.open(parsedUrl,
options: [UIApplicationOpenURLOptionUniversalLinksOnly: true as NSNumber],
completionHandler: { value in
./AppDelegate.swift:556: return UIApplication.shared.open(parsedUrl,
options: [UIApplicationOpenURLOptionUniversalLinksOnly: true as NSNumber],
completionHandler: { value in
Se inspecionarmos os resultados, veremos que openURL:options:completionHandler: está realmente sendo usado para links universais, então temos que continuar pesquisando. Por exemplo, podemos pesquisar por openURL(:
$ egrep -nr "openURL\(" ./Telegram-iOS/
./ApplicationContext.swift:763: UIApplication.shared.openURL(parsedUrl)
./ApplicationContext.swift:792: UIApplication.shared.openURL(URL(
string: "https://telegram.org/deactivate?phone=\(phone)")!
)
./AppDelegate.swift:423: UIApplication.shared.openURL(url)
./AppDelegate.swift:538: UIApplication.shared.openURL(parsedUrl)
...
Se inspecionarmos essas linhas, veremos como este método também está sendo usado para abrir "Configurações" ou para abrir a "Página da App Store".
Ao pesquisar apenas por :// vemos:
if documentUri.hasPrefix("file://"), let path = URL(string: documentUri)?.path {
if !url.hasPrefix("mt-encrypted-file://?") {
guard let dict = TGStringUtils.argumentDictionary(inUrlString: String(url[url.index(url.startIndex,
offsetBy: "mt-encrypted-file://?".count)...])) else {
parsedUrl = URL(string: "https://\(url)")
if let url = URL(string: "itms-apps://itunes.apple.com/app/id\(appStoreId)") {
} else if let url = url as? String, url.lowercased().hasPrefix("tg://") {
[[WKExtension sharedExtension] openSystemURL:[NSURL URLWithString:[NSString
stringWithFormat:@"tel://%@", userHandle.data]]];
Após combinar os resultados de ambas as pesquisas e inspecionar cuidadosamente o código-fonte, encontramos o seguinte trecho de código:
openUrl: { url in
var parsedUrl = URL(string: url)
if let parsed = parsedUrl {
if parsed.scheme == nil || parsed.scheme!.isEmpty {
parsedUrl = URL(string: "https://\(url)")
}
if parsed.scheme == "tg" {
return
}
}
if let parsedUrl = parsedUrl {
UIApplication.shared.openURL(parsedUrl)
Antes de abrir uma URL, o esquema é validado, "https" será adicionado se necessário e não abrirá nenhuma URL com o esquema "tg". Quando pronto, usará o método obsoleto openURL.
Se você tiver apenas o aplicativo compilado (IPA), ainda pode tentar identificar quais esquemas de URL estão sendo usados para consultar outros aplicativos:
- Verifique se
LSApplicationQueriesSchemesfoi declarado ou pesquise por esquemas de URL comuns. - Também use a string
://ou construa uma expressão regular para corresponder a URLs, pois o aplicativo pode não estar declarando alguns esquemas.
Você pode fazer isso primeiro verificando se o binário do aplicativo contém essas strings usando, por exemplo, o comando Unix strings:
strings <yourapp> | grep "someURLscheme://"
ou ainda melhor, use o comando iz/izz do radare2 ou rafind2, ambos encontrarão strings onde o comando Unix strings não encontrará. Exemplo de iGoat-Swift:
$ r2 -qc izz~iGoat:// iGoat-Swift
37436 0x001ee610 0x001ee610 23 24 (4.__TEXT.__cstring) ascii iGoat://?contactNumber=
Teste de Métodos Obsoletos¶
Pesquise por métodos obsoletos como:
Por exemplo, usando rabin2 encontramos esses três:
$ rabin2 -zzq Telegram\ X.app/Telegram\ X | grep -i "openurl"
0x1000d9e90 31 30 UIApplicationOpenURLOptionsKey
0x1000dee3f 50 49 application:openURL:sourceApplication:annotation:
0x1000dee71 29 28 application:openURL:options:
0x1000dee8e 27 26 application:handleOpenURL:
0x1000df2c9 9 8 openURL:
0x1000df766 12 11 canOpenURL:
0x1000df772 35 34 openURL:options:completionHandler:
...
Análise Dinâmica¶
Uma vez que você identificou os esquemas de URL personalizados que o aplicativo registrou, existem vários métodos que você pode usar para testá-los:
- Realizando solicitações de URL
- Identificando e interceptando o método manipulador de URL
- Testando a validação de origem dos esquemas de URL
- Fuzzing de esquemas de URL
Realizando Solicitações de URL¶
Usando o Safari¶
Para testar rapidamente um esquema de URL, você pode abrir as URLs no Safari e observar como o aplicativo se comporta. Por exemplo, se você escrever tel://123456789 na barra de endereços do Safari, um pop-up aparecerá com o número de telefone e as opções "Cancelar" e "Ligar". Se você pressionar "Ligar", ele abrirá o aplicativo Telefone e fará a chamada diretamente.
Você também pode conhecer páginas que acionam esquemas de URL personalizados, você pode simplesmente navegar normalmente para essas páginas e o Safari perguntará automaticamente quando encontrar um esquema de URL personalizado.
Usando o App de Notas¶
Como já visto em "Acionando Links Universais", você pode usar o aplicativo Notas e pressionar longamente os links que você escreveu para testar esquemas de URL personalizados. Lembre-se de sair do modo de edição para poder abri-los. Observe que você pode clicar ou pressionar longamente links incluindo esquemas de URL personalizados apenas se o aplicativo estiver instalado, caso contrário, eles não serão destacados como links clicáveis.
Usando Frida¶
Se você simplesmente quer que um aplicativo abra o esquema de URL, pode fazer isso usando o Frida. Exemplo usando iGoat-Swift:
$ frida -U iGoat-Swift
[iPhone::iGoat-Swift]-> function openURL(url) {
var UIApplication = ObjC.classes.UIApplication.sharedApplication();
var toOpen = ObjC.classes.NSURL.URLWithString_(url);
return UIApplication.openURL_(toOpen);
}
[iPhone::iGoat-Swift]-> openURL("tel://234234234")
true
Neste exemplo do Frida CodeShare o autor usa a API não pública LSApplication Workspace.openSensitiveURL:withOptions: para abrir as URLs (do aplicativo SpringBoard):
function openURL(url) {
var w = ObjC.classes.LSApplicationWorkspace.defaultWorkspace();
var toOpen = ObjC.classes.NSURL.URLWithString_(url);
return w.openSensitiveURL_withOptions_(toOpen, null);
}
Observe que o uso de APIs não públicas não é permitido na App Store, por isso nem mesmo testamos isso, mas temos permissão para usá-las para nossa análise dinâmica.
Identificando e Interceptando o Método Manipulador de URL¶
Se você não pode olhar o código-fonte original, terá que descobrir por si mesmo qual método o aplicativo usa para manipular as solicitações de esquema de URL que recebe. Você não pode saber se é um método Objective-C ou Swift, ou mesmo se o aplicativo está usando um obsoleto.
Criando o Link Você Mesmo e Deixando o Safari Abri-lo¶
Para isso, usaremos o observador de método ObjC do Frida CodeShare, que é um script extremamente útil que permite observar rapidamente qualquer coleção de métodos ou classes apenas fornecendo um padrão simples.
Neste caso, estamos interessados em todos os métodos do aplicativo iGoat-Swift contendo "openURL", portanto nosso padrão será *[* *openURL*]:
- O primeiro asterisco corresponderá a todos os métodos de instância
-e classe+. - O segundo corresponde a todas as classes Objective-C.
- O terceiro e o quarto permitem corresponder a qualquer método contendo a string
openURL.
$ frida -U iGoat-Swift --codeshare mrmacete/objc-method-observer
[iPhone::iGoat-Swift]-> observeSomething("*[* *openURL*]");
Observing -[_UIDICActivityItemProvider activityViewController:openURLAnnotationForActivityType:]
Observing -[CNQuickActionsManager _openURL:]
Observing -[SUClientController openURL:]
Observing -[SUClientController openURL:inClientWithIdentifier:]
Observing -[FBSSystemService openURL:application:options:clientPort:withResult:]
Observing -[iGoat_Swift.AppDelegate application:openURL:options:]
Observing -[PrefsUILinkLabel openURL:]
Observing -[UIApplication openURL:]
Observing -[UIApplication _openURL:]
Observing -[UIApplication openURL:options:completionHandler:]
Observing -[UIApplication openURL:withCompletionHandler:]
Observing -[UIApplication _openURL:originatingView:completionHandler:]
Observing -[SUApplication application:openURL:sourceApplication:annotation:]
...
A lista é muito longa e inclui os métodos que já mencionamos. Se acionarmos agora um esquema de URL, por exemplo "igoat://" do Safari e aceitarmos abri-lo no aplicativo, veremos o seguinte:
[iPhone::iGoat-Swift]-> (0x1c4038280) -[iGoat_Swift.AppDelegate application:openURL:options:]
application: <UIApplication: 0x101d0fad0>
openURL: igoat://
options: {
UIApplicationOpenURLOptionsOpenInPlaceKey = 0;
UIApplicationOpenURLOptionsSourceApplicationKey = "com.apple.mobilesafari";
}
0x18b5030d8 UIKit!__58-[UIApplication _applicationOpenURLAction:payload:origin:]_block_invoke
0x18b502a94 UIKit!-[UIApplication _applicationOpenURLAction:payload:origin:]
...
0x1817e1048 libdispatch.dylib!_dispatch_client_callout
0x1817e86c8 libdispatch.dylib!_dispatch_block_invoke_direct$VARIANT$mp
0x18453d9f4 FrontBoardServices!__FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__
0x18453d698 FrontBoardServices!-[FBSSerialQueue _performNext]
RET: 0x1
Agora sabemos que:
- O método
-[iGoat_Swift.AppDelegate application:openURL:options:]é chamado. Como vimos antes, é a maneira recomendada e não é obsoleta. - Ele recebe nossa URL como parâmetro:
igoat://. - Também podemos verificar a aplicação de origem:
com.apple.mobilesafari. - Também podemos saber de onde foi chamado, conforme esperado de
-[UIApplication _applicationOpenURLAction:payload:origin:]. - O método retorna
0x1que significaYES(o delegado manipulou a solicitação com sucesso).
A chamada foi bem-sucedida e agora vemos que o aplicativo iGoat-Swift foi aberto:

Observe que também podemos ver que o chamador (aplicação de origem) foi o Safari se olharmos no canto superior esquerdo da captura