內核知識第五講.驅動框架編寫,以及3環和0環通信.


 

         內核知識第五講.驅動框架編寫,以及3環和0環通信.

一丶了解內核驅動加載方式

內核加載方式有兩種方式.

1.動態加載方式.

2.靜態加載方式

動態加載方式:

 

  動態態加載方式則是調用3環API 進行代碼加載.

詳情請點擊 : 內核驅動加載工具的編寫.

 

靜態加載方式

  靜態的加載方式則是利用 后綴為.inf  的文件進行加載.

有關.inf的語法,可以百度或者通過學習WDK中提供的源碼例子進行學習.

動態加載一般很常用.

二丶驅動框架的介紹.

 

在講解內核驅動框架的是否,我們要先了解設備是什么.  設備和驅動之間的數據關系是什么.

1.什么是驅動.什么是設備

驅動:   驅動則是用來操作設備的. 

設備:   設備則是我們常說的外設. 比如鍵盤.  顯示器.  鼠標等等..

其中.任何一個驅動操作設備.  都應該提供公共的方法.

打開.  關閉.  讀. 寫. 控制....

如圖: 

  用戶操作設備的是否. 這個時候會通過內核驅動.提供的 回調方法.(也就是公共的接口)進而來操作設備.

 

 2.驅動和設備之間的關系.

驅動和設備之間的關系是一對多的關系.

驅動可以操作很多設備.

比如我們的鍵盤驅動有一個. 但是可以操作的鍵盤則有很多個. 你鍵盤壞了.換了很多.但是驅動則沒換過.

所以如果是數據關系的時候.   驅動作為外鍵放到設備表中.

例如以下:

  

設備

驅動

A鍵盤

標准驅動

B鍵盤

標准驅動

有了數據關系.那么就可以講解驅動框架了.

3.驅動框架的介紹.

驅動對象.設備對象.

在驅動中有這兩個概念. 

驅動對象:  簡單來說就是一個結構體,存儲了驅動的各種信息.

設備對象: 簡單來說也是一個結構體,存儲的是設備的各種信息.

但依據上面的數據關系來說. 設備對象中肯定會存儲驅動對象結構體的指針.  驅動對象做外鍵存儲到設備對象中.

設備對象結構體:

typedef struct _DRIVER_OBJECT {
    CSHORT Type;          //類型
    CSHORT Size;          //當前結構體大小.內核中任何一個結構體都是這兩個成員開頭.

    //
    // The following links all of the devices created by a single driver
    // together on a list, and the Flags word provides an extensible flag
    // location for driver objects.
    //

    PDEVICE_OBJECT DeviceObject;//設備對象指針,存疑? 不是說數據關系是 設備表中有驅動對象嗎. 怎么驅動對象表中有設備對象指針.???????
    ULONG Flags;          //通訊協議以及方式.

    //
    // The following section describes where the driver is loaded.  The count
    // field is used to count the number of times the driver has had its
    // registered reinitialization routine invoked.
    //

    PVOID DriverStart;
    ULONG DriverSize;
    PVOID DriverSection;
    PDRIVER_EXTENSION DriverExtension;

    //
    // The driver name field is used by the error log thread
    // determine the name of the driver that an I/O request is/was bound.
    //

    UNICODE_STRING DriverName;

    //
    // The following section is for registry support.  Thise is a pointer
    // to the path to the hardware information in the registry
    //

    PUNICODE_STRING HardwareDatabase;

    //
    // The following section contains the optional pointer to an array of
    // alternate entry points to a driver for "fast I/O" support.  Fast I/O
    // is performed by invoking the driver routine directly with separate
    // parameters, rather than using the standard IRP call mechanism.  Note
    // that these functions may only be used for synchronous I/O, and when
    // the file is cached.
    //

    PFAST_IO_DISPATCH FastIoDispatch;

    //
    // The following section describes the entry points to this particular
    // driver.  Note that the major function dispatch table must be the last
    // field in the object so that it remains extensible.
    //

    PDRIVER_INITIALIZE DriverInit;
    PDRIVER_STARTIO DriverStartIo;
    PDRIVER_UNLOAD DriverUnload;
    PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];  //提供的公共方法的接口. 創建.打開,關閉.讀寫控制... 里面存放的是函數指針.單用戶操作設備的是否.則會調用這些回調函數指針.

} DRIVER_OBJECT;
typedef struct _DRIVER_OBJECT *PDRIVER_OBJECT; 

存疑部分:

  上面標紅的部分. 不是說表關系應該是上面那種嗎.?如下圖所示

設備

驅動

A鍵盤

標准驅動

B鍵盤

標准驅動

可為何設計為這樣.

原因:

  我們的內核驅動可以操作設備. 但是我們要知道有多少設備怎么辦. 所以這里給了一個設備對象的指針. 而不是我們說的數據關系.

