Como quase peguei um vírus tentando vender botas



Eu sou uma daquelas pessoas que, com o início do outono, estão tentando passar menos tempo na rua. Em Moscou, isso não é difícil: você está limitado à rota de casa para o escritório e de volta. No entanto, o clima úmido pode causar desconforto na sala, especialmente se o seu local de trabalho, como o meu, estiver na janela, e cada segundo colega, reclamando de congestionamento, pede para arejar o escritório. Para não cair no baço, atualizei o guarda-roupa.

Pensando no destino de coisas desnecessárias, pensei no que fazer com elas: jogue fora, corte em trapos, entregue ao meu irmão mais novo para carregá-lo? Mas, por um lado, nenhum desses métodos era adequado: eram botas de couro de tamanho decente, mas me incomodavam em ordem. Eu decidi vendê-los no Avito. Enviei fotos, indiquei um nome falso (a segurança da informação é a mesma), tirei minhas botas, algumas outras coisas e fui dormir. Como eu sabia que isso resultaria em uma longa análise do aplicativo para ameaças ocultas?



Surpresa agradável


No dia seguinte a algumas chamadas duvidosas, recebi uma mensagem SMS interessante com o seguinte conteúdo:



Depois de alguns dias, recebi outra mensagem semelhante:



Surpreso que alguém tenha sido capaz de transferir dinheiro para mim na Internet (aparentemente, eu sou o único velho - ainda uso livros de economia de papel), cliquei no link no SMS.

Depois disso, me ofereceram o download de um aplicativo Android (arquivo apk). Felizmente ao baixar o arquivo, vi o seguinte:



É credível! Eu queria ansiosamente instalar tudo rapidamente e finalizá-lo.
Mas aqui, como sempre, o irritante sistema operacional Android, por algum motivo, não me permitiu executar o arquivo. "Me dê o dinheiro já!" Eu estava indignado. Eu tive que ir para as configurações e ativar alguma opção "Fontes desconhecidas", o telefone é realmente tão estúpido em 2018? A propósito, meu telefone é o Xiaomi Remdi com o Andoid 6.0.1 (nota para técnicos).



Isto foi seguido por uma cadeia de eventos estranhos. O telefone continuou a relatar fontes não confiáveis. Mas Avito é uma fonte confiável ! Eu tive que pesquisar no google, descobrir como contornar isso e desligá-lo nas configurações. Logo apareceu um certo antivírus que eu não instalei.



Por fim, vi a cobiçada janela de instalação padrão - não faz sentido olhar para as permissões hoje em dia , agora mesmo um notebook não será iniciado até que você tenha acesso total ao telefone. Momento alegre de conclusão da instalação do aplicativo e INÍCIO! Eu estava ansioso pelo dinheiro prometido. Além disso, o aplicativo solicitou privilégios de administrador, com os quais concordei com alegria. Infelizmente, o aplicativo se comportou de maneira estranha e não queria pagar, e logo desapareceu completamente da lista de aplicativos na tela comum.





Spoiler
Mais tarde, verifiquei em outro telefone - a Lenovo com o Android 4.4.2 a bordo. A lista de permissões de instalação acabou por ser muito maior. E como o Play Protection e o Anti-Virus não interferem, você só precisa permitir a instalação de fontes não confiáveis.





Altruísmo



Então, a que chegamos:

  • Demorou 20 minutos para instalar.
  • Eu não recebi o dinheiro.

Eu pensei que o problema era um erro de código, como geralmente acontece com os programadores. Decidi identificar quais erros ocorrem quando o aplicativo está sendo executado no meu telefone e enviar um relatório sobre isso ao desenvolvedor.

Spoiler
Agora existem tão poucas pessoas desinteressadas, uma delas sou eu.

É claro que esse tipo de aplicativo deve funcionar quando a Internet está conectada; portanto, para começar, tentei analisar o tráfego entre o telefone e o servidor de aplicativos.

Guia técnico de configuração de proxy
Você pode ouvir o tráfego da Internet a qualquer momento, desde o telefone até o servidor, seja um roteador doméstico, provedor de Internet local, backbone da Internet ou servidor de aplicativos.

Os problemas:

  • É necessário acesso a este equipamento.
  • É necessário separar o tráfego do aplicativo desejado do restante.
  • No caso de criptografia (e em 2018 tudo já está criptografado) - o conhecimento da chave é necessário.

Decidi seguir de uma maneira mais clássica e configurar proxies no telefone com um servidor proxy no meu próprio laptop. Para atenuar o problema de criptografia, decidi importar meu certificado e não iniciei outros aplicativos para separar o tráfego do aplicativo.
Eu escolhi o arroto suite como o programa do servidor proxy. Configurei um servidor proxy e exportei um certificado para o meu telefone.





Após configurar o software especializado, pude ver quais solicitações o aplicativo envia ao servidor:



Como você pode ver, os dados não são enviados (o valor desconhecido do host é indicado na coluna IP); além disso, não podem ser analisados ​​devido à presença de criptografia adicional no nível do aplicativo. A julgar por engano, o telefone não pôde determinar o endereço IP do servidor e seus subdomínios https: //*.sky-sync.pw pelo nome de domínio.

