
🔬 libaudioflinger.soを動的にフックすることで、リアルタイムVoIP音声キャプチャ(アップリンク/ダウンリンク)を実現する高度なAndroid研究ツールです。Android 14上のWhatsApp、Signal、Telegramでテストおよび構築済みです。
目標: Android の audioserver(具体的には libaudioflinger.so)をフックし、システムが 通信 モードのときにアップリンク(マイク)とダウンリンク(スピーカー)の PCM バッファを記録することで、VoIP ストリームキャプチャを実演します。
コントローラーアプリ (Android)
SELinux ポリシーの設定、ペイロード注入、データ移動、再生を管理する 9 つのボタンを備えた UI。
ペイロード (libaudiohook.so)
Dobby を使用して内部の AudioFlinger メソッドをフックし、生の PCM バッファをセッションディレクトリに書き込む C++ 共有ライブラリ(アプリプロジェクトの一部としてビルド)。
後処理
追加ツール tools/audioparser.py は、生の .ac(ダウンリンク)/.bc(アップリンク)ファイルを WAV に変換します。アプリのファイルマネージャーでも変換およびアプリ内再生が可能です。
調査ユーティリティ
初期の関数トレースとオフセット発見のための Frida スクリプト。シンボル/オフセットやその他のスニペットの解析ファイル。
この Android アプリケーションは、audioserver を介して VoIP 録音を管理するためのグラフィカルインターフェースを提供します。Android オーディオスタックをフックして VoIP アプリケーションのマイク音声とスピーカー音声をキャプチャするネイティブペイロードと統合されています。
このアプリはコントロールセンターとして機能し、必要な SELinux ポリシーの注入、監視の開始/停止、録音データの管理、キャプチャした VoIP 音声ファイルの再生を行うことができます。
メインアクティビティには 9 つのコントロールボタン があります:
ポリシー注入
audioserver とこのアプリが連携して動作するために必要な SELinux ポリシーを注入します。
監視開始
audioserver に共有ライブラリのペイロードを注入し、RecordTrack と PlaybackTrack をフックして VoIP 音声キャプチャを有効にします。プロセス注入には AndKittyInjector を使用します。
監視停止
audioserver プロセスを再起動し、監視を停止して注入されたフックを削除します。
データコピー
キャプチャした 生の PCM オーディオ を一時ディレクトリ /data/local/tmp/voip から /sdcard/voip にコピーします。
ログ表示
注入、フック、録音ステータスに関するログを表示します。
SELinux 有効化
SELinux の enforcing モードを復元します。
SELinux 無効化
SELinux を permissive モードに設定します。注入したポリシーが失敗した場合に使用します。
音声再生
カスタム VoIP ファイルマネージャー (/sdcard/voip/) を開きます:
データ削除
/data/local/tmp/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.webaudioserver_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 無効化」: ポリシーが厳しすぎる場合に、ラボ 環境で enforcing/permissive を切り替えます。
- 「データ削除」:
/data/local/tmp/voip/と/sdcard/voip/の両方をクリアします。
dlsym でシンボルを直接特定できません。dlsym するのは失敗する可能性があります。offsets.h)を使用し、Frida のシンボルダンプと IDA-PRO で検証します。AudioFlinger::setMode(IN_COMMUNICATION) でフックをキャプチャし、遷移をログに記録し、グローバルモードフラグを保存します。audioserver との IPC がブロックされる可能性があります。.tmp に書き込み、stop() で .ac(ダウンリンク)または .bc(アップリンク)に名前を変更します。frameworks/av/services/audioflinger (AudioFlinger, PlaybackThread, RecordThread)system/media/audio (audio formats, attributes, channel masks)/sdcard/voipaudioserver| 関数 | 目的 |
|---|
AudioFlinger::setMode | IN_COMMUNICATION モード(VoIP アクティブ)への移行/からの移行を追跡します。 |
RecordTrack::getNextBuffer | マイク(アップリンク)音声フレームをキャプチャします。 |
PlaybackThread::Track::getNextBuffer | スピーカー(ダウンリンク)音声フレームをキャプチャします。 |
Track::stop | ダウンリンクファイルを閉じて確定します。 |
RecordTrack::stop | アップリンクファイルを閉じて確定します。 |
android::AudioFlinger::RecordThread::RecordTrack::stop(void)_ZN7android12AudioFlinger12RecordThread11RecordTrack4stopEvlibaudioflinger.so に PLT フックがない
RecordTrack::getNextBuffer、Track::getNextBuffer など)は エクスポートされておらず、libaudioflinger.so にはそれらの PLT/GOT エントリがありません。
dlsym() や PLT ベースのフック機構は使用できませんでした。target_addr = base_addr(libaudioflinger) + offset
DobbyHook(target_addr, hook_fn, &orig_fn)