Outils pour utilisateurs

Outils du site


apk-hacking-en

Hacking Android applications

This page is currently being translated. Please check back later for the final version.

Introduction

I use the term “hacking” in its noble meaning: Understand the inner workings and alter the behaviour in order to adapt it to our needs.

In this page I will explain how to inspect and alter an Android application (.apk files). My motivation is twofold:

  • Have fun understanding how Android applications work and how to alter them (including Dalvik machine code).
  • Protect our privacy by disabled some features (GPS, etc.)

Side note: English is not my primary language and I apologize in advance for mistakes and clumsiness.

Reverse-engineering APK files

APK is the standard Android application packaging format. Any APK file can be installed on an Android system as long as you have authorized third party application installation in system configuration. APK files are simple ZIP files which contain a certain set of files (descriptive XML files, resources such as images and other files, .dex files containing executable code…)

Android applications are developed in Java, but compiled for a different virtual machine than the standard JVM. Android uses its own virtual machine named Dalvik. Its bytecode is different than the JVM.

  • In standard Java, a .java source code is compiled in .class file (containing Java bytecode). Class files and other resources are packed into .jar files.
  • With Android, a .java source code is compiled in .dex file (containing Dalvik bytecode). Dex files and other resources are packed into .apk files.

A specific java code giving usually the same bytecode, you can use some tools (like JAD) to “decompile” bytecode (.class) into java sources (.java). But this “decompilation” is far from perfect and is sometimes not possible. In all cases, it is not possible to recompile these java sources directly.

The same goes for dex files: You cannot recompile resulting java sources. This is not an interesting path to alter Android applications.

The right way to do it is to directly alter the Dalvik bytecode.

I will not provide here courses of Java and assembly language, but the knowledge of both will help to understand the article below.

PS: I performed this hack with no preliminary knowledge of Dalvik bytecode.

Tools

