Como descobri um ovo de páscoa na segurança do Android e não consegui um emprego no Google

O Google adora ovos de páscoa. Na verdade, ele os ama tanto que você pode encontrá-los em praticamente todos os produtos deles. A tradição dos ovos de páscoa para Android começou nas versões mais antigas do sistema operacional (acho que todos sabem o que acontece quando você acessa as configurações gerais e toca no número da versão algumas vezes).

Mas às vezes você pode encontrar um ovo de páscoa nos lugares mais improváveis. Existe até uma lenda urbana que, um dia, um programador pesquisou no Google o "bloqueio mutex", mas, em vez dos resultados da pesquisa, chegou ao foo.bar, resolveu todas as tarefas e conseguiu um emprego no Google.

Reconstrução
imagem

A mesma coisa (exceto sem o final feliz) aconteceu comigo. Mensagens ocultas onde definitivamente não poderia haver nenhuma, revertendo o código Java e suas bibliotecas nativas, uma VM secreta, uma entrevista no Google - tudo isso abaixo.

Droidguard


Uma noite chata, redefinii o telefone de fábrica e comecei a configurá-lo novamente. Para começar, uma nova instalação do Android me pediu para acessar a Conta do Google. E me perguntei: como funciona o processo de logon no Android? E a noite de repente se tornou menos chata.

Eu uso o Burp Suite do PortSwigger para interceptar e analisar o tráfego de rede. A versão comunitária gratuita é suficiente para nossos propósitos. Para ver as solicitações https, primeiro precisamos instalar o certificado do PortSwigger no dispositivo. Como dispositivo de teste, escolhi um Samsung Galaxy S de 8 anos com Android 4.4. Qualquer coisa mais recente que isso e você pode ter problemas com a fixação de certificados e outras coisas.

Com toda a honestidade, não há nada de especial nas solicitações da API do Google. O dispositivo envia informações sobre si mesmo e recebe tokens em resposta ... O único passo curioso é uma solicitação POST ao serviço antiabuso.



Após a solicitação, entre vários parâmetros muito normais, aparece um interessante, chamado droidguard_result . É uma string Base64 muito longa:



O DroidGuard é o mecanismo do Google para detectar bots e emuladores entre dispositivos reais. O SafetyNet, por exemplo, também usa os dados do DroidGuard. O Google também tem algo parecido com os navegadores - o Botguard.

Mas o que são esses dados? Vamos descobrir.

Buffers de protocolo


O que gera esse link ( www.googleapis.com/androidantiabuse/v1/x/create?alt=PROTO&key=AIzaSyBofcZsgLSS7BOnBjZPEkk4rYwzOIz-lTI ) e o que dentro do Android faz essa solicitação? Após uma breve investigação, verificou-se que o link, nesta forma exata, está localizado dentro de uma das classes ofuscadas do Google Play Services:

public bdd(Context var1, bdh var2) { this(var1, "https://www.googleapis.com/androidantiabuse/v1/x/create?alt=PROTO&key=AIzaSyBofcZsgLSS7BOnBjZPEkk4rYwzOIz-lTI", var2); } 

Como já vimos em Burp, as solicitações POST neste link têm o Tipo de conteúdo - application / x-protobuf (Google Protocol Buffers, protocolo do Google para serialização binária). Porém, não é json - é difícil descobrir exatamente o que está sendo enviado.

Buffers de protocolo funcionam assim:

  • Primeiro, descrevemos a estrutura da mensagem em um formato especial e a salvamos em um arquivo .proto;
  • Em seguida, compilamos os arquivos .proto e o compilador protoc gera o código-fonte em um idioma escolhido (no caso do Android, é Java);
  • Finalmente, usamos as classes geradas em nosso projeto.

Temos duas maneiras de decodificar mensagens protobuf. O primeiro é usar um analisador de protobuf e tentar recriar a descrição original dos arquivos .proto. O segundo é retirar as classes geradas por protocolo do Google Play Services, que foi o que eu decidi fazer.

Tomamos o arquivo .apk do Google Play Services da mesma versão que está instalada no dispositivo (ou, se o dispositivo estiver enraizado, basta pegar o arquivo diretamente a partir daí). Usando dex2jar, convertemos o arquivo .dex novamente em .jar e abrimos em um descompilador de sua escolha. Pessoalmente, gosto do Fernflower da JetBrains. Ele funciona como um plug-in para o IntelliJ IDEA (ou Android Studio); portanto, basta iniciar o Android Studio e abrir o arquivo com o link que estamos tentando analisar. Se o programa não estiver se esforçando muito, o código Java descompilado para a criação de mensagens protobuf poderá ser copiado e colado em seu projeto.