Isso pode significar apenas as seguintes opções:

  • O nome do domínio deixou de existir
    • Foi bloqueado pelo próprio proprietário.
    • Ele foi bloqueado pelo registrador na denúncia.
  • Problema com o servidor DNS
    • O servidor DNS não sabe o endereço, porque o desenvolvedor em produção lançou o endereço DNS local.
    • O servidor DNS bloqueou especificamente essa solicitação, o que não é surpreendente na era da censura na Internet.

Para verificar a suposição de um problema com o servidor DNS, tentei fazer solicitações de diferentes servidores DNS grandes: Google, Yandex, OpenDNS (geralmente o DNS local é censurado):



Pode-se ver que nenhum deles sabe nada sobre esse nome. Em seguida, examinei as informações whois sobre registro de domínio:



Curioso: o domínio está registrado, ou seja, provavelmente não é local, mas como o domínio não resolve, pode ter sido bloqueado pelo registrador por abuso. Mas para que? Que erro ele fez?

Para descobrir qual é esse aplicativo e como posso receber meu dinheiro, decidi usar a mágica da engenharia reversa.

Abismo


Se você é humanista e leu até aqui, isso é bom - para o desenvolvimento dos subsequentes você merece um prêmio póstumo .

Toolkit


Para descobrir o que está "sob o capô" do aplicativo, precisamos fazer o download de ferramentas especializadas. Você pode baixá-los individualmente:

  • Descompactar contêiner apk
    • Clássico - ApkTool .
    • Você pode descompactá-lo com um arquivador convencional, mas todos os recursos binários, incluindo aplicativos e o arquivo Manifest, serão ilegíveis.
  • Descompilador de código smali
    • O padrão é Dex2Jar , mas saiba que esse programa geralmente funciona de maneira torta.
    • Você precisa abordar o assunto com muito cuidado, porque o descompilador para o descompilador é diferente, consideraremos isso posteriormente.
  • Um programa para visualizar código descompilado , eu recomendaria o jd-gui

Ou você pode usar o produto, geralmente pago, onde há tudo de uma vez. Prefiro o JebDecompiler : ele pode simplesmente enviar um aplicativo apk à sua entrada e organizar tudo em guias, além de ser conveniente alternar entre smali e código Java descompilado.

Separadamente, quero observar:

  • Se houvesse libs nativas (a pasta / libs na estrutura do aplicativo), seria necessário um desmontador .
  • Você pode precisar de um conjunto de utilitários para trabalhar com o código smali (dex para smali, smali para dex).
  • Destaque de sintaxe no Notepad ++ para não quebrar os olhos.

Apenas idosos entram em batalha


Revisão


Quando você abre o código descompilado, fica imediatamente claro que ele está ofuscado.



