Skip to content

MASTG-TOOL-0001: Frida para Android

O Frida oferece suporte à interação com o runtime Java do Android por meio da Java API. Você poderá fazer hook e chamar funções tanto Java quanto nativas dentro do processo e suas bibliotecas nativas. Seus snippets JavaScript têm acesso total à memória, por exemplo, para ler e/ou escrever quaisquer dados estruturados.

Aqui estão algumas tarefas que as APIs do Frida oferecem e que são relevantes ou exclusivas no Android:

  • Instanciar objetos Java e chamar métodos estáticos e não estáticos de classes (Java API).
  • Substituir implementações de métodos Java (Java API).
  • Enumerar instâncias ativas de classes específicas através da varredura do Java heap (Java API).
  • Escanear a memória do processo em busca de ocorrências de uma string (Memory API).
  • Interceptar chamadas de funções nativas para executar seu próprio código na entrada e saída da função (Interceptor API).

Lembre-se de que no Android você também pode se beneficiar das ferramentas integradas fornecidas ao instalar o Frida, que incluem o Frida CLI (frida), frida-ps, frida-ls-devices e frida-trace, para citar alguns.

O Frida é frequentemente comparado ao Xposed, no entanto essa comparação está longe de ser justa, pois ambos os frameworks foram projetados com objetivos diferentes. Isso é importante entender como testador de segurança de aplicativos para que você saiba qual framework usar em qual situação:

  • O Frida é autônomo, tudo que você precisa é executar o binário frida-server de um local conhecido no seu dispositivo Android de destino (veja "Instalando o Frida" abaixo). Isso significa que, em contraste com o Xposed, ele não é instalado profundamente no sistema operacional de destino.
  • Reverter um aplicativo é um processo iterativo. Como consequência do ponto anterior, você obtém um feedback loop mais curto durante os testes, pois não precisa reinicializar (soft reboot) para aplicar ou simplesmente atualizar seus hooks. Portanto, você pode preferir usar o Xposed ao implementar hooks mais permanentes.
  • Você pode injetar e atualizar seu código JavaScript do Frida dinamicamente em qualquer ponto durante o runtime do seu processo (semelhante ao Cycript no iOS). Dessa forma, você pode realizar a chamada early instrumentation permitindo que o Frida inicie seu aplicativo ou pode preferir anexar a um aplicativo em execução que você pode ter levado a um determinado estado.
  • O Frida é capaz de lidar tanto com código Java quanto com código nativo (JNI), permitindo que você modifique ambos. Infelizmente, esta é uma limitação do Xposed, que não possui suporte a código nativo.

Observe que o Xposed, a partir do início de 2019, ainda não funciona no Android 9 (nível de API 28).

Instalando o Frida no Android

Para configurar o Frida no seu dispositivo Android:

Assumimos um dispositivo com root aqui, salvo indicação em contrário. Faça o download do binário frida-server na página de releases do Frida. Certifique-se de baixar o binário frida-server correto para a arquitetura do seu dispositivo Android ou emulador: x86, x86_64, arm ou arm64. Certifique-se de que a versão do servidor (pelo menos o número da versão principal) corresponda à versão da sua instalação local do Frida. O PyPI geralmente instala a versão mais recente do Frida. Se você não tiver certeza de qual versão está instalada, pode verificar com a ferramenta de linha de comando do Frida:

frida --version

Ou você pode executar o seguinte comando para detectar automaticamente a versão do Frida e baixar o binário frida-server correto:

wget https://github.com/frida/frida/releases/download/$(frida --version)/frida-server-$(frida --version)-android-arm.xz

Copie o frida-server para o dispositivo e execute-o:

adb push frida-server /data/local/tmp/
adb shell "chmod 755 /data/local/tmp/frida-server"
adb shell "su -c /data/local/tmp/frida-server &"

Usando o Frida no Android

Com o frida-server em execução, você agora deve ser capaz de obter uma lista de processos em execução com o seguinte comando (use a opção -U para indicar ao Frida que use dispositivos USB conectados ou emulador):

$ frida-ps -U
  PID  Nome
-----  --------------------------------------------------------------
  276  adbd
  956  android.process.media
  198  bridgemgrd
30692  com.android.chrome
30774  com.android.chrome:privileged_process0
30747  com.android.chrome:sandboxed
30834  com.android.chrome:sandboxed
 3059  com.android.nfc
 1526  com.android.phone
17104  com.android.settings
 1302  com.android.systemui
(...)

Ou restrinja a lista com a combinação de flags -Uai para obter todos os aplicativos (-a) atualmente instalados (-i) no dispositivo USB conectado (-U):

$ frida-ps -Uai
  PID  Nome                                      Identificador
-----  ----------------------------------------  ------------------------------
  766  Sistema Android                           android
30692  Chrome                                    com.android.chrome
 3520  Armazenamento de Contatos                 com.android.providers.contacts
    -  Uncrackable1                              sg.vantagepoint.uncrackable1
    -  Agente drozer                             com.mwr.dz

