
🔬 An advanced Android research tool for real-time VoIP audio capture (Uplink/Downlink) by dynamically hooking libaudioflinger.so. Tested and built on WhatsApp, Signal, and Telegram on Android 14.
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 internal AudioFlinger methods using Dobby and writes raw PCM buffers to a session directory.
Post-Processing
Additional tool, tools/audioparser.py converts 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 for audioserver and this app to work together.
Start Monitoring
Injects the shared library payload into audioserver, enabling VoIP audio capture by hooking RecordTrack and PlaybackTrack. Uses AndKittyInjector for Process Injection.
Stop Monitoring
Restarts the audioserver process, stopping monitoring and removing injected hooks.
Copy Data
Copies captured raw PCM audio from temp directory /data/local/tmp/voip to /sdcard/voip.
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/):
+----------------------+
| |
| 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:
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.webYou 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 audioserver under:
/data/local/tmp/voip/audioserver_session_<timestamp>/
Files are first created with a .tmp extension, then renamed on track stop:
<packageName>_<sampleRate>_<timestamp>.bc<packageName>_<sampleRate>_<timestamp>.acExample 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.
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)_ZN7android12AudioFlinger14PlaybackThread5Track4stopEvThese can be obtained from static analysis.
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) │
└───────────────────────────┘
Process used:
AudioFlinger manages playback/capture via PlaybackThread & RecordThread.PlaybackThread::Track (downlink) and RecordThread::RecordTrack (uplink).app/src/main/cpp/include/offsets.h) to survive stripped symbols.MODE_IN_COMMUNICATIONWe hook AudioFlinger::setMode(int mode) and maintain a global state:
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) 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/
audioserver interaction in your test environment.audioserver.audioserver./data/local/tmp/voip/… -> /sdcard/voip/ (and clears the temp directory). 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/.
dlsym.dlsym may fail.offsets.h), validated with Frida symbol dumps and IDA-PRO.AudioFlinger::setMode(IN_COMMUNICATION); log transitions; store a global mode flag.audioserver can be blocked..tmp first; on stop() rename to .ac (downlink) or (uplink).frameworks/av/services/audioflinger (AudioFlinger, PlaybackThread, RecordThread)system/media/audio (audio formats, attributes, channel masks)Delete Data
Deletes all audio data from both /data/local/tmp/voip and /sdcard/voip.
NOTE: After delete data, we need to reinject into audioserver for directory creation to save data.
| 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. |
android::AudioFlinger::RecordThread::RecordTrack::stop(void)_ZN7android12AudioFlinger12RecordThread11RecordTrack4stopEv.bclibaudioflinger.so
RecordTrack::getNextBuffer, Track::getNextBuffer, etc.) are not exported and libaudioflinger.so has no PLT/GOT entries for them.
dlsym() or PLT-based hooking mechanisms.target_addr = base_addr(libaudioflinger) + offset
DobbyHook(target_addr, hook_fn, &orig_fn)