而在設備對象中.存儲的則是我們的驅動對象指針.

而這里的指針.則是一個鏈表形式的. 為了方便遍歷.

例如:

  

typedef struct DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT) _DEVICE_OBJECT {
    CSHORT Type;
    USHORT Size;
    LONG ReferenceCount;
    struct _DRIVER_OBJECT *DriverObject;  //驅動對象做外鍵存儲
    struct _DEVICE_OBJECT *NextDevice;      //鏈表.
    struct _DEVICE_OBJECT *AttachedDevice;
    struct _IRP *CurrentIrp;
    PIO_TIMER Timer;
    ULONG Flags;                                // See above:  DO_...
    ULONG Characteristics;                      // See ntioapi:  FILE_...
    __volatile PVPB Vpb;
    PVOID DeviceExtension;
    DEVICE_TYPE DeviceType;
    CCHAR StackSize;
    union {
        LIST_ENTRY ListEntry;
        WAIT_CONTEXT_BLOCK Wcb;
    } Queue;
    ULONG AlignmentRequirement;
    KDEVICE_QUEUE DeviceQueue;
    KDPC Dpc;

    //
    //  The following field is for exclusive use by the filesystem to keep
    //  track of the number of Fsp threads currently using the device
    //

    ULONG ActiveThreadCount;
    PSECURITY_DESCRIPTOR SecurityDescriptor;
    KEVENT DeviceLock;

    USHORT SectorSize;
    USHORT Spare1;

    struct _DEVOBJ_EXTENSION  *DeviceObjectExtension;
    PVOID  Reserved;

} DEVICE_OBJECT;

typedef struct _DEVICE_OBJECT *PDEVICE_OBJECT; 

三丶編寫驅動框架.

上面我們已經簡單的了解了驅動對象.設備對象是什么了.那么現在開始編寫驅動框架

步驟

1.首先注冊設備回調函數.當用戶對設備進行操作的是否.驅動會調用這個回調函數進行操作.

2.創建設備.創建虛擬的設備給用戶使用.

3.指定通訊方式. 什么意思?比如ring3中操作設備進行讀寫的時候 如果用ReadFile讀取.那么你們的通訊方式是否是字符串

4.創建符號連接.

  符號連接: 我們知道.在操作系統下有C盤.D盤一說.但是在驅動下面.則沒有怎么一說.只有卷一說.所以我們要綁定一下.

PS: 鑒於篇幅原因.只寫重點.如果想要完整的驅動框架. 請下載資料進行查看.

1.注冊回調函數.

  pDriverObject->MajorFunction[IRP_MJ_CREATE] = 創建的回調函數指針;
  pDriverObject->MajorFunction[IRP_MJ_CLOSE] =  關閉的回調函數指針;
  pDriverObject->MajorFunction[IRP_MJ_READ] =   讀取的回調函數指針;
  pDriverObject->MajorFunction[IRP_MJ_WRITE] =  寫入的回調函數指針;
  pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = 控制的回調函數指針;

回調函數的寫法.

NTSTATUS 自定義的函數名(__in struct _DEVICE_OBJECT *DeviceObject,
                      __inout struct _IRP *Irp)
{
 
  .........
  return STATUS_SUCCESS;
}

 

2.創建虛擬設備.

創建設備等等.都屬於IO操作.

IO操作創建設備的API

NTSTATUS 
  IoCreateDevice(
    IN PDRIVER_OBJECT  DriverObject,      //調用者驅動對象的指針.一般是驅動入口的參數
    IN ULONG  DeviceExtensionSize,           //設備擴展大小
    IN PUNICODE_STRING  DeviceName  OPTIONAL, //設備對象名稱,注意不是我們ring3下的路徑.
    IN DEVICE_TYPE  DeviceType,         //我們的設備類型
    IN ULONG  DeviceCharacteristics,      //驅動的附加信息.
    IN BOOLEAN  Exclusive,            //創建的設備是否其余的可以使用,是否獨占
    OUT PDEVICE_OBJECT  *DeviceObject        //傳出參數,設備的信息.
    );

注意紅點標注:

  在內核中並沒有路徑一說.所以這個路徑是特殊的.

UNICODE_STRING 內核中新的字符串格式.其實是一個結構體.系統提供了操作這種結構體的API

我們拼接一個路徑

UNICODE_STRING  uStrDeviceName;

RtlInitUnicodeString(&uStrDeviceName,L"\\Device\\xxx名字即可");

注意,創建設備的時候.我們前邊需要加上 \Device.  這里因為是轉義字符.所以加了好多\\

拼接好則可以給了.

