Goal: Demonstrate VoIP stream capture by hooking Android's audioserver (specifically libaudioflinger.so) and recording uplink (mic) and downlink (speaker) PCM buffers when the system is in communication mode.
-
Controller App (Android)
UI with 9 buttons to manage SELINUX policy setup, payload injection, data movement, and playback. -
Payload (
libaudiohook.so)
C++ shared library (built as part of the app project) that hooks internalAudioFlingermethods using Dobby and writes raw PCM buffers to a session directory. -
Post-Processing
Additional tool,tools/audioparser.pyconverts raw.ac(Downlink)/.bc(Uplink)files to WAV. The appβs file manager can also convert & play in-app. -
Research Utilities
Frida scripts for initial function tracing & offset discovery; analysis files for symbol/offset and other snippets.
This Android application provides a graphical interface for managing VoIP recording through the audioserver. It integrates with a native payload that hooks into the Android audio stack to capture microphone and speaker audio from VoIP applications.
The app serves as a control center, allowing you to inject required SELinux policies, start/stop monitoring, manage recorded data, and play captured VoIP audio files.
The main activity contains 9 control buttons:
-
Inject Policies
Injects the required SELinux policies foraudioserverand this app to work together. -
Start Monitoring
Injects the shared library payload intoaudioserver, enabling VoIP audio capture by hookingRecordTrackandPlaybackTrack. Uses AndKittyInjector for Process Injection. -
Stop Monitoring
Restarts theaudioserverprocess, stopping monitoring and removing injected hooks. -
Copy Data
Copies captured raw PCM audio from temp directory/data/local/tmp/voipto/sdcard/voip.- Cleans up the temporary directory afterward.
-
View Logs
Displays logs related to injection, hooking, and recording status. -
Enable SELinux
Restores SELinux enforcing mode. -
Disable SELinux
Sets SELinux to permissive mode, Use when injected policies fail. -
Play Audios
Opens the custom VoIP File Manager (/sdcard/voip/):- Converts raw PCM files into WAV format in-memory.
- Plays the converted audio directly in-app.
-
Delete Data
Deletes all audio data from both/data/local/tmp/voipand/sdcard/voip. NOTE: After delete data, we need to reinject intoaudioserverfor directory creation to save data.
+----------------------+
| |
| App MainActivity |
| |
| |
|(9 Control Buttons UI)|
| |
| |
+----------+-----------+
|
|
|
+----------v-----------+
| |
| JNI Layer |
| |
| Selinux, Process Inje|
| ction, |
| |
| |
+----------------------+
This shared library is injected into audioserver to detect AUDIO_MODE_IN_COMMUNICATION and capture VoIP audio streams directly from Androidβs audio stack for predefined set of package names.
It uses the Dobby inline hooking framework to intercept critical methods inside libaudioflinger.so.
Both uplink (microphone) and downlink (speaker) audio streams are captured and written to session files for later processing.
The library installs hooks on the following functions inside AudioFlinger and related classes:
| Function | Purpose |
|---|---|
AudioFlinger::setMode |
Tracks transitions into/out of IN_COMMUNICATION mode (VoIP active). |
RecordTrack::getNextBuffer |
Captures microphone (uplink) audio frames. |
PlaybackThread::Track::getNextBuffer |
Captures speaker (downlink) audio frames. |
Track::stop |
Closes and finalizes downlink file. |
RecordTrack::stop |
Closes and finalizes uplink file. |
The library filters streams based on UID β Package mapping.
Currently monitored apps:
com.whatsappcom.whatsapp.w4b(WhatsApp Business)org.thoughtcrime.securesms(Signal)org.telegram.messengerorg.telegram.messenger.web
You can add other applications also iniside audioserver_hook.cpp:
static const char* kTargetPackages[] = {
"com.whatsapp",
"com.whatsapp.w4b",
"org.thoughtcrime.securesms",
"org.telegram.messenger",
"org.telegram.messenger.web"
};
-
A session directory is created at injection into
audioserverunder:/data/local/tmp/voip/audioserver_session_<timestamp>/ -
Files are first created with a
.tmpextension, then renamed on track stop:- Uplink (mic) β
<packageName>_<sampleRate>_<timestamp>.bc - Downlink (speaker) β
<packageName>_<sampleRate>_<timestamp>.ac
- Uplink (mic) β
-
Example session:
/data/local/tmp/voip/audioserver_session_1725389200/
βββ com.whatsapp_48000_1725389201_123456.ac
βββ com.whatsapp_48000_1725389201_123789.bc
Since target library is stripped and methods are not exported in libaudioflinger.so, we need exact offsets of methods and other related components for successful hooking.
We can extract those offsets from static analysis using IDA-PRO or from dynamic analysis using frida.
Once offsets are known we can replace them inside offsets.h header file.
- AUDIOFLINGER_SETMODE_OFFSET
- Method:
android::AudioFlinger::setMode(audio_mode_t) - Mangled name:
_ZN7android12AudioFlinger7setModeE11audio_mode_t
- Method:
- RECORDTRACK_GETNEXTBUFFER_OFFSET
- Method:
android::AudioFlinger::RecordThread::RecordTrack::getNextBuffer(android::AudioBufferProvider::Buffer *) - Mangled name:
_ZN7android12AudioFlinger12RecordThread11RecordTrack13getNextBufferEPNS_19AudioBufferProvider6BufferE
- Method:
- TRACK_GETNEXTBUFFER_OFFSET
- Method:
android::AudioFlinger::PlaybackThread::Track::getNextBuffer(android::AudioBufferProvider::Buffer *) - Mangled name:
_ZN7android12AudioFlinger14PlaybackThread5Track13getNextBufferEPNS_19AudioBufferProvider6BufferE
- Method:
- TRACK_STOP_OFFSET
- Method:
android::AudioFlinger::PlaybackThread::Track::stop(void) - Mangled name:
_ZN7android12AudioFlinger14PlaybackThread5Track4stopEv
- Method:
- RECORDTRACK_STOP_OFFSET
- Method:
android::AudioFlinger::RecordThread::RecordTrack::stop(void) - Mangled name:
_ZN7android12AudioFlinger12RecordThread11RecordTrack4stopEv
- Method:
- SAMPLERATE_OFFSET
- UID_OFFSET
- ISOUT_OFFSET
- FORMAT_OFFSET
- CHANNELMASK_OFFSET
- ATTR_OFFSET
These can be obtained from static analysis.
- Library -
libaudioflinger.so - Method -
android::AudioFlinger::ThreadBase::TrackBase::TrackBase
TrackBase::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)
- Only IN_COMMUNICATION mode is captured. Normal audio (music, ringtone, etc.) is ignored.
- Special handling for Telegram and Signal enforces correct sample rates (Telegram: 48000 Hz, Signal: 8000 Hz) to ignore other unnecessary audios like phone ring and phone disconnect sounds.
- PCM is stored raw; the controller app later converts PCM β WAV for playback.
βββββββββββββββββββββββββββββββ
β 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) β
βββββββββββββββββββββββββββββ
Process used:
- Dynamic tracing - Frida:
- Used Frida to trace all function calls during a VOIP Call
- Filtered all functions that are responsible for audio buffers handling.
- Then created Frida scripts to capture and handle audio buffers.
- AOSP review & class mapping
AudioFlingermanages playback/capture viaPlaybackThread&RecordThread.- Per-client audio is represented by
PlaybackThread::Track(downlink) andRecordThread::RecordTrack(uplink).
- Filtering to App specific streams
- Used UIDβpackage mapping to restrict to target apps.
- Stability and offsets
- Final native payload resolves addresses via base + hardcoded offsets (kept in
app/src/main/cpp/include/offsets.h) to survive stripped symbols.
- Final native payload resolves addresses via base + hardcoded offsets (kept in
We hook AudioFlinger::setMode(int mode) and maintain a global state:
- On each call, we log the mode change and update
g_currentAudioMode. - Hooks (in both mic and speaker hooks) check
g_currentAudioMode == AUDIO_MODE_IN_COMMUNICATION. - This ensures we only dump buffers while a VoIP session is active, avoiding music/ringtones/device playback/mic recordings etc.
.
βββ 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)
- Android Studio + NDK
- Rooted emulator (e.g., rootAVD) or test device you own (e.g., Magisk)
- Test VoIP apps installed (same ones you own/administer)
- Python for
audioparser.py
- Open
app/in Android Studio. - Ensure NDK is installed (SDK Manager β SDK Tools β NDK).
- Build Debug variant. The Gradle/CMake config will compile the native payload (
libaudiohook.so) as part of the project.
adb install -r app/build/outputs/apk/debug/app-debug.apk
or Install app-release.apk provided:
adb install -r app-release.apk
If you separate artifacts, you can also push the shared library for testing:
adb push app/src/main/jniLibs/arm64-v8a/libaudiohook.so /data/local/tmp/
- Launch the Controller App -> grant root when prompted.
- Tap "Inject Policies"
Prepares SELinux rules required for the app andaudioserverinteraction in your test environment. - Tap βStart Monitoringβ
The app uses AndKittyInjector to inject the payload intoaudioserver. - Place a test VoIP call (e.g., your own WhatsApp/Signal/Telegram account).
- Tap βStop Monitoringβ
Restartsaudioserver. - Tap βCopy Dataβ
Moves session files from/data/local/tmp/voip/β¦->/sdcard/voip/(and clears the temp directory). - Playback
- In-app: βPlay Audiosβ converts PCM -> WAV and plays.
- Or offline:
adb pull /sdcard/voip/ python3 tools/audioparser.py voip/
Additional buttons:
- βView Logsβ: See injector/hook status.
- βEnable SELinuxβ / βDisable SELinuxβ: Toggle enforcing/permissive in your lab if policies are too strict.
- βDelete Dataβ: Clear both
/data/local/tmp/voip/and/sdcard/voip/.
- Emulator: AVD API 34 (x86_64) - Android 14 - rooted with rootAVD
- Device: Samsung Galaxy S22 (ARM64) - Android 14 - Build S901EXXSCEYB1 - rooted with Magisk
- Whatsapp Business
- Signal
- Telegram
- Stripped symbols
- Issue: Symbols cannot be located directly via
dlsym. - Fix: Use Offsets from Static analysis.
- Issue: Symbols cannot be located directly via
- Different Symbol/Offset Across Builds
- Issue: Stripped symbols vary by build; direct
dlsymmay fail. - Fix: Use base + hardcoded offsets for tested builds (
offsets.h), validated with Frida symbol dumps and IDA-PRO.
- Issue: Stripped symbols vary by build; direct
- Mode Detection Accuracy
- Issue: Recording outside VoIP sessions yields noisy data.
- Fix: Hooks captures on
AudioFlinger::setMode(IN_COMMUNICATION); log transitions; store a global mode flag.
- SELinux
- Issue: Writing to temp directories & IPC with
audioservercan be blocked. - Fix: Provide a Inject Policies step to add necessary rules; expose Enable/Disable SELinux toggles for debugging in test environments.
- Issue: Writing to temp directories & IPC with
- File Lifecycle & Renaming
- Issue: Avoiding partial/corrupt outputs on thread stop.
- Fix: Write to
.tmpfirst; onstop()rename to.ac(downlink) or.bc(uplink).
- No PLT Hooks in
libaudioflinger.so- Issue: The target functions (
RecordTrack::getNextBuffer,Track::getNextBuffer, etc.) are not exported andlibaudioflinger.sohas no PLT/GOT entries for them.- We could not use standard
dlsym()or PLT-based hooking mechanisms.
- We could not use standard
- Fix: We used Dobby for inline function hooking.
- Hooks are installed at runtime using the formula:
target_addr = base_addr(libaudioflinger) + offset DobbyHook(target_addr, hook_fn, &orig_fn)
- Hooks are installed at runtime using the formula:
- Issue: The target functions (
- Dobby (Inline Hooking Framework) - https://www.github.com/jmpews/Dobby
- AndKittyInjector (Android process injector) - https://github.com/MJx0/AndKittyInjector
- AOSP
frameworks/av/services/audioflinger(AudioFlinger, PlaybackThread, RecordThread)system/media/audio(audio formats, attributes, channel masks)