VS2013 WDK8.1驱动开发3(手动加载NT驱动程序)
本系列博客为学习《Windows驱动开发技术详解》一书的学习笔记。
前言
上两文介绍了一个简单的NT驱动程序和一个简单的WDM驱动程序,在第一文中我们使用DriverMonitor工具对NT驱动程序进行加载测试。本文我们学习如何手动加载NT驱动程序。手动加载NT驱动程序有两种方法,一种是在注册表中创建一个服务,一种是编写程序。其实它们的原理是一致的,都是通过服务的方式加载NT驱动程序
注册表创建服务加载NT驱动程序
- 在Win7 64位虚拟机的注册表HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services下添加新项目,例如HelloNTDriver,这个名称就是服务的名称。
- 在这个项目下面添加如下子键:
- ImagePath为驱动程序的路径,必须以\??\开头,在这里我们使用第一文介绍过的NT驱动程序。
- Start为服务的启动模式,3表示为按需要启动服务。
- Type为1,表明此服务在内核模式下加载,是一个驱动程序。
- ErrorControl为1,表明加载出错时弹出错误信息。
- DisplayName,可以自定义
- 重启虚拟机后,打开管理员权限的命令行工具,输入net start HelloNTDriver即可加载驱动程序,输入net stop HelloNTDriver即可卸载驱动程序。如果我们提前开启DebugView工具,那么就可以在DebugView中看到调试信息。
编写程序加载NT驱动程序
NT驱动程序的动态加载主要由服务控制管理程序(Service Control Manager, SCM)系统组件完成。加载和卸载NT驱动分为4个步骤:
- 为NT驱动程序创建服务。
- 开启服务。
- 关闭服务。
- 删除创建的服务。
加载NT驱动的代码
/// @brief 加载NT驱动程序
/// 需要管理员权限, 否则会加载失败
/// 32位系统需要加载32位驱动, 64位系统需要加载64位驱动
/// @param[in] pDriverName 驱动名称
/// @param[in] pDriverPath 驱动文件路径
/// @return 成功返回true, 失败返回false
bool LoadNTDriver(IN const char* pDriverName, IN const char* pDriverPath)
{
bool bRet = false;
DWORD dwRet = FALSE;
char* pFullPathBuffer = NULL;
DWORD bufferSize = 256;
DWORD fullPathSize = 0;
SC_HANDLE hServiceManager = NULL; // 服务控制管理器句柄
SC_HANDLE hDriverService = NULL; // 驱动服务句柄
// 获取驱动程序文件全路径
pFullPathBuffer = new char[bufferSize];
ZeroMemory(pFullPathBuffer, bufferSize);
fullPathSize = GetFullPathNameA(pDriverPath, bufferSize, pFullPathBuffer, NULL);
if (fullPathSize > bufferSize)
{
delete[] pFullPathBuffer;
pFullPathBuffer = NULL;
pFullPathBuffer = new char[fullPathSize];
ZeroMemory(pFullPathBuffer, fullPathSize);
GetFullPathNameA(pDriverPath, bufferSize, pFullPathBuffer, NULL);
}
// 打开SCM管理器
// 需要管理员权限
hServiceManager = OpenSCManagerA(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (NULL == hServiceManager)
{
bRet = false;
printf("OpenSCManager Fail: %d\n", GetLastError());
goto SAFE_EXIT;
}
// 创建服务
hDriverService = CreateServiceA(
hServiceManager,
pDriverName,
pDriverName,
SERVICE_ALL_ACCESS,
SERVICE_KERNEL_DRIVER,
SERVICE_DEMAND_START,
SERVICE_ERROR_IGNORE,
pFullPathBuffer,
NULL,
NULL,
NULL,
NULL,
NULL);
if (NULL == hDriverService)
{
dwRet = GetLastError();
if (dwRet != ERROR_IO_PENDING && dwRet != ERROR_SERVICE_EXISTS)
{
printf("CreateService Fail: %d\n", dwRet);
bRet = false;
goto SAFE_EXIT;
}
else
{
printf("Service Is Exist\n");
}
// 如果服务已经存在则打开服务
hDriverService = OpenServiceA(hServiceManager, pDriverName, SERVICE_ALL_ACCESS);
if (NULL == hDriverService)
{
printf("Open Service Fail: %d\n", GetLastError());
bRet = false;
goto SAFE_EXIT;
}
}
// 启动服务
dwRet = StartServiceA(hDriverService, NULL, NULL);
if (FALSE == dwRet)
{
dwRet = GetLastError();
if (dwRet != ERROR_SERVICE_ALREADY_RUNNING)
{
printf("Start Service Fail: %d\n", dwRet);
bRet = false;
goto SAFE_EXIT;
}
printf("Service Had Been Started\n");
}
bRet = true;
SAFE_EXIT:
if (NULL != hDriverService)
{
CloseServiceHandle(hDriverService);
hDriverService = NULL;
}
if (NULL != hServiceManager)
{
CloseServiceHandle(hServiceManager);
hServiceManager = NULL;
}
if (pFullPathBuffer != NULL)
{
delete[] pFullPathBuffer;
pFullPathBuffer = NULL;
}
return bRet;
};
- 创建服务时需要填写驱动程序文件的全路径,所以我们在一开始先获取驱动程序文件的全路径。
- 创建服务时填写的参数SERVICE_DEMAND_START,意思为需要时启动我们的服务,也就是需要我们自己去启动,也可以设置为开机启动。
- 创建完成后我们就可以使用StartServiceA启动服务。
卸载NT驱动的代码
/// @brief 卸载NT驱动程序
/// 需要管理员权限, 否则会卸载失败
/// @param[in] pDriverName 驱动程序名称
/// @return 成功返回true, 失败返回false
bool UnLoadNTDriver(const char* pDriverName)
{
bool bRet = true;
DWORD dwRet = FALSE;
SC_HANDLE hServiceManager = NULL; // 服务控制管理器句柄
SC_HANDLE hDriverService = NULL; // 驱动服务句柄
SERVICE_STATUS serviceStatus;
// 打开SCM管理器
// 需要管理员权限
hServiceManager = OpenSCManagerA(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (NULL == hServiceManager)
{
bRet = false;
printf("OpenSCManager Fail: %d\n", GetLastError());
goto SAFE_EXIT;
}
// 打开服务
hDriverService = OpenServiceA(hServiceManager, pDriverName, SERVICE_ALL_ACCESS);
if (NULL == hDriverService)
{
printf("Open Service Fail: %d\n", GetLastError());
bRet = false;
goto SAFE_EXIT;
}
// 停止服务
dwRet = ControlService(hDriverService, SERVICE_CONTROL_STOP, &serviceStatus);
if (dwRet == FALSE)
{
printf("Control Service Stop Fail: %d\n", GetLastError());
}
// 删除服务
dwRet = DeleteService(hDriverService);
if (dwRet == FALSE)
{
printf("Delete Service Fail: %d\n", GetLastError());
}
SAFE_EXIT:
if (NULL != hDriverService)
{
CloseServiceHandle(hDriverService);
hDriverService = NULL;
}
if (NULL != hServiceManager)
{
CloseServiceHandle(hServiceManager);
hServiceManager = NULL;
}
return bRet;
}
测试驱动程序代码
/// @brief 测试驱动程序
void TestNTDriver()
{
HANDLE hDevice = CreateFileA(
"\\\\.\\HelloNTDriver",
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
0,
NULL);
if (hDevice == INVALID_HANDLE_VALUE)
printf("Open Device Fail\n");
else
printf("Open Device Success\n");
CloseHandle(hDevice);
}
- 将NT驱动程序代码中创建的链接符号作为参数传递给CreateFile就可以打开设备对象,原来的链接符号格式为"\\??\\HelloNTDriver",这里我们需要修改成"\\\\.\\HelloNTDriver"。
- Win32程序和驱动程序的沟通都是通过这样的方式,在前面的文章中我们已经对IRP_MJ_CREATE和IRP_MJ_CLOSE设置了派遣函数,CreateFile函数会触发IRP_MJ_CREATE的排遣函数,CloseHandle函数会触发IRP_MJ_CLOSE的排遣函数。
主程序代码
int main(int argc, char** argv)
{
if (argc == 3)
{
bool bRet = false;
bRet = LoadNTDriver(argv[1], argv[2]);
if (bRet)
printf("Load NT Driver Success\n");
else
printf("Load NT Driver Fail\n");
system("pause");
TestNTDriver();
system("pause");
bRet = UnLoadNTDriver(argv[1]);
if (bRet)
printf("UnLoad NT Driver Success\n");
else
printf("UnLoad NT Driver Fail\n");
}
system("pause");
return 0;
}
- 主程序接受两个命令行参数,第一个为驱动程序的名称(也用做服务名),第二个为驱动程序的路径。
- 主程序先加载驱动,之后测试驱动,最后卸载驱动。
运行程序
编译我们程序,将得到的可执行文件(LoadNTDriver.exe)和需要加载的NT驱动程序(chapter01-1.sys)放在虚拟机的相同目录下,打开管理员权限的命令行工具,运行如下命令:
之后我们会在DebugView中看到如下的调试信息:
如果在卸载驱动前我们可以在注册表HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services目录下看到系统自动创建的HelloNTDriver项:
后话
- 本文完整工程和代码托管在GitHub上点我查看。
- 如你你对chapter01-1.sys有疑问可以参考VS2013 WDK8.1驱动开发1(最简单的NT驱动)一文。
其他章节链接
VS2013 WDK8.1驱动开发3(手动加载NT驱动程序)
还没有人评论...