内核知识第七讲,内核中设备常用的三种通信方式,以及控制回调的编写
内核知识第七讲,内核中设备常用的三种通信方式,以及控制回调的编写
一丶ring3和ring0下的三种通讯方式
ring3和ring0下有常用三种通信方式:
1.缓冲区通信方式
2.直接IO通信方式
3.其它通信方式
缓冲区通信方式
我们的ring3和ring0通讯的时候.ring3会给一个虚拟地址. 然后内核中的参数会通过IRP来获取.
其中有个缓冲区. 我们只要操作这个缓冲区.那么对应的就是操作了三环的缓冲区.
例如:
当我们三环和0环通信的时候, 3环如果选择的是缓冲区通信. 那么久类似于上图. 操作系统会在高2G申请一个额外的缓冲区.
然后ring3下的缓冲区拷贝到里面. 然后我们的内核程序操作这个缓冲区之后. 操作系统将这个缓冲区的数据重新写入到ring3下的虚拟缓冲区中.
优点:
优点是安全.操作系统会创建一个中间层的缓冲区让我们进行操作.然后操作中间层就相当于操作ring3的缓冲区.
缺点:
高2G内核中的内存是很宝贵的.如果我们交互的时候.传出的数据太大.那么就会消耗计算机内存资源.
如果想看完整流程图,请查看WDK的帮助文档.
2.直接IO通信方式
为什么叫做直接通信方式. 原因是 ring3可以直接和ring0进行通讯了.不需要额外的缓冲区进行操作.
ring3的虚拟内存会通过内存映射的方式.映射到高2G的内存. 然后ring3的虚拟内存进行保护. 这样我们操作ring0的高2G缓冲区.就相当于操作ring3的缓冲区.
优点:
如果数据量比较大.可以使用这种
缺点:
不安全.如果我们的ring3不进行保护.那么通过C语言进行对ring3缓冲区越界访问.那么就相当于访问ring0的物理内存了.
不过幸好.内核中提供了宏让我们自己进行操作.
lpBuff = MmGetSystemAddressForMdlSafe(pIrp->MdlAddress, NormalPagePriority);
需要注意的是我们这种通信方式获取的缓冲区不是 IRP中的 SystemBuf;
3.其它通信方式
其它通信方式,这是直接使用用户的虚拟内存,也就是IRP中的 userBuf;
二丶控制回调的编写
以前我们操作设备的时候. 都是通过Read或者Write去操作.
但是现在我们有控制了.
这个时候我们要控制设备,就要编写控制码.
控制码:
CTL_CODE(FILE_DEVICE_UNKNOWN, (CODE_BASE+(code)), METHOD_BUFFERED, FILE_ANY_ACCESS)
VC++6.0给了一个宏.而NTDDK.h中也有这个. 如果你配置好了环境,那么你就要用VC中提供的了.
控制码的格式:
设备类型,控制码,通讯方式. 权限.
如果用我们的上面的宏,则填写即可.内部会自己进行移位运算.
完整代码:
PIO_STACK_LOCATION pIrpStack = NULL; PVOID lpBuff = NULL; ULONG IoControlCode; //获取控制的控制码 ULONG InputBufferLength; //用户输入的缓冲区,这个缓冲区一般做参数 ULONG OutputBufferLength; //ring0返回出去的缓冲区.一般做写出. NTSTATUS status = STATUS_UNSUCCESSFUL;//各种变量.暂时不用管. ULONG bytes = 0; KdBreakPoint(); KdPrint(("[FirstWDK] DispatchControl PID:%d TID:%d\n", PsGetCurrentProcessId(), PsGetCurrentThreadId())); pIrpStack = IoGetCurrentIrpStackLocation(pIrp); InputBufferLength = pIrpStack->Parameters.DeviceIoControl.InputBufferLength;//获取长度 OutputBufferLength = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength;//获取输出缓冲区 IoControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;//获取控制码 lpBuff = pIrp->AssociatedIrp.SystemBuffer; switch(IoControlCode) { case MYCTL_GET_GDT_SIZE: //自定义的控制码 { break; } case MYCTL_GET_GDT: { PCONTORL_PARAMS pParams = (PCONTORL_PARAMS)lpBuff; char szGDT[6]; if (InputBufferLength < sizeof(CONTORL_PARAMS)) break; __asm { sgdt szGDT } KdPrint(("limit:%p GDT:%p\n", *(short*)szGDT, *(int*)(szGDT + 2))); if (OutputBufferLength >= sizeof(szGDT)) { RtlCopyMemory(lpBuff, szGDT, sizeof(szGDT)); bytes = sizeof(szGDT); status = STATUS_SUCCESS; } break; } case MYCTL_GET_LDT: { break; } } pIrp->IoStatus.Status = status; pIrp->IoStatus.Information = bytes; IoCompleteRequest(pIrp, IO_NO_INCREMENT); return status; }
关于自定义的控制码.
这些本质来说就是我们的宏替换.
#define CODE_BASE 0x800 #define MY_CTL_CODE(code) CTL_CODE(FILE_DEVICE_UNKNOWN, (CODE_BASE+(code)), METHOD_BUFFERED, FILE_ANY_ACCESS) #define MYCTL_GET_GDT MY_CTL_CODE(1) #define MYCTL_GET_GDT_SIZE MY_CTL_CODE(2) #define MYCTL_GET_LDT MY_CTL_CODE(3)
我们每次定义,都要写CTL_CODE.很麻烦.所以我们用心的宏替换一下即可.
PS:
当控制码为缓冲区方式,直接方式.以及其它方式的时候.我们分别从IRP中获取的参数缓冲区是不同的.
1.当我们的控制码给定的是缓冲区通信方式
如果是缓冲区通信方式,那么获得的就是IRP中的SystemBuf. 这个缓冲区可以当做ring3传递的参数.然后你往这个缓冲区写数据,则是传出的数据
2.当我们的控制码为直接IO的方式
如果是直接IO的方式.那么你要二选一. 如果你指定了用户的输入缓冲区为直接IO方式,那么对应的输出缓冲区则是缓冲区方式,
那么用户缓冲区的获得方式就是使用上面介绍直接方式的API进行获取了.
例如:
lpBuff = MmGetSystemAddressForMdlSafe(pIrp->MdlAddress, NormalPagePriority);
3.如果我们的控制码为其它方式
如果是其它方式,那么用户的缓冲区是 IRP中的UserBuf缓冲区.而输入缓冲区则是用IRP中的.Type3InputBuffer
缓冲区.
坚持两字,简单,轻便,但是真正的执行起来确实需要很长很长时间.当你把坚持两字当做你要走的路,那么你总会成功. 想学习,有问题请加群.群号:725864912(收费)群名称: 逆向学习小分队 群里有大量学习资源. 以及定期直播答疑.有一个良好的学习氛围. 涉及到外挂反外挂病毒 司法取证加解密 驱动过保护 VT 等技术,期待你的进入。
详情请点击链接查看置顶博客 https://www.cnblogs.com/iBinary/p/7572603.html
本文来自博客园,作者:iBinary,未经允许禁止转载 转载前可联系本人.对于爬虫人员来说如果发现保留起诉权力.https://www.cnblogs.com/iBinary/p/8299232.html
欢迎大家关注我的微信公众号.不定期的更新文章.更新技术. 关注公众号后请大家养成 不白嫖的习惯.欢迎大家赞赏. 也希望在看完公众号文章之后 不忘 点击 收藏 转发 以及点击在看功能. QQ群: