
🔬 Uno strumento di ricerca Android avanzato per la cattura audio VoIP in tempo reale (Uplink/Downlink) mediante hook dinamici su libaudioflinger.so. Testato e realizzato su WhatsApp, Signal e Telegram su Android 14.
Obiettivo: Dimostrare la cattura di flussi VoIP effettuando l'hook dell'audioserver di Android (in particolare libaudioflinger.so) e registrando i buffer PCM in uplink (microfono) e downlink (altoparlante) quando il sistema è in modalità comunicazione.
App di Controllo (Android)
Interfaccia con 9 pulsanti per gestire la configurazione delle policy SELinux, l'iniezione del payload, lo spostamento dei dati e la riproduzione.
Payload (libaudiohook.so)
Libreria condivisa C++ (compilata come parte del progetto dell'app) che aggancia i metodi interni di AudioFlinger usando Dobby e scrive i buffer PCM grezzi in una directory di sessione.
Post-Elaborazione
Strumento aggiuntivo, tools/audioparser.py, converte i file grezzi .ac(Downlink)/.bc(Uplink) in WAV. Il file manager dell'app può anche convertire e riprodurre in-app.
Utility di Ricerca
Script Frida per il tracciamento iniziale delle funzioni e la scoperta degli offset; file di analisi per simboli/offset e altri frammenti.
Questa applicazione Android fornisce un'interfaccia grafica per gestire la registrazione VoIP tramite l'audioserver. Si integra con un payload nativo che si aggancia allo stack audio di Android per catturare l'audio del microfono e dell'altoparlante dalle applicazioni VoIP.
L'app funge da centro di controllo, consentendo di iniettare le policy SELinux richieste, avviare/fermare il monitoraggio, gestire i dati registrati e riprodurre i file audio VoIP catturati.
L'attività principale contiene 9 pulsanti di controllo:
Inietta Policy
Inietta le policy SELinux necessarie affinché audioserver e questa app funzionino insieme.
Avvia Monitoraggio
Inietta il payload della libreria condivisa in audioserver, abilitando la cattura dell'audio VoIP tramite hook di RecordTrack e PlaybackTrack. Usa AndKittyInjector per l'iniezione di processo.
Ferma Monitoraggio
Riavvia il processo audioserver, fermando il monitoraggio e rimuovendo gli hook iniettati.
Copia Dati
Copia l'audio PCM grezzo catturato dalla directory temporanea /data/local/tmp/voip a /sdcard/voip.
Visualizza Log
Mostra i log relativi all'iniezione, all'hook e allo stato della registrazione.
Abilita SELinux
Ripristina la modalità enforcing di SELinux.
Disabilita SELinux
Imposta SELinux in modalità permissive. Usalo quando le policy iniettate falliscono.
Riproduci Audio
Apre il File Manager VoIP personalizzato (/sdcard/voip/):
+----------------------+
| |
| App MainActivity |
| |
| |
|(9 Control Buttons UI)|
| |
| |
+----------+-----------+
|
|
|
+----------v-----------+
| |
| JNI Layer |
| |
| Selinux, Process Inje|
| ction, |
| |
| |
+----------------------+
Questa libreria condivisa viene iniettata in audioserver per rilevare AUDIO_MODE_IN_COMMUNICATION e catturare i flussi audio VoIP direttamente dallo stack audio di Android per un set predefinito di nomi pacchetto.
Usa il framework di hooking inline Dobby per intercettare metodi critici all'interno di libaudioflinger.so.
Entrambi i flussi audio uplink (microfono) e downlink (altoparlante) vengono catturati e scritti in file di sessione per l'elaborazione successiva.
La libreria installa hook sulle seguenti funzioni all'interno di AudioFlinger e classi correlate:
La libreria filtra i flussi in base al mapping UID → Pacchetto.
App attualmente monitorate:
com.whatsappcom.whatsapp.w4b (WhatsApp Business)org.thoughtcrime.securesms (Signal)org.telegram.messengerorg.telegram.messenger.webPuoi aggiungere altre applicazioni anche all'interno di audioserver_hook.cpp:
static const char* kTargetPackages[] = {
"com.whatsapp",
"com.whatsapp.w4b",
"org.thoughtcrime.securesms",
"org.telegram.messenger",
"org.telegram.messenger.web"
};
Una directory di sessione viene creata all'iniezione in audioserver in:
/data/local/tmp/voip/audioserver_session_<timestamp>/
I file vengono prima creati con estensione .tmp, poi rinominati all'arresto della traccia:
<packageName>_<sampleRate>_<timestamp>.bc<packageName>_<sampleRate>_<timestamp>.acEsempio di sessione:
/data/local/tmp/voip/audioserver_session_1725389200/
├── com.whatsapp_48000_1725389201_123456.ac
├── com.whatsapp_48000_1725389201_123789.bc
Poiché la libreria target è strippata e i metodi non sono esportati in libaudioflinger.so, abbiamo bisogno di offset esatti dei metodi e di altri componenti correlati per un hooking efficace.
Possiamo estrarre questi offset tramite analisi statica con IDA-PRO o analisi dinamica con Frida.
Una volta noti gli offset, possiamo sostituirli all'interno del file header 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)_ZN7android12AudioFlinger14PlaybackThread5Track4stopEvQuesti possono essere ottenuti tramite analisi statica.
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)
Viene catturata solo la modalità IN_COMMUNICATION. L'audio normale (musica, suoneria, ecc.) viene ignorato.
La gestione speciale per Telegram e Signal impone frequenze di campionamento corrette (Telegram: 48000 Hz, Signal: 8000 Hz) per ignorare altri audio non necessari come squilli del telefono e suoni di disconnessione.
Il PCM viene salvato grezzo; l'app di controllo converte successivamente PCM → WAV per la riproduzione.
┌─────────────────────────────┐
│ 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) │
└───────────────────────────┘
Processo utilizzato:
AudioFlinger gestisce riproduzione/cattura tramite PlaybackThread e RecordThread.PlaybackThread::Track (downlink) e RecordThread::RecordTrack (uplink).app/src/main/cpp/include/offsets.h) per sopravvivere ai simboli strippati.MODE_IN_COMMUNICATIONFacciamo l'hook di AudioFlinger::setMode(int mode) e manteniamo uno stato globale:
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/ in Android Studio.libaudiohook.so) come parte del progetto.adb install -r app/build/outputs/apk/debug/app-debug.apk
oppure installa l'app-release.apk fornita:
adb install -r app-release.apk
Se separi gli artefatti, puoi anche inviare la libreria condivisa per il test:
adb push app/src/main/jniLibs/arm64-v8a/libaudiohook.so /data/local/tmp/
audioserver nel tuo ambiente di test.audioserver.audioserver./data/local/tmp/voip/… a /sdcard/voip/ (e pulisce la directory temporanea). adb pull /sdcard/voip/
python3 tools/audioparser.py voip/
Pulsanti aggiuntivi:
- “Visualizza Log”: Visualizza lo stato di iniettore/hook.
- “Abilita SELinux” / “Disabilita SELinux”: Attiva/disattiva enforcing/permissive nel tuo laboratorio se le policy sono troppo restrittive.
- “Elimina Dati”: Cancella sia
/data/local/tmp/voip/che/sdcard/voip/.
dlsym.dlsym diretto potrebbe fallire.offsets.h), validati con dump dei simboli di Frida e IDA-PRO.AudioFlinger::setMode(IN_COMMUNICATION); registrare le transizioni; memorizzare un flag di modalità globale.audioserver possono essere bloccati..tmp; su stop() rinominare in (downlink) o (uplink).frameworks/av/services/audioflinger (AudioFlinger, PlaybackThread, RecordThread)system/media/audio (formati audio, attributi, maschere di canale)Elimina Dati
Elimina tutti i dati audio sia da /data/local/tmp/voip che da /sdcard/voip.
NOTA: Dopo l'eliminazione dei dati, è necessario re-iniettare il payload in audioserver per la creazione della directory e il salvataggio dei dati.
| Funzione | Scopo |
|---|
AudioFlinger::setMode | Tiene traccia delle transizioni dentro/fuori dalla modalità IN_COMMUNICATION (VoIP attivo). |
RecordTrack::getNextBuffer | Cattura i frame audio del microfono (uplink). |
PlaybackThread::Track::getNextBuffer | Cattura i frame audio dell'altoparlante (downlink). |
Track::stop | Chiude e finalizza il file downlink. |
RecordTrack::stop | Chiude e finalizza il file uplink. |
android::AudioFlinger::RecordThread::RecordTrack::stop(void)_ZN7android12AudioFlinger12RecordThread11RecordTrack4stopEv.ac.bclibaudioflinger.so
RecordTrack::getNextBuffer, Track::getNextBuffer, ecc.) non sono esportate e libaudioflinger.so non ha voci PLT/GOT per esse.
dlsym() o basati su PLT.target_addr = base_addr(libaudioflinger) + offset
DobbyHook(target_addr, hook_fn, &orig_fn)