Skip to content

MASTG-KNOW-0027: Detecção de Root

No contexto de anti-engenharia reversa, o objetivo da detecção de root é tornar a execução do aplicativo em um dispositivo root um pouco mais difícil, o que por sua vez bloqueia algumas das ferramentas e técnicas que os engenheiros reversos gostam de usar. Como a maioria das outras defesas, a detecção de root não é muito eficaz por si só, mas implementar várias verificações de root espalhadas por todo o aplicativo pode melhorar a eficácia do esquema geral de anti-manipulação.

Para Android, definimos "detecção de root" de forma um pouco mais ampla, incluindo a detecção de ROMs personalizadas, ou seja, determinar se o dispositivo é uma compilação Android padrão ou uma compilação personalizada.

A detecção de root também pode ser implementada por meio de bibliotecas como RootBeer.

Verificações de Existência de Arquivos

Talvez o método mais amplamente utilizado de detecção programática seja verificar a existência de arquivos normalmente encontrados em dispositivos root, como arquivos de pacote de aplicativos de root comuns e seus arquivos e diretórios associados, incluindo os seguintes:

/system/app/Superuser.apk
/system/etc/init.d/99SuperSUDaemon
/dev/com.koushikdutta.superuser.daemon/
/system/xbin/daemonsu

O código de detecção também frequentemente procura por binários que geralmente são instalados após o dispositivo ter sido root. Essas buscas incluem verificar a presença do busybox e tentar abrir o binário su em diferentes localizações:

/sbin/su
/system/bin/su
/system/bin/failsafe/su
/system/xbin/su
/system/xbin/busybox
/system/sd/xbin/su
/data/local/su
/data/local/xbin/su
/data/local/bin/su

Verificar se su está no PATH também funciona:

    public static boolean checkRoot(){
        for(String pathDir : System.getenv("PATH").split(":")){
            if(new File(pathDir, "su").exists()) {
                return true;
            }
        }
        return false;
    }

As verificações de arquivos podem ser facilmente implementadas tanto em Java quanto em código nativo. O seguinte exemplo JNI (adaptado de rootinspector) usa a chamada de sistema stat para recuperar informações sobre um arquivo e retorna "1" se o arquivo existir.

jboolean Java_com_example_statfile(JNIEnv * env, jobject this, jstring filepath) {
  jboolean fileExists = 0;
  jboolean isCopy;
  const char * path = (*env)->GetStringUTFChars(env, filepath, &isCopy);
  struct stat fileattrib;
  if (stat(path, &fileattrib) < 0) {
    __android_log_print(ANDROID_LOG_DEBUG, DEBUG_TAG, "NATIVE: stat error: [%s]", strerror(errno));
  } else
  {
    __android_log_print(ANDROID_LOG_DEBUG, DEBUG_TAG, "NATIVE: stat success, access perms: [%d]", fileattrib.st_mode);
    return 1;
  }

  return 0;
}

Execução de Comandos Privilegiados

Outra maneira de determinar se su existe é tentar executá-lo através do método Runtime.getRuntime.exec. Uma IOException será lançada se su não estiver no PATH. O mesmo método pode ser usado para verificar outros programas frequentemente encontrados em dispositivos root, como busybox e os links simbólicos que normalmente apontam para ele.

Verificação de Processos em Execução

O Supersu - de longe a ferramenta de root mais popular - executa um daemon de autenticação chamado daemonsu, portanto a presença desse processo é outro sinal de um dispositivo root. Os processos em execução podem ser enumerados com as APIs ActivityManager.getRunningAppProcesses e manager.getRunningServices, o comando ps e navegando pelo diretório /proc. Segue um exemplo implementado em rootinspector:

    public boolean checkRunningProcesses() {

      boolean returnValue = false;

      // Obtém processos de aplicativos atualmente em execução
      List<RunningServiceInfo> list = manager.getRunningServices(300);

      if(list != null){
        String tempName;
        for(int i=0;i<list.size();++i){
          tempName = list.get(i).process;

          if(tempName.contains("supersu") || tempName.contains("superuser")){
            returnValue = true;
          }
        }
      }
      return returnValue;
    }

Verificação de Pacotes de Aplicativos Instalados

Você pode usar o gerenciador de pacotes do Android para obter uma lista de pacotes instalados. Os seguintes nomes de pacote pertencem a ferramentas de root populares:

eu.chainfire.supersu
com.noshufou.android.su
com.koushikdutta.superuser
com.zachspong.temprootremovejb
com.ramdroid.appquarantine
com.topjohnwu.magisk

Verificação de Partições Graváveis e Diretórios do Sistema

Permissões incomuns em diretórios do sistema podem indicar um dispositivo personalizado ou root. Embora os diretórios do sistema e de dados normalmente sejam montados como somente leitura, às vezes você os encontrará montados como leitura-gravação quando o dispositivo está root. Procure por esses sistemas de arquivos montados com a flag "rw" ou tente criar um arquivo nos diretórios de dados.

Verificação de Compilações Android Personalizadas

Verificar sinais de compilações de teste e ROMs personalizadas também é útil. Uma maneira de fazer isso é verificar a tag BUILD por test-keys, que normalmente indica uma imagem Android personalizada. Verifique a tag BUILD da seguinte forma:

private boolean isTestKeyBuild()
{
String str = Build.TAGS;
if ((str != null) && (str.contains("test-keys")));
for (int i = 1; ; i = 0)
  return i;
}

A ausência de certificados Google Over-The-Air (OTA) é outro sinal de uma ROM personalizada: em compilações Android padrão, as atualizações OTA usam certificados públicos do Google.