**内核开发第一课**
当我们在用户层(R3)调用一个API(如CreateFile),这个API在用户层最终会调用ntdll.dll,通过sysentry或int 2e 进入内核层,从用户层传过来的参数和数据会封装成一个IRP包传给内核层。
R0和R3是如何通信的:
define IOCTL_BASE 0x800
define MY_CTL_CODE(i) \
CTL_CODE \
( \
FILE_DEVICE_UNKNOWN, \
IOCTL_BASE + i, \
METHOD_BUFFERED, \
FILE_ANY_ACCESS \
)
define IOCTRL_READ_PROCESS MY_CTL_CODE(0)
define IOCTRL_WRITE_PROCESS MY_CTL_CODE(1)
define IOCTRL_SEND_TO_APP MY_CTL_CODE(2)
define WIN32_LINK_NAME “\\.\Memory”
//读取进程内存
HANDLE hFile = CreateFile(WIN32_LINK_NAME,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
AfxMessageBox(“打开文件失败”);
return;
}
DWORD dwRead = 0;
if (!DeviceIoControl(hFile, IOCTRL_READ_PROCESS,
szBuff, sizeof(szBuff),
szBuff, sizeof(szBuff),
&dwRead,
NULL))
{
AfxMessageBox("读取数据失败");
}
1)打开一个符号链接,获取设备对象句柄。
注:符号链接:相当于设备对象的别名,由于用户层不能直接打开设备对象,所以需要提供一个符号符号链接。
2)通过DeviceIoControl这个API和内核层进行通信。
参数1:设备对象句柄
参数2:控制码(需要内核执行什么操作)
参数3:输入缓冲区(需要传递给内核层的数据)
参数4:输入缓冲区长度
参数5:输出缓冲区(用户接收内核层返回给用户层的数据)
参数6:输出缓冲区大小
参数7:读取的有效字节数
参数8:同步还是异步操作
2通信方式:
1 DO_BUFFERED_IO :内核层会申请一个和用户层缓冲区大小相同的缓冲区,然后将用户层输入缓冲区的数据拷贝到这个缓冲区,内核层执行完操作后会将这个内核缓冲区的数据拷贝到用户层的输出缓冲区。
特点:是内核安全的,但是效率低,需要来回拷贝。
2 DO_DIRECT_IO:锁住进程当前的这段虚拟内存(防止进程切换),然后将这段虚拟内存映射到系统空间。
特点:高效,但是需要牺牲物理内存
IRP结构:
这里头部主要包含了R0和R3的通信方式,堆栈包含了主功能号(如这个IRP是读请求还是写请求)次功能号(修改删除等请求)还有下面对应的结构体,记录了该缓冲区的长度等等各种信息。
内核框架执行流程:
R0层:
1、创建服务
2、去服务查找驱动路径,通过对象管理器生成驱动对象。并传递给DriverEntry,执行DriverEntry入口函数。
3、创建设备对象
4、创建设备符号链接
5、如果是过滤驱动,创建过滤驱动对象并绑定
6、注册分发函数
R3层:
R3打开符号链接,生成设备句柄,通过DeviceIoControl将命令和数据封装成IRP发送给内核层,内核层通过IRP获取应用层发送的命令和数据,交给对应的分发函数进行处理。