Isso mostrará os nomes e identificadores de todos os aplicativos; se estiverem em execução, também mostrará seus PIDs. Procure seu aplicativo na lista e anote o PID ou seu nome/identificador. A partir de agora, você se referirá ao seu aplicativo usando um deles. Uma recomendação é usar os identificadores, pois os PIDs mudarão a cada execução do aplicativo. Por exemplo, vamos pegar com.android.chrome. Você pode usar essa string agora em todas as ferramentas do Frida, por exemplo, no Frida CLI, no frida-trace ou a partir de um script Python.

Rastreando bibliotecas nativas com frida-trace

Para rastrear chamadas específicas de bibliotecas (de baixo nível), você pode usar a ferramenta de linha de comando frida-trace:

frida-trace -U com.android.chrome -i "open"

Isso gera um pequeno JavaScript em __handlers__/libc.so/open.js, que o Frida injeta no processo. O script rastreia todas as chamadas para a função open em libc.so. Você pode modificar o script gerado de acordo com suas necessidades usando a JavaScript API do Frida.

Infelizmente, o rastreamento de métodos de alto nível de classes Java ainda não é suportado (mas pode ser no futuro).

Frida CLI e a Java API

Use a ferramenta Frida CLI (frida) para trabalhar com o Frida interativamente. Ele conecta-se a um processo e fornece uma interface de linha de comando para a API do Frida.

frida -U com.android.chrome

Com a opção -l, você também pode usar o Frida CLI para carregar scripts, por exemplo, para carregar myscript.js:

frida -U -l myscript.js com.android.chrome

O Frida também fornece uma Java API, que é especialmente útil para lidar com aplicativos Android. Ela permite que você trabalhe diretamente com classes e objetos Java. Aqui está um script para substituir a função onResume de uma classe Activity:

Java.perform(function () {
    var Activity = Java.use("android.app.Activity");
    Activity.onResume.implementation = function () {
        console.log("[*] onResume() got called!");
        this.onResume();
    };
});

O script acima chama Java.perform para garantir que seu código seja executado no contexto da JVM. Ele instancia um wrapper para a classe android.app.Activity via Java.use e substitui a função onResume. A nova implementação da função onResume imprime um aviso no console e chama o método onResume original invocando this.onResume sempre que uma atividade é retomada no aplicativo.

jadx pode gerar snippets do Frida através de seu navegador de código gráfico. Para usar esse recurso, abra o APK ou DEX com jadx-gui, navegue até o método de destino, clique com o botão direito no nome do método e selecione "Copy as frida snippet (f)". Por exemplo, usando o MASTG Android UnCrackable L1:

As etapas acima colocam a seguinte saída na área de transferência, que você pode colar em um arquivo JavaScript e alimentar no frida -U -l.

let a = Java.use("sg.vantagepoint.a.a");
a["a"].implementation = function (bArr, bArr2) {
    console.log('a is called' + ', ' + 'bArr: ' + bArr + ', ' + 'bArr2: ' + bArr2);
    let ret = this.a(bArr, bArr2);
    console.log('a ret value is ' + ret);
    return ret;
};

O código acima faz hook no método a dentro da classe sg.vantagepoint.a.a e registra seus parâmetros de entrada e valores de retorno.

O Frida também permite que você pesquise e trabalhe com objetos instanciados que estão no heap. O script a seguir procura por instâncias de objetos android.view.View e chama seus métodos toString. O resultado é impresso no console:

setImmediate(function() {
    console.log("[*] Starting script");
    Java.perform(function () {
        Java.choose("android.view.View", {
             "onMatch":function(instance){
                  console.log("[*] Instance found: " + instance.toString());
             },
             "onComplete":function() {
                  console.log("[*] Finished heap search")
             }
        });
    });
});

A saída seria semelhante a esta:

[*] Starting script
[*] Instance found: android.view.View{7ccea78 G.ED..... ......ID 0,0-0,0 #7f0c01fc app:id/action_bar_black_background}
[*] Instance found: android.view.View{2809551 V.ED..... ........ 0,1731-0,1731 #7f0c01ff app:id/menu_anchor_stub}
[*] Instance found: android.view.View{be471b6 G.ED..... ......I. 0,0-0,0 #7f0c01f5 app:id/location_bar_verbose_status_separator}
[*] Instance found: android.view.View{3ae0eb7 V.ED..... ........ 0,0-1080,63 #102002f android:id/statusBarBackground}
[*] Finished heap search

Você também pode usar os recursos de reflection do Java. Para listar os métodos públicos da classe android.view.View, você pode criar um wrapper para essa classe no Frida e chamar getMethods a partir da propriedade class do wrapper:

Java.perform(function () {
    var view = Java.use("android.view.View");
    var methods = view.class.getMethods();
    for(var i = 0; i < methods.length; i++) {
        console.log(methods[i].toString());
    }
});

Isso imprimirá uma lista muito longa de métodos no terminal:

public boolean android.view.View.canResolveLayoutDirection()
public boolean android.view.View.canResolveTextAlignment()
public boolean android.view.View.canResolveTextDirection()
public boolean android.view.View.canScrollHorizontally(int)
public boolean android.view.View.canScrollVertically(int)
public final void android.view.View.cancelDragAndDrop()
public void android.view.View.cancelLongPress()
public final void android.view.View.cancelPendingInputEvents()
...