最近想自己写个驱动加载程序。(平常用的加载程序不能拖动选择文件)
于是梳理一下驱动加载的两种方法
代码和程序也贴在github一份。
一般加载驱动的步骤为
- 打开服务控制器
- 根据任务条件创建服务
- 打开设备或服务
- 设置设备或者服务的状态.
- 清理工作(关闭服务或者设备的句柄)
需要的函数:
通过OpenSCManager
函数打开SCM,获取其句柄
通过CreateService
函数利用SCM句柄创建一个服务
通过ControlService
传入的标志位安装启动服务等等。
两步安装驱动:
SC_HANDLE sh = OpenSCManager(
NULL, // 机器名称,NULL表示本机.
NULL, // 设备管理器数据库,NULL表示默认值
SC_MANAGER_ALL_ACCESS // 打开的权限
);
SC_HANDLE m_hServiceDDK = CreateService(
sh,//SCManager句柄
DriverName.c_str(),//驱动服务名称
DriverName.c_str(),//驱动服务显示名称
SERVICE_ALL_ACCESS,//访问权限
SERVICE_KERNEL_DRIVER,//服务类型(驱动程序)
SERVICE_DEMAND_START,//启动方式(需要时启动,注册表驱动程序的Start值)
SERVICE_ERROR_IGNORE,//错误忽略
szFilePath,//驱动程序文件路径
NULL,//加载组命令
NULL,//TagId
NULL,//依存关系
NULL,//服务启动名
NULL);//密码
这两个函数做完后,核心的安装就完成了。
启动驱动
BOOL WINAPI StartService(
_In_ SC_HANDLE hService,
_In_ DWORD dwNumServiceArgs,
_In_opt_ LPCTSTR *lpServiceArgVectors
);
控制状态,停止驱动
m_hServiceDDK = OpenService(sh, DriverName.c_str(), SERVICE_STOP);
SERVICE_STATUS svcsta = { 0 };
BOOL bRet = ControlService(m_hServiceDDK, SERVICE_CONTROL_STOP, &svcsta);
卸载驱动
m_hServiceDDK = OpenService(sh, DriverName.c_str(), SERVICE_STOP | DELETE);
DeleteService(m_hServiceDDK);
在返回结果异常的判断GetLastError()
中,加入了几个常见的判断
ERROR_SERVICE_ALREADY_RUNNING
ERROR_SERVICE_NOT_FOUND
ERROR_SERVICE_NEVER_STARTED
ERROR_SERVICE_NOT_ACTIVE
ERROR_SERVICE_DOES_NOT_EXIST
-
代码比较长,先贴一下安装代码,其余的可以在附件查看
void CDriverLoaderDlg::OnBnClickedButtonInstall()
{
if (wcslen(szFilePath)==0)
{
Msg(_T("请选择文件"));
return;
}
size_t pos = wstring(szFilePath).find_last_of('\\');
DriverName.assign(wstring(szFilePath).substr(pos + 1));
sh = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (!sh)
{
Msg(_T("打开服务控制器失败,请检查是否以管理员权限运行"));
CloseServiceHandle(sh);
return;
}
Msg(CString(DriverName.c_str()));
SC_HANDLE m_hServiceDDK = CreateService(
sh,//SMC句柄
DriverName.c_str(),//驱动服务名称(驱动程序的在注册表中的名字)
DriverName.c_str(),//驱动服务显示名称(注册表驱动程序的DisplayName值)
SERVICE_ALL_ACCESS,//权限(所有访问权限)
SERVICE_KERNEL_DRIVER,//服务类型(驱动程序)
SERVICE_DEMAND_START,//启动方式(需要时启动,注册表驱动程序的Start值)
SERVICE_ERROR_IGNORE,//错误控制(忽略,注册表驱动程序的ErrorControl值)
szFilePath,//服务的二进制文件路径(驱动程序文件路径, 注册表驱动程序的ImagePath值)
NULL,//加载组命令
NULL,//TagId
NULL,//依存关系
NULL,//服务启动名
NULL);//密码
if (!m_hServiceDDK)
{
if (GetLastError() == ERROR_SERVICE_EXISTS)
{
Msg(_T("驱动已经存在"));
if(!m_hServiceDDK)m_hServiceDDK = OpenService(sh, DriverName.c_str(), SERVICE_ALL_ACCESS);
}
else {
TCHAR msg[100];
wprintf_s(msg, "安装失败,错误码 %p", GetLastError());
Msg(msg);
Msg(_T("Error while Install ,error code:" + GetLastError()));
MessageBox(NULL, DriverName.c_str(), MB_OK);
}
}
else {
Msg(_T("驱动安装成功!"));
}
CloseServiceHandle(sh);
CloseServiceHandle(m_hServiceDDK);
}
-
效果(目标平台win10)
最后和普通的monitor加载的效果是一样的。
https://upload-images.jianshu.io/upload_images/13348817-1cffcb454efc67b5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240
编外:
这个是RootKits的技术。
ZwSetSystemInformation函数是个未公开的函数,调用38号则会加载驱动。
原型如下
typedef NTSTATUS(__stdcall *ZwSetSystemInformation)(
IN DWORD SystemInformationClass,
IN OUT PVOID SystemInformation,
IN ULONG SystemInformationLength
);
我们需要手动获取函数的地址
RtlInitUnicodeString
,ZwSetSystemInformation
这种方法的特性是比较简便隐秘,相比上一个方法不会在进程中主动查找服务管理器等敏感API。但是,这种方法没有提供卸载的特性,驱动加载后,只能通过重启系统来卸载。
const INT SystemLoadAndCallImage = 38;
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWCH Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
typedef struct _SYSTEM_LOAD_AND_CALL_IMAGE {
UNICODE_STRING ModuleName;
} SYSTEM_LOAD_AND_CALL_IMAGE, *PSYSTEM_LOAD_AND_CALL_IMAGE;
typedef void(*RTLINITUNICODESTRING)(
PUNICODE_STRING DestinationString,
PCWSTR SourceString
);
typedef NTSTATUS(__stdcall *ZwSetSystemInformation)(
IN DWORD SystemInformationClass,
IN OUT PVOID SystemInformation,
IN ULONG SystemInformationLength
);
bool load(PWCHAR path) {
SYSTEM_LOAD_AND_CALL_IMAGE Images;
RTLINITUNICODESTRING RtlInitUnicodeString;
ZwSetSystemInformation fZwSetSystemInformation;
if (!(RtlInitUnicodeString = (RTLINITUNICODESTRING)GetProcAddress(GetModuleHandle(_T("ntdll.dll")), "RtlInitUnicodeString")))
return false;
if (!(fZwSetSystemInformation = (ZwSetSystemInformation)GetProcAddress(GetModuleHandle(_T("ntdll.dll")), "ZwSetSystemInformation")))
return false;
RtlInitUnicodeString(&(Images.ModuleName), path);
if (!NT_SUCCESS(fZwSetSystemInformation(SystemLoadAndCallImage, &Images, sizeof(SYSTEM_LOAD_AND_CALL_IMAGE))))
return false;
return true;
}
//注意path的格式,和普通的ring0读设备的路径一个问号不同的是
//这里有两个问号,以\\??\\开头,后面是驱动路径。例如\\??\\C:\\YOURDRIVERPATH.sys