status = IoCreateDevice(pDriverObject, 
                          0, 
                          &ustrDeviceName,          //設備路徑
                          FILE_DEVICE_UNKNOWN,//設備類型設置為不知道
                          FILE_DEVICE_SECURE_OPEN, 
                          FALSE,                           //是否獨占
                          &pDevObj);
  if (status != STATUS_SUCCESS)
  {
    return status;
  }        

 

3.設置通訊方式.

pDevObj->Flags |= DO_BUFFERED_IO; //指定通訊方式,為緩沖區

4.創建符號連接

我們創建的符號連接.可以通過 Win0bj工具進行查看. 這個工具可以查看所有設備.但是只有0環才可以操作.

 status = IoCreateSymbolicLink(&g_ustrSymbolName, &ustrDeviceName);
  if (status != STATUS_SUCCESS)
  {
    //刪除設備
    IoDeleteDevice(pDevObj);
    return status;
  }

注意符號連接名字.我們也是初始化得出的.

RtlInitUnicodeString(&g_ustrSymbolName, L"\\DosDevices\\xxxx名字");

完整的框架已經寫完.剩下的就是 三環和0環驅動通訊. 依賴我們寫的框架.

 

四丶三環和0環的通訊.

三環操作設備的API就是 CreateFile ReadFile.....等等.不做介紹了.

利用三環程序.操作我們的設備.從中讀取內容.

HANDLE hFile = CreateFile("\\\\?\\符號連接名稱", 
             GENERIC_WRITE | GENERIC_READ, 
             0, 
             NULL, 
             OPEN_EXISTING, 
             FILE_ATTRIBUTE_NORMAL, 
             NULL);

打開我們的設備.注意文件名並不是路徑.而是我們綁定的符號連接. 這里我們可以寫成\\?

讀取內容.

char szBuf[10];
ReadFile(hFile, szBuff, sizeof(szBuff), &dwBytes, NULL)

請注意,當讀取設備的是否.我們一開始注冊的回調函數就會來. 這時候我們給它什么都可以了.

但是此時需要講解一下通訊協議.

當讀取的是否,回調函數會來. 然后操作系統會填寫 struct _IRP 結構體.用來和我們的零環通信.

typedef struct _IRP {
  .            //省略了兩個成員,這兩個成員一個是類型.一個是大小.
  .
  PMDL  MdlAddress;
  ULONG  Flags;
  union {
    struct _IRP  *MasterIrp;
    .
    .
    PVOID  SystemBuffer;//ring3下的緩沖區.操作系統會填寫.我們給里面填寫什么內容.那么ring3就讀取到什么.
  } AssociatedIrp;
  .
  .
  IO_STATUS_BLOCK  IoStatus;
  KPROCESSOR_MODE  RequestorMode;
  BOOLEAN PendingReturned;
  .
  .
  BOOLEAN  Cancel;
  KIRQL  CancelIrql;
  .
  .
  PDRIVER_CANCEL  CancelRoutine;
  PVOID UserBuffer;
  union {
    struct {
    .
    .
    union {
      KDEVICE_QUEUE_ENTRY DeviceQueueEntry;
      struct {
        PVOID  DriverContext[4];
      };
    };
    .
    .
    PETHREAD  Thread;
    .
    .
    LIST_ENTRY  ListEntry;
    .
    .
    } Overlay;
  .
  .
  } Tail;
} IRP, *PIRP;

我們有了緩沖區,但是不知道緩沖區的大小.這個是否需要通過IO函數.從當前棧中獲取參數.

IoGetCurrentIrpStackLocation(pIrp)

返回當前IRP的棧.我們從返回值中獲取參數即可.

操作完成之后,我們完成IRP請求即可.這個IRP請求主要是為了多線程使用.有的時候可能在讀取的是否.線程就切換了.

 

ring0下的讀取回調完整編寫.

 //獲取當前irp堆棧
  PIO_STACK_LOCATION pIrpStack = NULL;
  PVOID lpBuff = NULL;
  ULONG Length = 0;

  //PsGetCurrentThreadId();
  KdBreakPoint();

  __try
  {
    pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
    //check

    lpBuff = pIrp->AssociatedIrp.SystemBuffer;
    Length = pIrpStack->Parameters.Read.Length;
    
    KdPrint(("[FirstWDK] DispatchRead\n"));
    
    RtlStringCbCopyA(lpBuff, Length, "Hello World!");
    //check


    pIrp->IoStatus.Status = STATUS_SUCCESS;
    pIrp->IoStatus.Information = 6;
    
    //完成Irp請求
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    //check
  }
  __except(1)
  {
  }


  return STATUS_SUCCESS;

 

課堂3環和0環的完整代碼資料:

  鏈接:https://pan.baidu.com/s/1edffGy 密碼:vpo0

原創不易,轉載請注明出處.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM