Análise de Vulnerabilidade no EvilParcel

1. Introdução


Em meados de abril, publicamos notícias sobre o Trojan Android.InfectionAds.1 , que explorava várias vulnerabilidades críticas no sistema operacional Android. Um deles - CVE-2017-13156 (também conhecido como Janus ) - permite que um programa malicioso infecte arquivos APK sem danificar sua assinatura digital.

O outro é CVE-2017-13315. Ele concede ao Trojan privilégios avançados e pode instalar e desinstalar aplicativos de forma independente. Uma análise detalhada do Android.InfectionAds.1 está disponível na nossa biblioteca de vírus, pode ser encontrada aqui . Vamos nos aprofundar na vulnerabilidade CVE-2017-13315 com mais detalhes e ver como é.

CVE-2017-13315 pertence ao grupo de vulnerabilidades que receberam o nome geral EvilParcel. Eles são encontrados em várias classes de sistema do sistema operacional Android. Devido a erros neste último ao trocar dados entre aplicativos e o sistema, torna-se possível substituir esses dados. Programas maliciosos que exploram as vulnerabilidades do EvilParcel recebem privilégios mais altos e podem fazer o seguinte com sua ajuda:

  • instalar e desinstalar aplicativos com quaisquer permissões sem confirmação do usuário;
  • quando usado em conjunto com outras vulnerabilidades, infectar programas instalados no dispositivo e substituir originais "limpos" por cópias infectadas;
  • Redefinir código de bloqueio de tela para dispositivo Android
  • Redefinir o PIN da tela de bloqueio do dispositivo Android.

