欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

驱动学习笔记三

驱动学习笔记三

驱动学习笔记(三)
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;

上传的附件 我的工具和HelloDDKsys.rar

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途。
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功