Observando o código descompilado, vemos que Build. * Constantes estão sendo enviadas dentro da mensagem protobuf. (ok, isso não foi muito difícil de adivinhar).

 ... var3.a("4.0.33 (910055-30)"); a(var3, "BOARD", Build.BOARD); a(var3, "BOOTLOADER", Build.BOOTLOADER); a(var3, "BRAND", Build.BRAND); a(var3, "CPU_ABI", Build.CPU_ABI); a(var3, "CPU_ABI2", Build.CPU_ABI2); a(var3, "DEVICE", Build.DEVICE); ... 

Infelizmente, porém, na resposta do servidor, todos os campos de protobuf se transformaram em sopa de letrinhas após a ofuscação. Mas podemos descobrir o que está lá usando um manipulador de erros. Veja como os dados provenientes do servidor são verificados:

 if (!var7.d()) { throw new bdf("byteCode"); } if (!var7.f()) { throw new bdf("vmUrl"); } if (!var7.h()) { throw new bdf("vmChecksum"); } if (!var7.j()) { throw new bdf("expiryTimeSecs"); } 

Aparentemente, foi assim que os campos foram chamados antes da ofuscação: byteCode , vmUrl , vmChecksum e expiryTimeSecs . Esse esquema de nomenclatura já nos dá algumas idéias.

Combinamos todas as classes descompiladas do Google Play Services em um projeto de teste, renomeie-as, gere comandos de teste Build Build. * E inicie (imitando qualquer dispositivo que desejarmos). Se alguém quiser fazer isso sozinho, aqui está o link para o meu GitHub .

Se a solicitação estiver correta, o servidor retornará isso:
00: 06: 26.761 [principal] INFO daresponse.AntiabuseResponse - byteCode size: 34446
00: 06: 26.761 [principal] INFO daresponse.AntiabuseResponse - vmChecksum: C15E93CCFD9EF178293A2334A1C9F9B08F115993
00: 06: 26.761 [principal] INFO daresponse.AntiabuseResponse - vmUrl: www.gstatic.com/droidguard/C15E93CCFD9EF178293A2334A1C9F9B08F115993
00: 06: 26.761 [principal] INFO daresponse.AntiabuseResponse - expiryTimeSecs: 10

Etapa 1 concluída. Agora vamos ver o que está oculto por trás do link vmUrl .

Segredo APK


O link nos leva diretamente a um arquivo .apk, nomeado após seu próprio hash SHA-1. É bastante pequeno - apenas 150 KB. E isso é justificado: se ele é baixado por cada um dos 2 bilhões de dispositivos Android, isso representa 270 TB de tráfego nos serviços do Google.



DroidGuardService classe DroidGuardService , como parte do Google Play Services, baixa o arquivo no dispositivo, descompacta, extrai .dex e usa a classe com.google.ccc.abuse.droidguard.DroidGuard por meio de reflexão. Se houver um erro, o DroidGuardService alterna do DroidGuard para o Droidguasso. Mas isso é outra história inteiramente.

Essencialmente, a classe DroidGuard é um wrapper JNI simples em torno da biblioteca .so nativa. A ABI da biblioteca nativa corresponde ao que enviamos no campo CPU_ABI na solicitação protobuf: podemos solicitar armeabi, x86 ou mesmo MIPS.

O serviço DroidGuardService si não contém nenhuma lógica interessante para trabalhar com a classe DroidGuard . Ele simplesmente cria uma nova instância do DroidGuard , envia o byteCode a partir da mensagem protobuf, chama um método público, que retorna uma matriz de bytes. Essa matriz é então enviada ao servidor dentro do parâmetro droidguard_result .

Para ter uma idéia aproximada do que está acontecendo no DroidGuard , podemos repetir a lógica do DroidGuardService (mas sem fazer o download do .apk, pois já temos a biblioteca nativa). Podemos pegar um arquivo .dex do APK secreto, convertê-lo em .jar e depois usá-lo em nosso projeto. O único problema é como a classe DroidGuard carrega a biblioteca nativa. O bloco de inicialização estática chama o método loadDroidGuardLibrary() :

 static { try { loadDroidGuardLibrary(); } catch (Exception ex) { throw new RuntimeException(ex); } } 

