
🔬 Un outil de recherche Android avancé pour la capture audio VoIP en temps réel (Uplink/Downlink) en hookant dynamiquement libaudioflinger.so. Testé et développé sur WhatsApp, Signal et Telegram sous Android 14.
Objectif : Démontrer la capture de flux VoIP en hookant l'audioserver d'Android (plus précisément libaudioflinger.so) et en enregistrant les buffers PCM en liaison montante (micro) et liaison descendante (haut-parleur) lorsque le système est en mode communication.
Application de contrôle (Android)
Interface avec 9 boutons pour gérer la configuration des politiques SELinux, l'injection du payload, le déplacement des données et la lecture audio.
Payload (libaudiohook.so)
Bibliothèque partagée C++ (compilée dans le projet de l'application) qui hooke les méthodes internes d'AudioFlinger à l'aide de Dobby et écrit les buffers PCM bruts dans un répertoire de session.
Post-traitement
Outil supplémentaire, tools/audioparser.py, convertit les fichiers bruts .ac (liaison descendante)/.bc (liaison montante) en WAV. Le gestionnaire de fichiers de l'application peut également convertir et lire ces fichiers dans l'application.
Utilitaires de recherche
Scripts Frida pour le traçage initial des fonctions et la découverte des offsets ; fichiers d'analyse pour les symboles/offsets et autres extraits.
Cette application Android fournit une interface graphique pour gérer l'enregistrement VoIP via l'audioserver. Elle s'intègre à un payload natif qui hooke la pile audio Android pour capturer l'audio du microphone et du haut-parleur des applications VoIP.
L'application sert de centre de contrôle : elle permet d'injecter les politiques SELinux requises, de démarrer/arrêter la surveillance, de gérer les données enregistrées et de lire les fichiers audio VoIP capturés.
L'activité principale contient 9 boutons de contrôle :
Injecter les politiques
Injecte les politiques SELinux requises pour que audioserver et cette application fonctionnent ensemble.
Démarrer la surveillance
Injecte le payload de bibliothèque partagée dans audioserver, activant la capture audio VoIP en hookant RecordTrack et PlaybackTrack. Utilise AndKittyInjector pour l'injection de processus.
Arrêter la surveillance
Redémarre le processus audioserver, arrêtant la surveillance et supprimant les hooks injectés.
Copier les données
Copie l'audio PCM brut capturé depuis le répertoire temporaire /data/local/tmp/voip vers /sdcard/voip.
Afficher les journaux
Affiche les journaux relatifs à l'injection, au hooking et à l'état de l'enregistrement.
Activer SELinux
Rétablit le mode enforcing de SELinux.
Désactiver SELinux
Passe SELinux en mode permissif. À utiliser lorsque les politiques injectées échouent.
Lire les audios
Ouvre le gestionnaire de fichiers VoIP personnalisé (/sdcard/voip/) :
+----------------------+
| |
| App MainActivity |
| |
| |
|(9 Control Buttons UI)|
| |
| |
+----------+-----------+
|
|
|
+----------v-----------+
| |
| JNI Layer |
| |
| Selinux, Process Inje|
| ction, |
| |
| |
+----------------------+
Cette bibliothèque partagée est injectée dans audioserver pour détecter AUDIO_MODE_IN_COMMUNICATION et capturer les flux audio VoIP directement depuis la pile audio Android pour un ensemble prédéfini de noms de packages.
Elle utilise le framework de hooking inline Dobby pour intercepter des méthodes critiques dans libaudioflinger.so.
Les flux audio liaison montante (microphone) et liaison descendante (haut-parleur) sont capturés et écrits dans des fichiers de session pour un traitement ultérieur.
La bibliothèque installe des hooks sur les fonctions suivantes dans AudioFlinger et les classes associées :
La bibliothèque filtre les flux en fonction du mappage UID → Package.
Applications actuellement surveillées :
com.whatsappcom.whatsapp.w4b (WhatsApp Business)org.thoughtcrime.securesms (Signal)org.telegram.messengerorg.telegram.messenger.webVous pouvez ajouter d'autres applications également dans audioserver_hook.cpp :
static const char* kTargetPackages[] = {
"com.whatsapp",
"com.whatsapp.w4b",
"org.thoughtcrime.securesms",
"org.telegram.messenger",
"org.telegram.messenger.web"
};
Un répertoire de session est créé lors de l'injection dans audioserver sous :
/data/local/tmp/voip/audioserver_session_<timestamp>/
Les fichiers sont d'abord créés avec une extension .tmp, puis renommés à l'arrêt de la piste :
<packageName>_<sampleRate>_<timestamp>.bc<packageName>_<sampleRate>_<timestamp>.acExemple de session :
/data/local/tmp/voip/audioserver_session_1725389200/
├── com.whatsapp_48000_1725389201_123456.ac
├── com.whatsapp_48000_1725389201_123789.bc
Étant donné que la bibliothèque cible est strippée et que les méthodes ne sont pas exportées dans libaudioflinger.so, nous avons besoin des offsets exacts des méthodes et des autres composants associés pour hooker avec succès.
Nous pouvons extraire ces offsets à partir d'une analyse statique avec IDA-PRO ou d'une analyse dynamique avec Frida.
Une fois les offsets connus, nous pouvons les remplacer dans le fichier d'en-tête offsets.h.
android::AudioFlinger::setMode(audio_mode_t)_ZN7android12AudioFlinger7setModeE11audio_mode_tandroid::AudioFlinger::RecordThread::RecordTrack::getNextBuffer(android::AudioBufferProvider::Buffer *)_ZN7android12AudioFlinger12RecordThread11RecordTrack13getNextBufferEPNS_19AudioBufferProvider6BufferEandroid::AudioFlinger::PlaybackThread::Track::getNextBuffer(android::AudioBufferProvider::Buffer *)_ZN7android12AudioFlinger14PlaybackThread5Track13getNextBufferEPNS_19AudioBufferProvider6BufferEandroid::AudioFlinger::PlaybackThread::Track::stop(void)_ZN7android12AudioFlinger14PlaybackThread5Track4stopEvCes offsets peuvent être obtenus par analyse statique.
libaudioflinger.soandroid::AudioFlinger::ThreadBase::TrackBase::TrackBaseTrackBase::TrackBase(
IAfThreadBase *thread,
const sp<Client>& client,
const audio_attributes_t& attr,
uint32_t sampleRate,
audio_format_t format,
audio_channel_mask_t channelMask,
size_t frameCount,
void *buffer,
size_t bufferSize,
audio_session_t sessionId,
pid_t creatorPid,
uid_t clientUid,
bool isOut,
const alloc_type alloc,
track_type type,
audio_port_handle_t portId,
std::string metricsId)
┌─────────────────────────────┐
│ VoIP Apps │
│ (WhatsApp / Signal / TG) │
└───────────────┬─────────────┘
│ Audio in/out
▼
┌─────────────────────────────┐
│ audioserver │
│ (libaudioflinger.so) │
└───────┬─────────┬───────────┘
│ │
uplink│ │downlink
(mic) │ │(speaker)
▼ ▼
┌───────────-──┐ ┌────────────-─┐
│ RecordTrack │ │ Track │
│ getNextBuffer│ │ getNextBuffer│
└───────┬────-─┘ └───────┬────-─┘
│ │
┌─────▼─────┐ ┌─────▼─────┐
│ hook_... │ │ hook_... │
│ (uplink) │ │ (downlink)│
└─────┬─────┘ └─────┬─────┘
│ │
└──────┬───┬──────┘
│ │
▼ ▼
┌───────────────────────────┐
│ /data/local/tmp/voip/ │
│ audioserver_session_* │
│ - *.bc (uplink) │
│ - *.ac (downlink) │
└───────────────────────────┘
Processus utilisé :
AudioFlinger gère la lecture/capture via PlaybackThread et RecordThread.PlaybackThread::Track (liaison descendante) et RecordThread::RecordTrack (liaison montante).app/src/main/cpp/include/offsets.h) pour survivre aux symboles strippés.MODE_IN_COMMUNICATIONNous hookons AudioFlinger::setMode(int mode) et maintenons un état global :
g_currentAudioMode.g_currentAudioMode == AUDIO_MODE_IN_COMMUNICATION..
├── Android_App/
| ├── app/ # Android Studio project (App + Payload)
| │ ├── src/
| │ │ ├── main/
| │ │ │ ├── java/ # Controller App Java/Kotlin sources
| │ │ │ ├── cpp/ # Payload source (built with the app)
| │ │ │ │ └── include/
| │ │ │ │ └── offsets.h # Function offsets for tested builds
├── tools/
│ ├── audioparser.py # Converts .ac/.bc raw files → WAV
│ ├── frida/ # Frida scripts for initial hook discovery
│ │ ├── host_audioserver.py
│ │ ├── agent_audioserver.js
├── app-release.apk # Signed release apk
└── README.md # Root README (design, build, run instructions)
audioparser.pyapp/ dans Android Studio.libaudiohook.so) dans le cadre du projet.adb install -r app/build/outputs/apk/debug/app-debug.apk
ou installez app-release.apk fourni :
adb install -r app-release.apk
Si vous séparez les artefacts, vous pouvez également pousser la bibliothèque partagée pour test :
adb push app/src/main/jniLibs/arm64-v8a/libaudiohook.so /data/local/tmp/
audioserver dans votre environnement de test.audioserver.audioserver./data/local/tmp/voip/… vers /sdcard/voip/ (et vide le répertoire temporaire). adb pull /sdcard/voip/
python3 tools/audioparser.py voip/
Boutons supplémentaires :
- « View Logs » : voir l'état de l'injecteur/hook.
- « Enable SELinux » / « Disable SELinux » : basculer entre enforcing/permissif dans votre lab si les politiques sont trop strictes.
- « Delete Data » : efface à la fois
/data/local/tmp/voip/et/sdcard/voip/.
dlsym.dlsym direct peut échouer.offsets.h), validés avec les dumps de symboles Frida et IDA-PRO.AudioFlinger::setMode(IN_COMMUNICATION) ; journaliser les transitions ; stocker un indicateur de mode global.audioserver peuvent être bloqués..tmp ; lors du , renommer en (liaison descendante) ou (liaison montante).frameworks/av/services/audioflinger (AudioFlinger, PlaybackThread, RecordThread)system/media/audio (formats audio, attributs, masques de canaux)Supprimer les données
Supprime toutes les données audio de /data/local/tmp/voip et /sdcard/voip.
REMARQUE : après la suppression des données, nous devons réinjecter le payload dans audioserver pour que la création du répertoire permette d'enregistrer les données.
| Fonction | Objectif |
|---|
AudioFlinger::setMode | Suit les transitions d'entrée/sortie du mode IN_COMMUNICATION (VoIP actif). |
RecordTrack::getNextBuffer | Capture les trames audio du microphone (liaison montante). |
PlaybackThread::Track::getNextBuffer | Capture les trames audio du haut-parleur (liaison descendante). |
Track::stop | Ferme et finalise le fichier de liaison descendante. |
RecordTrack::stop | Ferme et finalise le fichier de liaison montante. |
android::AudioFlinger::RecordThread::RecordTrack::stop(void)_ZN7android12AudioFlinger12RecordThread11RecordTrack4stopEvstop().ac.bclibaudioflinger.so
RecordTrack::getNextBuffer, Track::getNextBuffer, etc.) ne sont pas exportées et libaudioflinger.so n'a pas d'entrées PLT/GOT pour elles.
dlsym() standard ni les mécanismes de hooking basés sur PLT.target_addr = base_addr(libaudioflinger) + offset
DobbyHook(target_addr, hook_fn, &orig_fn)