Skip to content

Reverse engineering d'une APK Android

Une APK est une archive ZIP. Tout son contenu est extractible sans outil spécialisé — les ressources, le manifest et le code compilé sont présents à plat dans l’archive. La difficulté réside dans le fait que le code et certains fichiers XML sont sous forme binaire, pas lisibles directement.

Terminal window
unzip app.apk -d apk_extracted/

L’arborescence extraite :

apk_extracted/
├── AndroidManifest.xml # configuration de l'app (binaire, illisible à ce stade)
├── classes.dex # code compilé Dalvik (DEX), illisible
├── classes2.dex # code DEX supplémentaire (si présent)
├── assets/ # fichiers bruts embarqués (JSON, JS, bases de données…)
├── res/ # ressources (layouts XML, images, couleurs…)
│ └── values/
│ └── strings.xml # chaînes de l'app (souvent des infos utiles)
├── lib/ # bibliothèques natives (.so) par architecture
└── META-INF/ # signature et certificats

Les fichiers dans assets/ et res/ sont lisibles directement :

Terminal window
# Chercher des URLs, clés API ou tokens dans les ressources
grep -r "http" apk_extracted/assets/
grep -r "api_key\|secret\|token\|password" apk_extracted/res/values/strings.xml
# Lister les fichiers de configuration embarqués
find apk_extracted/assets/ -name "*.json" -o -name "*.xml" -o -name "*.db"

apktool décode les fichiers XML binaires (manifest, layouts) et convertit le code DEX en smali — une représentation assembleur lisible.

Terminal window
apktool d app.apk -o apk_decoded/

Le manifest contient les informations de configuration de l’application :

Terminal window
cat apk_decoded/AndroidManifest.xml

Ce qu’on y cherche :

<!-- Permissions déclarées — identifier les accès réseau, stockage, caméra… -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<!-- Activities exportées — points d'entrée accessibles sans authentification -->
<activity android:name=".AdminActivity" android:exported="true" />
<!-- Métadonnées — souvent des clés API de services tiers -->
<meta-data android:name="com.google.android.maps.v2.API_KEY"
android:value="AIzaSyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" />
Terminal window
cat apk_decoded/res/values/strings.xml

Contient les chaînes de caractères de l’interface, mais aussi parfois des URLs d’endpoints, des clés de configuration ou des identifiants codés en dur.

JADX décompile directement le code DEX en Java lisible, sans passer par le smali. C’est l’outil le plus efficace pour comprendre la logique applicative.

Terminal window
# Via le binaire releases GitHub (skylot/jadx)
wget https://github.com/skylot/jadx/releases/latest/download/jadx-1.5.0.zip
unzip jadx-1.5.0.zip -d jadx/
Terminal window
# Décompiler en Java dans un répertoire de sortie
jadx -d jadx_output/ app.apk
# Décompiler uniquement les ressources (plus rapide)
jadx --no-src -d jadx_output/ app.apk
Terminal window
jadx-gui app.apk

JADX-GUI permet de naviguer dans les classes, rechercher des chaînes et suivre les appels de méthodes.

Terminal window
# Credentials et clés codés en dur
grep -r "password\|secret\|apiKey\|token\|api_key" jadx_output/sources/ --include="*.java"
# URLs d'API backend
grep -r "http\|https\|retrofit\|OkHttp" jadx_output/sources/ --include="*.java"
# Logique d'authentification
grep -r "login\|auth\|jwt\|sha\|md5\|encrypt" jadx_output/sources/ --include="*.java" -l

Les applications React Native compilées avec le moteur Hermes livrent leur code JavaScript sous forme de bytecode binaire. Le fichier index.android.bundle dans assets/ n’est pas du JavaScript lisible.

Terminal window
file assets/index.android.bundle
assets/index.android.bundle: Hermes JavaScript bytecode, version 96

Un cat ou strings ne retourne que des fragments textuels sans la logique du code. Il faut désassembler le bytecode.

hbctool (l’outil historique) n’est plus maintenu pour les versions récentes de Hermes. Utiliser hermes-dec à la place (https://github.com/P1sec/hermes-dec).

Mise en place de l’environnement Python sous WSL :

Terminal window
sudo apt update && sudo apt install python3 python3-venv python3-full -y
# Créer un environnement virtuel isolé
python3 -m venv ~/ctf_env
source ~/ctf_env/bin/activate

Installation de hermes-dec :

Terminal window
pip install hermes-dec

Désassemblage :

Terminal window
hbc-disassemble assets/index.android.bundle output_hbc/

La sortie contient le bytecode désassemblé. Les chaînes de caractères (URLs, clés, noms de variables) sont directement lisibles dans les tables de constantes.

Décompilation vers du JavaScript : hermes-dec propose aussi une décompilation partielle vers JS pour certaines versions de bytecode :

Terminal window
hbc-decompile assets/index.android.bundle output.js

Ce qu’on cherche dans le bundle React Native

Section titled “Ce qu’on cherche dans le bundle React Native”
Terminal window
# Après désassemblage, chercher dans la sortie texte
grep -iE "(api|url|endpoint|secret|key|token|password)" output_hbc/*.hasm
# Ou directement via strings sur le bundle (résultats partiels mais rapides)
strings assets/index.android.bundle | grep -iE "(https?://|api_key|secret|token)"
1. Extraire l'APK
unzip app.apk -d apk_extracted/
2. Lire les ressources brutes
grep -r "api\|key\|secret\|token" apk_extracted/assets/ apk_extracted/res/
3. Décoder le manifest et les XML
apktool d app.apk -o apk_decoded/
cat apk_decoded/AndroidManifest.xml
4. Décompiler en Java
jadx -d jadx_output/ app.apk
grep -r "password\|secret" jadx_output/sources/
5. Si React Native — vérifier le type du bundle
file apk_extracted/assets/index.android.bundle
6. Si Hermes bytecode — désassembler
source ~/ctf_env/bin/activate
hbc-disassemble assets/index.android.bundle output_hbc/
grep -iE "(api|secret|token)" output_hbc/*.hasm

L’extraction brute (unzip) donne immédiatement accès aux ressources et révèle parfois des secrets sans décompilation. apktool rend le manifest lisible et donne accès aux chaînes de l’interface. JADX expose la logique applicative en Java. Pour les applications React Native avec Hermes, il faut une étape supplémentaire de désassemblage du bytecode — strings ne suffit pas à lire la logique du code.