Em seguida, o método loadDroidGuardLibrary() lê library.txt (localizado na raiz do arquivo .apk) e carrega a biblioteca com esse nome pela chamada System.load(String filename) . Não é muito conveniente para nós, pois precisaríamos criar o .apk de uma maneira muito específica para colocar library.txt e o arquivo .so em sua raiz. Seria muito mais conveniente manter o arquivo .so na pasta lib e carregá-lo através de System.loadLibrary(String libname) .

Não é difícil de fazer. Usaremos smali / baksmali - assembler / disassembler para arquivos .dex. Depois de usá-lo, o classes.dex se transforma em vários arquivos .smali. A classe com.google.ccc.abuse.droidguard.DroidGuard deve ser modificada, para que o bloco de inicialização estática chame o método System.loadLibrary("droidguard") vez de loadDroidGuardLibrary() . A sintaxe do Smali é bastante simples, o novo bloco de inicialização é assim:

 .method static constructor <clinit>()V .locals 1 const-string v0, "droidguard" invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V return-void .end method 

Em seguida, usamos backsmali para compilar tudo de volta em .dex e depois convertemos em .jar. No final, obtemos um arquivo .jar que podemos usar em nosso projeto - aqui está , a propósito.

A seção inteira relacionada ao DroidGuard tem algumas seqüências de caracteres. A parte mais importante é fazer o download da matriz de bytes que obtivemos na etapa anterior após endereçar o serviço antiabuso e entregá-lo ao construtor DroidGuard :

 private fun runDroidguard() { var byteCode: ByteArray? = loadBytecode("bytecode.base64"); byteCode?.let { val droidguard = DroidGuard(applicationContext, "addAccount", it) val params = mapOf("dg_email" to "test@gmail.com", "dg_gmsCoreVersion" to "910055-30", "dg_package" to "com.google.android.gms", "dg_androidId" to UUID.randomUUID().toString()) droidguard.init() val result = droidguard.ss(params) droidguard.close() } } 

Agora podemos usar o criador de perfil do Android Studio e ver o que acontece durante o trabalho do DroidGuard:



O método nativo initNative () coleta dados sobre o dispositivo e chama os métodos Java hasSystemFeature(), getMemoryInfo(), getPackageInfo() ... isso é algo, mas ainda não vejo nenhuma lógica sólida. Bem, tudo o que resta é desmontar o arquivo .so.

libdroidguard.so


Felizmente, analisar a biblioteca nativa não é mais difícil do que fazê-lo com arquivos .dex e .jar. Precisávamos de um aplicativo semelhante ao Hex-Rays IDA e algum conhecimento do código montador x86 ou ARM. Eu escolhi o ARM, já que eu tinha um dispositivo enraizado para depuração. Se você não tiver uma, poderá pegar uma biblioteca x86 e depurar usando um emulador.

Um aplicativo semelhante ao Hex-Rays IDA decompila o binário em algo semelhante ao código C. Se abrirmos o método Java_com_google_ccc_abuse_droidguard_DroidGuard_ssNative , veremos algo assim:

 __int64 __fastcall Java_com_google_ccc_abuse_droidguard_DroidGuard_initNative(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9) ... v14 = (*(_DWORD *)v9 + 684))(v9, a5); v15 = (*(_DWORD *)v9 + 736))(v9, a5, 0); ... 

Não parece muito promissor. Primeiro, precisamos fazer algumas etapas preliminares para transformar isso em algo mais útil. O descompilador não sabe nada sobre JNI, por isso instalamos o Android NDK e importamos o arquivo jni.h. Como sabemos, os dois primeiros parâmetros de um método JNI são JNIEnv* e jobject (this) . Podemos descobrir os tipos de outros parâmetros no código Java do DroidGuard. Após atribuir os tipos corretos, as compensações sem sentido se transformam em chamadas de método JNI:

 __int64 __fastcall Java_com_google_ccc_abuse_droidguard_DroidGuard_initNative(_JNIEnv *env, jobject thiz, jobject context, jstring flow, jbyteArray byteCode, jobject runtimeApi, jobject extras, jint loggingFd, int runningInAppSide) { ... programLength = _env->functions->GetArrayLength)(_env, byteCode); programBytes = (jbyte *)_env->functions->GetByteArrayElements)(_env, byteCode, 0); ... 

