目标:
- 用户一键使用: 驱动加载(将驱动的安装、启动、停止、卸载的服务放到R3程序中来实现,作为可发布程序使用)
- 通过驱动的control回调函数和控制码来实现通讯
0x01 项目管理
环境整理:
创建一个空白项目,主要是为了用空白项目的解决方案
把Ring3和驱动加入到解决方案,环境里删掉刚创建的空白项目即可,就不用频繁的切换环境
把Ring3和驱动的输出都放到bin目录下(两个都需要设置),便于实验
整理后的效果
0x02 实验验证: 创建服务
2.1 验证过程
Ring3.cpp
#include <stdio.h> #include <windows.h> /* 如果没有R3得程序怎么加载呢? 微软有个inf文件,这个东西可以安装,在微软的官方文档中有说明inf内容怎么写; OS会解析inf这个配置文件,帮助动态加载驱动 例: 如果硬件需要开机启动加载,微软再开机时会自动加载inf文件 驱动加载的方式: 1. inf文件来加载 2. 动态加载(服务) 创建服务就是安装驱动(很多软件都可以使用打印机,那么就可调用打印机的驱动,驱动的角色是服务),也就是需要4个api就可以完成这些工作; 创建服务 启动服务 停止服务 卸载服务 */ void showErr(const char *name) { LPVOID lpMsgBuf; FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language (LPTSTR)&lpMsgBuf, 0, NULL ); printf("[%s] Error %s", name, (LPCTSTR)lpMsgBuf); LocalFree(lpMsgBuf); } int LoadDriver(const char *pszPath, const char *pszServiceName) { SC_HANDLE hSCM = NULL; SC_HANDLE hService = NULL; int iRet = -1; hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); if (hSCM == NULL) { showErr("OpenSCManager"); return 0; } printf("[%s] OpenSCManager Ok hSCM:%p \n", __FUNCTION__,hSCM); hService = CreateService(hSCM, pszServiceName, pszServiceName, SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, pszPath, NULL, NULL, NULL, NULL, NULL); if (hService == NULL) { showErr("CreateService"); goto SAFE_EXIT; /* 使用了goto之后,使用局部变量,必须先定义后使用 */ } printf("[%s] CreateService Ok hService:%p \n", __FUNCTION__, hService); /* CreateService 使用分析 SC_HANDLE CreateService( SC_HANDLE hSCManager, // handle to SCM database 服务是由服务管理器来管理,这里拿到服务管理器;SCM(service control manager) ,因为是本地,前两个为NULL,最后一个是权限,如果选择所有权限,就是既能创建又能删除 LPCTSTR lpServiceName, // name of service to start LPCTSTR lpDisplayName, // display name DWORD dwDesiredAccess, // type of access to service DWORD dwServiceType, // type of service DWORD dwStartType, // when to start service 五种启动方式,选第3个,重启系统后就不再加载 DWORD dwErrorControl, // severity of service failure LPCTSTR lpBinaryPathName, // name of binary file LPCTSTR lpLoadOrderGroup, // name of load ordering group LPDWORD lpdwTagId, // tag identifier LPCTSTR lpDependencies, // array of dependency names LPCTSTR lpServiceStartName, // account name LPCTSTR lpPassword // account password); */ iRet = 0; SAFE_EXIT: if (CloseServiceHandle(hSCM) == 0) { showErr("CloseServiceHandle"); } return iRet; } int UnLoadDriver(const char *name) { return 0; } int main() { printf("[%s] Install Driver ... \n", __FUNCTION__); if (LoadDriver("sample2.sys", "51asmMark") < 0) return 0; printf("[%s] Install Driver Ok \n", __FUNCTION__); if (UnLoadDriver("51asmMark") < 0) return 0; #if 0 // 打开设备, 这里文件的路径就是在驱动中创建的设备名字 HANDLE hFile = CreateFile("\\\\.\\\\51asmMark", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); printf("hFile:%p err:%p\n", hFile, GetLastError()); // 读写设备 char acBuf[60] = { 0 }; printf("acBuf:%p\n", acBuf); /* 驱动要输出R3的缓存地址,这里作为对照 */ DWORD dwBytes = 0; BOOL bRet = ReadFile(hFile, acBuf, sizeof(acBuf), &dwBytes, NULL); printf("bRet:%d dwBytes:%d acBuf:%s\n", bRet, dwBytes, acBuf); // 控制设备 // 关闭设备 #endif system("pause"); return 0; }
2.2 问题分析
期间遇到一个问题,折腾老半天,无效,如下图:
解决: 拒绝访问,是权限不足,关闭vs2019,用管理员的权限运行(感谢前辈的博客);
2.3 结果分析
服务创建成功了
2.4 原理分析
那么问题来了, creatServer 这个api做了什么?
其实在OS中记录下驱动的信息,regedit 注册表中看一下,注册表的每一个表象,都是创建的服务creatServer的参数,因此开机时,OS遍历注册表 就可以遍历驱动
也就是可以通过这个目录遍历所有系统安装的驱动
0x03 启动服务
3.1 出现问题1
问题1: 第二次创建时,提示服务已经存在了???
因为此时在创建服务失败后并没有判断服务是否已经存在了,那么就需要根据错误码判断服务是否存在,如果服务存在,那就直接打开服务就行; 下图中左侧打印出错误码,dwErr;
Ring.cpp 中的一个函数,解决这个问题
int LoadDriver(const char *pszPath, const char *pszServiceName) { SC_HANDLE hSCM = NULL; SC_HANDLE hService = NULL; int iRet = -1; hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); if (hSCM == NULL) { showErr("OpenSCManager"); return 0; } printf("[%s] OpenSCManager Ok hSCM:%p \n", __FUNCTION__,hSCM); hService = CreateService(hSCM, pszServiceName, pszServiceName, SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, pszPath, NULL, NULL, NULL, NULL, NULL); if (hService == NULL) { //if (GetLastError() == 1073) // 当前服务已经存在,那就直接打开用 if (GetLastError() == ERROR_SERVICE_EXISTS) { hService = OpenService(hSCM, pszServiceName, SERVICE_ALL_ACCESS); if (hService == NULL) { showErr("OpenService"); goto SAFE_EXIT; } printf("[%s] OpenService Ok hService:%p \n", __FUNCTION__, hService); } else { showErr("CreateService"); goto SAFE_EXIT; } } printf("[%s] CreateService Ok hService:%p \n", __FUNCTION__, hService); /* CreateService 使用分析 SC_HANDLE CreateService( SC_HANDLE hSCManager, // handle to SCM database 服务是由服务管理器来管理,这里拿到服务管理器;SCM(service control manager) ,因为是本地,前两个为NULL,最后一个是权限,如果选择所有权限,就是既能创建又能删除 LPCTSTR lpServiceName, // name of service to start LPCTSTR lpDisplayName, // display name DWORD dwDesiredAccess, // type of access to service DWORD dwServiceType, // type of service DWORD dwStartType, // when to start service 五种启动方式,选第3个,重启系统后就不再加载 DWORD dwErrorControl, // severity of service failure LPCTSTR lpBinaryPathName, // name of binary file LPCTSTR lpLoadOrderGroup, // name of load ordering group LPDWORD lpdwTagId, // tag identifier LPCTSTR lpDependencies, // array of dependency names LPCTSTR lpServiceStartName, // account name LPCTSTR lpPassword // account password); */ if (!StartService(hService, NULL, NULL)) /* 后两个参数,因为是驱动的服务,所以三环这里给NULL */ { showErr("StartService"); goto SAFE_EXIT; } iRet = 0; SAFE_EXIT: if (hService != NULL) { if (CloseServiceHandle(hService) == 0) { showErr("CloseServiceHandle"); } } if (hSCM != NULL) { if (CloseServiceHandle(hSCM) == 0) { showErr("CloseServiceHandle"); } } return iRet; }
3.2 出现问题2
问题2:找不到指定文件
1、 不能再win10上运行
2、 驱动一直没有卸载过,卸载函数没写过
3.3 卸载实验验证
Ring3.cpp的UnLoadDriver函数
int UnLoadDriver(const char * pszServiceName) { SC_HANDLE hSCM = NULL; SC_HANDLE hService = NULL; int iRet = -1; SERVICE_STATUS oldState = { 0 }; hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); if (hSCM == NULL) { showErr("OpenSCManager"); return 0; } printf("[%s] OpenSCManager Ok hSCM:%p \n", __FUNCTION__, hSCM); hService = OpenService(hSCM, pszServiceName, SERVICE_ALL_ACCESS); if (hService == NULL) { showErr("OpenService"); goto SAFE_EXIT; } printf("[%s] OpenService Ok hService:%p \n", __FUNCTION__, hService); /* 1. DeleteService之前,如果服务正在运行怎么办? 那就是先暂停服务 2 .OS 的peding状态:向硬件发送的请求还没有完成,此时驱动卸不掉,停不掉(如果驱动中的派遣函数没有IoCompleteRequest完成请求,就会有这种情况) */ if (!ControlService(hService, SERVICE_CONTROL_STOP, &oldState)) { if (GetLastError() == ERROR_SERVICE_NOT_ACTIVE) // 服务没有启动的时候 判断错误码1062,那就启动服务; { printf("[%s] ControlService not active hService:%p \n", __FUNCTION__, hService); } else if (GetLastError() == ERROR_DEPENDENT_SERVICES_RUNNING) // peding 状态 { printf("[%s] Please reboot system hService:%p \n", __FUNCTION__, hService); goto SAFE_EXIT; } else { showErr("ControlService"); goto SAFE_EXIT; } } printf("[%s] ControlService stop service Ok hService:%p \n", __FUNCTION__, hService); if (!DeleteService(hService)) { showErr("DeleteService"); goto SAFE_EXIT; } printf("[%s] DeleteService Ok hService:%p \n", __FUNCTION__, hService); iRet = 0; SAFE_EXIT: if (hService != NULL) { if (CloseServiceHandle(hService) == 0) { showErr("CloseServiceHandle"); } } if (hSCM != NULL) { if (CloseServiceHandle(hSCM) == 0) { showErr("CloseServiceHandle"); } } return iRet; }
3.4 卸载结果分析
卸载成功,但是创建找不到指定文件
3.5 极端情况(加载器不会考虑这种情况)
3.5.1
极端情况分析: 有可能停掉驱动,但是驱动的回调函数还没有完成 IoCompleteRequest,这时的线程被挂起,服务停不掉,也会报错;
NTSTATUS DispatchRead(_DEVICE_OBJECT* DeviceObject, _IRP* Irp) { UNREFERENCED_PARAMETER(DeviceObject); UNREFERENCED_PARAMETER(Irp); /* 验证这里是R3的缓存地址,输出一下 */ DbgPrint("51[asm][%s:%d] Irp->UserBuffer:%p", __FUNCTION__, __LINE__, Irp->UserBuffer); RtlCopyMemory(Irp->UserBuffer,g_zMsg,sizeof(g_zMsg)); Irp->IoStatus.Status = STATUS_SUCCESS; /* 告诉R3返回的状态 */ Irp->IoStatus.Information = sizeof(g_zMsg); /* 告诉R3读取的字节数 */ IoCompleteRequest(Irp, IO_NO_INCREMENT); /* 如果驱动中的派遣函数没有IoCompleteRequest完成请求,R3就会有这peding状态的情况 */ return STATUS_SUCCESS; /* R3的ReadFile的返回值 */ }
3.5.2
OS 的peding状态:向硬件发送的请求还没有完成,此时驱动卸不掉,停不掉
3.6 实验验证:加载卸载
Ring3.cpp
#include <stdio.h> #include <windows.h> /* 如果没有R3得程序怎么加载呢? 微软有个inf文件,这个东西可以安装,在微软的官方文档中有说明inf内容怎么写; OS会解析inf这个配置文件,帮助动态加载驱动 例: 如果硬件需要开机启动加载,微软再开机时会自动加载inf文件 驱动加载的方式: 1. inf文件来加载 2. 动态加载(服务) 创建服务就是安装驱动(很多软件都可以使用打阴界,那么就可调用打印机的驱动,驱动的角色是服务),也就是需要4个api就可以完成这些工作; 创建服务 启动服务 停止服务 卸载服务 */ void showErr(const char *name) { LPVOID lpMsgBuf; DWORD dwErr = GetLastError(); FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dwErr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language (LPTSTR)&lpMsgBuf, 0, NULL ); printf("[%s] Error Code:%d Msg:%s", name, dwErr, (LPCTSTR)lpMsgBuf); //MessageBox(NULL, (LPCTSTR)lpMsgBuf, "Error", MB_OK | MB_ICONINFORMATION); // Free the buffer. LocalFree(lpMsgBuf); } int LoadDriver(const char *pszPath, const char *pszServiceName) { SC_HANDLE hSCM = NULL; SC_HANDLE hService = NULL; int iRet = -1; hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); if (hSCM == NULL) { showErr("OpenSCManager"); return 0; } printf("[%s] OpenSCManager Ok hSCM:%p \n", __FUNCTION__,hSCM); hService = CreateService(hSCM, pszServiceName, pszServiceName, SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, pszPath, NULL, NULL, NULL, NULL, NULL); if (hService == NULL) { //if (GetLastError() == 1073) // 当前服务已经存在,那就直接打开用 if (GetLastError() == ERROR_SERVICE_EXISTS) { hService = OpenService(hSCM, pszServiceName, SERVICE_ALL_ACCESS); if (hService == NULL) { showErr("OpenService"); goto SAFE_EXIT; } printf("[%s] OpenService Ok hService:%p \n", __FUNCTION__, hService); } else { showErr("CreateService"); goto SAFE_EXIT; } } printf("[%s] CreateService Ok hService:%p \n", __FUNCTION__, hService); /* CreateService 使用分析 SC_HANDLE CreateService( SC_HANDLE hSCManager, // handle to SCM database 服务是由服务管理器来管理,这里拿到服务管理器;SCM(service control manager) ,因为是本地,前两个为NULL,最后一个是权限,如果选择所有权限,就是既能创建又能删除 LPCTSTR lpServiceName, // name of service to start LPCTSTR lpDisplayName, // display name DWORD dwDesiredAccess, // type of access to service DWORD dwServiceType, // type of service DWORD dwStartType, // when to start service 五种启动方式,选第3个,重启系统后就不再加载 DWORD dwErrorControl, // severity of service failure LPCTSTR lpBinaryPathName, // name of binary file LPCTSTR lpLoadOrderGroup, // name of load ordering group LPDWORD lpdwTagId, // tag identifier LPCTSTR lpDependencies, // array of dependency names LPCTSTR lpServiceStartName, // account name LPCTSTR lpPassword // account password); */ if (!StartService(hService, NULL, NULL)) /* 后两个参数,因为是驱动的服务,所以三环这里给NULL */ { showErr("StartService"); goto SAFE_EXIT; } iRet = 0; SAFE_EXIT: if (hService != NULL) { if (CloseServiceHandle(hService) == 0) { showErr("CloseServiceHandle"); } } if (hSCM != NULL) { if (CloseServiceHandle(hSCM) == 0) { showErr("CloseServiceHandle"); } } return iRet; } int UnLoadDriver(const char * pszServiceName) { SC_HANDLE hSCM = NULL; SC_HANDLE hService = NULL; int iRet = -1; SERVICE_STATUS oldState = { 0 }; hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); if (hSCM == NULL) { showErr("OpenSCManager"); return 0; } printf("[%s] OpenSCManager Ok hSCM:%p \n", __FUNCTION__, hSCM); hService = OpenService(hSCM, pszServiceName, SERVICE_ALL_ACCESS); if (hService == NULL) { showErr("OpenService"); goto SAFE_EXIT; } printf("[%s] OpenService Ok hService:%p \n", __FUNCTION__, hService); /* 1. DeleteService之前,如果服务正在运行怎么办? 那就是先暂停服务 2 .OS 的peding状态:向硬件发送的请求还没有完成,此时驱动卸不掉,停不掉(如果驱动中的派遣函数没有IoCompleteRequest完成请求,就会有这种情况) */ if (!ControlService(hService, SERVICE_CONTROL_STOP, &oldState)) { if (GetLastError() == ERROR_SERVICE_NOT_ACTIVE) // 服务没有启动的时候 判断错误码1062 { printf("[%s] ControlService not active hService:%p \n", __FUNCTION__, hService); } else if (GetLastError() == ERROR_DEPENDENT_SERVICES_RUNNING) // peding 状态 { printf("[%s] Please reboot system hService:%p \n", __FUNCTION__, hService); goto SAFE_EXIT; } else { showErr("ControlService"); goto SAFE_EXIT; } } printf("[%s] ControlService stop service Ok hService:%p \n", __FUNCTION__, hService); if (!DeleteService(hService)) { showErr("DeleteService"); goto SAFE_EXIT; } printf("[%s] DeleteService Ok hService:%p \n", __FUNCTION__, hService); iRet = 0; SAFE_EXIT: if (hService != NULL) { if (CloseServiceHandle(hService) == 0) { showErr("CloseServiceHandle"); } } if (hSCM != NULL) { if (CloseServiceHandle(hSCM) == 0) { showErr("CloseServiceHandle"); } } return iRet; } int main() { printf("[%s] Install Driver ... \n", __FUNCTION__); LoadDriver("sample2.sys", "51asmMark"); //if (LoadDriver("sample2.sys", "51asmMark") < 0) // return 0; printf("[%s] Install Driver Ok \n", __FUNCTION__); #if 1 // 打开设备, 这里文件的路径就是在驱动中创建的设备名字 HANDLE hFile = CreateFile("\\\\.\\\\51asmMark", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); printf("hFile:%p err:%p\n", hFile, GetLastError()); // 读写设备 char acBuf[60] = { 0 }; printf("acBuf:%p\n", acBuf); /* 驱动要输出R3的缓存地址,这里作为对照 */ DWORD dwBytes = 0; BOOL bRet = ReadFile(hFile, acBuf, sizeof(acBuf), &dwBytes, NULL); printf("bRet:%d dwBytes:%d acBuf:%s\n", bRet, dwBytes, acBuf); // 控制设备 // 关闭设备 #endif UnLoadDriver("51asmMark"); printf("[%s] UnLoadDriver Driver Ok \n", __FUNCTION__); system("pause"); return 0; }
3.7 实验结果分析
启动服务的时候,系统路径找不到,验证有误,驱动路径有问题,修改为绝对路径试试;
3.8 实验验证:路径修改为绝对路径
修改为绝对路径(虚拟机中驱动的绝对路径)
int main() { printf("[%s] Install Driver ... \n", __FUNCTION__); LoadDriver("C:\\Documents and Settings\\cobb\\桌面\\sample2.sys", "51asmMark"); //if (LoadDriver("sample2.sys", "51asmMark") < 0) // return 0; printf("[%s] Install Driver Ok \n", __FUNCTION__); // 打开设备, 这里文件的路径就是在驱动中创建的设备名字 HANDLE hFile = CreateFile("\\\\.\\\\51asmMark", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { showErr("CreateFile"); } else { printf("hFile:%p \n", hFile); // 读写设备 char acBuf[60] = { 0 }; printf("acBuf:%p\n", acBuf); /* 驱动要输出R3的缓存地址,这里作为对照 */ DWORD dwBytes = 0; BOOL bRet = ReadFile(hFile, acBuf, sizeof(acBuf), &dwBytes, NULL); if (!bRet) { showErr("ReadFile"); } else { printf("bRet:%d dwBytes:%d acBuf:%s\n", bRet, dwBytes, acBuf); } // 控制设备 // 关闭设备 CloseHandle(hFile); } UnLoadDriver("51asmMark"); printf("[%s] UnLoadDriver Driver Ok \n", __FUNCTION__); system("pause"); return 0; }
3.9 结果分析
成功获取到hello,成功加载和卸载;
加载工具的实现已完成;
0x04 通过control来通讯
控制码: 驱动的DispatchRead被调用了,用户发来了请求,那么驱动怎么才能知道用户想要干嘛? 是遍历文件?遍历进程? …. 于是需要用控制码来标注一下,R3想要让驱动具体做什么,就发送对应的控制码;
R3和R0要分开,驱动中不直接访问三环的内存地址,而是在内核中申请一段内存,请求完成时,把内存拷贝给R3
问题: 读了数据又想写数据怎么办?
假如说R3写数据的请求是关闭某个进程,那么需要传给驱动一个pid, 同时R3需要一个结果,判断这个进程有没有关闭成功。
readfile+缓冲区,writefile+缓冲区,这就需要两个缓冲区来做交互,并且这两个回调一般是给硬件设备用的;
如果使用控制,那可以一次传两个缓冲区,可以同时读写数据,相对更加方便;
4.1 实验验证:能否调用驱动control函数
Ring3.cpp 中的main函数
int main() { printf("[%s] Install Driver ... \n", __FUNCTION__); LoadDriver("C:\\Documents and Settings\\cobb\\桌面\\sample2.sys", "51asmMark"); //if (LoadDriver("sample2.sys", "51asmMark") < 0) // return 0; printf("[%s] Install Driver Ok \n", __FUNCTION__); // 打开设备, 这里文件的路径就是在驱动中创建的设备名字 HANDLE hFile = CreateFile("\\\\.\\\\51asmMark", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { showErr("CreateFile"); } else { printf("hFile:%p \n", hFile); #if 0 // 读写设备 char acBuf[60] = { 0 }; printf("acBuf:%p\n", acBuf); /* 驱动要输出R3的缓存地址,这里作为对照 */ DWORD dwBytes = 0; BOOL bRet = ReadFile(hFile, acBuf, sizeof(acBuf), &dwBytes, NULL); if (!bRet) { showErr("ReadFile"); } else { printf("bRet:%d dwBytes:%d acBuf:%s\n", bRet, dwBytes, acBuf); } #endif char szInBuf[MAXBYTE] = { 0 }; char szOutBuf[MAXBYTE] = { 0 }; DWORD dwBytes = 0; /* 缓冲区写进去的数据个数 */ BOOL bRet = DeviceIoControl(hFile, 100, szInBuf, sizeof(szInBuf), szOutBuf, sizeof(szOutBuf), &dwBytes, NULL); if (!bRet) { showErr("DeviceIoControl"); } else { // 显示结果 printf("bRet:%d dwBytes:%d szOutBuf:%s\n", bRet, dwBytes, szOutBuf); } // 控制设备 // 关闭设备 CloseHandle(hFile); } UnLoadDriver("51asmMark"); printf("[%s] UnLoadDriver Driver Ok \n", __FUNCTION__); system("pause"); return 0; }
4.2 结果分析
调用了control,
4.2.1
NT框架的设计问题:irp里没有R3的控制码
系统采用分层驱动的方式,例如:键盘是两个驱动加载组成的;
例: 有线键盘 不同usb接口键盘 逻辑大量重复
那就: 一个负责接口驱动,一个负责键盘逻辑驱动
4.3 实验验证:驱动中获取R3地址
Ring3.cpp 的main函数
int main() { printf("[%s] Install Driver ... \n", __FUNCTION__); LoadDriver("C:\\Documents and Settings\\cobb\\桌面\\sample2.sys", "51asmMark"); //if (LoadDriver("sample2.sys", "51asmMark") < 0) // return 0; printf("[%s] Install Driver Ok \n", __FUNCTION__); // 打开设备, 这里文件的路径就是在驱动中创建的设备名字 HANDLE hFile = CreateFile("\\\\.\\\\51asmMark", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { showErr("CreateFile"); } else { printf("hFile:%p \n", hFile); char szInBuf[MAXBYTE] = { 0 }; char szOutBuf[MAXBYTE] = { 0 }; printf("szInBuf:%p szOutBuf:%p size:%d\n", szInBuf, szOutBuf, MAXBYTE); /* 驱动要输出R3的缓存地址,这里作为对照验证 */ DWORD dwBytes = 0; /* 缓冲区写进去的数据个数 */ BOOL bRet = DeviceIoControl(hFile, 100, szInBuf, sizeof(szInBuf), szOutBuf, sizeof(szOutBuf), &dwBytes, NULL); if (!bRet) { showErr("DeviceIoControl"); } else { // 显示结果 printf("bRet:%d dwBytes:%d szOutBuf:%s\n", bRet, dwBytes, szOutBuf); } // 控制设备 // 关闭设备 CloseHandle(hFile); } UnLoadDriver("51asmMark"); printf("[%s] UnLoadDriver Driver Ok \n", __FUNCTION__); system("pause"); return 0; }
sample.cpp 的DispatchControl函数
NTSTATUS DispatchControl(_DEVICE_OBJECT* DeviceObject, _IRP* Irp) { UNREFERENCED_PARAMETER(DeviceObject); DbgPrint("51[asm][%s:%d]", __FUNCTION__, __LINE__); /* 类似函数调用,要给下一层驱动传参数,参数:有的分开,有的共用; 共用参数放到了IRP里了; 不共用的参数,都会有自己的内存空间==》"IRP堆栈" ( 缓冲区大小 控制码......) 然后要向IO管理器获取到IRP堆栈(要的是这层驱动的irp堆栈地址),参数给irp,因为IRP堆栈就放在IRP后面; IoGetCurrentIrpStackLocation(Irp); 传参是irp,因为irp堆栈放到irp后面; 但是不知道拿到那一层,自己不能去拿到这个地址,这是系统来拿,R3的其他参数都在这里main 不同的回调函数会有不同的行为,IoGetCurrentIrpStackLocation返回值里是共用体; */ PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(Irp); PVOID pUserBuffer = Irp->UserBuffer; PVOID pType3InputBuffer = pIrpStack->Parameters.DeviceIoControl.Type3InputBuffer; ULONG uInputBufferLength = pIrpStack->Parameters.DeviceIoControl.InputBufferLength; ULONG uIoControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode; ULONG uOutputBufferLength = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength; DbgPrint("51[asm][%s:%d] uIoControlCode:%08x pUserBuffer:%p pType3InputBuffer:%p", __FUNCTION__, __LINE__, uIoControlCode, pUserBuffer, pType3InputBuffer); DbgPrint("51[asm][%s:%d] uInputBufferLength:%d uOutputBufferLength:%d", __FUNCTION__, __LINE__, uInputBufferLength, uOutputBufferLength); Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; /* 1. 这个函数表示 请求是否结束 2. 第一个参数:Irp(I/O Request Packet) 把所有的请求参数封装到这个结构体了 3. 第二个参数,让挂起的线程快速恢复,把线程的优先级往上提; 4. 当前IO_NO_INCREMENT: 不要提升优先级,什时候唤醒就什么时候结束 5. 除了入口函数和卸载函数外,其他全部函数都是一样的,要这样处理; */ IoCompleteRequest(Irp,IO_NO_INCREMENT); return STATUS_SUCCESS; //return IoCallDriver(Irp); // 分层驱动,这里进入下一层驱动来获取需要的值 }
4.4 现象分析:
这里只拿到一个缓冲区,另外一个缓冲区拿不到了,这就是为什么不要用R3的缓冲区,内核返回R3的缓冲区是有安全风险的
OS处理的原理:
R3输出缓冲区给驱动的过程:OS申请一个R0级别的内存,先把R3输出缓冲区的数据拷贝到OS里,驱动从R0申请的内存中再读数据; 驱动读取完数据之后要返回,路径相反;
而readfile参数是三环的,从内核里读数据,数据会被覆盖,无法读到数据,这是系统缓冲区;这就是为什么不能使用readfile
所以驱动中拿到这几个R3的数据没有意义的,是无效的参数;
4.5 实验验证: 内核读写数据的正确姿势
sample.cpp 的DispatchControl函数
NTSTATUS DispatchControl(_DEVICE_OBJECT* DeviceObject, _IRP* Irp) { UNREFERENCED_PARAMETER(DeviceObject); DbgPrint("51[asm][%s:%d]", __FUNCTION__, __LINE__); /* 类似函数调用,要给下一层驱动传参数,参数:有的分开,有的共用; 共用参数放到了IRP里了; 不共用的参数,都会有自己的内存空间==》"IRP堆栈" ( 缓冲区大小 控制码......) 然后要向IO管理器获取到IRP堆栈(要的是我这层驱动的irp堆栈地址),参数给irp,因为IRP堆栈就放在IRP后面; IoGetCurrentIrpStackLocation(Irp); 传参是irp,因为irp堆栈放到irp后面; 但是不知道拿到那一层,自己不能去拿到这个地址,这是系统来拿,R3的其他参数都在这里main 不同的回调函数会有不同的行为,IoGetCurrentIrpStackLocation返回值里是共用体; */ PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(Irp); //PVOID pUserBuffer = Irp->UserBuffer; //PVOID pType3InputBuffer = pIrpStack->Parameters.DeviceIoControl.Type3InputBuffer; //ULONG uInputBufferLength = pIrpStack->Parameters.DeviceIoControl.InputBufferLength; //ULONG uIoControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode; //ULONG uOutputBufferLength = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength; //DbgPrint("51[asm][%s:%d] uIoControlCode:%08x pUserBuffer:%p pType3InputBuffer:%p", __FUNCTION__, __LINE__, uIoControlCode, pUserBuffer, pType3InputBuffer); //DbgPrint("51[asm][%s:%d] uInputBufferLength:%d uOutputBufferLength:%d", __FUNCTION__, __LINE__, uInputBufferLength, uOutputBufferLength); /* 内核中读写数据: 获取输出缓冲区的大小(地址在高2G), 数据的读写都是在这里进行 系统会让这里的缓冲区大小和R3输出缓冲区一样大 */ PVOID pSystemBuffer = Irp->AssociatedIrp.SystemBuffer; ULONG uLength = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength; DbgPrint("51[asm][%s:%d] pSystemBuffer:%p uLength:%d", __FUNCTION__, __LINE__, pSystemBuffer, uLength); Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; /* 1. 这个函数表示 请求是否结束 2. 第一个参数:Irp(I/O Request Packet) 把所有的请求参数疯转到这个结构体了 3. 第二个参数,让挂起的线程快速恢复,把线程的优先级往上提; 4. 当前IO_NO_INCREMENT: 不要提升优先级,什时候唤醒就什么时候结束 5. 除了入口函数和卸载函数外,其他全部函数都是一样的,要这样处理; */ IoCompleteRequest(Irp,IO_NO_INCREMENT); return STATUS_SUCCESS; //return IoCallDriver(Irp); // 分层驱动,这里进入下一层驱动来获取需要的值 }
4.6 结果分析
输出了高2G的地址
4.7 尝试读写内核数据
R3传给驱动"hello",驱动回复"word".
Ring3.cpp 的main函数
int main() { printf("[%s] Install Driver ... \n", __FUNCTION__); LoadDriver("C:\\Documents and Settings\\cobb\\桌面\\sample2.sys", "51asmMark"); //if (LoadDriver("sample2.sys", "51asmMark") < 0) // return 0; printf("[%s] Install Driver Ok \n", __FUNCTION__); // 打开设备, 这里文件的路径就是在驱动中创建的设备名字 HANDLE hFile = CreateFile("\\\\.\\\\51asmMark", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { showErr("CreateFile"); } else { printf("hFile:%p \n", hFile); char szInBuf[MAXBYTE] = { 0 }; char szOutBuf[MAXBYTE] = { "hello" }; printf("szInBuf:%p szOutBuf:%p size:%d\n", szInBuf, szOutBuf, MAXBYTE); /* 驱动要输出R3的缓存地址,这里作为对照验证 */ DWORD dwBytes = 0; /* 缓冲区写进去的数据个数 */ BOOL bRet = DeviceIoControl(hFile, 100, szInBuf, sizeof(szInBuf), szOutBuf, sizeof(szOutBuf), &dwBytes, NULL); if (!bRet) { showErr("DeviceIoControl"); } else { // 显示结果 printf("bRet:%d dwBytes:%d szInBuf:%s\n", bRet, dwBytes, szInBuf); } // 控制设备 // 关闭设备 CloseHandle(hFile); } UnLoadDriver("51asmMark"); printf("[%s] UnLoadDriver Driver Ok \n", __FUNCTION__); system("pause"); return 0; }
sample.cpp 的DispatchControl函数
NTSTATUS DispatchControl(_DEVICE_OBJECT* DeviceObject, _IRP* Irp) { UNREFERENCED_PARAMETER(DeviceObject); DbgPrint("51[asm][%s:%d]", __FUNCTION__, __LINE__); /* 类似函数调用,要给下一层驱动传参数,参数:有的分开,有的共用; 共用参数放到了IRP里了; 不共用的参数,都会有自己的内存空间==》"IRP堆栈" ( 缓冲区大小 控制码......) 然后要向IO管理器获取到IRP堆栈(要的是我这层驱动的irp堆栈地址),参数给irp,因为IRP堆栈就放在IRP后面; IoGetCurrentIrpStackLocation(Irp); 传参是irp,因为irp堆栈放到irp后面; 但是不知道拿到那一层,自己不能去拿到这个地址,这是系统来拿,R3的其他参数都在这里main 不同的回调函数会有不同的行为,IoGetCurrentIrpStackLocation返回值里是共用体; */ PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(Irp); //PVOID pUserBuffer = Irp->UserBuffer; //PVOID pType3InputBuffer = pIrpStack->Parameters.DeviceIoControl.Type3InputBuffer; //ULONG uInputBufferLength = pIrpStack->Parameters.DeviceIoControl.InputBufferLength; //ULONG uIoControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode; //ULONG uOutputBufferLength = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength; //DbgPrint("51[asm][%s:%d] uIoControlCode:%08x pUserBuffer:%p pType3InputBuffer:%p", __FUNCTION__, __LINE__, uIoControlCode, pUserBuffer, pType3InputBuffer); //DbgPrint("51[asm][%s:%d] uInputBufferLength:%d uOutputBufferLength:%d", __FUNCTION__, __LINE__, uInputBufferLength, uOutputBufferLength); /* 内核中读写数据: 获取输出缓冲区的大小(地址在高2G), 数据的读写都是在这里进行 系统会让这里的缓冲区大小和R3输出缓冲区一样大 不要在Ring0下访问Ring3地址,会产生安全问题 */ PVOID pSystemBuffer = Irp->AssociatedIrp.SystemBuffer; ULONG uLength = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength; ULONG uIoControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode; DbgPrint("51[asm][%s:%d] uIoControlCode:%08x pSystemBuffer:%p uLength:%d", __FUNCTION__, __LINE__, uIoControlCode, pSystemBuffer, uLength); switch (uIoControlCode) { case 100: DbgPrint("51[asm][%s:%d] Enum Process pid:%s", __FUNCTION__, __LINE__, pSystemBuffer); RtlStringCchCopyA((LPSTR)pSystemBuffer, uLength, "word"); //如果要向R3写数据,也是写入这个缓存中 break; case 200: break; default: break; } Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = uLength; // 返回值的长度要返回去 /* 1. 这个函数表示 请求是否结束 2. 第一个参数:Irp(I/O Request Packet) 把所有的请求参数疯转到这个结构体了 3. 第二个参数,让挂起的线程快速恢复,把线程的优先级往上提; 4. 当前IO_NO_INCREMENT: 不要提升优先级,什时候唤醒就什么时候结束 5. 除了入口函数和卸载函数外,其他全部函数都是一样的,要这样处理; */ IoCompleteRequest(Irp,IO_NO_INCREMENT); return STATUS_SUCCESS; //return IoCallDriver(Irp); // 分层驱动,这里进入下一层驱动来获取需要的值 }
4.8 结果分析
数据没有读到,也没有返回回去,这是因为控制码的原因,控制码的定义是有规范的
微软定义一个宏 CTL_CODE ,来表示控制码,可查看文档;
4.9 控制码讲解
编写控制函数的第一步就是要定义控制码(也就是定义一套自己的控制码)
控制码从3环的API
可以看出它是一个整型;而且控制码是有规范的,不能胡乱定义.
这么定义控制码是很麻烦的,所以微软提供了一个宏来完成控制码的定义
示例: #define IOCTL_GET_REG1 CTL_CODE(DeviceType, Function, Method, Access) 只需要填写上这四个参数就行了 DeviceType:(DeviceType)设备类型;内核驱动就写FILE_DEVICE_UNKNOWN就行了 Function:(FunctionCode)控制码;可以定义自己的控制码,但是要大于0x800;(0x800以下被微软保留使用了) Method:(TransferType);通信方式 Access:(RequiredAccess)访问权限;就是读写的权限,一般给所有权限FILE_ANY_ACCESS; 定义好以后就是这样 #define IOCTL_GET_REG1 CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_NEITHER,FILE_ANY_ACCESS) #define IOCTL_GET_REG2 CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_NEITHER,FILE_ANY_ACCESS) 控制码会越来越多,所以一般我们还会再套一个宏,方便后期的修改. #define MY_CTL_CODE(code) CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_NEITHER,FILE_ANY_ACCESS) #define IOCTL_GET_REG1 MY_CTL_CODE(0x801) #define IOCTL_GET_REG2 MY_CTL_CODE(0x802) 或者再多加一个宏 #define MY_CODE_BASE 0x800 #define MY_CTL_CODE(code) CTL_CODE(FILE_DEVICE_UNKNOWN,MY_CODE_BASE + code,METHOD_NEITHER,FILE_ANY_ACCESS) #define IOCTL_GET_REG1 MY_CTL_CODE(0) #define IOCTL_GET_REG2 MY_CTL_CODE(1)
控制码不按格式定义的话,驱动可能收不到,或者收到后直接系统蓝屏
4.10 实验验证:通过控制码的数据读写
Ring3.cpp 的main函数和添加的宏
#include <stdio.h> #include <windows.h> #include <WinIoCtl.h> /* use CTL_CODE */ // 参数2:Values of less than 0x800 are reserved for Microsoft // 参数3:METHOD_BUFFERED表示操作系统来创建缓冲区 #define IOCTL_ENUM_PROCESS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED , FILE_ANY_ACCESS) #define IOCTL_KILL_PROCESS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED , FILE_ANY_ACCESS) // 中间部分省略 ... int main() { printf("[%s] Install Driver ... \n", __FUNCTION__); LoadDriver("C:\\Documents and Settings\\cobb\\桌面\\sample2.sys", "51asmMark"); //if (LoadDriver("sample2.sys", "51asmMark") < 0) // return 0; printf("[%s] Install Driver Ok \n", __FUNCTION__); // 打开设备, 这里文件的路径就是在驱动中创建的设备名字 HANDLE hFile = CreateFile("\\\\.\\\\51asmMark", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { showErr("CreateFile"); } else { printf("hFile:%p \n", hFile); char szInBuf[MAXBYTE] = { 0 }; char szOutBuf[MAXBYTE] = { "hello" }; printf("szInBuf:%p szOutBuf:%p size:%d\n", szInBuf, szOutBuf, MAXBYTE); /* 驱动要输出R3的缓存地址,这里作为对照验证 */ DWORD dwBytes = 0; /* 缓冲区写进去的数据个数 */ BOOL bRet = DeviceIoControl(hFile, IOCTL_ENUM_PROCESS, szInBuf, sizeof(szInBuf), szOutBuf, sizeof(szOutBuf), &dwBytes, NULL); if (!bRet) { showErr("DeviceIoControl"); } else { // 显示结果 printf("bRet:%d dwBytes:%d szInBuf:%s szOutBuf:%s\n", bRet, dwBytes, szInBuf, szOutBuf); } // 控制设备 // 关闭设备 CloseHandle(hFile); } UnLoadDriver("51asmMark"); printf("[%s] UnLoadDriver Driver Ok \n", __FUNCTION__); system("pause"); return 0; }
sample.h
#pragma once #include <ntddk.h> #include <Wdm.h> #include <Ntstrsafe.h> #define DEVICE_NAME L"\\Device\\51asmMark" #define SYMBOL_NAME L"\\DosDevices\\51asmMark" //#define SYMBOL_NAME L"\\??\\51asmMark" // 参数2:Values of less than 0x800 are reserved for Microsoft // 参数3:METHOD_BUFFERED表示操作系统来创建缓冲区 #define IOCTL_ENUM_PROCESS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED , FILE_ANY_ACCESS) #define IOCTL_KILL_PROCESS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED , FILE_ANY_ACCESS) VOID Unload(__in struct _DRIVER_OBJECT* DriverObject); extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath); NTSTATUS DispatchCreat(struct _DEVICE_OBJECT* DeviceObject, struct _IRP* Irp); NTSTATUS DispatchClose(struct _DEVICE_OBJECT* DeviceObject, struct _IRP* Irp); NTSTATUS DispatchRead(struct _DEVICE_OBJECT* DeviceObject, struct _IRP* Irp); NTSTATUS DispatchWrite(struct _DEVICE_OBJECT* DeviceObject, struct _IRP* Irp); NTSTATUS DispatchControl(struct _DEVICE_OBJECT* DeviceObject, struct _IRP* Irp);
sample.cpp 的DispatchControl函数
NTSTATUS DispatchControl(_DEVICE_OBJECT* DeviceObject, _IRP* Irp) { UNREFERENCED_PARAMETER(DeviceObject); DbgPrint("51[asm][%s:%d]", __FUNCTION__, __LINE__); /* 类似函数调用,要给下一层驱动传参数,参数:有的分开,有的共用; 共用参数放到了IRP里了; 不共用的参数,都会有自己的内存空间==》"IRP堆栈" ( 缓冲区大小 控制码......) 然后要向IO管理器获取到IRP堆栈(要的是我这层驱动的irp堆栈地址),参数给irp,因为IRP堆栈就放在IRP后面; IoGetCurrentIrpStackLocation(Irp); 传参是irp,因为irp堆栈放到irp后面; 但是不知道拿到那一层,自己不能去拿到这个地址,这是系统来拿,R3的其他参数都在这里main 不同的回调函数会有不同的行为,IoGetCurrentIrpStackLocation返回值里是共用体; */ PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(Irp); //PVOID pUserBuffer = Irp->UserBuffer; //PVOID pType3InputBuffer = pIrpStack->Parameters.DeviceIoControl.Type3InputBuffer; //ULONG uInputBufferLength = pIrpStack->Parameters.DeviceIoControl.InputBufferLength; //ULONG uIoControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode; //ULONG uOutputBufferLength = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength; //DbgPrint("51[asm][%s:%d] uIoControlCode:%08x pUserBuffer:%p pType3InputBuffer:%p", __FUNCTION__, __LINE__, uIoControlCode, pUserBuffer, pType3InputBuffer); //DbgPrint("51[asm][%s:%d] uInputBufferLength:%d uOutputBufferLength:%d", __FUNCTION__, __LINE__, uInputBufferLength, uOutputBufferLength); /* 内核中读写数据: 获取输出缓冲区的大小(地址在高2G), 数据的读写都是在这里进行 系统会让这里的缓冲区大小和R3输出缓冲区一样大 不要在Ring0下访问Ring3地址,会产生安全问题 */ PVOID pSystemBuffer = Irp->AssociatedIrp.SystemBuffer; ULONG uLength = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength; ULONG uIoControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode; DbgPrint("51[asm][%s:%d] uIoControlCode:%08x pSystemBuffer:%p uLength:%d", __FUNCTION__, __LINE__, uIoControlCode, pSystemBuffer, uLength); switch (uIoControlCode) { case IOCTL_ENUM_PROCESS: DbgPrint("51[asm][%s:%d] Enum Process pid:%p", __FUNCTION__, __LINE__, pSystemBuffer); RtlStringCchCopyA((LPSTR)pSystemBuffer, uLength, "word"); //如果要向R3写数据,也是写入这个缓存中 break; case IOCTL_KILL_PROCESS: DbgPrint("51[asm][%s:%d] Kill Process pid:%p", __FUNCTION__, __LINE__, pSystemBuffer); break; default: break; } Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = uLength; // 返回值的长度要返回去 /* 1. 这个函数表示 请求是否结束 2. 第一个参数:Irp(I/O Request Packet) 把所有的请求参数疯转到这个结构体了 3. 第二个参数,让挂起的线程快速恢复,把线程的优先级往上提; 4. 当前IO_NO_INCREMENT: 不要提升优先级,什时候唤醒就什么时候结束 5. 除了入口函数和卸载函数外,其他全部函数都是一样的,要这样处理; */ IoCompleteRequest(Irp,IO_NO_INCREMENT); return STATUS_SUCCESS; //return IoCallDriver(Irp); // 分层驱动,这里进入下一层驱动来获取需要的值 }
4.11 结果分析
驱动没有操作R3的地址,缓冲区地址是高2G,并且成功输出"word"
Ring3.cpp 的main函数
也可以这么做,这个做法类似 管道 ,匿名管道;可进可出
int main() { // 部分省略 .... char szBuf[MAXBYTE] = { "hello" }; printf("szBuf:%p size:%d\n", szBuf, MAXBYTE); /* 驱动要输出R3的缓存地址,这里作为对照验证 */ DWORD dwBytes = 0; /* 缓冲区写进去的数据个数 */ BOOL bRet = DeviceIoControl(hFile, IOCTL_ENUM_PROCESS, szBuf, sizeof(szBuf), szBuf, sizeof(szBuf), &dwBytes, NULL); if (!bRet) { showErr("DeviceIoControl"); } else { // 显示结果 printf("bRet:%d dwBytes:%d szBuf:%s\n", bRet, dwBytes, szBuf); } // 部分省略 .... return 0; }
4.12 同步问题
- 在驱动里操作全局变量也是会有同步问题的
- 可以直接用临界区来搞定
- 必须上个同步,要不然有两个线程同时操作就完了
KeEnterCriticalRegion(); //进入临界区 // ... 这里就是操作全局变量,对象的代码 KeLeaveCriticalRegion(); //退出临界区 //可以获取一下进程ID,来测试一下 PsGetCurrentProcessId();
KeEnterCriticalRegion(); switch (uIoControlCode) { case IOCTL_ENUM_PROCESS: DbgPrint("51[asm][%s:%d] Enum Process pid:%p", __FUNCTION__, __LINE__, pSystemBuffer); RtlStringCchCopyA((LPSTR)pSystemBuffer, uLength, "word"); //如果要向R3写数据,也是写入这个缓存中 break; case IOCTL_KILL_PROCESS: DbgPrint("51[asm][%s:%d] Kill Process pid:%p", __FUNCTION__, __LINE__, pSystemBuffer); break; default: break; } KeLeaveCriticalRegion();
0x05 完整代码记录
Ring3.cpp
#include <stdio.h> #include <windows.h> #include <WinIoCtl.h> /* use CTL_CODE */ // 参数2:Values of less than 0x800 are reserved for Microsoft // 参数3:METHOD_BUFFERED表示操作系统来创建缓冲区 #define IOCTL_ENUM_PROCESS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED , FILE_ANY_ACCESS) #define IOCTL_KILL_PROCESS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED , FILE_ANY_ACCESS) struct ProcessInfo { int pid; char name[256]; }; /* 如果没有R3得程序怎么加载呢? 微软有个inf文件,这个东西可以安装,在微软的官方文档中有说明inf内容怎么写; OS会解析inf这个配置文件,帮助动态加载驱动 例: 如果硬件需要开机启动加载,微软再开机时会自动加载inf文件 驱动加载的方式: 1. inf文件来加载 2. 动态加载(服务) 创建服务就是安装驱动(很多软件都可以使用打阴界,那么就可调用打印机的驱动,驱动的角色是服务),也就是需要4个api就可以完成这些工作; 创建服务 启动服务 停止服务 卸载服务 */ void showErr(const char *name) { LPVOID lpMsgBuf; DWORD dwErr = GetLastError(); FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dwErr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language (LPTSTR)&lpMsgBuf, 0, NULL ); printf("[%s] Error Code:%d Msg:%s", name, dwErr, (LPCTSTR)lpMsgBuf); //MessageBox(NULL, (LPCTSTR)lpMsgBuf, "Error", MB_OK | MB_ICONINFORMATION); // Free the buffer. LocalFree(lpMsgBuf); } int LoadDriver(const char *pszPath, const char *pszServiceName) { SC_HANDLE hSCM = NULL; SC_HANDLE hService = NULL; int iRet = -1; hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); if (hSCM == NULL) { showErr("OpenSCManager"); return 0; } printf("[%s] OpenSCManager Ok hSCM:%p \n", __FUNCTION__,hSCM); hService = CreateService(hSCM, pszServiceName, pszServiceName, SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, pszPath, NULL, NULL, NULL, NULL, NULL); if (hService == NULL) { //if (GetLastError() == 1073) // 当前服务已经存在,那就直接打开用 if (GetLastError() == ERROR_SERVICE_EXISTS) { hService = OpenService(hSCM, pszServiceName, SERVICE_ALL_ACCESS); if (hService == NULL) { showErr("OpenService"); goto SAFE_EXIT; } printf("[%s] OpenService Ok hService:%p \n", __FUNCTION__, hService); } else { showErr("CreateService"); goto SAFE_EXIT; } } printf("[%s] CreateService Ok hService:%p \n", __FUNCTION__, hService); /* CreateService 使用分析 SC_HANDLE CreateService( SC_HANDLE hSCManager, // handle to SCM database 服务是由服务管理器来管理,这里拿到服务管理器;SCM(service control manager) ,因为是本地,前两个为NULL,最后一个是权限,如果选择所有权限,就是既能创建又能删除 LPCTSTR lpServiceName, // name of service to start LPCTSTR lpDisplayName, // display name DWORD dwDesiredAccess, // type of access to service DWORD dwServiceType, // type of service DWORD dwStartType, // when to start service 五种启动方式,选第3个,重启系统后就不再加载 DWORD dwErrorControl, // severity of service failure LPCTSTR lpBinaryPathName, // name of binary file LPCTSTR lpLoadOrderGroup, // name of load ordering group LPDWORD lpdwTagId, // tag identifier LPCTSTR lpDependencies, // array of dependency names LPCTSTR lpServiceStartName, // account name LPCTSTR lpPassword // account password); */ if (!StartService(hService, NULL, NULL)) /* 后两个参数,因为是驱动的服务,所以三环这里给NULL */ { showErr("StartService"); goto SAFE_EXIT; } iRet = 0; SAFE_EXIT: if (hService != NULL) { if (CloseServiceHandle(hService) == 0) { showErr("CloseServiceHandle"); } } if (hSCM != NULL) { if (CloseServiceHandle(hSCM) == 0) { showErr("CloseServiceHandle"); } } return iRet; } int UnLoadDriver(const char * pszServiceName) { SC_HANDLE hSCM = NULL; SC_HANDLE hService = NULL; int iRet = -1; SERVICE_STATUS oldState = { 0 }; hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); if (hSCM == NULL) { showErr("OpenSCManager"); return 0; } printf("[%s] OpenSCManager Ok hSCM:%p \n", __FUNCTION__, hSCM); hService = OpenService(hSCM, pszServiceName, SERVICE_ALL_ACCESS); if (hService == NULL) { showErr("OpenService"); goto SAFE_EXIT; } printf("[%s] OpenService Ok hService:%p \n", __FUNCTION__, hService); /* 1. DeleteService之前,如果服务正在运行怎么办? 那就是先暂停服务 2 .OS 的peding状态:向硬件发送的请求还没有完成,此时驱动卸不掉,停不掉(如果驱动中的派遣函数没有IoCompleteRequest完成请求,就会有这种情况) */ if (!ControlService(hService, SERVICE_CONTROL_STOP, &oldState)) { if (GetLastError() == ERROR_SERVICE_NOT_ACTIVE) // 服务没有启动的时候 判断错误码1062,那就启动服务; { printf("[%s] ControlService not active hService:%p \n", __FUNCTION__, hService); } else if (GetLastError() == ERROR_DEPENDENT_SERVICES_RUNNING) // peding 状态 { printf("[%s] Please reboot system hService:%p \n", __FUNCTION__, hService); goto SAFE_EXIT; } else { showErr("ControlService"); goto SAFE_EXIT; } } printf("[%s] ControlService stop service Ok hService:%p \n", __FUNCTION__, hService); if (!DeleteService(hService)) { showErr("DeleteService"); goto SAFE_EXIT; } printf("[%s] DeleteService Ok hService:%p \n", __FUNCTION__, hService); iRet = 0; SAFE_EXIT: if (hService != NULL) { if (CloseServiceHandle(hService) == 0) { showErr("CloseServiceHandle"); } } if (hSCM != NULL) { if (CloseServiceHandle(hSCM) == 0) { showErr("CloseServiceHandle"); } } return iRet; } int main() { printf("[%s] Install Driver ... \n", __FUNCTION__); LoadDriver("C:\\Documents and Settings\\cobb\\桌面\\sample2.sys", "51asmMark"); //if (LoadDriver("sample2.sys", "51asmMark") < 0) // return 0; printf("[%s] Install Driver Ok \n", __FUNCTION__); // 打开设备, 这里文件的路径就是在驱动中创建的设备名字 HANDLE hFile = CreateFile("\\\\.\\\\51asmMark", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { showErr("CreateFile"); } else { printf("hFile:%p \n", hFile); #if 0 // 读写设备 char acBuf[60] = { 0 }; printf("acBuf:%p\n", acBuf); /* 驱动要输出R3的缓存地址,这里作为对照 */ DWORD dwBytes = 0; BOOL bRet = ReadFile(hFile, acBuf, sizeof(acBuf), &dwBytes, NULL); if (!bRet) { showErr("ReadFile"); } else { printf("bRet:%d dwBytes:%d acBuf:%s\n", bRet, dwBytes, acBuf); } #endif char szBuf[MAXBYTE] = { "hello" }; printf("szBuf:%p size:%d\n", szBuf, MAXBYTE); /* 驱动要输出R3的缓存地址,这里作为对照验证 */ DWORD dwBytes = 0; /* 缓冲区写进去的数据个数 */ BOOL bRet = DeviceIoControl(hFile, IOCTL_ENUM_PROCESS, szBuf, sizeof(szBuf), szBuf, sizeof(szBuf), &dwBytes, NULL); if (!bRet) { showErr("DeviceIoControl"); } else { // 显示结果 printf("bRet:%d dwBytes:%d szBuf:%s\n", bRet, dwBytes, szBuf); } // 控制设备 // 关闭设备 CloseHandle(hFile); } UnLoadDriver("51asmMark"); printf("[%s] UnLoadDriver Driver Ok \n", __FUNCTION__); system("pause"); return 0; }
sample.h
#pragma once #include <ntddk.h> #include <Wdm.h> #include <Ntstrsafe.h> #define DEVICE_NAME L"\\Device\\51asmMark" #define SYMBOL_NAME L"\\DosDevices\\51asmMark" //#define SYMBOL_NAME L"\\??\\51asmMark" // 参数2:Values of less than 0x800 are reserved for Microsoft // 参数3:METHOD_BUFFERED表示操作系统来创建缓冲区 #define IOCTL_ENUM_PROCESS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED , FILE_ANY_ACCESS) #define IOCTL_KILL_PROCESS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED , FILE_ANY_ACCESS) VOID Unload(__in struct _DRIVER_OBJECT* DriverObject); extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath); NTSTATUS DispatchCreat(struct _DEVICE_OBJECT* DeviceObject, struct _IRP* Irp); NTSTATUS DispatchClose(struct _DEVICE_OBJECT* DeviceObject, struct _IRP* Irp); NTSTATUS DispatchRead(struct _DEVICE_OBJECT* DeviceObject, struct _IRP* Irp); NTSTATUS DispatchWrite(struct _DEVICE_OBJECT* DeviceObject, struct _IRP* Irp); NTSTATUS DispatchControl(struct _DEVICE_OBJECT* DeviceObject, struct _IRP* Irp); /* IRP_MJ_DEVICE_CONTROL: 读写可以控制摄像头的打开关闭,控制则可以控制其他的,如:摄像头的旋转...[除了硬件厂商,其他人可不知道怎么让摄像头旋转] 是用来写不通用的功能; 读写的参数需要传一个缓冲区和一个长度,而control的参数,微软不知道传什么参数 control就是要传一些只有硬件厂家才能解析的数据 显卡(NVIDIA)的驱动安装之后,R3上有界面,设置显卡的一些特性,会调用DispatchControl这个函数,这里可以做一些硬件独有的功能; 有一种软件可以拿到硬件的各种信息:有没有可能把control的参数逆清楚,模仿参数发送数据,这样就可以拿到硬件的各种信息了 前几天在闲鱼买了太Macmini主机,最担心的就是有人通过这种方式来修改主机里的信息,如:8+256的主机刷成32+2t的,买家需要反复擦亮眼睛奥; */
sample.cpp
#include "sample.h" char g_zMsg[] = "hello"; // 代表OS的任何数据 VOID Unload(__in struct _DRIVER_OBJECT* DriverObject) { UNREFERENCED_PARAMETER(DriverObject); /* 删除符号链接 */ UNICODE_STRING strDosDeviceName; RtlInitUnicodeString(&strDosDeviceName, SYMBOL_NAME); IoDeleteSymbolicLink(&strDosDeviceName); /* 驱动对象和设备对象绑定之后,设备对象会被记录到这里 DriverObject->DeviceObject; 需要判断对象是否绑定成功*/ if (DriverObject->DeviceObject != NULL) { IoDeleteDevice(DriverObject->DeviceObject); DbgPrint("51[asm][%s:%d] IoDeleteDevice OK!",__FUNCTION__,__LINE__); } DbgPrint("51[asm][%s:%d] Unload", __FUNCTION__, __LINE__); } NTSTATUS DriverEntry( PDRIVER_OBJECT DriverObject, /* PDRIVER_OBJECT 这个结构体保存了驱动的所有信息 */ PUNICODE_STRING RegistryPath) { UNREFERENCED_PARAMETER(DriverObject); UNREFERENCED_PARAMETER(RegistryPath); NTSTATUS Status = STATUS_SUCCESS; /* 注册派遣函数 */ DriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchCreat; DriverObject->MajorFunction[IRP_MJ_CLOSE] = DispatchClose; DriverObject->MajorFunction[IRP_MJ_READ] = DispatchRead; DriverObject->MajorFunction[IRP_MJ_WRITE] = DispatchWrite; DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchControl; /* rtl: run time library C库(运行库) 微软重新做的C库,这个库更加安全; 这里做字符串使用的初始化 */ UNICODE_STRING userDevName; RtlInitUnicodeString(&userDevName, DEVICE_NAME); /* 绑定设备 */ PDEVICE_OBJECT pDeviceObj = NULL; Status = IoCreateDevice(DriverObject, 0, &userDevName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN | FILE_READ_ONLY_DEVICE | FILE_WRITE_ONCE_MEDIA, FALSE, &pDeviceObj); if (!NT_SUCCESS(Status)) /* 函数返回值统一是Status,这里安全性检查 */ { DbgPrint("51[asm][%s:%d] IoCreateDevice err status:%p", __FUNCTION__, __LINE__, Status); DriverObject->DriverUnload = Unload; /* 卸载函数中一定要删除设备,否则卸载后无法再次启动,提示设备名已存在 */ return Status; } /* IoCreateDevice 参数填写注意事项: PDEVICE_OBJECT pDeviceObj = NULL; // 结构体里的 PVOID DeviceExtension; 可以申请空间大小 IoCreateDevice( IN PDRIVER_OBJECT DriverObject, // 驱动对象 IN ULONG DeviceExtensionSize, // 设备扩展: 微软定义了一个结构体 PDEVICE_OBJECT,如果硬件希望在结构体中增加一些成员,这里需要写成员的大小,OS会申请一段内存空间,那就就可以无限的给这个结构体增加成员;这里是额外申请一些空间 IN PUNICODE_STRING DeviceName OPTIONAL, // 给设备名字是必须唯一的,注册的名字和其他的冲突了,会创建失败;可以给个NULL,微软会随机一个名字; 文档中说明设备名必须是有格式的: \Device\DeviceName // 注意: PUNICODE_STRING 这里的字符串Unicode字符串,字符串需要经过处理 IN DEVICE_TYPE DeviceType, // 选择为止设备就行,但是一定得给,否则无法通讯 FILE_DEVICE_UNKNOWN IN ULONG DeviceCharacteristics, // 权限 IN BOOLEAN Exclusive, // 关键参数,是否独占的方式, 独占: 只允许一个人打开这个设备 (杀毒软件会独占) OUT PDEVICE_OBJECT * DeviceObject ); */ /* 创建符号链接 */ UNICODE_STRING strDosDeviceName; RtlInitUnicodeString(&strDosDeviceName, SYMBOL_NAME); Status = IoCreateSymbolicLink(&strDosDeviceName, &userDevName); /* R3使用的名字在文档中选:MS-DOS Device Names R0使用的名字在文档中选:NT Device Names */ if (!NT_SUCCESS(Status)) { /* 驱动对象和设备对象绑定之后,设备对象会被记录到这里 DriverObject->DeviceObject; 需要判断对象是否绑定成功*/ if (DriverObject->DeviceObject != NULL) { /* 删除符号链接 */ IoDeleteSymbolicLink(&strDosDeviceName); IoDeleteDevice(DriverObject->DeviceObject); DbgPrint("51[asm][%s:%d] IoCreateSymbolicLink err!", __FUNCTION__, __LINE__); } return Status; } DriverObject->DriverUnload = Unload; DbgPrint("51[asm][%s:%d] hello word! DriverEntry:%p,Unload:%p,&status:%p,pDeviceObj:%p", __FUNCTION__, __LINE__, DriverEntry, Unload, &Status,&pDeviceObj); return Status; } NTSTATUS DispatchCreat(_DEVICE_OBJECT* DeviceObject, _IRP* Irp) { UNREFERENCED_PARAMETER(DeviceObject); UNREFERENCED_PARAMETER(Irp); DbgPrint("51[asm][%s:%d]", __FUNCTION__, __LINE__); // 这样返回的数据才是完整的 Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; } NTSTATUS DispatchClose(_DEVICE_OBJECT* DeviceObject, _IRP* Irp) { UNREFERENCED_PARAMETER(DeviceObject); UNREFERENCED_PARAMETER(Irp); DbgPrint("51[asm][%s:%d]", __FUNCTION__, __LINE__); // 这样返回的数据才是完整的 Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; } NTSTATUS DispatchRead(_DEVICE_OBJECT* DeviceObject, _IRP* Irp) { UNREFERENCED_PARAMETER(DeviceObject); UNREFERENCED_PARAMETER(Irp); /* 验证这里是R3的缓存地址,输出一下 */ DbgPrint("51[asm][%s:%d] Irp->UserBuffer:%p", __FUNCTION__, __LINE__, Irp->UserBuffer); RtlCopyMemory(Irp->UserBuffer,g_zMsg,sizeof(g_zMsg)); Irp->IoStatus.Status = STATUS_SUCCESS; /* 告诉R3返回的状态 */ Irp->IoStatus.Information = sizeof(g_zMsg); /* 告诉R3读取的字节数 */ IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; /* R3的ReadFile的返回值 */ } NTSTATUS DispatchWrite(_DEVICE_OBJECT* DeviceObject, _IRP* Irp) { UNREFERENCED_PARAMETER(DeviceObject); UNREFERENCED_PARAMETER(Irp); DbgPrint("51[asm][%s:%d]", __FUNCTION__, __LINE__); // 这样返回的数据才是完整的 Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; } NTSTATUS DispatchControl(_DEVICE_OBJECT* DeviceObject, _IRP* Irp) { UNREFERENCED_PARAMETER(DeviceObject); DbgPrint("51[asm][%s:%d]", __FUNCTION__, __LINE__); /* 类似函数调用,要给下一层驱动传参数,参数:有的分开,有的共用; 共用参数放到了IRP里了; 不共用的参数,都会有自己的内存空间==》"IRP堆栈" ( 缓冲区大小 控制码......) 然后要向IO管理器获取到IRP堆栈(要的是我这层驱动的irp堆栈地址),参数给irp,因为IRP堆栈就放在IRP后面; IoGetCurrentIrpStackLocation(Irp); 传参是irp,因为irp堆栈放到irp后面; 但是不知道拿到那一层,自己不能去拿到这个地址,这是系统来拿,R3的其他参数都在这里main 不同的回调函数会有不同的行为,IoGetCurrentIrpStackLocation返回值里是共用体; */ PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(Irp); //PVOID pUserBuffer = Irp->UserBuffer; //PVOID pType3InputBuffer = pIrpStack->Parameters.DeviceIoControl.Type3InputBuffer; //ULONG uInputBufferLength = pIrpStack->Parameters.DeviceIoControl.InputBufferLength; //ULONG uIoControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode; //ULONG uOutputBufferLength = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength; //DbgPrint("51[asm][%s:%d] uIoControlCode:%08x pUserBuffer:%p pType3InputBuffer:%p", __FUNCTION__, __LINE__, uIoControlCode, pUserBuffer, pType3InputBuffer); //DbgPrint("51[asm][%s:%d] uInputBufferLength:%d uOutputBufferLength:%d", __FUNCTION__, __LINE__, uInputBufferLength, uOutputBufferLength); /* 内核中读写数据: 获取输出缓冲区的大小(地址在高2G), 数据的读写都是在这里进行 系统会让这里的缓冲区大小和R3输出缓冲区一样大 不要在Ring0下访问Ring3地址,会产生安全问题 */ PVOID pSystemBuffer = Irp->AssociatedIrp.SystemBuffer; ULONG uLength = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength; ULONG uIoControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode; DbgPrint("51[asm][%s:%d] uIoControlCode:%08x pSystemBuffer:%p uLength:%d", __FUNCTION__, __LINE__, uIoControlCode, pSystemBuffer, uLength); switch (uIoControlCode) { case IOCTL_ENUM_PROCESS: DbgPrint("51[asm][%s:%d] Enum Process pid:%p", __FUNCTION__, __LINE__, pSystemBuffer); RtlStringCchCopyA((LPSTR)pSystemBuffer, uLength, "word"); //如果要向R3写数据,也是写入这个缓存中 break; case IOCTL_KILL_PROCESS: DbgPrint("51[asm][%s:%d] Kill Process pid:%p", __FUNCTION__, __LINE__, pSystemBuffer); break; default: break; } Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = uLength; // 返回值的长度要返回去 /* 1. 这个函数表示 请求是否结束 2. 第一个参数:Irp(I/O Request Packet) 把所有的请求参数疯转到这个结构体了 3. 第二个参数,让挂起的线程快速恢复,把线程的优先级往上提; 4. 当前IO_NO_INCREMENT: 不要提升优先级,什时候唤醒就什么时候结束 5. 除了入口函数和卸载函数外,其他全部函数都是一样的,要这样处理; */ IoCompleteRequest(Irp,IO_NO_INCREMENT); return STATUS_SUCCESS; //return IoCallDriver(Irp); // 分层驱动,这里进入下一层驱动来获取需要的值 }