Uma vez eu precisei digitalizar aplicativos de rede Wi-Fi do Android e obter um cálculo detalhado dos dados sobre pontos de acesso.
Aqui eu tive que enfrentar várias dificuldades: na
documentação fora do Android, muitas das classes descritas ficaram obsoletas (nível de API> 26), o que não estava refletido nela; a descrição de algumas coisas na documentação é mínima (por exemplo, o campo de recursos da classe
ScanResult no momento da gravação não foi descrito de forma alguma, embora contenha muitos dados importantes). A terceira dificuldade pode ser que, quando você se aproxima do Wi-Fi, que é diferente de ler a teoria e configurar o roteador para o host local, você precisa lidar com várias abreviações que parecem ser compreensíveis separadamente. Mas pode não ser óbvio como relacioná-los e estruturá-los (o julgamento é subjetivo e depende da experiência anterior).
Este artigo descreve como obter dados abrangentes em ambientes Wi-Fi a partir do código Android sem NDK, hacks, mas apenas usando a API do Android e entender como interpretá-los.
Não puxaremos e começaremos a escrever código.
1. Crie um projeto
A nota foi projetada para aqueles que criaram o projeto Android mais de uma vez, portanto, omitimos os detalhes deste item. O código abaixo será apresentado no idioma Kotlin, minSdkVersion = 23.
2. Permissões de acesso
Para trabalhar com o Wi-Fi do aplicativo, você precisará obter várias permissões do usuário. De acordo com a
documentação , para varrer a rede em dispositivos com versões do SO após 8.0, além de acessar para visualizar o estado do ambiente de rede, você precisa acessar para alterar o estado do módulo Wi-Fi do dispositivo ou acessar as coordenadas (aproximadas ou exatas). A partir da versão 9.0, você deve solicitar ao usuário esses e outros itens e, ao mesmo tempo, solicitar explicitamente ao usuário que ative o serviço de localização. Não se esqueça de explicar galantemente ao usuário que esse é um capricho do Google, e não nosso desejo de espioná-lo :)
Total, no AndroidManifest.xml, adicione:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
E no código que possui um link para a Atividade atual:
import android.app.Activity import android.content.Context import android.location.LocationManager import androidx.core.app.ActivityCompat .... if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { ActivityCompat.requestPermissions( activity, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.CHANGE_WIFI_STATE), 1 ) makeEnableLocationServices(activity.applicationContext) } else { ActivityCompat.requestPermissions( activity, arrayOf(Manifest.permission.CHANGE_WIFI_STATE), 1 ) } fun makeEnableLocationServices(context: Context) {
3. Crie o BroadcastReceiver e assine a atualização de eventos na verificação do ambiente de rede Wi-Fi
val wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager val wifiScanReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val success = intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false) if (success) { scanSuccess() } } } val intentFilter = IntentFilter() intentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION) context.registerReceiver(wifiScanReceiver, intentFilter) val success = wifiManager.startScan() if (!success) { } .... private fun scanSuccess() { val results: List<ScanResult> = wifiManager.scanResults }
O método WiFiManager.startScan na documentação está marcado como privado da API versão 28, mas desativado.
O guia sugere usá-lo.
No total, recebemos uma lista de objetos
ScanResult .
4. Examinamos o ScanResult e entendemos os termos
Vamos dar uma olhada em alguns campos desta classe e descrever o que eles significam:
SSID - Service Set Identifier é o nome da rede.
BSSID - Identificador básico do conjunto de serviços - endereço MAC do adaptador de rede (ponto Wi-Fi)
nível - Indicador de intensidade do sinal recebido [dBm (dBm russo) - Decibel, potência de referência 1 mW.] - Indicador do nível do sinal recebido. Leva um valor de 0 a -100, quanto mais longe de 0, mais energia do sinal é perdida ao longo do caminho do ponto de Wi-Fi ao seu dispositivo. Mais detalhes podem ser encontrados, por exemplo, na
Wikipedia . Aqui, digo que, usando a classe
WifiManager do Android
, você pode calibrar o nível do sinal em uma escala de excelente a terrível com a etapa que você selecionou:
val wifiManager = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager val numberOfLevels = 5 val level = WifiManager.calculateSignalLevel(level, numberOfLevels)
frequência - a frequência do ponto de Wi-Fi [Hz]. Além da própria frequência, você pode estar interessado no chamado canal. Cada ponto tem sua própria limpeza de trabalho. No momento da redação deste artigo, o alcance mais popular de pontos de Wi-Fi é de 2,4 GHz. Mas, para ser mais preciso, o ponto transfere informações para o seu telefone em uma frequência numerada próxima à nomeada. O número de canais e os valores das frequências correspondentes são
padronizados . Isso é feito para que pontos próximos funcionem em diferentes frequências, não interferindo um com o outro e nem reduzindo mutuamente a velocidade e a qualidade da transmissão. Ao mesmo tempo, os pontos não funcionam na mesma frequência, mas na faixa de frequência (
parâmetro channelWidth), denominada largura do canal. Ou seja, pontos que operam em canais vizinhos (e não apenas em canais vizinhos, mas mesmo em 3 deles mesmos) criam interferência entre si. Este código simples pode ser útil para você, o que permite calcular o número do canal pelo valor da frequência para pontos com frequência de 2,4 e 5 Ghz:
val channel: Int get() { return if (frequency in 2412..2484) { (frequency - 2412) / 5 + 1 } else if (frequency in 5170..5825) { (frequency - 5170) / 5 + 34 } else { -1 } }
recursos é o campo mais interessante para análise, que levou muito tempo para trabalhar. Aqui, os "recursos" do ponto são gravados na linha. Nesse caso, não é possível procurar detalhes da interpretação da sequência na documentação. Aqui estão alguns exemplos do que pode estar nessa linha:
[WPA-PSK-TKIP+CCMP][WPA2-PSK-TKIP+CCMP][WPS][ESS] [WPA2-PSK-CCMP][ESS] [WPA2-PSK-CCMP+TKIP][ESS] [WPA-PSK-CCMP+TKIP][WPA2-PSK-CCMP+TKIP][ESS] [ESS][WPS]
5. Compreender abreviações e recursos de análise
Vale ressaltar que as classes do pacote android.net.wifi. * Usa o utilitário linux
wpa_supplicant sob o capô e a saída no campo de recursos é uma cópia do campo de sinalizadores durante a varredura.
Agiremos sequencialmente. Primeiro, considere a saída de um formato no qual os elementos dentro dos colchetes são separados por um "-":
[WPA-PSK-TKIP+CCMP] [WPA2-PSK-CCMP]
O primeiro valor descreve o chamado
método de autenticação Ou seja, qual sequência de ações deve ser executada pelo dispositivo e pelo ponto de acesso para que o ponto de acesso permita a sua utilização e como criptografar a carga útil. No momento da redação deste post, as opções mais comuns são WPA e WPA2, nas quais cada dispositivo conectado diretamente ou através dos chamados. O servidor RADIUS (WPA-Enterprice) fornece uma senha em um canal criptografado. Muito provavelmente, em sua casa, o ponto de acesso fornece uma conexão de acordo com este esquema. A diferença entre a segunda versão e a primeira em uma cifra mais robusta: AES contra TKIP inseguro. O WPA3, que é mais complexo e avançado, também está sendo introduzido gradualmente. Teoricamente, pode haver uma opção com a solução de preço corporativo do CCKM (Cisco Centralized Key Managment), mas nunca o encontrei.
O ponto de acesso pode ser configurado para autenticação pelo endereço MAC. Ou, se o ponto de acesso fornecer dados usando o algoritmo WEP desatualizado, praticamente não haverá autenticação (a chave secreta aqui é a chave de criptografia). Essas opções são classificadas como OUTRAS.
Também existe um método favorito em wi-fi público com a Detecção de Portal Captive oculta - uma solicitação de autenticação por meio de um navegador. Esses pontos de acesso parecem abertos ao scanner (que são do ponto de vista da conexão física). Portanto, nós os classificamos como ABERTO.
O segundo valor pode ser designado como
o algoritmo de gerenciamento de chaves . É um parâmetro do método de autenticação descrito acima. Fala sobre como ocorre exatamente a troca de chaves de criptografia. Vamos considerar as opções possíveis. O EAP - usado no mencionado WPA-Enterprice, usa um banco de dados para verificar os dados de autenticação inseridos. SAE - usado no avançado WPA3, mais resistente ao rebentamento. PSK é a opção mais comum, envolve digitar a senha e transmiti-la em forma criptografada. IEEE8021X - de acordo com o padrão internacional (exceto o suportado pela família WPA). OWE (criptografia sem fio oportunista) é uma extensão do padrão IEEE 802.11, para os pontos que classificamos como ABERTO. O OWE garante a segurança dos dados transmitidos através de uma rede insegura, criptografando-os. Uma variante também é possível quando não há chaves de acesso; vamos chamar esta opção NONE.
O terceiro parâmetro é o chamado
método de criptografia (esquemas de criptografia) - exatamente como a cifra é usada para proteger os dados transmitidos. Listamos as opções. WEP - usa uma cifra de fluxo RC4, a chave secreta é uma chave de criptografia, considerada inaceitável no mundo da criptografia moderna. TKIP - usado no WPA, CKIP - no WPA2. TKIP + CKIP - pode ser especificado em pontos capazes de WPA e WPA2 para compatibilidade com versões anteriores.
Em vez de três elementos, você pode encontrar uma marca WEP solitária:
[WEP]
Como discutimos acima, isso é suficiente para não especificar o algoritmo para o uso de chaves, que não existe, e o método de criptografia, que é um por padrão.
Agora considere este suporte:
[ESS]
Este é
o modo de operação Wi-Fi ou a
topologia de redes Wi-Fi . Você pode encontrar o modo BSS (Basic Service Set) - quando houver um ponto de acesso através do qual os dispositivos conectados se comunicam. Pode ser encontrado em redes locais. Normalmente, são necessários pontos de acesso para conectar dispositivos de diferentes redes locais, de forma que eles façam parte do Extended Service Sets - ESS. O tipo de IBSSs (conjuntos de serviços básicos independentes) indica que o dispositivo faz parte de uma rede ponto a ponto.
O sinalizador WPS também pode cair:
[WPS]
WPS (Wi-Fi Protected Setup) - um protocolo para inicialização semiautomática de uma rede Wi-Fi. Para inicializar, o usuário digita uma senha de 8 caracteres ou pressiona um botão no roteador. Se o seu ponto de acesso for do primeiro tipo e esse sinalizador estiver destacado em frente ao nome do seu ponto de acesso, é altamente recomendável que você vá ao painel de administração e desative o acesso via WPS. O fato é que muitas vezes um PIN de 8 dígitos pode ser reconhecido pelo endereço MAC ou resolvido no tempo previsível, do qual alguém imundo disponível pode tirar proveito.
6. Crie um modelo e uma função de análise
Com base no que descobrimos acima, descrevemos com classes de dados o que aconteceu:
enum class AuthMethod { WPA3, WPA2, WPA,
Agora, escreveremos uma função que analisará o campo de recursos:
private fun parseCapabilities(capabilitiesString: String): List < Capability > { val capabilities: List < Capability > = capabilitiesString .splitByBrackets() .filter { !it.isTopology() && !it.isWps() } .flatMap { parseCapability(it) } return if (!capabilities.isEmpty()) { capabilities } else { listOf(Capability(AuthMethod.OPEN, KeyManagementAlgorithm.NONE, CipherMethod.NONE)) } } private fun parseCapability(part: String): List < Capability > { if (part.contains("WEP")) { return listOf(Capability( AuthMethod.OTHER, KeyManagementAlgorithm.WEP, CipherMethod.WEP )) } val authScheme = when { part.contains("WPA3") - > AuthMethod.WPA3 part.contains("WPA2") - > AuthMethod.WPA2 part.contains("WPA") - > AuthMethod.WPA else - > null } val keyManagementAlgorithm = when { part.contains("OWE") - > KeyManagementAlgorithm.OWE part.contains("SAE") - > KeyManagementAlgorithm.SAE part.contains("IEEE802.1X") - > KeyManagementAlgorithm.IEEE8021X part.contains("EAP") - > KeyManagementAlgorithm.EAP part.contains("PSK") - > KeyManagementAlgorithm.PSK else - > null } val capabilities = ArrayList < Capability > () if (part.contains("TKIP") || part.contains("CCMP")) { if (part.contains("TKIP")) { capabilities.add(Capability( authScheme ? : AuthMethod.OPEN, keyManagementAlgorithm ? : KeyManagementAlgorithm.NONE, CipherMethod.TKIP )) } if (part.contains("CCMP")) { capabilities.add(Capability( authScheme ? : AuthMethod.OPEN, keyManagementAlgorithm ? : KeyManagementAlgorithm.NONE, CipherMethod.CCMP )) } } else if (authScheme != null || keyManagementAlgorithm != null) { capabilities.add(Capability( authScheme ? : AuthMethod.OPEN, keyManagementAlgorithm ? : KeyManagementAlgorithm.NONE, CipherMethod.NONE )) } return capabilities } private fun parseTopologyMode(capabilitiesString: String): TopologyMode ? { return capabilitiesString .splitByBrackets() .mapNotNull { when { it.contains("ESS") - > TopologyMode.ESS it.contains("BSS") - > TopologyMode.BSS it.contains("IBSS") - > TopologyMode.IBSS else - > null } } .firstOrNull() } private fun parseWPSAvailable(capabilitiesString: String): Boolean { return capabilitiesString .splitByBrackets() .any { it.isWps() } } private fun String.splitByBrackets(): List < String > { val m = Pattern.compile("\\[(.*?)\\]").matcher(this) val parts = ArrayList < String > () while (m.find()) { parts.add(m.group().replace("[", "").replace("]", "")) } return parts } private fun String.isTopology(): Boolean { return TopologyMode.values().any { this == it.name } } private fun String.isWps(): Boolean { return this == "WPS" }
8. Olhamos para o resultado
Vou pesquisar na rede e mostrar o que aconteceu. Mostrando os resultados de uma saída simples por meio do Log.d:
Capability of Home-Home [WPA2-PSK-CCMP][ESS][WPS] ... capabilities=[Capability(authScheme=WPA2, keyManagementAlgorithm=PSK, cipherMethod=CCMP)], topologyMode=ESS, availableWps=true
A questão de se conectar à rede a partir do código do aplicativo permaneceu incerta. Só posso dizer que, para ler as senhas salvas no SO de um dispositivo móvel, você precisa de direitos de root e vontade de vasculhar o sistema de arquivos para ler wpa_supplicant.conf. Se a lógica do aplicativo envolver a inserção de uma senha de fora, a conexão poderá ser feita através da classe
android.net.wifi.WifiManager .
Agradecimentos a
Egor Ponomarev pelas adições valiosas.
Se você acha que precisa adicionar ou consertar algo, escreva nos comentários :)