pcy190 发表于 2019-2-27 23:30

编写自己的驱动加载程序

本帖最后由 pcy190 于 2019-3-1 16:18 编辑

最近想自己写个驱动加载程序。(平常用的加载程序不能拖动选择文件)
于是梳理一下驱动加载的两种方法
代码和程序也贴在(https://github.com/pcy190/DriverKit/tree/master/DriverLoader)一份。
## 一般加载驱动的步骤为
- 打开服务控制器
- 根据任务条件创建服务
- 打开设备或服务
- 设置设备或者服务的状态.
- 清理工作(关闭服务或者设备的句柄)

需要的函数:
通过`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;
                        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

#### 编外:
- 在写MFC的时候,为了实现拖放文件来获取文件路径的功能,众所周知添加AcceptFile等属性即可,但是我在vs2017&win10的开发环境下却没有效果,后来查询得知,可能是被win10的安全措施屏蔽了。需要在初始化的时候加上如下代码
```
ChangeWindowMessageFilter(WM_DROPFILES, MSGFLT_ADD);//解决Win10下的拖放文件问题
      ChangeWindowMessageFilter(0x0049, MSGFLT_ADD);
```
- 由于打开服务管理器需要特权,我们在vs的配置**调试->属性->链接器->清单文件**中
https://upload-images.jianshu.io/upload_images/13348817-c7290ffbada536c3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240
把`UAC执行级别`改成如图所示即可。


## 通过ZwSetSystemInformation加载驱动
这个是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
```

最后,virscan安全检测一下
https://upload-images.jianshu.io/upload_images/13348817-a7b8471415739008.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240

源代码和二进制文件
https://github.com/pcy190/DriverKit/tree/master/DriverLoader

网盘链接
下载: https://www.lanzouj.com/i3a4yhi 密码:happy

wozzi 发表于 2019-2-28 23:15

感谢分享,收藏

mashan2014 发表于 2019-3-2 13:55

好经验,感谢分享!

独霸首席 发表于 2019-7-22 10:27

很不错哟~

M60 发表于 2019-7-22 11:17

感谢分享!

_退隐帆海 发表于 2019-8-8 12:04

感谢分享,学习到了
页: [1]
查看完整版本: 编写自己的驱动加载程序