Here are the tools we are going to use:

  • dex2jar to convert .apk in .jar
  • JD-GUI, an application to decompiler .jar files.
  • APK-Studio, to alter the .dex files, re-generate .apk files and re-sign them (because I'm lazy).

Why:

  • Converting .apk in .jar and running them into JD-GUI will help us to inspect the code: We will get (for most .dex files) the corresponding java source code. This will make code inspection much easier than reading raw Dalvik code.
  • Once we have spotted the code we want to change, we will use APK-Studio to directly alter the corresponding Dalvik bytecode.

APK-Studio permet de montrer le code machine Dalvik sous forme de source Smali. Smali est une sorte de langage assembleur qui donne du bytecode Dalvik. Le code Smali n'est pas très difficile à comprendre et se compare facilement au source Java.

Practical case

Download the APK file of the application you want to alter, either by copy-pasting the GooglePlay URL in this page, either by using this application to backup the APK.

As an example, we will remove the geolocation of the application Crédit Agricole Alsace Vosges (com.cavo.MonCAAlsace.apk, a French Bank application). Every time you want to browse your accounts, the application tries to geolocate you. There is not option to disable this. I do not find this behaviour acceptable.

Step 1 : Convert APK into JAR

./d2j-dex2jar.sh com.cavo.MonCAAlsace.apk

or

d2j-dex2jar.bat com.cavo.MonCAAlsace.apk

This will create the file com.cavo.MonCAAlsace-dex2jar.jar.

Step 2 : Extract decompilated java sources

Run JD-GUI, open the .jar file created in previous step, then go to menu “Files” > “Save all sources…”.

This will create com.cavo.MonCAAlsace-dex2jar.src.zip. This files contains all decompiled java sources from the application.

Notes:

  • Pas de miracle: Ces sources java ne sont pas recompilables.
  • Certaines classes ou méthodes ne seront pas décompilées (parce-que JAD n'y sera pas parvenu). Dans ce cas, JD-GUI affiche directement le code Dalvik.

Nous n'allons pas utiliser ces sources java pour les modifier. Nous allons juste les utiliser pour trouver que ce nous cherchons.

Dézippez le fichier, ce qui vous permettra ensuite rechercher dans les sources (Je n'utilise par la recherche interne de JD-GUI car je ne la trouve pas fiable).

Étape 3 : Rechercher ce que nous voulons modifier

Note importante: Un certain pourcentage d'applications Android ont leur code “obfusqué”: Toutes les méthodes et attributs sont renommés (a/aa/ab/ac/….) afin de rendre le code incompréhensible. Cela complique en effet la lecture du code, mais ces applications doivent faire appel aux API Android qui doivent - elles - porter des noms explicites. On arrive donc généralement à retrouver ses petits.

Dans notre cas, vous voulons empêcher l'application de faire appel aux fonctions GPS pour nous localiser. L'opération est donc assez simple: Localiser les appels aux API GPS et les neutraliser.

Dans le SDK Android, les fonctions GPS sont gérées par le LocationManager. Nous allons donc simplement rechercher ce mot dans les sources. On les trouve là:

  • \com\cavo\MonCAAlsace\Accueil\accueil.java
  • \com\cavo\MonCAAlsace\Bibliotheques\ARLayout.java
  • \com\cavo\MonCAAlsace\Quotidien\agences.java
  • \com\cavo\MonCAAlsace\Quotidien\geoloc_agence.java
  • \com\cavo\MonCAAlsace\Quotidien\ra_agences.java
  • \com\flurry\android\monolithic\sdk\impl\in.java

Prenons en exemple la première, accueil.java:

On trouve l'appel au LocationManager dans l'initialisation:

      ...
      Global.Tablette = isTablet(this);
      AppRater.app_launched(getParent());
      ((LocationManager)getSystemService("location")).requestLocationUpdates("network", 10000L, 0.0F, this);
      this.progressDialog = new ProgressDialog(getParent());
      this.progressDialog.setCancelable(false);
      ...

Ils instancient un LocationManager, puis lui demandent une geolocalisation rapide (par le réseau), et quand c'est fait, appeler la callback onLocationChanged que possède this.

Pour cette classe, il suffit donc de neutraliser cette ligne. Allons-voir dans le source Dalvik. Accrochez-vous à votre caleçon, on plonge.

Étape 4 : Modifier le code Smali/Dalvik

Lancez APK-Studio, menu File > APK. Dans la fenêtre qui s'affiche:

  • Project name: Entrez ce que vous voulez. Par exemple 'CA'.
  • APK Path: indiquez-lui votre fichier APK.
  • Project location: Créez un répertoire vide et indiquez-le là. APK-Studio y placera ses fichiers de travail.

Cliquez sur le bouton “Create” et laissez-le mouliner.

Dans APK-Studio, ouvrez CA/smali/android/com/cavo/MonCAAlsace/Accueil/accueil.smali. Vous devez voir un code qui commence comme cela:

.class public Lcom/cavo/MonCAAlsace/Accueil/accueil;
.super Landroid/app/Activity;
.source "accueil.java"

# interfaces
.implements Landroid/location/LocationListener;


# static fields
.field private static final ALERT_DIALOG:I

.field public static Actus:Landroid/widget/Button;

.field public static Agences:Landroid/widget/Button;

...

Il s'agit de la classe Accueil.java que nous voulons modifier.

Que fait La ligne qui nous intéresse ?

((LocationManager)getSystemService("location")).requestLocationUpdates("network", 10000L, 0.0F, this);
  • Elle appelle la fonction getSystemService (en passant la valeur “location”).
  • Elle caste le résultat en LocationManager.
  • Elle appelle la méthode requestLocationUpdates en passant les paramètres “network”, 10000, 0 et this.

Voyons si vous vous y retrouvez dans le code Dalvik correspondant:

    .line 146
    const-string v5, "location"
    move-object/from16 v0, p0
    invoke-virtual {v0, v5}, Lcom/cavo/MonCAAlsace/Accueil/accueil;->getSystemService(Ljava/lang/String;)Ljava/lang/Object;
    move-result-object v2
    check-cast v2, Landroid/location/LocationManager;
    
    .line 147
    .local v2, "locMan":Landroid/location/LocationManager;
    const-string v3, "network"
    const-wide/16 v4, 0x2710
    const/4 v6, 0x0
    move-object/from16 v7, p0
    invoke-virtual/range {v2 .. v7}, Landroid/location/LocationManager;->requestLocationUpdates(Ljava/lang/String;JFLandroid/location/LocationListener;)V

Pas si compliqué, hein ?

Vous pouvez voir, dans la section .line 146, l'appel (invoke) à la fonction getSystemService(), le stockage du résultat dans le registre v2, puis le casting en LocationManager. Et dans la section .line 147, l'appel à la méthode requestLocationUpdates() avec différents paramètres.

  • Dalvik utilise des registres (v0,v1,v2…) dans lesquels il charge les valeurs à traiter.
  • p1,p2,p3… sont les paramètres passés à la méthode. p0 est équivalent à this.
  • Les constantes sont signalées simplement avec leur type et leur valeur.
  • Les appels de méthode sont invoke-*
  • Les variables locales déclarées avec .local
  • etc.
  • Je ne vais pas rappeler ici l'intégralité de ce langage assembleur (voir ), mais vous pouvez constater qu'on arrive assez facilement à retrouver la correspondance entre les instructions java et les instructions Dalvik.

Note: Vous ne devez pas supprimer les directives .lines. L'assembleur Smali/Dalvik s'en sert pour se repérer.

Pour annuler complètement la ligne, on va effectuer une opération classique: la 'NOPer'. (NOP est une instruction qui ne fait rien: “NOP” = “No OPeration”).

Le code devient:

    .line 146
    nop

    .line 147
    nop

(Ne touchez pas ce qu'il y a avant .line 146 ni à ce qu'il y a après .line 148.)

Il va nous falloir trouver tous les autres appels au LocationManager et les neutraliser.

Voici les autres fichiers modifiés:

  • \CA\smali\com\cavo\MonCAAlsace\Bibliotheques\ARLayout.smali:
    .line 50
    iget-object v0, p0, Lcom/cavo/MonCAAlsace/Bibliotheques/ARLayout;->locMan:Landroid/location/LocationManager;
    const-string v1, "network"
    const-wide/16 v2, 0x64
    const/high16 v4, 0x3f800000
    move-object v5, p0
    invoke-virtual/range {v0 .. v5}, Landroid/location/LocationManager;->requestLocationUpdates(Ljava/lang/String;JFLandroid/location/LocationListener;)V
    .line 50
    nop
  • \CA\smali\com\cavo\MonCAAlsace\Quotidien\geoloc_agence.smali:
    .line 101
    iget-object v0, p0, Lcom/cavo/MonCAAlsace/Quotidien/geoloc_agence;->lm:Landroid/location/LocationManager;
    const-string v1, "network"
    const-wide/16 v2, 0x2710
    const/4 v4, 0x0
    move-object v5, p0
    invoke-virtual/range {v0 .. v5}, Landroid/location/LocationManager;->requestLocationUpdates(Ljava/lang/String;JFLandroid/location/LocationListener;)V
    .line 101
    nop
  • \CA\smali\com\cavo\MonCAAlsace\Quotidien\ra_agences.smali:
    .line 195
    sget-object v1, Lcom/cavo/MonCAAlsace/Quotidien/ra_agences;->ctx:Landroid/content/Context;
    const-string v2, "location"
    invoke-virtual {v1, v2}, Landroid/content/Context;->getSystemService(Ljava/lang/String;)Ljava/lang/Object;
    move-result-object v0
    check-cast v0, Landroid/location/LocationManager;

    .line 196
    .local v0, "locMan":Landroid/location/LocationManager;
    const-string v1, "network"
    const-wide/16 v2, 0x2710
    const/4 v4, 0x0
    iget-object v5, p0, Lcom/cavo/MonCAAlsace/Quotidien/ra_agences;->gpsListener:Landroid/location/LocationListener;
    invoke-virtual/range {v0 .. v5}, Landroid/location/LocationManager;->requestLocationUpdates(Ljava/lang/String;JFLandroid/location/LocationListener;)V
    .line 195
    nop

    .line 196
    .local v0, "locMan":Landroid/location/LocationManager;
    nop
  • \CA\smali\com\flurry\android\monolithic\sdk\impl\in.smali:
    .line 161
    invoke-static {p1}, Landroid/text/TextUtils;->isEmpty(Ljava/lang/CharSequence;)Z
    move-result v0
    if-nez v0, :cond_0

    .line 162
    iget-object v0, p0, Lcom/flurry/android/monolithic/sdk/impl/in;->f:Landroid/location/LocationManager;
    const-wide/32 v2, 0x1b7740
    const/4 v4, 0x0
    iget-object v5, p0, Lcom/flurry/android/monolithic/sdk/impl/in;->i:Lcom/flurry/android/monolithic/sdk/impl/io;
    invoke-static {}, Landroid/os/Looper;->getMainLooper()Landroid/os/Looper;
    move-result-object v6
    move-object v1, p1
    invoke-virtual/range {v0 .. v6}, Landroid/location/LocationManager;->requestLocationUpdates(Ljava/lang/String;JFLandroid/location/LocationListener;Landroid/os/Looper;)V
    .line 161
    nop

    .line 162
    nop

Étape 5: Re-packager l'APK

Une fois les modifications apportées aux différentes classes dans APK-Studio, rien de plus simple pour générer l'APK correspondant:

  • Sauvegardez vos modifications: Menu Files > Save all.
  • Re-générez l'APK: Menu Project > Build.

L'opération prendra un certain temps.

Regardez ensuite dans votre répertoire projet /CA/build/rebuilt.apk. Voilà votre fichier .apk modifié prêt à être installé.

Dé-installez l'application d'origine et installez ce nouvel APK. (Si l'application plante, c'est que vos modification ne sont pas correctes.)

Dans notre cas, le lancement de l'application “Crédit Agricole” ne déclenchera plus l'appel au GPS (même si ce dernier n'est pas désactivé).

Notes

  • Là nous avons neutralisé les appels aux fonctions de géolocalisation car l'application accepte de fonctionner sans, mais certaines applications refuseront. Dans ce cas, il vous faudra changer de stratégie: Leur fournir malgré tout les données, mais avec des latitutes/longitudes bidon (par exemple en allant modifier la callback onLocationChanged() du programme.)
  • Cette méthode vous obligera à re-faire votre hack à chaque mise à jour de l'application en question.
  • Les choses peuvent être plus compliquées pour les applications qui chargent dynamiquement du code.
  • L'obfuscation peut compliquer la compréhension du code. Dans ce cas il peut être utile de charger les sources générées par JD-GUI dans un IDE (IntelliJ, Eclipse…) afin de faire un peu de refactoring (renommage des méthodes et attributs), ce qui aidera grandement à comprendre le code.
  • Dans notre exemple nous avons juste NOPé le code qui nous embêtait, mais dans certains cas il vous faudra faire des choses plus complexes (renvoyer null directement (return-void), instancier un objet du bon type (non initialisé) et le renvoyer, shunter un saut conditionnel, etc.)
apk-hacking-en.txt · Dernière modification: 2014/10/27 12:16 par sebsauvage