Se tivermos paciência suficiente para rastrear a matriz de bytes recebida do servidor antiabuso, ficaremos ... desapontados. Infelizmente, não há uma resposta simples para "o que está acontecendo aqui?". É um código de bytes puro e destilado, e a biblioteca nativa é uma máquina virtual. Alguma criptografia AES espalhada na parte superior e, em seguida, a VM lê o código de byte, byte a byte e executa comandos. Cada byte é um comando seguido por operandos. Não existem muitos comandos, apenas cerca de 70: read int, read byte, read string, chame o método Java, multiplique dois números, if-goto etc.

Acorde neo


Decidi ir ainda mais longe e descobrir a estrutura do código de bytes para esta VM. Há outro problema com as chamadas: algumas vezes (uma vez a cada duas semanas), há uma nova versão da biblioteca nativa na qual os pares de comandos de bytes são misturados. Isso não me impediu e decidi recriar a VM usando Java.

O que o código de byte faz é todo o trabalho de rotina na coleta de informações sobre o dispositivo. Por exemplo, ele carrega uma string com o nome de um método, obtém seu endereço através do dlsym e é executado. Na minha versão Java da VM, recriei apenas mais ou menos cinco métodos e aprendi a interpretar os 25 primeiros comandos do código de bytes do serviço antiabuso. No 26º comando, a VM leu outra sequência criptografada do código de byte. De repente, descobriu-se que não é o nome de outro método. Longe disso.
Comando da máquina virtual nº 26
Chamada de método vm-> vm_method_table [2 * 0x77]
Método vmMethod_readString
o índice é 0x9d
o comprimento da string é 0x0066
(nova chave é gerada)
bytes de cadeia codificados são EB 4E E6 DC 34 13 35 4A DD 55 B3 91 33 05 61 04 C0 54 FD 95 2F 18 72 04 C1 55 E1 92 28 11 66 04 DD 4F B3 94 33 04 35 0A C1 4E B2 DB 12 17 79 4F 92 55 FC DB 33 05 35 45 C6 01 F7 89 29 1F 71 43 C7 40 E1 9F 6B 1E 70 48 DE 4E B8 CD 75 44 23 14 85 14 A7 C2 7F 40 26 42 84 17 A2 BB 21 19 7 43 DE 44 BD 98 29 1B
bytes de cadeia decodificados são 59 6F 75 27 72 65 20 6E 6F 74 20 6A 75 73 74 20 72 75 6E 6E 69 6E 67 20 73 74 72 69 6E 67 73 20 6F 6E 20 6F 75 72 20 2E 73 6F 21 20 54 61 6C 6B 20 74 6F 20 75 73 20 61 74 20 64 72 6F 69 64 67 75 61 72 64 2D 68 65 6C 6C 6F 2B 36 33 32 36 30 37 35 34 39 39 36 33 66 36 36 31 40 67 6F 6F 67 6C 65 2E 63 6F 6D
o valor da string decodificada é ( você não está apenas executando strings no nosso .so! Fale conosco pelo e-mail droidguard@google.com )
Isso é estranho. Uma máquina virtual nunca falou comigo antes. Eu pensei que se você começar a ver mensagens secretas direcionadas a você, estará ficando louco. Apenas para ter certeza de que ainda estava em sã consciência, executei algumas centenas de respostas diferentes do serviço anti-abuso por meio da minha VM. Literalmente, a cada 25 a 30 comandos, havia uma mensagem oculta no código de bytes. Eles costumam repetir, mas abaixo estão alguns exclusivos. No entanto, editei os endereços de email: cada mensagem tinha um endereço diferente, algo como "droidguard+tag@google.com", com a tag exclusiva para cada uma.
droidguard@google.com: não seja um estranho!
Você entrou! Fale conosco em droidguard@google.com
Saudações de droidguard@google.com intrépido viajante! Diga oi!
Foi fácil encontrar isso? droidguard@google.com gostaria de saber
O pessoal do droidguard@google.com gostaria de receber sua opinião!
O que é todo esse gobbledygook? Pergunte a droidguard@google.com ... eles saberiam!
Ei! Gostaria de vê-lo aqui. Você já falou com droidguard@google.com?
Você não está apenas executando strings no nosso .so! Fale conosco em droidguard@google.com
Eu sou o escolhido? Eu pensei que era hora de parar de mexer com o DroidGuard e conversar com o Google, pois eles me pediram.

Sua ligação é muito importante para nós


