ring 0とring 3層間のインタラクション
11647 ワード
Windowsのring 0レイヤの開発を行う場合は、ring 3レイヤと対話する必要があります.データ間の相互転送を行う.利用可能な方法はDeviceIoCntrol,ReadFileです.私は普段、ring 3とring 0層でDeviceIoControlでデータ転送をしています.今日はDeviceIoControlとring 0のイベント通知ring 3を書きます!
まずドライバをロードした後、ring 3レイヤでCreateFile()を呼び出してring 0レイヤで生成されたLinkNameを開き、デバイスオブジェクトのハンドルを取得します.次にDeviceIoControl()を呼び出し、Io ManngerによってIRPパケットを構築して送信する
BOOL WINAPI DeviceIoControl(_in HANDLE hDevice,//デバイスオブジェクトのハンドル__inDWORD dwIoControl Code,//IO制御コード__in_opt LPVOID lpInBuffer,//ring 3からring0のバッファ__inDWORD nInBufferSize,//バッファサイズ__out_opt LPVOID lpOutBuffer,//ring 3からring 0を発行してデータを返すバッファ__inDWORD nOutBufferSize,//アウトバッファサイズ_out_opt LPDWORD lpBytesReturned,//転送データサイズ_inout_opt LPOVERLPPED lpOverlapped//オーバーラップ構造、同期/非同期);
まずring 3とring 0のインタラクションには3つの方法があります.
METHOD_BUFFERED:バッファモード
SystemBuffer:ring 3とring 0の間でシステムによるデータのコピー
ring 0レイヤで、DriverObject->MajorFunction[IRP_MJ_DEVICEIOCONTROL]で設定したインスタンスでは、次のようになります.
Io処理が完了する後、Irp->IoStatusについて.Information = OutputSize;//IoManagerがコピーしたメモリサイズを返すように伝える
METHOD_IN_DIRECTとMETHOD_OUT_DIRECTダイレクトメモリモード
Dricet in/out:MDLで物理ページをロックし、ring 3,ring 0の間で直接読み書きし、一般的にMETHOD_を使用するIN_DIRECT
バッファモードと同様に、ユーザが提供する入力バッファの内容は、IRPにおけるpIrp->AssociatedIrpにコピーされる.SystemBufferメモリアドレス、コピー長はDeviceIoControlが指定した入力バイト数です.
ダイレクトメモリモードでは、オペレーティングシステムがDeviceIoControlで指定した出力バッファの物理ページをロックし、カーネルモードアドレスの下でアドレスを再マッピングします.大量のデータの交流に適して、しかもMETHOD_に対してNEITHERの方が安全です.
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]のルーチンでInputBufferとOutBufferを取得する
処理完了後
METHOD_NEITHER:Neitherモード
UserBuffer:何でもない方法で、lpInBufferはIoManagerによってコピー転送され、OutBufferはユーザーアドレス空間に直接アクセスし、危険であり、スレッド切り替えはUserBufferに影響を与える可能性があります.例えば、我々のプロセスAで発行されるIo制御コードは、OutBufferのアドレスがプロセスAにおいて例えば0 x 0018 ff 44であり、このときring0層においてOutBuffer=Irp->UserBufferによって得られるアドレスも0 x 0018 ff 44である.ring 0層のアドレス空間はシステム全体に共通している.ring 0レイヤでOutBufferにアクセスした場合、CPUのユーザ空間がプロセスBに切り替わり、ring 0が0 x 0018 ff 44のアドレスを読み書きしている間にクラッシュする可能性が高い.このときの0 x 0018 ff 44はプロセスBに属するアドレス空間であるため、このアドレス空間が物理ページをマッピングしているかどうかは不明であり、なければクラッシュする.
DeviceIoControl()のいくつかのパターンが書かれていますが、次にring 0がイベント通知ring 3を介して通知されることをまとめます.たとえば、ring 0のモニタリングプログラムで、指定したイベントが発生したときにring 3を通知するアプリケーションで、このときにイベント通知が使用されます.ネーミングイベントを使用して通知されます.ネーミングイベントの作成には、ring 0でイベントを作成し、ring 3でイベントハンドルを取得し、イベントを待つ2つの方法があります.もう1つはring 3がイベントを作成し、ring 0からイベントハンドルを取得することです.
まず、ring 0でイベントを作成し、ring 3で待機します.
名前付きイベントの名前は必ず\BaseNamedObjects\のディレクトリの下にあり、WinObjで指定されたディレクトリの下に作成された名前付きイベントが表示されます.
WDKによるIoCreateNotificationEvent()の説明
ring 3層でOpenEvent()関数を呼び出してイベントを開きます.MSDNはOpenEvent()について次のように説明します.
英語のドキュメントを自分で翻訳します.
dwDesiredAccess:アクセス方法を説明し、オブジェクトのセキュリティ属性が許可しないアクセス方法であれば失敗します.
DELETE(0 x 001,000 L)オブジェクトを削除する必要があります
READ_CONTROL (0x00020000L)
SYNCRONIZE(0 x 01000000 L)はイベントオブジェクトに同期してアクセスし、スレッドがイベントオブジェクトがSignaled Stateになるのを待つことを許可する
WRITE_DAC (0x00040000L)
WRITE_OWNER (0x00080000L)
lpName:名前付きイベントの名前ここで「Global\NotifyEvent」名前付きイベントはシステム全体に属します
その後ring 3のスレッドで待機できます
ring 0でイベントをトリガーする
ring 0のUnloadDriver()ルーチンでイベントハンドルを閉じ、参照カウントを1減らします.
ring 0から名前付きイベントを作成するにはring 3で操作し、ring 0リソースの使用量が大きくなります.一般的にring 3でイベントオブジェクトを作成し、ring 0で操作します.
イベントハンドルをring 0//DeviceIoControl()に送信します.
ring 0でイベントハンドルを使用してイベントオブジェクトを取得するObReferenceObjectByHandle()
ring 0でKeSetEvent()を呼び出してイベントをSignaled Stateにすると、ring 3がイベント応答を待つスレッドが下に実行されます.
ring 0でKeWaitForSigleObject()を呼び出してイベントオブジェクトを待機
まずドライバをロードした後、ring 3レイヤでCreateFile()を呼び出してring 0レイヤで生成されたLinkNameを開き、デバイスオブジェクトのハンドルを取得します.次にDeviceIoControl()を呼び出し、Io ManngerによってIRPパケットを構築して送信する
BOOL WINAPI DeviceIoControl(_in HANDLE hDevice,//デバイスオブジェクトのハンドル__inDWORD dwIoControl Code,//IO制御コード__in_opt LPVOID lpInBuffer,//ring 3からring0のバッファ__inDWORD nInBufferSize,//バッファサイズ__out_opt LPVOID lpOutBuffer,//ring 3からring 0を発行してデータを返すバッファ__inDWORD nOutBufferSize,//アウトバッファサイズ_out_opt LPDWORD lpBytesReturned,//転送データサイズ_inout_opt LPOVERLPPED lpOverlapped//オーバーラップ構造、同期/非同期);
まずring 3とring 0のインタラクションには3つの方法があります.
METHOD_BUFFERED:バッファモード
SystemBuffer:ring 3とring 0の間でシステムによるデータのコピー
ring 0レイヤで、DriverObject->MajorFunction[IRP_MJ_DEVICEIOCONTROL]で設定したインスタンスでは、次のようになります.
1 PIO_STACK_LOCATION IrpSp;
2 IrpSp = IoGetCurrentIrpStackLocation(Irp); // Irp
3 InputBuffer = Irp->AssociatedIrp.SystemBuffer; //InputBuffer , IoManager
4 OutputBuffer = Irp->AssociatedIrp.SystemBuffer; // Iomanager
5 InputSize = IrpSp->Parameters.DeviceIoControl.InputBufferLength;
6 OutputSize = IrpS->Parameters.DeviceIoControl.OutputBufferLength;
Io処理が完了する後、Irp->IoStatusについて.Information = OutputSize;//IoManagerがコピーしたメモリサイズを返すように伝える
METHOD_IN_DIRECTとMETHOD_OUT_DIRECTダイレクトメモリモード
Dricet in/out:MDLで物理ページをロックし、ring 3,ring 0の間で直接読み書きし、一般的にMETHOD_を使用するIN_DIRECT
バッファモードと同様に、ユーザが提供する入力バッファの内容は、IRPにおけるpIrp->AssociatedIrpにコピーされる.SystemBufferメモリアドレス、コピー長はDeviceIoControlが指定した入力バイト数です.
ダイレクトメモリモードでは、オペレーティングシステムがDeviceIoControlで指定した出力バッファの物理ページをロックし、カーネルモードアドレスの下でアドレスを再マッピングします.大量のデータの交流に適して、しかもMETHOD_に対してNEITHERの方が安全です.
1 #define CTL_CODE( DeviceType, Function, Method, Access ) ( \
2 ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) )
3 #define CTL_MDL \
4 CTL_CODE(FILE_DEVICE_UNKNOWN,0x830,METHOD_IN_DIRECT,FILE_ANY_ACCESS)
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]のルーチンでInputBufferとOutBufferを取得する
1 InputBuffer = Irp->AssociatedIrp.SystemBuffer;
2 OutputBuffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress,NormalPagePriority);
3 InputSize = pIrp->Parameters.DeviceIoControl.InputBufferLength;
4 OutputSize = pIrp->Parameters.DeviceIoControl.OutputBufferLength;
5 ulIoContorlCode = pIrp->Parameters.DeviceIoControl.IoControlCode;
処理完了後
1 Irp->IoStatus.Status = STATUS_SUCCESS;
2 Irp->IoStatus.Information = OutputSize;
3 IoCompleteRequest(Irp,IO_NO_INCREMENT);
METHOD_NEITHER:Neitherモード
UserBuffer:何でもない方法で、lpInBufferはIoManagerによってコピー転送され、OutBufferはユーザーアドレス空間に直接アクセスし、危険であり、スレッド切り替えはUserBufferに影響を与える可能性があります.例えば、我々のプロセスAで発行されるIo制御コードは、OutBufferのアドレスがプロセスAにおいて例えば0 x 0018 ff 44であり、このときring0層においてOutBuffer=Irp->UserBufferによって得られるアドレスも0 x 0018 ff 44である.ring 0層のアドレス空間はシステム全体に共通している.ring 0レイヤでOutBufferにアクセスした場合、CPUのユーザ空間がプロセスBに切り替わり、ring 0が0 x 0018 ff 44のアドレスを読み書きしている間にクラッシュする可能性が高い.このときの0 x 0018 ff 44はプロセスBに属するアドレス空間であるため、このアドレス空間が物理ページをマッピングしているかどうかは不明であり、なければクラッシュする.
#define CTL_CODE( DeviceType, Function, Method, Access ) ( \
((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) )
#define CTL_MDL \
CTL_CODE(FILE_DEVICE_UNKNOWN,0x830,METHOD_NEITHER,FILE_ANY_ACCESS)
1 PIO_STACK_LOCATION IrpSp;
2 IrpSp = IoGetCurrentIrpStackLocation(Irp);
3 pvInputBuffer = IrpSp->Parameters.DeviceIoControl.Type3InputBuffer;
4 pvOutputBuffer = Irp->UserBuffer; //UserBuffer
5 ulOutputLen = IrpSp->Parameters.DeviceIoControl.OutputBufferLength;
6 ProbeForWrite(pvOutputBuffer,ulOutputLen,sizeof(CHAR)); // , ProbeForRead()
7 ulIoControlCode = IrpSp->Parameters.DeviceIoControl.IoControlCode;
DeviceIoControl()のいくつかのパターンが書かれていますが、次にring 0がイベント通知ring 3を介して通知されることをまとめます.たとえば、ring 0のモニタリングプログラムで、指定したイベントが発生したときにring 3を通知するアプリケーションで、このときにイベント通知が使用されます.ネーミングイベントを使用して通知されます.ネーミングイベントの作成には、ring 0でイベントを作成し、ring 3でイベントハンドルを取得し、イベントを待つ2つの方法があります.もう1つはring 3がイベントを作成し、ring 0からイベントハンドルを取得することです.
まず、ring 0でイベントを作成し、ring 3で待機します.
名前付きイベントの名前は必ず\BaseNamedObjects\のディレクトリの下にあり、WinObjで指定されたディレクトリの下に作成された名前付きイベントが表示されます.
#define EVENT_NAME L"\\BaseNamedObjects\\NotifyEvent"
1 PKEVENT EventObject = NULL;
2 HANDLE hEvent = NULL;
3 UNICODE_STRING uniEventName;
4 RtlInitUnicodeString(&uniEventName,EVENT_NAME);
5 EventObject = IoCreateNotificationEvent(&uniEventName,&hEvent); // , Signaled State
KeClearEvent(EventObject); // Unsignaled State
WDKによるIoCreateNotificationEvent()の説明
PKEVENT //
IoCreateNotificationEvent(
IN PUNICODE_STRING EventName, //
OUT PHANDLE EventHandle //
);
ring 3層でOpenEvent()関数を呼び出してイベントを開きます.MSDNはOpenEvent()について次のように説明します.
HANDLE WINAPI OpenEvent(
__in DWORD dwDesiredAccess,
__in BOOL bInheritHandle,
__in LPCTSTR lpName
);
英語のドキュメントを自分で翻訳します.
dwDesiredAccess:アクセス方法を説明し、オブジェクトのセキュリティ属性が許可しないアクセス方法であれば失敗します.
DELETE(0 x 001,000 L)オブジェクトを削除する必要があります
READ_CONTROL (0x00020000L)
SYNCRONIZE(0 x 01000000 L)はイベントオブジェクトに同期してアクセスし、スレッドがイベントオブジェクトがSignaled Stateになるのを待つことを許可する
WRITE_DAC (0x00040000L)
WRITE_OWNER (0x00080000L)
lpName:名前付きイベントの名前ここで「Global\NotifyEvent」名前付きイベントはシステム全体に属します
HEVENT hEvent = OpenEvent(SYNCHRONIZE,FALSE,L"Global\\NotifyEvent");
その後ring 3のスレッドで待機できます
WaitForSingleObject(hEvent,INFINITE)==WAIT_OBJECT_0 //
ring 0でイベントをトリガーする
KeSetEvent(EventObject,0,FALSE); // Signaled State ,ring3 WaitForSingleObject() ,
KeClearEvent(EventObject); // Unsignaled State
ring 0のUnloadDriver()ルーチンでイベントハンドルを閉じ、参照カウントを1減らします.
ZwClose(hEvent);
ring 0から名前付きイベントを作成するにはring 3で操作し、ring 0リソースの使用量が大きくなります.一般的にring 3でイベントオブジェクトを作成し、ring 0で操作します.
//MSDN CreateEvent()
HANDLE WINAPI CreateEvent(
__in_opt LPSECURITY_ATTRIBUTES lpEventAttributes,
__in BOOL bManualReset,
__in BOOL bInitialState,
__in_opt LPCTSTR lpName
);
HANDLE hEvent = CreateEvent(NULL,FALSE,FALSE,NULL) //
イベントハンドルをring 0//DeviceIoControl()に送信します.
ring 0でイベントハンドルを使用してイベントオブジェクトを取得するObReferenceObjectByHandle()
NTSTATUS
ObReferenceObjectByHandle(
IN HANDLE Handle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_TYPE ObjectType OPTIONAL,
IN KPROCESSOR_MODE AccessMode,
OUT PVOID *Object,
OUT POBJECT_HANDLE_INFORMATION HandleInformation OPTIONAL
);
PKEVENT EventObject ;
NTSTATUS Status = STATUS_SUCCESS;
// , ObDrefrenceObject(),
Status =ObReferenceObjectByHandle(hEvent,
SYNCHRONIZE,
*ExEventObjectType,
KernelMode,
&EventObject,
NULL);
//WDK KeSetEvent
LONG
KeSetEvent(
IN PRKEVENT Event,
IN KPRIORITY Increment,
IN BOOLEAN Wait
);
ring 0でKeSetEvent()を呼び出してイベントをSignaled Stateにすると、ring 3がイベント応答を待つスレッドが下に実行されます.
ring 0でKeWaitForSigleObject()を呼び出してイベントオブジェクトを待機
// WDK
NTSTATUS
KeWaitForSingleObject(
IN PVOID Object,
IN KWAIT_REASON WaitReason,
IN KPROCESSOR_MODE WaitMode,
IN BOOLEAN Alertable,
IN PLARGE_INTEGER Timeout OPTIONAL // NULL ring3 INFINITE
);
Status = KeWaitForSingleObject(EventObject,
Executive, KernelMode, FALSE, NULL);