目标: 演示如何通过挂钩 Android 的 audioserver(特别是 libaudioflinger.so)来捕获 VoIP 音频流,并在系统处于通信模式时录制上行(麦克风)和下行(扬声器)的 PCM 缓冲区。
控制器应用(Android)
包含 9 个按钮的 UI,用于管理 SELINUX 策略设置、载荷注入、数据传输和播放。
载荷(libaudiohook.so)
C++ 共享库(作为应用项目的一部分构建),使用 Dobby 挂钩 AudioFlinger 的内部方法,并将原始 PCM 缓冲区写入会话目录。
后处理
附加工具 tools/audioparser.py 可将原始 .ac(Downlink)/.bc(Uplink) 文件转换为 WAV。应用的文件管理器也可以在应用内进行转换和播放。
研究工具
用于初始函数跟踪和偏移量发现的 Frida 脚本;用于符号/偏移量分析的文件及其他代码片段。
该 Android 应用提供了通过 audioserver 管理 VoIP 录音的图形界面。它与一个原生载荷集成,该载荷挂钩 Android 音频栈,以从 VoIP 应用中捕获麦克风和扬声器音频。
该应用充当控制中心,允许你注入所需的 SELinux 策略、启动/停止监控、管理录制的数据,以及播放捕获的 VoIP 音频文件。
主 Activity 包含 9 个控制按钮:
注入策略
注入所需的 SELinux 策略,使 audioserver 与本应用能够协同工作。
开始监控
将共享库载荷注入 audioserver,通过挂钩 RecordTrack 和 PlaybackTrack 实现 VoIP 音频捕获。使用 AndKittyInjector 进行进程注入。
停止监控
重启 audioserver 进程,停止监控并移除注入的挂钩。
复制数据
将捕获的原始 PCM 音频从临时目录 /data/local/tmp/voip 复制到 /sdcard/voip。
查看日志
显示与注入、挂钩和录制状态相关的日志。
启用 SELinux
恢复 SELinux 强制模式。
禁用 SELinux
将 SELinux 设置为宽容模式,在注入的策略失败时使用。
播放音频
打开自定义的 VoIP 文件管理器(/sdcard/voip/):
删除数据
删除 /data/local/tmp/voip 和 /sdcard/voip 中的所有音频数据。
:删除数据后,我们需要重新注入 ,以创建用于保存数据的目录。
+----------------------+
| |
| App MainActivity |
| |
| |
|(9 Control Buttons UI)|
| |
| |
+----------+-----------+
|
|
|
+----------v-----------+
| |
| JNI Layer |
| |
| Selinux, Process Inje|
| ction, |
| |
| |
+----------------------+
该共享库被注入到 audioserver 中,用于检测 AUDIO_MODE_IN_COMMUNICATION,并针对预定义的包名集合直接从 Android 音频栈捕获 VoIP 音频流。
它使用 Dobby 内联挂钩框架来拦截 libaudioflinger.so 内部的关键方法。
**上行(麦克风)和下行(扬声器)**音频流都会被捕获,并写入会话文件以供后续处理。
该库在 AudioFlinger 及相关类内部对以下函数安装挂钩:
该库根据 UID → 包名映射 过滤音频流。
当前监控的应用:
com.whatsappcom.whatsapp.w4b (WhatsApp Business)org.thoughtcrime.securesms (Signal)org.telegram.messengerorg.telegram.messenger.web你也可以在 audioserver_hook.cpp 内部添加其他应用:
static const char* kTargetPackages[] = {
"com.whatsapp",
"com.whatsapp.w4b",
"org.thoughtcrime.securesms",
"org.telegram.messenger",
"org.telegram.messenger.web"
};
注入 audioserver 时会创建一个会话目录,位于:
/data/local/tmp/voip/audioserver_session_<timestamp>/
文件首先以 .tmp 扩展名创建,然后在轨道停止时重命名:
<packageName>_<sampleRate>_<timestamp>.bc<packageName>_<sampleRate>_<timestamp>.ac示例会话:
/data/local/tmp/voip/audioserver_session_1725389200/
├── com.whatsapp_48000_1725389201_123456.ac
├── com.whatsapp_48000_1725389201_123789.bc
由于目标库已剥离符号,且方法未在 libaudioflinger.so 中导出,因此我们需要方法和相关组件的精确偏移量才能成功挂钩。
我们可以通过 IDA-PRO 进行静态分析,或使用 Frida 进行动态分析来提取这些偏移量。
一旦获得偏移量,我们就可以在 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)_ZN7android12AudioFlinger14PlaybackThread5Track4stopEv这些可以通过静态分析获得。
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) │
└───────────────────────────┘
使用的方法:
AudioFlinger 通过 PlaybackThread 和 RecordThread 管理播放/捕获。PlaybackThread::Track(下行)和 RecordThread::RecordTrack(上行)表示。app/src/main/cpp/include/offsets.h 中)解析地址,以应对符号剥离。MODE_IN_COMMUNICATION我们挂钩 AudioFlinger::setMode(int mode) 并维护一个全局状态:
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.py 的 Pythonapp/。libaudiohook.so)作为项目的一部分进行编译。adb install -r app/build/outputs/apk/debug/app-debug.apk
或安装提供的 app-release.apk:
adb install -r app-release.apk
如果你要单独分发产物,也可以推送共享库进行测试:
adb push app/src/main/jniLibs/arm64-v8a/libaudiohook.so /data/local/tmp/
audioserver 的交互准备所需的 SELinux 规则。audioserver。audioserver。/data/local/tmp/voip/… 移动到 /sdcard/voip/(并清理临时目录)。 adb pull /sdcard/voip/
python3 tools/audioparser.py voip/
其他按钮:
- “查看日志”:查看注入器/挂钩状态。
- “启用 SELinux” / “禁用 SELinux”:如果策略过于严格,可在你的实验室环境中切换强制/宽容模式。
- “删除数据”:清除
/data/local/tmp/voip/和/sdcard/voip/。
dlsym 直接定位符号。dlsym 可能会失败。offsets.h),并通过 Frida 符号转储和 IDA-PRO 验证。AudioFlinger::setMode(IN_COMMUNICATION) 上挂钩捕获;记录状态转换;存储全局模式标志。audioserver 的 IPC 可能会被阻止。.tmp;在 stop() 时重命名为 .ac(下行)或 .bc(上行)。libaudioflinger.so 中没有 PLT 挂钩
frameworks/av/services/audioflinger(AudioFlinger、PlaybackThread、RecordThread)system/media/audio(音频格式、属性、声道掩码)audioserver| Function | Purpose |
|---|
AudioFlinger::setMode | 跟踪进入/退出 IN_COMMUNICATION 模式(VoIP 活动)的状态转换。 |
RecordTrack::getNextBuffer | 捕获麦克风(上行)音频帧。 |
PlaybackThread::Track::getNextBuffer | 捕获扬声器(下行)音频帧。 |
Track::stop | 关闭并最终确定下行文件。 |
RecordTrack::stop | 关闭并最终确定上行文件。 |
android::AudioFlinger::RecordThread::RecordTrack::stop(void)_ZN7android12AudioFlinger12RecordThread11RecordTrack4stopEvRecordTrack::getNextBuffer、Track::getNextBuffer 等)未导出,且 libaudioflinger.so 中没有对应的 PLT/GOT 条目。
dlsym() 或基于 PLT 的挂钩机制。target_addr = base_addr(libaudioflinger) + offset
DobbyHook(target_addr, hook_fn, &orig_fn)