
🔬 libaudioflinger.so를 동적으로 후킹(hooking)하여 실시간 VoIP 오디오 캡처(Uplink/Downlink)를 수행하는 고급 Android 연구 도구입니다. Android 14의 WhatsApp, Signal, Telegram에서 테스트 및 구축되었습니다.
목표: Android의 audioserver(특히 libaudioflinger.so)를 후킹하고 시스템이 통화(communication) 모드에 있을 때 업링크(마이크) 및 다운링크(스피커) PCM 버퍼를 기록하여 VoIP 스트림 캡처를 시연합니다.
컨트롤러 앱 (Android)
SELINUX 정책 설정, 페이로드 주입, 데이터 이동, 재생을 관리하는 9개의 버튼이 있는 UI입니다.
페이로드 (libaudiohook.so)
앱 프로젝트의 일부로 빌드되는 C++ 공유 라이브러리로, Dobby를 사용하여 내부 AudioFlinger 메서드를 후킹하고 원시 PCM 버퍼를 세션 디렉터리에 기록합니다.
후처리
추가 도구인 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 오디오 스트림을 캡처합니다.
libaudioflinger.so 내부의 중요한 메서드를 가로채기 위해 Dobby 인라인 후킹 프레임워크를 사용합니다.
업링크(마이크) 및 다운링크(스피커) 오디오 스트림이 모두 캡처되어 이후 처리를 위해 세션 파일에 기록됩니다.
이 라이브러리는 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
대상 라이브러리가 스트리핑(stripped)되어 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/
추가 버튼:
- "로그 보기(View Logs)": 인젝터/후크 상태를 확인합니다.
- "SELinux 활성화" / "SELinux 비활성화": 정책이 너무 엄격한 경우 랩에서 강제/허용 모드를 전환합니다.
- "데이터 삭제(Delete Data)":
/data/local/tmp/voip/와/sdcard/voip/를 모두 정리합니다.
dlsym으로 심볼을 직접 찾을 수 없습니다.dlsym은 실패할 수 있습니다.offsets.h)에 대해 base + 하드코딩된 오프셋을 사용하고, Frida 심볼 덤프와 IDA-PRO로 검증합니다.AudioFlinger::setMode(IN_COMMUNICATION)에서 캡처 후크를 걸고, 전환을 기록하며, 전역 모드 플래그를 저장합니다.audioserver와의 IPC가 차단될 수 있습니다..tmp로 작성하고, stop()에서 .ac(다운링크) 또는 .bc(업링크)로 이름을 변경합니다.frameworks/av/services/audioflinger (AudioFlinger, PlaybackThread, RecordThread)system/media/audio (오디오 형식, 속성, 채널 마스크)/sdcard/voipaudioserver| Function | Purpose |
|---|
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)