-
-
[原创]Drivers32:媒体编解码与驱动映射
-
发表于: 9小时前 70
-
Drivers32:媒体编解码与驱动映射
这个程序的功能是:在不写入注册表、不加载任何媒体模块的前提下,分别读取 64 位和 32 位 Drivers32 映射,输出每条映射的来源、值名、注册表类型和原始数据。
要完成这项只读核查,分为四步:
- 确定 64 位与 32 位 Drivers32 映射来源。
- 以只读权限打开来源键,并测量枚举缓冲区容量。
- 按索引枚举全部值,并在缓冲区不足时重试。
- 按注册表类型解释数据,再输出可复查的记录。
输入来自 HKLM\Software\Microsoft\Windows NT\CurrentVersion\Drivers32 及其 WOW6432Node 对应位置。Windows 注册表由注册表管理器维护,用键和值保存系统和软件配置。Drivers32 的值名保存媒体类别或编解码器标识,值数据保存关联的模块文本。程序从两个来源读取原始值,最后得到带来源标签的只读映射记录。
64 位 Drivers32 与 32 位 Drivers32 -> 打开并测量键 -> 枚举值名和原始字节 -> 按类型显示记录
一、第一步:确定 64 位与 32 位 Drivers32 映射来源
第一步要区分两份配置来源。Drivers32 保存媒体类别或编解码器标识到模块文本的映射。应用或多媒体框架根据格式提出处理请求,ACM 或 VCM 等组件再选择适合的模块。此处读取到的是注册表配置,文件是否存在和模块是否已加载需要另行验证。
媒体格式信息 -> 应用或多媒体框架提出处理请求 -> ACM 或 VCM 查询登记能力 -> Drivers32 返回模块文本
1. 媒体播放需要把格式标识映射到处理模块
Drivers32 用于把媒体格式标识映射到处理模块。音频和视频文件包含编码格式、采样率、通道数、帧大小等信息,应用程序或 Windows 多媒体组件需要根据格式找到合适的处理器。早期 Windows 多媒体架构使用驱动和编解码器模块提供波形音频、MIDI、音频压缩和视频压缩能力。
Drivers32 是保存这类“媒体类别或格式标识映射到模块文本”关系的注册表位置。值名表达类别或编码器身份,值数据表达关联模块。它不用于保存进程启动列表,也不等同于设备管理器列出的物理硬件驱动列表。枚举结果应按映射关系解释。
媒体数据格式标识 -> Windows 多媒体组件查询 Drivers32 -> 值名(类别或编解码器标识) -> 值数据(模块文本) -> 对应的媒体处理模块
2. 常见值名前缀表达不同媒体接口类别
值名需要按媒体接口类别解释。wave、midi 等名称可对应传统波形音频或 MIDI 类别。msacm.* 常与 Audio Compression Manager(ACM)标识有关。vidc.* 常与 Video Compression Manager(VCM)格式标识有关。具体前缀和后缀应保留原文,因为它们可能携带 FOURCC 或兼容性信息。
值数据可能是 wdmaud.drv、msacm32.drv、.acm 文件名、.dll 文件名或完整路径。没有目录的文件名不能直接与当前工作目录拼接。它表示模块文本,后续若要查找文件,必须说明使用了哪一种系统模块定位规则。
3. 32 位映射与 64 位映射应作为不同配置来源
旧式媒体模块也受进程位数约束。32 位应用程序和 64 位应用程序不能在同一进程中加载彼此架构不匹配的模块。Windows 因此可能在标准路径和 WOW6432Node 路径保存两份 Drivers32 映射。
即使同一个值名和模块文本在两处都出现,也应保留两条记录及其来源标签。一个来源缺失、一个来源为空、一个来源有值,是三种不同的观察结果。不以一个视图的内容填补另一个视图。
4. 注册表映射与模块实际使用状态彼此独立
读取结果需要控制结论范围。枚举到某个 Drivers32 值能证明注册表中存在这条映射,不能单独证明文件仍存在、模块架构与当前进程匹配、模块已被加载或当前媒体文件正在使用该编解码器。
本次读取只保存注册表事实,包括原始映射文本、来源键和视图标签。文件属性、PE 架构、签名状态和运行时模块证据属于其它检查阶段,每个阶段只说明自己实际观察到的内容。
5. 映射记录需要保留可复查的字段集合
每条映射都要能回到原始来源。一个完整记录包含完整注册表键路径、逻辑架构标签、值名、值类型、原始字节长度和可验证时的解码文本。相同模块文本被多个类别引用时,值名仍然保留。同一类别在不同架构来源出现时,来源路径仍然保留。
下面的两条记录看起来使用同一模块,实际回答的是不同问题:
来源:64 位 Drivers32
值名:wave
模块文本:wdmaud.drv
来源:32 位 Drivers32
值名:wave
模块文本:wdmaud.drv
第一条和第二条并不能互相替代。它们分别来自可重定向注册表的不同位置,面对的应用程序架构也不同。将它们去重后,只能知道某个文本曾出现,无法再确定它对哪个配置范围有意义。
6. 模块文本先按原文保留
模块文本可能是完整路径,也可能只有文件名。读取阶段不按当前工作目录拼接路径,也不加载该文件。程序将能确认的字符串原文连同类型和来源输出,避免把没有依据的文件定位结果写成配置事实。
后续文件检查需要单独说明定位规则。候选路径不存在、打开被拒绝、目标是目录、PE 格式无效、架构不匹配和签名校验失败都是独立结果,不应混入本次注册表枚举结论。
7. 驱动文件、用户态模块和内核驱动不是同一个概念
文件扩展名不能直接等同于运行层级。Drivers32 中可能看到 .drv、.acm、.dll 或完整路径文本。它们表达的是传统多媒体组件使用的模块名称或文件位置。这些模块通常由用户态多媒体子系统按相应接口加载。内核驱动则由服务控制管理器和内核 I/O 路径管理,具有不同的加载时机、签名要求、内存地址空间和 IRQL 运行约束。
.drv 是历史 Windows 驱动模型常用的模块扩展名,文件本身可能仍是用户态 PE 映像。.acm 常用于音频压缩管理器组件。.dll 是动态链接库的常见扩展名。扩展名帮助读者理解注册惯例,却不能独立证明一个文件是可加载 PE、当前已加载模块或内核态代码。本次程序不检查 PE 头、文件签名和实际模块列表,只读取 Drivers32 映射。
8. ACM、VCM 与应用程序之间通过格式协商连接
编码格式和模块加载动作需要连贯理解。ACM(Audio Compression Manager,音频压缩管理器)为传统音频格式转换提供统一接口。应用或多媒体组件提出源格式、目标格式和转换操作,ACM 根据注册的驱动能力寻找可处理的组件。VCM(Video Compression Manager,视频压缩管理器)承担类似的视频编解码协商工作,vidc.* 这类标识常与视频格式代码关联。
格式协商由调用方先描述媒体格式,再由对应管理器选择满足能力、架构和配置条件的模块。模块名称只是候选信息,实际选择还受系统组件版本、已注册编解码器能力、进程位数和调用接口影响。
完成第一步后,已经获得两份不能相互替代的来源路径和架构标签。接下来需要以只读方式打开每个来源,并取得枚举缓冲区的初始容量。
二、第二步:以只读权限打开来源键,并测量枚举容量
第二步要取得可查询的键句柄。HKEY 是注册表管理器返回的已打开键句柄,代表当前进程对一个键的访问引用。打开成功后,调用方负责用 RegCloseKey 释放该引用。预定义根键 HKEY_LOCAL_MACHINE 由系统管理,不能关闭。
打开父键后,RegQueryInfoKeyW 可给出当前值数量、最长值名和最大数据长度。这些数字只用于第一次分配缓冲区。值数量是提示值,枚举期间配置可能变化,所以后续仍要处理实际返回的长度。
ERROR_ACCESS_DENIED 表示当前令牌没有获得查询权限。ERROR_FILE_NOT_FOUND 表示来源键在本次读取时不存在。程序将状态与完整键路径和视图标签一起输出,读者可以区分权限不足和配置缺失。
1. 一条记录要能回答“来自哪里、是什么、读取到了什么”
输出要避免把多个阶段拼成单个结论。一条可复查的 Drivers32 记录包含根键、路径、视图、值名、REG 类型和原始字节。文本解释严格按照 API 返回的长度进行,不展开环境变量,也不追加文件检查结果。
当记录显示 msacm.msg711 对应 msg711.acm 时,含义是“当前来源键里有这项映射”。它不能证明文件当前可用、某个进程已加载它,或某段音频由它解码。将注册表事实和其它证据分开呈现,记录的适用范围才清楚。
2. HKEY、访问掩码和缓冲区是注册表 API 的三类基础对象
调用注册表 API 前要理解资源和权限。HKEY 是注册表管理器返回的已打开键句柄,它代表当前进程在指定键上的一次访问引用。RegOpenKeyExW 成功后调用方拥有这份引用,必须调用 RegCloseKey 释放。预定义根键如 HKEY_LOCAL_MACHINE 由系统提供,不能关闭。
REGSAM 是注册表访问掩码类型。KEY_QUERY_VALUE 允许读取值,KEY_ENUMERATE_SUB_KEYS 允许读取直接子键名,KEY_WOW64_64KEY 和 KEY_WOW64_32KEY 指定要访问的软件注册表视图。只读枚举只申请必需权限,权限不足时保留 ERROR_ACCESS_DENIED,不通过写入、提升或切换视图掩盖失败。
名称缓冲区和数据缓冲区的单位不同。RegEnumValueW 的 lpcchValueName 使用 UTF-16 宽字符数,lpcbData 使用字节数。同一个 DWORD 变量不能同时保存这两种容量。值名与值数据也是不同字段,媒体类别的值名和模块文本的数据需要同时保存。
3. 标准读取先打开、再测量、再枚举、最后关闭
枚举时所有成功资源都要在失败路径释放。下面的最小流程先请求只读权限,使用两个独立容量变量,然后在 ERROR_NO_MORE_ITEMS 正常结束时关闭键。示范中的 nameCapacity 是 wchar_t 个数,dataCapacity 是字节数。缓冲区只在调用期间使用,成功后以 API 返回的实际长度构造记录。
HKEY key = nullptr;
const LSTATUS openStatus = RegOpenKeyExW(
HKEY_LOCAL_MACHINE, // 输入:机器范围根键。系统所有,不由本段关闭。
drivers32Path, // 输入:NUL 结尾 UTF-16 Drivers32 相对路径。
0, // 输入:保留,必须为 0。
KEY_QUERY_VALUE, // 输入:仅枚举值所需的最小权限。
&key); // 输出:成功时得到 HKEY,本段负责 RegCloseKey。
if (openStatus == ERROR_SUCCESS) {
for (DWORD index = 0;; ++index) {
std::vector<wchar_t> name(256, L'\0'); // 名称容量单位为 wchar_t。
std::vector<BYTE> data(1024); // 数据容量单位为字节。
DWORD nameCapacity = static_cast<DWORD>(name.size());
DWORD dataCapacity = static_cast<DWORD>(data.size());
DWORD type = REG_NONE;
const LSTATUS status = RegEnumValueW(
key, index, name.data(), &nameCapacity, nullptr, &type,
data.data(), &dataCapacity);
if (status == ERROR_NO_MORE_ITEMS) break;
if (status == ERROR_MORE_DATA) {
// 正确处理是分别扩容 name 和 data 后重试同一 index。不能把它当作枚举结束。
continue;
}
if (status != ERROR_SUCCESS) {
// 记录 index、来源路径和 status,继续下一项。不把失败值当成空字符串。
continue;
}
// 此处仅使用 nameCapacity 个 UTF-16 字符与 dataCapacity 个原始字节创建一条映射记录。
}
RegCloseKey(key); // 释放本段成功打开的 HKEY。关闭后 key 不能再传给任何注册表 API。
}
下面的错误把 ERROR_MORE_DATA 当成“没有下一个值”,会在第一个较长名称或较大数据处截断整个枚举。错误发生在混淆“缓冲区不足”和“正常结束”这两种返回状态。
if (RegEnumValueW(key, index, name, &nameChars, nullptr, &type, data, &dataBytes) != ERROR_SUCCESS) {
break; // 错误:ERROR_MORE_DATA 需要扩容重试,ERROR_NO_MORE_ITEMS 才是正常结束。
}
完成第二步后,已经得到两个来源各自的只读 HKEY 与首次分配所需的容量。接下来要按索引读取每个值,并在系统返回容量不足时重试同一个索引。
三、第三步:按索引枚举全部值,并处理缓冲区不足
第三步要从每个已经打开的来源键读取全部值。本机 Drivers32 与 WOW6432Node Drivers32 分别枚举,记录保留完整键路径和架构标签。相同值名或相同模块文本不能用来合并记录。
// 意义:打开注册表子键。
// 返回:ERROR_SUCCESS 表示成功。其它返回值是 Win32 错误码。
// 成功时 phkResult 接收 HKEY,调用方必须关闭。
LSTATUS RegOpenKeyExW(
HKEY hKey, LPCWSTR lpSubKey, DWORD ulOptions,
REGSAM samDesired, PHKEY phkResult
);
// 意义:关闭打开成功的注册表键句柄。
// 返回:ERROR_SUCCESS 表示成功。
LSTATUS RegCloseKey(HKEY hKey);
const wchar_t* sources[] = {
L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Drivers32",
L"Software\\WOW6432Node\\Microsoft\\Windows NT\\CurrentVersion\\Drivers32",
};
模块文本可能是 wdmaud.drv、msacm32.drv、.acm 文件名、.dll 文件名或完整路径。读取阶段先保留原文,不按普通启动命令切分空格。
1. 枚举所有值时保持名称、类型和原始字节
Drivers32 使用值枚举结构。值名如 wave、midi、vidc.*、msacm.* 表示类别或编码器标识,值数据才是对应模块文本。
// 意义:查询键中的值数、最大值名长度和最大值数据长度。
// 返回:ERROR_SUCCESS 表示成功。值名长度单位是 wchar_t 个数,
// 值数据长度单位是字节。
LSTATUS RegQueryInfoKeyW(
HKEY hKey, LPWSTR lpClass, LPDWORD lpcchClass, LPDWORD lpReserved,
LPDWORD lpcSubKeys, LPDWORD lpcbMaxSubKeyLen, LPDWORD lpcbMaxClassLen,
LPDWORD lpcValues, LPDWORD lpcbMaxValueNameLen, LPDWORD lpcbMaxValueLen,
LPDWORD lpcbSecurityDescriptor, PFILETIME lpftLastWriteTime
);
// 意义:按索引读取一个 Drivers32 值。
// 返回:ERROR_SUCCESS 表示成功。ERROR_NO_MORE_ITEMS 表示结束。
// ERROR_MORE_DATA 表示值名或数据容量不足。
LSTATUS RegEnumValueW(
HKEY hKey, DWORD dwIndex, LPWSTR lpValueName, LPDWORD lpcchValueName,
LPDWORD lpReserved, LPDWORD lpType, LPBYTE lpData, LPDWORD lpcbData
);
正确代码让值名和数据使用不同缓冲区。ERROR_MORE_DATA 时分别扩大 UTF-16 名称缓冲区和字节数据缓冲区,避免单位混淆。
std::vector<wchar_t> name(maxNameChars + 1, L'\0');
std::vector<BYTE> data(maxDataBytes);
DWORD nameChars = static_cast<DWORD>(name.size());
DWORD dataBytes = static_cast<DWORD>(data.size());
DWORD type = REG_NONE;
LSTATUS status = RegEnumValueW(key, index, name.data(), &nameChars,
nullptr, &type,
data.empty() ? nullptr : data.data(), &dataBytes);
if (status == ERROR_SUCCESS) {
std::wstring mediaClass(name.data(), nameChars);
data.resize(dataBytes);
}
字符串值只在 REG_SZ 或 REG_EXPAND_SZ 且字节数能被 sizeof(wchar_t) 整除时转换。其它类型保留原始类型和字节数,避免把错误数据展示为模块路径。
// 错误示例:只根据模块文本建立记录。
// 多个媒体类别可以映射到同一个 wdmaud.drv,类别身份会丢失。
record.module = decodedText;
完成第三步后,已经取得每条映射的值名、类型和实际返回的字节长度。接下来需要依照注册表类型判断哪些数据可以安全显示为文本或数值,哪些数据只能保留原始长度。
四、第四步:按注册表类型解释数据并输出记录
第四步要让输出保持准确。REG_SZ 和 REG_EXPAND_SZ 通常保存 UTF-16 文本。程序仅在实际字节数能被 sizeof(wchar_t) 整除时将它们显示为文本,并在末尾确有结束 NUL 时移除一个 NUL。REG_DWORD 仅在原始数据正好为 4 字节时显示为无符号数。其它类型保留类型名和原始字节数,不将未知数据猜成模块路径。
显示文本前还要转义 NUL、回车和换行。这样控制字符不会截断控制台行,也不会伪造额外的输出记录。每条输出同时保留来源、视图、值名和类型,读取结果可以回到相应注册表位置复查。
模块文本是映射数据,完整路径、文件存在性、映像架构和签名验证需要后续独立步骤。wdmaud.drv 这类名称没有目录,直接按当前工作目录拼接会产生无依据的路径。
完成第四步后,得到的是 Drivers32 的只读配置记录。该记录说明映射在读取时存在,不能独立证明模块已加载或媒体数据已经被成功处理。
完整可运行程序在附件

fcaK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4j5h3&6Y4N6$3g2A6j5$3#2Q4x3X3g2D9j5h3&6*7L8%4g2#2i4K6u0W2j5$3!0E0i4K6u0r3K9i4k6%4x3X3R3K6P5h3g2%4K9X3q4V1
[内核课程]《Windows内核攻防实战》!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。