驱动学习笔记(三)
1:学了这么多,来个经典的HelloWorld吧,源码如下很简单:
#include <ntddk.h>
// 提供一个Unload函数只是为了能够支持动态卸载,防止驻留内存
VOID DriverUnload(PDRIVER_OBJECT driver)
{
// 但什么都不做,只打印一句话:
DbgPrint("first: Our driver is unloading…\r\n");
}
// DriverEntry,入口函数。相当于main。
NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path)
{
#if DBG//调试命令
// _asm int 3
#endif
// 这是我们的内核模块的入口,可以在这里写入我们想写的东西。
// 我在这里打印一句话。
DbgPrint("first: Hello, world!");
// 设置一个卸载函数便于这个函数能退出。
driver->DriverUnload = DriverUnload;
return STATUS_SUCCESS;//返回成功,否则不执行
}
使用makefile文件和sources文件在DDK的build命令行下编译,记住,源文件扩展名一定要用.c,明确规定驱动
的入口一定要是C程序编译方式,否则将引起错误,若想在C++中编写驱动,前面要加上extern "C",表示以C程序
方式编译。编译后会得到一个sys文件。怎么安装驱动程序呢?书上介绍的是使用srvinstw.exe,可是我嫌他烦,
于是自己写了个加载驱动的程序,MFC写的,一键安装卸载驱动,在附件里,观察驱动输出
信息使用debugview.exe来查看。我们的第一个驱动就生成了。开启虚拟机来看一下效果吧。直接使用windbg看得
也比较清楚,可以正确加载和卸载。
若果将上面的调试命令前双斜线去掉,就可以在WinDbg中调试了,在windbg中可以清清楚楚看到执行了哪些语句。
无聊下我给他拖到IDA中看了一下,我只会F5,结果一看啊,跟我写的差不多,IDA太牛叉了。学驱动嘛,会编也要会
逆,不仅提高自己的编程知识,也能快速从别人那获得最新的知识。话说我汇编学的比较烂,闲来无事就找了个简单
的驱动逆了一下,下面这个驱动程序是驱动开发技术详解上的一个HelloNT驱动的实例,没做多少事,以前在刚想学
驱动编程时从网上下的,反正自己也没那么高水平,一下子逆个高难度的,就从最简单的开始吧。为以后学习打好基
础,更想学习好IDA这个工具,学习好静态分析,以后编写的驱动我都拿来用IDA分析一遍,训练自己的学习能力。我是
菜鸟,不对的地方欢迎指正。源文件及我用到的自己的加载驱动的程序我上传在附件中,附件我的程序有时运行在别得
机器上可能会出现MFC那几个动态链接库缺少的情况,我都打包在里面,加载驱动的程序我是照着网上的例子自己写的
很粗糙,也很简单,就是利用服务控制管理器(SCM)这类函数来创建加载服务。
/***************1:首先是入口函数,DriverEntry,下面是逆过后的函数***********************************/
int __stdcall DriverEntry(_DRIVER_OBJECT *pDriverObject, _UNICODE_STRING *pRegistryPath)
{
int v3; // ST04_4@1
DbgPrint("Enter DriverEntry\n");
pDriverObject->DriverUnload = HelloDDKUnload;
pDriverObject->MajorFunction[0] = HelloDDKDispatchRoutine;
pDriverObject->MajorFunction[2] = HelloDDKDispatchRoutine;
pDriverObject->MajorFunction[4] = HelloDDKDispatchRoutine;
pDriverObject->MajorFunction[3] = HelloDDKDispatchRoutine;
v3 = CreateDevice(pDriverObject);
DbgPrint("DriverEntry end\n");
return v3;
}
基本还原的差不多了,也不需要看什么了,唯一一个值得注意的就是V3这个局部变量,虽然定义为整型,但是依据
这一句v3 = CreateDevice(pDriverObject);和 return v3;在加上驱动入口的返回值是NTSTATUS,可以明确知道
他就是NTSTATUS这个驱动编程中最常见的返回类型了。我们看看汇编代码什么样的
INIT:00010810 ;INIT指明此函数只在加载的时候需要载入,后可被卸载
INIT:00010810 ; int __stdcall DriverEntry(_DRIVER_OBJECT *pDriverObject, _UNICODE_STRING *pRegistryPath)
INIT:00010810 _DriverEntry@8 proc near ; CODE XREF: GsDriverEntry(x,x)+Bj
INIT:00010810
INIT:00010810 status = dword ptr -4
INIT:00010810 pDriverObject = dword ptr 8
INIT:00010810 pRegistryPath = dword ptr 0Ch
INIT:00010810
INIT:00010810 mov edi, edi
INIT:00010812 push ebp
INIT:00010813 mov ebp, esp
INIT:00010815 push ecx
INIT:00010816 push offset aEnterDriverent ; 字符串enter DriverEntry
INIT:0001081B call _DbgPrint ; 调用DbgPrint打印上面那串字符串
INIT:00010820 add esp, 4
INIT:00010823 mov eax, [ebp+pDriverObject]
INIT:00010826 mov dword ptr [eax+34h], offset ?HelloDDKUnload@@YGXPAU_DRIVER_OBJECT@@@Z
; 我们的卸载函数
INIT:0001082D mov ecx, [ebp+pDriverObject]
INIT:00010830 mov dword ptr [ecx+38h], offset ?HelloDDKDispatchRoutine@@YGJPAU_DEVICE_
OBJECT@@PAU_IRP@@@Z ; 下面是各种IRP的分发函数
INIT:00010837 mov edx, [ebp+pDriverObject]
INIT:0001083A mov dword ptr [edx+40h], offset ?HelloDDKDispatchRoutine@@YGJPAU_DEVICE_
OBJECT@@PAU_IRP@@@Z ; HelloDDKDispatchRoutine(_DEVICE_OBJECT *,_IRP *)
INIT:00010841 mov eax, [ebp+pDriverObject]
INIT:00010844 mov dword ptr [eax+48h], offset ?HelloDDKDispatchRoutine@@YGJPAU_DEVICE_
OBJECT@@PAU_IRP@@@Z ; HelloDDKDispatchRoutine(_DEVICE_OBJECT *,_IRP *)
INIT:0001084B mov ecx, [ebp+pDriverObject]
INIT:0001084E mov dword ptr [ecx+44h], offset ?HelloDDKDispatchRoutine@@YGJPAU_DEVICE_
OBJECT@@PAU_IRP@@@Z ; HelloDDKDispatchRoutine(_DEVICE_OBJECT *,_IRP *)
INIT:00010855 mov edx, [ebp+pDriverObject]
INIT:00010858 push edx ; pDriverObject
INIT:00010859 call ?CreateDevice@@YGJPAU_DRIVER_OBJECT@@@Z ; 调用创建设备函数(CreateDevice),传入驱动对象指针_DRIVRR_OBJECT
INIT:0001085E mov [ebp+status], eax
INIT:00010861 push offset aDriverentryEnd ; 看下面的那个DbgPrint就知道上面这个压栈的只是一串提示字符串罢了
INIT:00010866 call _DbgPrint
INIT:0001086B add esp, 4
INIT:0001086B add esp, 4
INIT:0001086E mov eax, [ebp+status]
INIT:00010871 mov esp, ebp
INIT:00010873 pop ebp
INIT:00010874 retn 8
INIT:00010874 _Drive
////////////////逆向CreateDevice函数////////////////////////////////////
2:接着看看最重要的创建设备函数。先看一下IDA逆向后的代码;能看懂的我都注释了
其实看名字可以猜的,依照我们编程的习惯
int __stdcall CreateDevice(_DRIVER_OBJECT *pDriverObject)
{
int result; // eax@2
void *v2; // 这里的指针类型返回值都被用void代替了
void *v3; // edx@3
_UNICODE_STRING DeviceName; // UnicodeString设备名
void *pDevExt; // [sp+8h] [bp-1Ch]@3
int status; // [sp+Ch] [bp-18h]@1
_UNICODE_STRING symLinkName; //符号链接名
_UNICODE_STRING devName; // [sp+18h] [bp-Ch]@1
void *pDevObj; // [sp+20h] [bp-4h]@1
RtlInitUnicodeString(&devName, L"\\Device\\MyDDKDevice");//初始化设备名
*(_DWORD *)&DeviceName.Length = *(_DWORD *)&devName;-|
}这两句应该就是把devName赋值给DeviceName方便
}IoCreateDevice去使用
DeviceName.Buffer = devName.Buffer;------------------|
//从这个函数的最后一个参数(PDEVICE_OBJECT *)&pDevObj来看,上面的这个变量void *pDevObj;返回类型就是
//PDEVICE_OBJECT,IDA把变量pDevObj的类型理解成void*,其实是PDEVICE_OBJECT,又进一步了
status = IoCreateDevice(pDriverObject, 0x14u, &DeviceName, 0x22u, 0, 1, (PDEVICE_OBJECT *)&pDevObj);
//下面判断返回值的
if ( status >= 0 )
{
//4u不知道是个什么家伙,但是pDevObj我们知道是PDEVICE_OBJECT这个结构体,查看DDK这个结构体说明
//+ 7表示PDEVICE_OBJECT结构体偏移7*4==28字节处,根据我在驱动编程笔记一中提到的DEVICE_OBJECT;
//在+0x01c(+28处) 为Flags标志,那么这一句可以理解为pDevObj->Flags设置标志,具体这个标志我就不
//清楚了
*((_DWORD *)pDevObj + 7) |= 4u;
/*******************************************/
//pDevObj + 10==DEVICE_OBJECT结构体偏移10*4=40字节处,即偏移+28H处+0x028 DeviceExtension :
//呵呵,看来是设备扩展,这么就容易理解了,pDevExt是设备扩展结构体指针,下面这几句大概都是跟
//这个结构体有关的,从中可以大概猜出这个结构体有哪些成员,此句就是初始化设备扩展
pDevExt = (void *)*((_DWORD *)pDevObj + 10);
/*******************************************/
//下面这一句表明这个结构体的第一个成员就是设备对象,计算机总是从零开始计算的
*(_DWORD *)pDevExt = pDevObj;
v2 = pDevExt;//从这可知v2是设备扩展结构体指针
//第二个成员是设备名称,UNICODE_STRING
*((_DWORD *)pDevExt + 1) = *(_DWORD *)&devName;
*((_DWORD *)v2 + 2) = devName.Buffer;
//初始化符号链接名,用于下面创建符号链接
RtlInitUnicodeString(&symLinkName, L"\\??\\HelloDDK");
v3 = pDevExt;
//设备扩展的第三个成员就是符号链接名
*((_DWORD *)pDevExt + 3) = *(_DWORD *)&symLinkName;
*((_DWORD *)v3 + 4) = symLinkName.Buffer;
status = IoCreateSymbolicLink(&symLinkName, &devName);//创建符号链接,判断返回值
if ( status >= 0 )
{
result = 0;
}
else
{
IoDeleteDevice((PDEVICE_OBJECT)pDevObj);//如果创建符号链接失败,则删除创建成功的设备
result = status;
}
}
else
{
result = status;//从此处可知前面定义的int result变量,其实也是个NTSTATUS类型
}
return result;
}
/*******************下面给出我的伪代码**************************************/
NTSTATUS _stdcall CreateDevice(_DRIVER_OBJECT *pDriverObject)
{
NTSTATUS result;
NTSTATUS status;
PDEVICE_OBJECT pDevObj;//设备对象
_UNICODE_STRING DeviceName;//设备名称
_UNICODE_STRING symLinkName; //符号链接名
PDEVICE_EXTENSION pDevExt;//设备扩展
RtlInitUnicodeString(&DeviceName, L"\\Device\\MyDDKDevice");//初始化设备名
status = IoCreateDevice(pDriverObject, 0x14u, &DeviceName, 0x22u, 0, 1, (PDEVICE_OBJECT *)&pDevObj);
if(status>=0)//这一句其实就是我们常用判断是否成功的NT_SUCCESS(status)
{
//如果创建成功,那么就设置相关标志并且填充设备扩展结构体
pDevObj->Flags |= 4u;
pDevExt = (PDEVICE_EXTENSION)=pDevObj->DeviceExtension;
pDevExt->pDevObj = pDevObj;
pDevExt->DevName = DeviceName;
pDevExt->SymboName = symLinkName;
//还原的设备扩展结构体在最后面
RtlInitUnicodeString(&symLinkName, L"\\??\\HelloDDK");
status = IoCreateSymbolicLink(&symLinkName, &devName);//创建符号链接,判断返回值
if(status<=0)
{
IoDeleteDevice((PDEVICE_OBJECT)pDevObj);//如果创建符号链接失败,则删除设备
result = status;
}
else {result = status;}
}
retern result;
}
/********************3:分发函数******************************/
//分发函数比较简单,给出IDA F5后的代码,一目了然,相应的汇编也能看得大差不差
int __stdcall HelloDDKDispatchRoutine(_DEVICE_OBJECT *pDevObj, _IRP *pIrp)
{
_IRP *v3; // edx@1
DbgPrint("Enter HelloDDKDispatchRoutine\n");
pIrp->IoStatus.___u0.Status = 0;//设置IRP状态
v3 = pIrp;
pIrp->IoStatus.Information = 0;//填充Information为0
LOBYTE(v3) = 0;
IofCompleteRequest(pIrp, v3);//设置IRP为完成
DbgPrint("Leave HelloDDKDispatchRoutine\n");
return 0;
}
/***********************4:卸载函数*********************************/
//此函数也比较简单,相信不用还原都能看的出来干什么了。
void __stdcall HelloDDKUnload(_DRIVER_OBJECT *pDriverObject)
{
void *v1; // ST04_4@3
unsigned __int16 *v2; // eax@3
_UNICODE_STRING pLinkName; // 符号链接名
_DEVICE_OBJECT *pNextObj; // 下一个设备对象
DbgPrint("Enter DriverUnload\n");
pNextObj = pDriverObject->DeviceObject;//设备链中的第一个设备
while ( pNextObj )//这个循环遍历驱动创建设备链
{
v1 = pNextObj->DeviceExtension;//获得下一个设备的设备扩展
v2 = (unsigned __int16 *)*((_DWORD *)v1 + 4);
*(_DWORD *)&pLinkName.Length = *((_DWORD *)v1 + 3);
pLinkName.Buffer = v2;
IoDeleteSymbolicLink(&pLinkName);//删除相应的符号链接
pNextObj = pNextObj->NextDevice;//指向设备链中的下一个设备
IoDeleteDevice(*(PDEVICE_OBJECT *)v1);//删除设备
}
}
/*****************5:还原的设备扩展结构体***********************/
typedef struct _DEVICE_EXTENSION {
PDEVICE_OBJECT pDevObj;
UNICODE_STRING DevName; //设备名称
UNICODE_STRING SymboName; //符号链接名
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;