Como eu entendo isso?

  • Nomes de classe legíveis por humanos
    isqpwcmx.isfdztgb.adscjobz.nxscomkr.jypbdxnt.utagwpym.wprtdznb.swldgrhm.yrbjpktq.wukovicq;
  • Código inacessível
     if(0 != 0) {</li> String v1 = "flnwznvh";</li> if(v1.length() != 661 && v1.charAt(0) == 104) {</li> v1.length();</li> } 
  • Criptografia de string
     vcgrnfjx.execSQL(nvhdzjfo.xipswfqb(new String[]{"f741f04a4991fc2f0a0029f610bbd1c250dfe115fb7770b892f75d8718b822d273251013991b4407e224fa3f9d4e92f6","378f40211b6e32a5406cd97e85bcf9ad","6378a459b1c20edf", "gexnfwok", "meazfhdp", "bsmotaxn"}) 

É improvável que o programador tenha sido tão louco a ponto de desenvolver o código inicialmente. Provavelmente, ele usou um dos ofuscadores disponíveis ao público. Essa etapa é bastante comum para complicar a análise de código, a fim de proteger a propriedade intelectual, por exemplo, mas que tipo de "reviravolta" para o pesquisador.

Vamos prestar atenção à principal função de criptografia:



A própria função de criptografia aceita 3 linhas de entrada (se houver, o restante não faz sentido):

  1. texto cifrado
  2. a chave
  3. vetor de inicialização para CBC - AES

Esta função é referenciada no programa pelo menos 213 vezes:



Noto que é uma chave importante para a análise normal de código. Em seguida, você precisa pensar que temos as seguintes maneiras de analisar o programa:

  1. Restaure a lógica da função, colete todas as chamadas na análise estática, decodifique as linhas. Pode ser difícil e longo, mas dará um resultado de 100%.
  2. Faça alterações no código smali do aplicativo, compile novamente, execute o aplicativo e pegue as linhas descriptografadas nos logs. É fácil, mas não se sabe como o aplicativo se comportará em um determinado lançamento e talvez você não veja a imagem toda (não recebe chamadas para todas as funções). Além disso, pode haver problemas com a auto-verificação pela aplicação do certificado e (ou) integridade.
  3. Se for difícil restaurar a lógica da função, você poderá coletar todas as chamadas de funções e extrair essas funções com os parâmetros necessários diretamente na dinâmica (usando, por exemplo, o software Frida .

Escolheremos o método número 1 como o mais confiável e confiável.

Desobuscação


Faça uma reserva imediatamente, a desofuscação geralmente é um processo longo e entediante, portanto, você precisa avaliar corretamente seu prazo. Para nossa análise, basta descriptografar todas as linhas pelo menos de alguma maneira e fazê-lo no tempo mínimo, mesmo de muleta, do que mexer por um mês por uma vaga opção ideal.

Desobstrução de alta qualidade faz sentido no caso de engenharia reversa completa, por exemplo, ladrões de propriedade intelectual precisam fazer isso quando tentam copiar a solução de um concorrente ou se você frequentemente precisa analisar programas processados ​​por um ofuscador, mas esse não é o nosso caso.

Código-fonte após o descompilador JEB Decompiler v.1.4

Spoiler
 public static String podxiwkt(String[] args) { int v6; int v4; byte[] v2; Cipher v1; String v10 = args[0]; String v7 = args[1]; String v0 = args[2]; if(v10 == null) { goto label_9; } if(v10.length() != 0) { goto label_11; } goto label_9; label_11: IvParameterSpec v5 = new IvParameterSpec(v0.getBytes()); try { v1 = Cipher.getInstance("AES/CBC/NoPadding"); goto label_15; } catch(NoSuchPaddingException v3) { } catch(NoSuchAlgorithmException v3_1) { } String v11 = ""; goto label_10; label_15: SecretKeySpec v9 = new SecretKeySpec(v7.getBytes(), "AES"); int v11_1 = 2; try { v1.init(v11_1, ((Key)v9), ((AlgorithmParameterSpec)v5)); v2 = Base64.decode(v1.doFinal(bwdoclkr.xkvasepi(v10)), 0); if(v2.length <= 0) { goto label_48; } v4 = 0; v6 = v2.length - 1; label_29: if(v6 < 0) { goto label_38; } if(v2[v6] != 0) { goto label_33; } } catch(Exception v3_2) { goto label_51; } ++v4; label_33: --v6; goto label_29; label_38: if(v4 <= 0) { goto label_48; } try { byte[] v8 = new byte[v2.length - v4]; System.arraycopy(v2, 0, v8, 0, v2.length - v4); v2 = v8; } catch(Exception v3_2) { label_51: v11 = ""; goto label_10; } label_48: v11 = new String(v2); goto label_10; label_9: v11 = ""; label_10: return v11; } } 

Nota do descompilador
A propósito, o dex2jar geralmente trava. Portanto, na figura abaixo, pode ser visto que o dex2jar versão 2.0 não conseguiu lidar e acabou de emitir um código smali.



Sua versão mais recente, compilada a partir das fontes, produziu um código descompilado para esta função, mas não pôde descompilar muitos outros (esse é o truque).





Conclusão: considere cuidadosamente a escolha de um descompilador - isso economizará muito tempo e será mais fácil do que analisar o código smali.


Portanto, se apenas colarmos esse código no IDE agora, ele não funcionará devido a erros.

É importante lembrar: O descompilador não é necessário para produzir um código válido escrito pelo desenvolvedor. Ele é apenas um salva-vidas na análise e faz suposições sobre como o código pode ser escrito. Na maioria dos casos, após a otimização pelo compilador, a tarefa de restaurar o código original deixa de ser trivial.

Exemplo de descompilação incorreta:

 if(v10 == null) { goto label_9; } if(v10.length() != 0) { goto label_11; } goto label_9; … label_9: v11 = ""; return v11; 

Vemos que ficou ruim e inoperante. Reescrevemos:

 if ((v10 == null) || (v10.length() == 0)) { return ""; } 

Agora está muito mais claro, aqui está a verificação de entrada usual. Nesse caso, precisamos:

  • Substitua todos os "goto" por outras construções de idioma, como "Ir para" tem sido um operador inválido.
  • Substitua as chamadas da biblioteca Android pelas chamadas da biblioteca Java (se tentarmos executar o código no Java IDE).
  • Inserir classes dependentes referenciadas pelo nosso código.
  • Pense por si mesmo o que está errado.

Como resultado, obtemos:

 package com.company; //package isqpwcmx.isfdztgb.adscjobz.nxscomkr.jypbdxnt.utagwpym.wprtdznb.swldgrhm.yrbjpktq; import java.util.Base64; //import android.util.Base64; //import bnxvhlyg.nkhoirul.zfxogwqi.mdpqejcw.srnepbly.pcbvwxrs.vixdqclm.wnuqvrhp.bnvceayd.bwdoclkr; //   import java.security.Key; import java.security.NoSuchAlgorithmException; import java.security.spec.AlgorithmParameterSpec; import javax.crypto.Cipher; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; public abstract class Main { public Main() { super(); } //hex to ascii public static byte[] xkvasepi(String str) { byte[] v0 = null; if(str != null && str.length() >= 2) { int v2 = str.length() / 2; v0 = new byte[v2]; int v1; for(v1 = 0; v1 < v2; ++v1) { v0[v1] = ((byte)Integer.parseInt(str.substring(v1 * 2, v1 * 2 + 2), 16)); } } return v0; } public static String podxiwkt(String[] args) { int v6; int v4; byte[] v2; Cipher v1; String v10 = args[0]; //text String v7 = args[1]; //key String v0 = args[2]; //IV //check if ((v10 == null) || (v10.length() == 0)) { return ""; } IvParameterSpec v5 = new IvParameterSpec(v0.getBytes()); try { v1 = Cipher.getInstance("AES/CBC/NoPadding"); } catch(NoSuchPaddingException v3) { return ""; } catch(NoSuchAlgorithmException v3_1) { return ""; } SecretKeySpec v9 = new SecretKeySpec(v7.getBytes(), "AES"); int v11_1 = 2; try { v1.init(v11_1, ((Key)v9), ((AlgorithmParameterSpec)v5)); //v2 = Base64.decode(v1.doFinal(bwdoclkr.xkvasepi(v10)), 0); v2=v1.doFinal(xkvasepi(v10)); //check if(v2.length <= 0) { return new String(v2); } } catch(Exception v3_2) { return ""; } v4=0; for (v6=v2.length-1;v6>=0;v6--){ if (v2[v6]==0) ++v4; } if(v4 > 0) { try { byte[] v8 = new byte[v2.length - v4]; System.arraycopy(v2, 0, v8, 0, v2.length - v4); v2 = v8; } catch (Exception v3_2) { return ""; } } v2 = Base64.getDecoder().decode(v2); return new String(v2); } public static void main(String[] args) { // write your code here System.out.println(podxiwkt(new String[] { "b1acd584a6eae4ca6321b1f7cdf9ba9617112b4fb39e76c8def876346e3032fbd32b2d188a09715f27124c1bf9facfdc", "637904cd08aeb2d3f6a21b5c7e84f519", "8f4c796d5a3120eb", "zcmwgvdn", "mkngbsyr", "rwcdaieu" })); } } { "b1acd584a6eae4ca6321b1f7cdf9ba9617112b4fb39e76c8def876346e3032fbd32b2d188a09715f27124c1bf9facfdc", "637904cd08aeb2d3f6a21b5c7e84f519", "8f4c796d5a3120eb", "zcmwgvdn", "mkngbsyr", "rwcdaieu"})); package com.company; //package isqpwcmx.isfdztgb.adscjobz.nxscomkr.jypbdxnt.utagwpym.wprtdznb.swldgrhm.yrbjpktq; import java.util.Base64; //import android.util.Base64; //import bnxvhlyg.nkhoirul.zfxogwqi.mdpqejcw.srnepbly.pcbvwxrs.vixdqclm.wnuqvrhp.bnvceayd.bwdoclkr; //   import java.security.Key; import java.security.NoSuchAlgorithmException; import java.security.spec.AlgorithmParameterSpec; import javax.crypto.Cipher; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; public abstract class Main { public Main() { super(); } //hex to ascii public static byte[] xkvasepi(String str) { byte[] v0 = null; if(str != null && str.length() >= 2) { int v2 = str.length() / 2; v0 = new byte[v2]; int v1; for(v1 = 0; v1 < v2; ++v1) { v0[v1] = ((byte)Integer.parseInt(str.substring(v1 * 2, v1 * 2 + 2), 16)); } } return v0; } public static String podxiwkt(String[] args) { int v6; int v4; byte[] v2; Cipher v1; String v10 = args[0]; //text String v7 = args[1]; //key String v0 = args[2]; //IV //check if ((v10 == null) || (v10.length() == 0)) { return ""; } IvParameterSpec v5 = new IvParameterSpec(v0.getBytes()); try { v1 = Cipher.getInstance("AES/CBC/NoPadding"); } catch(NoSuchPaddingException v3) { return ""; } catch(NoSuchAlgorithmException v3_1) { return ""; } SecretKeySpec v9 = new SecretKeySpec(v7.getBytes(), "AES"); int v11_1 = 2; try { v1.init(v11_1, ((Key)v9), ((AlgorithmParameterSpec)v5)); //v2 = Base64.decode(v1.doFinal(bwdoclkr.xkvasepi(v10)), 0); v2=v1.doFinal(xkvasepi(v10)); //check if(v2.length <= 0) { return new String(v2); } } catch(Exception v3_2) { return ""; } v4=0; for (v6=v2.length-1;v6>=0;v6--){ if (v2[v6]==0) ++v4; } if(v4 > 0) { try { byte[] v8 = new byte[v2.length - v4]; System.arraycopy(v2, 0, v8, 0, v2.length - v4); v2 = v8; } catch (Exception v3_2) { return ""; } } v2 = Base64.getDecoder().decode(v2); return new String(v2); } public static void main(String[] args) { // write your code here System.out.println(podxiwkt(new String[] { "b1acd584a6eae4ca6321b1f7cdf9ba9617112b4fb39e76c8def876346e3032fbd32b2d188a09715f27124c1bf9facfdc", "637904cd08aeb2d3f6a21b5c7e84f519", "8f4c796d5a3120eb", "zcmwgvdn", "mkngbsyr", "rwcdaieu" })); } } 

Este código cumpre com sucesso. Depois que seu trabalho se torna claro, ele pode ser simplificado e simplificado, levando ao tipo supostamente lacônico escrito pelo programador (a menos que, é claro, suas mãos não tenham sido inicialmente tortas).

Nota
A propósito, nesse caso, a descriptografia de seqüências de caracteres pode ser demonstrada usando vários recursos online. Um exemplo de chamada de uma string criptografada dentro de um programa:



Aqui, o vetor de inicialização deve primeiro ser convertido para o formato Hex:



Substitua todos os valores:



E no final, decodifique da base64:



Como resultado, obtemos a sequência usual e a chamada assume uma aparência significativa.

Assim, você precisa percorrer todo o código e coletar todas as seqüências criptografadas, agora podemos descriptografá-las independentemente. O ponto importante aqui é que, na fase de “modificar e comentar o código”, podemos trabalhar no nível smali e no nível Java (smali descompilado).

Vantagens da modificaçãoContras Mods
SmaliVocê pode fazer alterações, recompilar em dex e descompilar com novas linhasNem sempre é fácil trabalhar com o código smali. Se a alteração estiver incorreta, o aplicativo não compilará
JavaGeralmente, é muito mais fácil extrair dados de operações de nível superior.A maioria dos visualizadores de código Java não pode editar.

Outro exemplo de linha

 vcgrnfjx.execSQL(nvhdzjfo.xipswfqb(new String[]{"f741f04a4991fc2f0a0029f610bbd1c250dfe115fb7770b892f75d8718b822d273251013991b4407e224fa3f9d4e92f6","378f40211b6e32a5406cd97e85bcf9ad","6378a459b1c20edf", "gexnfwok", "meazfhdp", "bsmotaxn"}) 

A partir daqui, é muito fácil pegar parâmetros usando uma expressão regular, do que escrever um regular no código a seguir:

Exemplo de código Smali 1

 00000280 new-instance v13, Ljava/lang/StringBuilder; 00000284 invoke-direct {v13}, Ljava/lang/StringBuilder;-><init>()V 0000028A const/4 v14, 0x6 0000028C new-array v14, v14, [Ljava/lang/String; 00000290 const/4 v15, 0x0 00000292 const-string v16, "f741f04a4991fc2f0a0029f610bbd1c250dfe115fb7770b892f75d8718b822d273251013991b4407e224fa3f9d4e92f6" 00000296 aput-object v16, v14, v15 0000029A const/4 v15, 0x1 0000029C const-string v16, "378f40211b6e32a5406cd97e85bcf9ad" 000002A0 aput-object v16, v14, v15 000002A4 const/4 v15, 0x2 000002A6 const-string v16, "6378a459b1c20edf" 000002AA aput-object v16, v14, v15 000002AE const/4 v15, 0x3 000002B0 const-string v16, "gexnfwok" 000002B4 aput-object v16, v14, v15 000002B8 const/4 v15, 0x4 000002BA const-string v16, "meazfhdp" 000002BE aput-object v16, v14, v15 000002C2 const/4 v15, 0x5 000002C4 const-string v16, "bsmotaxn" 000002C8 aput-object v16, v14, v15 


Exemplo de código Smali 2

 0000008E new-array v0, v0, [Ljava/lang/String; 00000092 move-object/from16 v89, v0 00000096 const/16 v90, 0x0 0000009A const-string v91, "4500b5e2e2ad26b7545eb54d70ab360ae28c9d031e2afcc3f6a2b2ac488ea440" 0000009E aput-object v91, v89, v90 000000A2 const/16 v90, 0x1 000000A6 const-string v91, "da96f678922d4b07350b3a184ecc1f5e" 000000AA aput-object v91, v89, v90 000000AE const/16 v90, 0x2 000000B2 const-string v91, "0cf69e3d2745a1b8" 000000B6 aput-object v91, v89, v90 000000BA const/16 v90, 0x3 000000BE const-string v91, "jhiqsaoe" 000000C2 aput-object v91, v89, v90 000000C6 const/16 v90, 0x4 000000CA const-string v91, "khbqxurn" 000000CE aput-object v91, v89, v90 


Exemplo de código Smali 3

 00000D3E new-array v0, v0, [Ljava/lang/String; 00000D42 move-object/16 v298, v0 00000D48 const/4 v0, 0x0 00000D4A move/16 v299, v0 00000D50 const-string v0, "b286945744e085f4d5c19916fd261481" 00000D54 move-object/16 v300, v0 00000D5A move-object/from16 v0, v300 00000D5E move-object/from16 v1, v298 00000D62 move/from16 v2, v299 00000D66 aput-object v0, v1, v2 00000D6A const/4 v0, 0x1 00000D6C move/16 v299, v0 00000D72 const-string v0, "df6883742b2911ac5ac7b4dee065390f" 00000D76 move-object/16 v300, v0 00000D7C move-object/from16 v0, v300 00000D80 move-object/from16 v1, v298 00000D84 move/from16 v2, v299 00000D88 aput-object v0, v1, v2 00000D8C const/4 v0, 0x2 00000D8E move/16 v299, v0 00000D94 const-string v0, "90a463ce2df17b58" 00000D98 move-object/16 v300, v0 00000D9E move-object/from16 v0, v300 00000DA2 move-object/from16 v1, v298 00000DA6 move/from16 v2, v299 00000DAA aput-object v0, v1, v2 00000DAE const/4 v0, 0x3 00000DB0 move/16 v299, v0 00000DB6 const-string v0, "cupyzsgq" 00000DBA move-object/16 v300, v0 00000DC0 move-object/from16 v0, v300 00000DC4 move-object/from16 v1, v298 00000DC8 move/from16 v2, v299 00000DCC aput-object v0, v1, v2 


Como podemos ver, as variáveis ​​internas mudam, a sequência de comandos varia, o número de argumentos também varia; além disso, no programa, a função de descriptografia é chamada não diretamente, mas através das funções da camada. Tente escrever uma regra para procurar essa construção, evitar erros na captura de strings de outras funções e faça tudo isso rapidamente ( boa sorte ).

Plano de armadilha:

  1. Extrairemos todos os valores do código descompilado.
  2. Descriptografar.
  3. Substitua o texto cifrado pelo texto aberto no código smali. Substituímos, por exemplo, em vez do primeiro operador. (Seria mais profissional cortar toda a chamada de função e deixar a string descriptografada retornada, mas, novamente, há um grande risco de interromper o programa)
  4. Vamos coletar o código smali em um arquivo dex.
  5. Será conveniente analisar mais detalhadamente o analisador de código, onde começamos.

Se você coletar todo o código descompilado em um único arquivo, obterá cerca de 20.000 linhas, o que para análise manual exige muito tempo, o que custa claramente mais do que as botas que eu coloquei à venda. Primeiro, colete todas as linhas com uma expressão regular.



Vemos 593 partidas, mais uma dúzia que não se enquadrava nessa regra, a família tem sua ovelha negra . Um exemplo:



Classifique, filtre, totalize 422 linhas únicas:



Passamos pela função de descriptografia que restauramos anteriormente. Resultado:



Substitua o texto cifrado pelo código aberto no smali-code usando Python:

 import os words_replace=dict() words_replace["0018aacad3d146266317d8d8c51785fd"]="imei" words_replace["016d15e4d0a72667c61428e736a6f3b8"]="WakeLock" words_replace["032c534efb6c9990cd845a08c5a08b95"]="inbox" #…  .. # smali- #      def change(path): print("file="+path) file_handle = open(path, 'r') context_full = file_handle.read() file_handle.close() for i in words_replace: context_full=context_full.replace(i, words_replace[i]) #print (i+""+words_replace[i]) file_handle = open(path, 'w') context_full = file_handle.write(context_full) file_handle.close() #      smali- for top, dirs, files in os.walk('C:\\work\\test'): for nm in files: path=os.path.join(top, nm) print (path) change(path) 

Coletamos arquivos smali em dex:



Agora isso pode ser analisado de alguma forma (lendo o primeiro argumento de toda a construção):



Análise


Portanto, temos 20.000 linhas de código mais ou menos legíveis, não precisamos fazer uma análise completa. É necessário entender a funcionalidade como um todo. Aqui, de fato, apenas a capacidade de ler o código-fonte Java é necessária. Percorra o código, observe as referências cruzadas, renomeie variáveis ​​e funções.

Qual é a melhor maneira de analisar um aplicativo Android, especialmente um grande?

Opção 1: você pode mover-se do arquivo Manifest

Por exemplo, sequencialmente, do LAUNCHER, tente relaxar toda a cadeia de chamadas. A propósito, não esqueça que ainda existem “Receptores” e “Serviços” que podem alterar a execução linear do programa.



Arquivo de manifesto completo
 <?xml version="1.0" encoding="utf-8" standalone="no"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="internalOnly" package="xfmpuwon.mtnbupnc.ihqdgjal.ndgmqawx.bjunzerq.cznfpnoq.fzevcuym.jmpdiqft"> <uses-permission android:name="xfmpuwon.mtnbupnc.ihqdgjal.ndgmqawx.bjunzerq.cznfpnoq.fzevcuym.jmpdiqft.permission.C2D_MESSAGE"/> <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/> <uses-permission android:name="android.permission.SEND_SMS"/> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.READ_PHONE_STATE"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/> <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/> <uses-permission android:name="android.permission.RECEIVE_SMS"/> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <uses-permission android:name="android.permission.QUICKBOOT_POWERON"/> <uses-permission android:name="android.permission.READ_SMS"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/> <permission android:name="xfmpuwon.mtnbupnc.ihqdgjal.ndgmqawx.bjunzerq.cznfpnoq.fzevcuym.jmpdiqft.permission.C2D_MESSAGE" android:protectionLevel="signature"/> <application android:allowBackup="true" android:icon="@drawable/icon" android:label="@string/tgiwmpqy" android:noHistory="true"> <activity android:configChanges="orientation" android:excludeFromRecents="true" android:label="@string/tgiwmpqy" android:launchMode="singleTop" android:name="zemquyog.csrtmnak.xrkfygen.wkahrnjd.acnfunjh.rgipxbuf.lruiwxeg.blqndche.dcjihbou" android:screenOrientation="portrait"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <activity android:configChanges="orientation" android:launchMode="singleTop" android:name="xbfrscou.hxrvwnoi.djvpcqri.enlnrfio.aoegxbiu.heywzmnb.znfnxcht.nazcxobq" android:screenOrientation="portrait"/> <activity android:configChanges="orientation" android:launchMode="singleTop" android:name="hcfkagds.timkagsd.oetvghzr.fcioynvl.psynofdj.slcghdjz.tapnwsdk.gzvwnban.htenafdb.qwebhzgy" android:noHistory="true" android:screenOrientation="portrait"/> <activity android:configChanges="orientation" android:excludeFromRecents="true" android:launchMode="singleTop" android:name="njfbwmre.voefarqx.ftuxvngl.wrmshxqj.zdenywgn.eiwyunlg.jysgkbam.yrijthab.vstqxpuo.iplamgxf" android:priority="2147483647" android:screenOrientation="portrait"/> <receiver android:name="gfbaznoc.asyoqtnm.kbetoqca.mqysobzu.gqwfibrv.dorxijuk.wgzkmiep.ywnnurzv.csfpqhrn" android:permission="android.permission.BIND_DEVICE_ADMIN"> <meta-data android:name="@string/pkzrlscm" android:resource="@xml/ynqukvnb"/> <intent-filter android:priority="2147483646"> <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/> </intent-filter> </receiver> <receiver android:name="ykwbodxc.gymjhibn.kgmdfqor.hbasvmfz.yegkmaif.ortzknvm.quplincn.cuxytvhs.fqonzuts.cyuoxgqi.znumwyct" android:permission="com.google.android.c2dm.permission.SEND"> <intent-filter> <action android:name="com.google.android.c2dm.intent.RECEIVE"/> <action android:name="com.google.android.c2dm.intent.REGISTRATION"/> <action android:name="com.google.android.c2dm.intent.UNREGISTRATION"/> <category android:name="xfmpuwon.mtnbupnc.ihqdgjal.ndgmqawx.bjunzerq.cznfpnoq.fzevcuym.jmpdiqft"/> </intent-filter> </receiver> <receiver android:enabled="true" android:exported="true" android:name="kqwihjot.nvkqjloc.grjnyknm.owydvckh.mugknwdx.enhcyvja.mhvbpcue.ztbwjhfo"> <intent-filter android:priority="2147483646"> <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED"/> <action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/> <action android:name="android.intent.action.QUICKBOOT_POWERON"/> <action android:name="android.intent.action.BOOT_COMPLETED"/> <action android:name="android.intent.action.USER_PRESENT"/> <action android:name="android.intent.action.BATTERY_OKAY"/> <action android:name="android.intent.action.BATTERY_LOW"/> <action android:name="android.intent.action.ACTION_POWER_CONNECTED"/> <action android:name="android.intent.action.ACTION_POWER_DISCONNECTED"/> <action android:name="android.intent.action.APP_ERROR"/> <action android:name="android.intent.action.HEADSET_PLUG"/> <action android:name="android.intent.action.PHONE_STATE"/> <action android:name="android.intent.action.NEW_OUTGOING_CALL"/> <action android:name="android.provider.Telephony.SMS_RECEIVED"/> <action android:name="android.intent.action.TIME_TICK"/> <action android:name="android.intent.action.SCREEN_ON"/> <action android:name="android.intent.action.SCREEN_OFF"/> <action android:name="android.net.conn.CONNECTIVITY_CHANGE"/> <action android:name="android.net.wifi.WIFI_STATE_CHANGED"/> <action android:name="android.intent.action.DREAMING_STOPPED"/> <category android:name="android.intent.category.HOME"/> </intent-filter> </receiver> <receiver android:name="btnsxnuz.wmjizbky.lynvjxqz.zinomjuv.yizlgcnf.qwoikgnc.wnrskjea.wfqgmeny.lcgvqrms.ocwkgblp"> <intent-filter android:priority="2147483646"> <action android:name="android.provider.Telephony.SMS_RECEIVED"/> </intent-filter> </receiver> <service android:name="ltvsrezg.ehxndrat.twnnyxrf.nqynefws.dhbalcnr.ynjkuxod.nhoxmsbq.nackoyhn.voycgfek.znhwkqba.taxvnfyn"/> <service android:name="rbnakfzo.qsreiubk.pwvlnngs.twoxnhfv.mftarcnd.pfioxcub.xjlaftqr.nxrqvlwh"/> <service android:enabled="true" android:name="xfmpuwon.mtnbupnc.ihqdgjal.ndgmqawx.bjunzerq.cznfpnoq.fzevcuym.jmpdiqft.ugshpjvo"/> </application> </manifest> 


Opção 2: você pode passar de linhas interessantes



Parte de seqüências de caracteres descriptografadas
 system_update.apk () () , error = , unregistered = ,  .permission.C2D_MESSAGE //sky-sync.pw/ //sms/inbox /system_update.apk ALLCONTACTS ALLMSG AUTHENTICATION_FAILED Acquiring wakelock Application BLOCKER_BANKING_START BLOCKER_EXTORTIONIST_START BLOCKER_STOP BLOCKER_UPDATE_START Banking CHANGE_GCM_ID CONTACTS CONTACTS_PRO CREATE TABLE IF NOT EXISTS END Error|No process list|No access Extortionist Foreground GCM returned invalid number of GCMBaseIntentService GCMBroadcastReceiver GCMIntentService- GCMRegistrar GCM_LIB GET MESSAGE Mobile Network NEWMSG Not retrying failed operation ONLINE PAGE POST Process finished with exit code 0 RESTART Received deleted messages Registering receiver Releasing wakelock SERVICE_NOT_AVAILABLE SSL START STOP Saving regId on app version Scheduling registration retry, backoff = Setting registeredOnServer status as Stop System UNBLOCK UPDATE UPDATE_PATTERNS URL UTF-8 Update WakeLock Wakelock reference is null Wi-Fi WiMax _success add_msg_ok address android.intent.action.QUICKBOOT_POWERON answer_text answer_to api_url app appVersion application application/vnd.android.package-archive apps_list ask backoff_ms blocker blocker_banking blocker_banking_autolock blocker_banking_forced_access blocker_banking_success blocker_extortionist blocker_extortionist_autolock blocker_extortionist_forced_access blocker_extortionist_success blocker_update blocker_update_forced_access blocker_update_success body build callback cardSuccess check com.android.settings com.google.android.c2dm.intent.RECEIVE com.google.android.c2dm.intent.REGISTER com.google.android.c2dm.intent.REGISTRATION com.google.android.c2dm.intent.UNREGISTER com.google.android.gcm com.google.android.gcm.intent.RETRY com.google.android.gsf com.htc.intent.action.QUICKBOOT_POWERON command command_receive contactslist country data date delete deleted_messages device_block disableDataConnectivity enableDataConnectivity error failure file deleted. first_start force-locked gafzpjxb.cix gcm gcm_id gcm_register gcm_register_ok getITelephony get_message_list id integer primary key autoincrement, id=? imei immunity inbox init_bootable init_imei is_admin is_awake_display is_imunnity is_locked is_network_type is_top_activity job job_date job_id komgejif.hqr locked message message_delivered message_type method model msg msg_id msglist name not nypjtinq.nvp ok onServer onServerExpirationTime onServerLifeSpan operator org.android.sys.admin.disabled org.android.sys.admin.enabled org.android.sys.admin.request org.android.sys.command.receive org.android.sys.launch.first org.android.sys.sms.pro.sent org.android.sys.sms.push org.android.sys.sms.sent outbox page params pattern patterns personal phone phone_list privet process_list protocol qwertyuiopasdfghjklzxcvbnm receive regId regex register register_ok registrationId = registration_id repeat resetting backoff for ru save_contacts_list save_message_history sender sent sent_status sid ss status stop_blocker text text, text/html time token total_deleted type unknown unregistered until url useragent utf-8 value version xpls yes   !     ...                   !  ?                ! 

Opção 3: você pode passar de recursos interessantes (ativos, bibliotecas)

Nesse caso, a opção 3 era preferível. Existem três arquivos html interessantes na pasta / assets (contêiner apk). Aqui está a visão deles no navegador:





Parece duvidoso para o programa oficial de transferência de pagamentos Avito, não lhe parece? Vamos acompanhar o que acontece quando você pressiona a tecla para enviar dados bancários na página com o logotipo do Sberbank. JavaScript chama a função sendCardData() :



E então é transferido para o código Java através da chamada ok.performClick() :



No código Java, o processamento é realizado:



Além disso, tudo isso é criptografado na classe mcrypt :



Dentro da função, os dados são criptografados da mesma maneira que foi considerado anteriormente:



Mas, para o resto, as teclas são conectadas:



Tentamos descriptografar através do recurso online:



E converta de base64. Sucesso! Podemos descriptografar todos os dados do aplicativo: testados no tráfego capturado anteriormente.

O aplicativo relata ao servidor sobre todos os eventos
 { "sid":15, "imei":"861117030537111", "phone":"System", "message":"     22.10.2018 23:30:47", "time":"1540240247", "msg_id":1, "status":"unknown", "type":"inbox", "method":"message" } 

Também transfere periodicamente todos os aplicativos em execução
 { "sid": 15, "imei": "861117030537111", "country": "ru", "operator": "MTS RUS", "phone": "", "model": "Xiaomi Redmi 3X", "version": "6.0.1", "application": "", "build": "30.0.2", "process_list": [ "Background|com.android.bluetooth|com.android.bluetooth.hid.HidService", "Background|com.android.settings:remote|com.android.settings.wifi.MiuiWifiService", "Background|com.android.phone|org.codeaurora.ims.ImsService", "Background|system|com.qualcomm.location.LocationService", ..., "Background|xfmpuwon.mtnbupnc.ihqdgjal.ndgmqawx.bjunzerq.cznfpnoq.fzevcuym.jmpdiqft|ltvsrezg.ehxndrat.twnnyxrf.nqynefws.dhbalcnr.ynjkuxod.nhoxmsbq.nackoyhn.voycgfek.znhwkqba.taxvnfyn" ], "apps_list": [ "com.introspy.config", "com.google.android.youtube", "com.google.android.googlequicksearchbox", "org.telegram.messenger", ..., "com.google.android.inputmethod.latin", "jakhar.aseem.diva" ], "method": "register" } 

Se eu tivesse uma janela de entrada de dados bancários na dinâmica, os dados estariam no tráfego. Assim, podemos concluir que este é um aplicativo de "phishing".

Aqueles que foram cuidadosos notaram que o arquivo Manifest tem algumas permissões e o aplicativo tem uma funcionalidade mais rica. Faremos uma análise profunda da funcionalidade em outro artigo. Enquanto isso, sucesso!

Conclusões


Estou decepcionado por não vender botas. E as conclusões são as seguintes:

  • Não venda botas no Avito
  • Não clique em links obscuros (mesmo que de amigos e mesmo se "você precisar emprestar urgentemente 100 rublos - uma questão de vida ou morte")
  • Não baixe aplicativos que não sejam do Google Play ou da AppStore
    • Desconecte a instalação de "fontes não confiáveis" se você realmente não entender o que é o quê.
    • Não desconecte o "Play Protection".
    • Lembre-se de que pode haver malware no Google Play
  • Instale o antivírus no telefone (realmente funciona).
  • Se você é um desenvolvedor, não ofusque o código, deixe as pessoas se certificarem de suas boas intenções ( apenas brincando )
  • Se você é um pesquisador, não trabalha com comida; analise aplicativos no seu tempo livre e publique relatórios. Juntos, tornaremos o mundo um lugar melhor.

PS: Tentei escrever o artigo levemente em um formato humorístico e enviá-lo da maneira mais simples possível, porque nem eu provavelmente gostaria de ler na sexta-feira uma séria pausa com o título "Engenharia Reversa Ofuscou o Aplicativo Android Mal-Intencionado".

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


All Articles