Atualmente, existem 7 vulnerabilidades conhecidas desse tipo:

  • CVE-2017-0806 (erro na classe GateKeeperResponse), publicado em outubro de 2017;
  • CVE-2017-13286 (erro na classe OutputConfiguration, publicada em abril de 2018;
  • CVE-2017-13287 (erro na classe VerifyCredentialResponse), publicada em abril de 2018;
  • CVE-2017-13288 (erro na classe PeriodicAdvertizingReport), publicada em abril de 2018;
  • CVE-2017-13289 (bug na classe ParcelableRttResults), publicado em abril de 2018;
  • CVE-2017-13311 (bug na classe SparseMappingTable), publicada em maio de 2018;
  • CVE-2017-13315 (erro na classe DcParamObject), publicado em maio de 2018.

Todos eles ameaçam dispositivos com versões do SO Android 5.0 - 8.1 que não possuem as atualizações de segurança de maio de 2018 e posteriores instaladas.

Pré-requisitos para as vulnerabilidades do EvilParcel


Vamos ver como surgem as vulnerabilidades do EvilParcel. Primeiro de tudo, veremos alguns dos recursos dos aplicativos Android. No sistema operacional Android, todos os programas interagem entre si, bem como com o próprio sistema operacional, enviando e recebendo objetos do tipo Intent. Esses objetos podem conter um número arbitrário de pares de valores-chave dentro de um objeto do tipo Bundle.

Ao transmitir Intent, o objeto Bundle é convertido (serializado) em uma matriz de bytes agrupada no Parcel e, ao ler chaves e valores de um Bundle serializado, ele é desserializado automaticamente.

No Bundle, a string é a chave e o valor pode ser quase qualquer coisa. Por exemplo, um tipo primitivo, sequência ou contêiner contendo tipos ou sequências primitivas. Além disso, pode ser um objeto do tipo Parcelable.

Assim, no Bundle, você pode colocar um objeto de qualquer tipo que implemente a interface Parcelable. Para fazer isso, você precisará implementar os métodos writeToParcel () e createFromParcel () para serializar e desserializar o objeto.

Como um bom exemplo, vamos criar um simples pacote serializado. Vamos escrever um código que coloque três pares de valores-chave no Bundle e o serialize:

Demonstração do pacote = novo pacote ();
demo.putString ("String", "Olá, Mundo!");
demo.putInt ("Inteiro", 42);
demo.putByteArray ("ByteArray", novo byte [] {1, 2, 3, 4, 5, 6, 7, 8});
Parcel parcel = Parcel.obtain ();
parcel.writeBundle (demo);

Depois de executar esse código, obtemos um pacote do seguinte formato:



Figura 1. Estrutura de um objeto Bundle serializado.

Vamos prestar atenção aos seguintes recursos da serialização de pacotes:

  • todos os pares de valores-chave são escritos um após o outro;
  • antes de cada valor, seu tipo é indicado (13 para uma matriz de bytes, 1 para um número inteiro, 0 para uma sequência e assim por diante);
  • antes dos dados de comprimento variável, seu tamanho é indicado (comprimento para a cadeia, número de bytes para a matriz);
  • todos os valores são gravados com um alinhamento de 4 bytes.

Devido ao fato de que todas as chaves e valores no Bundle são gravados sequencialmente, ao acessar uma chave ou o valor de um objeto Bundle serializado, o último é desserializado completamente, incluindo a inicialização de todos os objetos Parcelable contidos nele.

Parece, qual poderia ser o problema? E é que em algumas classes de sistema que implementam Parcelable, os métodos createFromParcel () e writeToParcel () podem encontrar erros. Nessas classes, o número de bytes lidos no método createFromParcel () será diferente do número de bytes gravados no método writeToParcel (). Se você colocar um objeto dessa classe dentro do Bundle, os limites do objeto dentro do Bundle serão alterados após a re-serialização. E é aí que as condições para explorar a vulnerabilidade do EvilParcel são criadas.

Aqui está um exemplo de uma classe com um erro semelhante:

class Demo implements Parcelable { byte[] data; public Demo() { this.data = new byte[0]; } protected Demo(Parcel in) { int length = in.readInt(); data = new byte[length]; if (length > 0) { in.readByteArray(data); } } public static final Creator<Demo> CREATOR = new Creator<Demo>() { @Override public Demo createFromParcel(Parcel in) { return new Demo(in); } }; @Override public void writeToParcel(Parcel parcel, int i) { parcel.writeInt(data.length); parcel.writeByteArray(data); } } 

Se o tamanho da matriz de dados for 0, ao criar um objeto em createFromParcel (), um int (4 bytes) será lido e dois int (8 bytes) serão gravados em writeToParcel (). O primeiro int será gravado em uma chamada explícita para writeInt. O segundo int será gravado quando writeByteArray () for chamado, porque o comprimento da matriz é sempre gravado no Parcel antes dele (veja a Figura 1).

Situações em que o tamanho da matriz de dados é 0 são raras. Mas mesmo quando isso acontece, o programa continua funcionando se apenas um objeto for transmitido em formato serializado por vez (no nosso exemplo, o objeto Demo). Portanto, esses erros, em regra, passam despercebidos.

Agora vamos tentar colocar um objeto Demo com um comprimento zero da matriz no Bundle:


Figura 2. O resultado da adição de um objeto Demo com comprimento zero da matriz ao Bundle.

Serializamos o objeto:


Figura 3. Objeto de pacote configurável após serialização.

Vamos tentar desserializar:


Figura 4. Após desserializar o objeto Bundle.

Qual é o resultado? Considere um trecho de pacote:


Figura 5. Estrutura do pacote após a desserialização do pacote.

Nas Figuras 4 e 5, vemos que durante a desserialização, um int foi lido no método createFromParcel em vez de dois escritos anteriormente. Portanto, todos os valores subsequentes do pacote configurável não foram lidos corretamente. O valor 0x0 no endereço 0x60 foi lido como o comprimento da próxima chave. E o valor 0x1 no endereço 0x64 foi lido como uma chave. Nesse caso, o valor 0x31 no endereço 0x68 foi lido como o tipo de valor. Não há valores no pacote cujo tipo é 0x31, portanto, readFromParcel () relatou fielmente um erro (exceção).

Como isso pode ser usado na prática e se tornar uma vulnerabilidade? Vamos ver! O erro descrito acima nas classes do sistema Parcelable permite construir Bundle, que pode diferir durante a primeira e desserialização repetida. Para demonstrar isso, modifique o exemplo anterior:

 Parcel data = Parcel.obtain(); data.writeInt(3); // 3 entries data.writeString("vuln_class"); data.writeInt(4); // value is Parcelable data.writeString("com.drweb.testbundlemismatch.Demo"); data.writeInt(0); // data.length data.writeInt(1); // key length -> key value data.writeInt(6); // key value -> value is long data.writeInt(0xD); // value is bytearray -> low(long) data.writeInt(-1); // bytearray length dummy -> high(long) int startPos = data.dataPosition(); data.writeString("hidden"); // bytearray data -> hidden key data.writeInt(0); // value is string data.writeString("Hi there"); // hidden value int endPos = data.dataPosition(); int triggerLen = endPos - startPos; data.setDataPosition(startPos - 4); data.writeInt(triggerLen); // overwrite dummy value with the real value data.setDataPosition(endPos); data.writeString("A padding"); data.writeInt(0); // value is string data.writeString("to match pair count"); int length = data.dataSize(); Parcel bndl = Parcel.obtain(); bndl.writeInt(length); bndl.writeInt(0x4C444E42); // bundle magic bndl.appendFrom(data, 0, length); bndl.setDataPosition(0); 

Esse código cria um pacote serializado que contém uma classe vulnerável. Vejamos o resultado da execução deste código:


Figura 6. Criando um pacote com uma classe vulnerável.

Após a primeira desserialização, este pacote configurável conterá as seguintes chaves:


Figura 7. Resultado da desserialização de um Bundle com uma classe vulnerável.

Agora, serialize novamente o Bundle resultante, deserialize-o novamente e veja a lista de chaves:


Figura 8. Resultado da nova serialização e desserialização de um Bundle com uma classe vulnerável.

O que nós vemos? A chave oculta (com o valor da sequência "Olá!") Apareceu no pacote, que não existia antes. Considere o snippet Parcel deste pacote para entender por que isso aconteceu:


Figura 9. Estrutura do pacote do objeto Bundle com a classe vulnerável após dois ciclos de serialização-desserialização.

Aqui, a essência das vulnerabilidades do EvilParcel fica mais clara. É possível criar um pacote especialmente formado que conterá uma classe vulnerável. A alteração dos limites desta classe permitirá que você coloque qualquer objeto neste Bundle - por exemplo, Intent, que aparecerá no Bundle somente após a segunda desserialização. Isso tornará possível ocultar o Intent dos mecanismos de proteção do sistema operacional.

Operação EvilParcel


O Android.InfectionAds.1 usando o CVE-2017-13315 instalou e desinstalou programas sem a intervenção do proprietário do dispositivo infectado. Mas como vai isso?

Em 2013, o erro 7699048 também foi descoberto, também conhecido como Launch AnyWhere. Permitia que um aplicativo de terceiros executasse atividades arbitrárias em nome do sistema de usuário mais privilegiado. O diagrama abaixo mostra seu mecanismo de ação:


Figura 10. Esquema do erro 7699048.

Com essa vulnerabilidade, um aplicativo de exploração pode implementar o serviço AccountAuthenticator, projetado para adicionar novas contas ao sistema operacional. Graças ao bug 7699048, o exploit é capaz de executar atividades para instalar, desinstalar, substituir aplicativos, redefinir o PIN ou o bloqueio de padrão e fazer outras coisas desagradáveis.

O Google corrigiu essa lacuna proibindo o lançamento de atividades arbitrárias do AccountManager. Agora, o AccountManager permite apenas o lançamento de atividades provenientes do mesmo aplicativo. Para fazer isso, ele verifica e compara a assinatura digital do programa que iniciou o início da atividade com a assinatura do aplicativo em que a atividade iniciada está localizada. É assim:

 if (result != null && (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) { /* * The Authenticator API allows third party authenticators to * supply arbitrary intents to other apps that they can run, * this can be very bad when those apps are in the system like * the System Settings. */ int authenticatorUid = Binder.getCallingUid(); long bid = Binder.clearCallingIdentity(); try { PackageManager pm = mContext.getPackageManager(); ResolveInfo resolveInfo = pm.resolveActivityAsUser(intent, 0, mAccounts.userId); int targetUid = resolveInfo.activityInfo.applicationInfo.uid; if (PackageManager.SIGNATURE_MATCH != pm.checkSignatures(authenticatorUid, targetUid)) { throw new SecurityException( "Activity to be started with KEY_INTENT must " + "share Authenticator's signatures"); } } finally { Binder.restoreCallingIdentity(bid); } } 

Parece que o problema foi resolvido, mas nem tudo aqui é tão bom. Aconteceu que essa correção pode ser contornada usando a vulnerabilidade conhecida EvilParcel CVE-2017-13315! Como já sabemos, após corrigir o Launch AnyWhere, o sistema verifica a assinatura digital do aplicativo. Se essa verificação for bem-sucedida, o Bundle será passado para IAccountManagerResponse.onResult (). Ao mesmo tempo, onResult () é chamado pelo mecanismo IPC, para que o Bundle seja serializado novamente. Na implementação onResult (), acontece o seguinte:

 /** Handles the responses from the AccountManager */ private class Response extends IAccountManagerResponse.Stub { public void onResult(Bundle bundle) { Intent intent = bundle.getParcelable(KEY_INTENT); if (intent != null && mActivity != null) { // since the user provided an Activity we will silently start intents // that we see mActivity.startActivity(intent); // leave the Future running to wait for the real response to this request } //<.....> } //<.....> } 

Em seguida, o Bundle é extraído da chave de intenção e a atividade é iniciada sem verificações. Como resultado, para iniciar uma atividade arbitrária com direitos do sistema, basta construir o Bundle de forma que o campo de intenção fique oculto na primeira desserialização e apareça na segunda desserialização. E, como já vimos, é precisamente essa tarefa que as vulnerabilidades do EvilParcel cumprem.

No momento, todas as vulnerabilidades conhecidas desse tipo são corrigidas por correções nas próprias classes Parcelable vulneráveis. No entanto, o reaparecimento de classes vulneráveis ​​no futuro não pode ser descartado. A implementação do Bundle e o mecanismo para adicionar novas contas ainda são os mesmos de antes. Eles ainda permitem que você crie exatamente a mesma exploração ao descobrir (ou novas) classes Parcelable vulneráveis. Além disso, a implementação dessas classes ainda é feita manualmente, e o programador deve ficar de olho no comprimento constante do objeto Parcelable serializado. E este é um fator humano com todas as consequências. No entanto, esperamos que esses erros sejam o mínimo possível e as vulnerabilidades do EvilParcel não incomodem os usuários de dispositivos Android.

Você pode verificar se há vulnerabilidades no EvilParcel no seu dispositivo móvel usando nosso antivírus Dr.Web Security Space . O “Auditor de Segurança” interno relatará os problemas identificados e dará recomendações para resolvê-los.

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


All Articles