Eu contei minhas descobertas no e-mail que encontrei. Para tornar os resultados um pouco mais impressionantes, automatizei um pouco o processo de análise. O problema é que cadeias de caracteres e matrizes de bytes são armazenadas no código de bytes criptografado. A VM os decodifica usando constantes alinhadas pelo compilador. Usando um aplicativo semelhante ao Hex-Rays IDA, você pode extraí-los facilmente. Porém, a cada nova versão, as constantes são alteradas e é bastante inconveniente extraí-las sempre manualmente.

Mas a análise de Java da biblioteca nativa mostrou-se surpreendentemente simples. Usando jelf (uma biblioteca para analisar arquivos ELF), encontramos o deslocamento do método Java_com_google_ccc_abuse_droidguard_DroidGuard_initNative no binário e, em seguida, usando o Capstone (uma estrutura de desmontagem com ligações para vários idiomas, incluindo Java), obtemos o código do assembler e procuramos carregar constantes em registros.

No final, recebi um aplicativo que emulava todo o processo do DroidGuard: faz uma solicitação ao serviço antiabuso, baixa o arquivo .apk, descompacta, analisa a biblioteca nativa, extrai as constantes necessárias, seleciona o mapeamento dos comandos da VM e interpreta o código de bytes. Compilei tudo e enviei para o Google. Enquanto estava no assunto, comecei a me preparar para uma mudança e procurei no Glassdoor um salário médio no Google. Decidi não concordar com nada menos que seis dígitos.

A resposta não demorou muito. Um email de um membro da equipe DroidGuard dizia simplesmente: "Por que você está fazendo isso?"



"Porque eu posso" - respondi. Um funcionário do Google me explicou que o DroidGuard deveria proteger o Android de hackers (você não diz!) E seria prudente manter o código fonte da minha VM do DroidGuard para mim. Nossa conversa terminou aí.

Entrevista


Um mês depois, recebi outro email. A equipe DroidGuard em Zurique precisava de um novo funcionário. Eu estava interessado em participar? Claro!

Não há atalhos para entrar no Google. Tudo o que meu contato pôde fazer foi encaminhar meu currículo para o departamento de RH. Depois disso, tive que passar pela habitual burocracia e uma série de entrevistas.

Há muitas histórias por aí sobre entrevistas no Google. Algoritmos, tarefas das Olimpíadas e programação do Google Docs não são o meu estilo, então comecei meus preparativos. Li o curso de Algoritmos do Coursera dezenas de vezes, resolvi centenas de tarefas no Hackerrank e aprendi a percorrer um gráfico em ambas as dimensões com os olhos fechados.

Dois meses se passaram. Dizer que me senti preparado seria um eufemismo. O Google Docs se tornou meu IDE favorito. Eu senti que sabia tudo o que há para saber sobre algoritmos. É claro que eu conhecia minhas fraquezas e percebi que provavelmente não passaria na série de cinco entrevistas em Zurique, mas ir à Disneyland do programador de graça era uma recompensa por si só. O primeiro passo foi uma entrevista por telefone para eliminar os candidatos mais fracos e não perder tempo dos desenvolvedores de Zurique em reuniões pessoais. O dia estava marcado, o telefone tocou ...



... e eu imediatamente falhei no meu primeiro teste. Eu tive sorte - eles fizeram uma pergunta que eu já vi na internet e já resolvi. Tratava-se de serializar uma matriz de strings. Ofereci-me para codificar cadeias de caracteres no Base64 e salvá-las através de um divisor. O entrevistador me pediu para desenvolver um algoritmo Base64. Depois disso, a entrevista se transformou em uma espécie de monólogo, onde os entrevistados me explicaram como o Base64 funciona e tentei lembrar de operações de bits em Java.

Se alguém do Google estiver lendo isso
Gente, vocês são gênios se chegarem lá! Sério. Não consigo imaginar como é possível eliminar todos os obstáculos que eles colocam diante de você.

Três dias após a ligação, recebi um e-mail dizendo que eles não querem mais me entrevistar. E foi assim que minha comunicação com o Google terminou.

Por que há mensagens no DroidGuard pedindo para conversar, eu ainda não faço ideia. Provavelmente apenas para estatísticas. O cara para quem eu escrevi me disse que as pessoas realmente escrevem lá, mas a frequência varia: às vezes eles recebem três respostas em uma semana, às vezes uma por ano.

Acredito que existem maneiras mais fáceis de obter uma entrevista no Google. Afinal, você pode perguntar a qualquer um dos 100.000 funcionários (embora nem todos sejam desenvolvedores, é certo). Mas foi uma experiência divertida, no entanto.

Source: https://habr.com/ru/post/pt446790/


All Articles