تفکرات صفر و یکی

نوشته های من در مورد سیستم عامل، درایور نویسی، مهندسی معکوس، امنیت و هر چیز سطح پایین دیگر

تفکرات صفر و یکی

نوشته های من در مورد سیستم عامل، درایور نویسی، مهندسی معکوس، امنیت و هر چیز سطح پایین دیگر

آخرین نظرات

در قسمت قبل طریقه ساخت یک درایور و لود کردن آن را یاد گرفتیم، و همچنین با ابزارهایی مانند OSRLoader و DbgView آشنا شدیم. در این آموزش هم قصد داریم ببینیم چگونه میتوان درایوری نوشت که بتواند با یک برنامه سطح کاربر ارتباط برقرار کند در واقع اطلاعاتی به درایور بفرسیم و دریافت کنیم.

مقدمه

در ابتدا باید بگویم مثالی که برای این آموزش انتخاب کرده ام یکی از مثالهای بسته WDK به نام ioctl است که در مسیر زیر قرار گرفته.

[WDK path]\7600.16385.1\src\general\ioctl\

اینجا دو پوشه به نامهای wdm و kmdf وجود دارد. این دو  در واقع معماری های متفاوت درایور نویسی در ویندوز است. معماری دیگری هم قبلا وجود داشت به نام VxD که مربوط به ویندوزهای نسخه 95-98 می شد و الان دیگه استفاده چندانی ندارد. آموزش هایی که من فعلا قرار می دهم بر پایه wdm است و فعلا با kmdf کاری نداریم. در این نمونه سورس روش های مختلفی که می توان میان سطح کاربر و سطح کرنل ارتباط برقرار کرد را نشان داده است. من قسمت هایی از این سورس را حذف کرده ام نسخه تغییر یافته را از این لینک می توانید بگیرید و ما در ادامه در مورد مفاهیمی مانند IOCTL, IRP, Driver Object, Device Name, Symbolic Link آشنا می شویم.

Driver Object چیست؟

هر درایوری که در سیستم لود شده باشد از دید سیستم عامل یک Driver Object به حساب می آید. در واقع در سیستم عامل ویندوز خیلی مفاهیم مثل فایل (File)، پوشه (Directory)، فرایند (Process)،‌ نخ (Thread) و ... را به صورت Object می بیند. هر کدام از این آبجکتها برای خود ساختار مشخصی دارند. برای آبجکت درایور در ویندوز ما ساختاری به نام _DRIVER_OBJECT داریم که در پایین محتویات آن را می بینید. من این محتویات را از فایل wdm.h داخل بسته WDK برداشته ام

typedef struct _DRIVER_OBJECT {
CSHORT Type;
CSHORT Size;

PDEVICE_OBJECT DeviceObject;
ULONG Flags;

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

UNICODE_STRING DriverName;

PUNICODE_STRING HardwareDatabase;

PFAST_IO_DISPATCH FastIoDispatch;

PDRIVER_INITIALIZE DriverInit;
PDRIVER_STARTIO DriverStartIo;
PDRIVER_UNLOAD DriverUnload;
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];

} DRIVER_OBJECT;

شما در آموزش اول با DriverUnload آشنا شدید که تابعی تعریف کردیم که در هنگام Unload شدن درایور توسط سیستم عامل صدا زده می شود.  از این مقادیر یکسری از نامشان تا حدودی مشخص است که چه هستند. باقی هم فعلا چون به کار ما نمیاید کاری با آنها  نداریم. چیزی که بیشتر مورد توجه ما است MajorFunction است.

MajorFunction چیست؟

اینجا ما یک آرایه از اشارگرهایی از نوع DRIVER_DISPATCH داریم و طول آرایه ما IRP_MJ_MAXIMUM_FUNCTION + 1 می باشد.  مقادیر DRIVER_DISPATCH و IRP_MJ_MAXIMUM_FUNCTION  را هم از فایل wdm.h بدست می آوریم

 DRIVER_DISPATCH در واقع تعریف یک تابع است به این صورت

DRIVER_DISPATCH (
_In_ struct _DEVICE_OBJECT *DeviceObject,
_Inout_ struct _IRP *Irp
);

typedef DRIVER_DISPATCH *PDRIVER_DISPATCH;

و IRP_MJ_MAXIMUM_FUNCTION که مقداری برابر 0x1b دارد که به صورت هگزادسیمال آمده که در واقع در مبنای دسیمال عدد ۲۷ می شود

#define IRP_MJ_MAXIMUM_FUNCTION 0x1b

این به معنی است که این آرایه می تواند ۲۸ (۲۷+۱) تابع از نوع  DRIVER_DISPATCH را در خود داشته باشد.

DriverObject->MajorFunction[0] = Function0
DriverObject->MajorFunction[1] = Function1
DriverObject->MajorFunction[2] = Function2
DriverObject->MajorFunction[3] = Function3
...
DriverObject->MajorFunction[27] = Function27

از آنجایی که این توابع برای هر ایندکس از این آرایه قرار است کار مشخصی انجام دهد ایندکس های ۰-۲۷ خود به صورت یک نام قابل فهم تعریف شده اند.

#define IRP_MJ_CREATE 0x00
#define IRP_MJ_CREATE_NAMED_PIPE 0x01
#define IRP_MJ_CLOSE 0x02
#define IRP_MJ_READ 0x03
#define IRP_MJ_WRITE 0x04
...
...
#define IRP_MJ_PNP 0x1b

در نتیجه اگر ما بخواهیم از این تعاریف استفاده کنیم به این شکل استفاده می کنیم

DriverObject->MajorFunction[IRP_MJ_CREATE] = Function0
DriverObject->MajorFunction[IRP_MJ_CREATE_NAMED_PIPE] = Function1
DriverObject->MajorFunction[IRP_MJ_CLOSE] = Function2
DriverObject->MajorFunction[IRP_MJ_READ] = Function3
...
DriverObject->MajorFunction[IRP_MJ_PNP] = Function27

هر درایور برای اینکه ارتباط با دنیای بیرون داشته باشد که این ارتباط می تواند از طرف درایورهای دیگر یا از طرف برنامه های سطح کاربر باشد. ملزم است آرایه MajorFunciton را مقدار دهی کند. بسته به نوع و کارکرد درایور شما مقداردهی این آرایه متفاوت خواهد بود. راهنمایی که همراه WDK نصب می شود برای هر نوع درایور مشخص کرده که مقدار دهی چه Major Funciton هایی واجب و کدام اختیاری است. 

اگر دقت کنید همه این تعاریف با عبارت IRP شروع می شوند. IRP مختصر شده I/O Request Packet است. در مورد IRP  در آموزش های بعدی توضیح میدهم وقتی به Filter Driver برسیم. ولی در همین حد بگویم که تمام درخواست هایی که به درایور میرسد به بصورت یک ساختار IRP در می آید.

برنامه های سطح کاربر با فراخوانی یکسری API ها می توانند درخواست خود را به درایور بفرستند. و تابع مربوط به درخواست در درایور اجرا شود  در پایین لیست API و در مقابل در خواستی که تولید می شود آورده ام. 

CreateFile ==> IRP_MJ_CREATE
ReadFile ==> IPR_MJ_READ
WriteFile ==> IRP_MJ_WRITE
CloseFile ==> IRP_MJ_CLOSE
DeviceIoControl ==> IRP_MJ_DEVICE_CONTROL

تابع CreateFile که سمت برنامه سطح کاربر استفاده می شود یک مسیر فایل به عنوان پارامتر می گیرد که با آن فایل کار کند. ما از همین تابع برای ارتباط با درایورمان استفاده می کنیم با این تفاوت که مسیری که به این تابع می دهیم یک مسیر خاص است که بعد می گویم چطور این مسیر خاص را درست کرد.. باقی توابع از هندلی که از CreateFile به دست آمده استفاده میکنند. حالا در ادامه نمونه سورسی را که آماده کرده ام بررسی میکنیم تا بیشتر با این موضوع آشنا بشویم.

توضیح در مورد این توابع و پارامترهای آنها را به عهده خودتان می گذارم به جز DeviceIoControl.

بررسی Major Function در سورس کد sioctl.c

نمونه سورسی که ما استفاده کرده ایم بخشی که Major Funciton ها را مقدار میدهد به این صورت است.

DriverObject->MajorFunction[IRP_MJ_CREATE] = SioctlCreateClose;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = SioctlCreateClose;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = SioctlDeviceControl;

در این درایور سه درخواست کنترل می شود که دو تا از این درخواست ها توسط یک تابع بررسی می شود. قبلا گفتم که یکسری درخواست ها هستند که کنترل برخی درخواست ها برایشان اجباری است.

تابع SioctlCreateClose وظیفه کنترل درخواست های IRP_MJ _CREATE و IRP_MJ_CLOSE  را دارد. کد مربوط به این تابع را در زیر می بینید. اگر دقت کنید تابع از نوع DRIVER_DISPATCH است که بالاتر دیدیم.

NTSTATUS
SioctlCreateClose(
PDEVICE_OBJECT DeviceObject,
PIRP Irp
)
{
UNREFERENCED_PARAMETER(DeviceObject);

PAGED_CODE();

Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;

IoCompleteRequest( Irp, IO_NO_INCREMENT );

return STATUS_SUCCESS;
}

اگر به کد دقت کنید می بینید تقریبا کاری انجام نمی دهد. در واقع به خاطر اجباری بودن این درخواست مجبوریم تابعی برای دریافت این درخواست راشته باشیم. حالا این کد دقیقا چه کار می کند

اولا: درخواست مربوطه توسط درایور بررسی شد و خطایی رخ نداد، با این کد

Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;

دوما: در این کد می گوییم این درخواست در این مرحله به اتمام رسیده و به سیستم عامل نیز با این تابع اعلام میکنیم. شاید الان این کد زیاد معنی نداشته باشد ولی به هر حال این کار را باید در مورد درخواست های اجباری انجام داد. من در آموزش دیگری که در مورد IRP است این مباحث را توضیح می دهم.

IoCompleteRequest( Irp, IO_NO_INCREMENT );

حالا میرسیم به تابع SioctlDeviceControl که می شود گفت مهمترین قسمت از کد این درایور است. در این تابع درخواستی که از سمت کاربر می آید بررسی می شود و متناسب با در خواست کاری را انجام می دهد. بخشی که تصمیم می گیرد بر اساس دستور سطح کاربر چکار باید کند به این صورت تعریف شده است.

switch ( irpSp->Parameters.DeviceIoControl.IoControlCode )
{
case IOCTL_SIOCTL_METHOD_BUFFERED:
// کد مربوط به این دستور
break;

case IOCTL_SIOCTL_METHOD_NEITHER:
// کد مربوط به این دستور
break;

case IOCTL_SIOCTL_METHOD_IN_DIRECT:
// کد مربوط به این دستور
break;

case IOCTL_SIOCTL_METHOD_OUT_DIRECT:
// کد مربوط به این دستور
break;

default:
}

مقدارهایی که برای هر دستور case مشخص شده فرمان هایی است که از طرف کاربر می آید این نام گذاری را توسط خودمان انجام می شود و قابل تغییر هستند. به جای هر کدام از مقادیر IOCTL_SIOCTL_METHOD_BUFFERED یا IOCTL_SIOCTL_METHOD_NEITHER و ... هر نامی را می توان تعریف کزد.

از این چهار مورد من فقط یکی را در این آموزش آورده ام، چون آوردن باقی هم حجم و هم پیچیپگی این آموزش را زیاد می کرد. در مورد این دستورات که به آنها IOCTL و چگونه تولیدشان بخشی جدا اختصاص داده ام.

دسترسی به بافر داده

از سه طریق می توانیم به بافر حاوی داده ارسالی دسترسی داشده باشیم این سه روش به قرار زیر است. 

  1. روش Buffered I/O : در این روش سیستم عامل "حافظه سیستمی" به اندازه حافظه ی داده ای که می خواهید به درایور بفرستید ایجاد میکند و داده ما را در آن قرار می دهد. و بالعکس در موقع دریافت داده سیستم عامل داده را از "حافظه سیستمی" به حافظه کاربر انتقال می دهد
  2. روش Direct I/O : در این روش سیستم عامل آدرس سطح کاربر را قفل می کند و بعد از این آدرس حافظه اصطلاحا یک MDL (Memory Descritor List) می سازد و آن را به درایور می فرستد.
  3. روش نه Buffered I/O و نه Direct I/O : در این حالت آدرس مجازی که ما در سطح کاربر به داده خود اختصاص داده ایم را عینا در سطح کرنل دریافت می کنیم pDeviceObject->Flags

نکته: به نظرم آمد که این قسمت توضیح بیشتری نیاز دارد در نتیجه پست جدایی با عنوان "روش های دسترسی به بافر داده در درایورهای ویندوزی" آماده کرده ام که بد نیست آن را هم مطالعه کنید. 

بررسی بخش دسترسی به حافظه داده از کد sioctl.c مربوط به درایور

برای دسترسی به حافظه داده ما سه کار باید انجام دهیم

اول) اندازه حافظه ورودی یا خروجی را می توانیم به صورت زیر بدست آوریم. از این لینک اطلاعات بیشتر در مورد ساختار  IO_STACK_LOCATION را دریافت کنید.

irpSp = IoGetCurrentIrpStackLocation( Irp );
inBufLength = irpSp->Parameters.DeviceIoControl.InputBufferLength;
outBufLength = irpSp->Parameters.DeviceIoControl.OutputBufferLength;

دوم) گرفتن حافظه داده مان که به این صورت بدست می آید. از این لینک اطلاعات بیشتر در مورد ساختار IRP

inBuf = Irp->AssociatedIrp.SystemBuffer;
outBuf = Irp->AssociatedIrp.SystemBuffer;

سوم) گرفتن کد IOCTL که از سمت برنامه کاربر رسیده و در دستور switch-case استفاده شده

irpSp->Parameters.DeviceIoControl.IoControlCode

NT Device Name و DOS Device Name

تا الان در مورد اینکه چگونه با تعریف Major Funciton ها درخواست هایی که به درایور ما می آیند را کنترل کنیم و همچنین مختصر توضیحی هم در رابطه با روش های موجود برای دسترسی به حافظه داده ای درخواستی مان داده ایم. حالا مساله دیگری که باید با آن آشنا باشید Device Name است. در ویندوز برای دسترسی به دستگاه ها از API های مربوط به فایل استفاده می شود مثل Files, Hard Disk, Serial Port, Parallel Port, ... و اگر قبلا با C/C++ و API های ویندوز کد سیستمی نوشته باشید احتمالا می دانید که با تابعی مثل CreateFile اول یک هندل از دستگاه مربوطه می گیریم بعد مثلا با توابعی مثل ReadFile یا WriteFile داده می خواندیم و می نوشتیم در هنگام هندل گرفتن با تابع CreateFile هم از نام هایی مانند \\.\PhysicalDrive0, \\.\PhysicalDrive1, \\.\COM1, ... استفاده می کردیم. حالا حتما می گویید این دستگاه ها چکار به درایور دارد. خوب در واقع کسی(برنامه سطح کاربر و درایور های سطح کرنل دیگر) دسترسی مستقیم به درایور شما ندارند و برای اینکه دیگران بتوانند به شما درخواستی بفرستند تا دریاور شما درخواست مربوطه را پردازش کند باید یک Device Name ساخته شود. این Device Name خود دو نوع است NT Device Name که این نام فقط قابل دسترسی برای دیگر درایورها و کدهای سطح کرنل است و برنامه های سطح کربر دسترسی به این نام ندارند. این نام ها با عبارت \Device\ شروع می شوند. دیگری DOS Device Name است که این نام امکان دسترسی برنامه های سطح کاربر را مهیا می کند که این نامها هم با عبارت \DosDevices\ شروع می شوند. کاری که شما باید انجام بدهید این است که اول NT Device Name را با تابع IoCreateDevice بسازید و بعد باید یک رشته تعریف کنید که حاوی نام DOS شما می شود و در نهایت این دو با هم توسط تابعی به نام IoCreateSymbolicLink متصل می شوند. این کار حتما باید انجام شود یعنی باید نام NT شما ایجاد شده باشد و چون برنامه سطح کاربر دسترسی به این نام ندارد نیاز است نامی تعریف کنیم که در سطح کاربر قابل دسترسی باشد.این کدی است که این نام ها را ایجاد میکند. نام های Symbolic Link با عبارت \\.\ شروع می شوند که مثال هایی برای این نوع بالاتر زده ام 

#define NT_DEVICE_NAME L"\\Device\\SIOCTL"
#define DOS_DEVICE_NAME L"\\DosDevices\\IoctlTest"

...
...

RtlInitUnicodeString( &ntUnicodeString, NT_DEVICE_NAME );

ntStatus = IoCreateDevice(
DriverObject,
0,
&ntUnicodeString,
FILE_DEVICE_UNKNOWN,
FILE_DEVICE_SECURE_OPEN,
FALSE,
&deviceObject );

...
...

RtlInitUnicodeString( &ntWin32NameString, DOS_DEVICE_NAME );

ntStatus = IoCreateSymbolicLink(
&ntWin32NameString, &ntUnicodeString );

اکثر توابع کرنلی از رشته های نوع UNICODE_STRING استفاده میکنند. این رشته ساختاری است با تعریف زیر. در نتیجه رشته خود را با استفاده از تابع RtlInitUnicodeString تبدیل به UNICODE_STRING می کنیم.

typedef struct _LSA_UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;

در مورد پارامترهای تابع IoCreateDevice

۱) DriverObject: این مقدار را از پارامتری که در DriverEntry است می توانید بگیرید

۲) DeviceExtensionSize: یک مقدار عددی است که به اندازه آن برایتان فضایی تخصیصی داده می شود و از این فضا می شود به عنوان نگهداری وضعیت های داخلی درایور استفاده کرد. ما چون نیاز نداریم صفر قرار می دهیم

۳) DeviceName: همان نام NT که بالاتر به آن اشاره کردیم که باید از نوع UNICODE_STRING باشد

۴) DeviceType: بسته به کاری که می خواهیم انجام دهیم می توانیم از نوع های تعریف شده برای device خود استفاده کنیم. این مقادیر را در این لینک می توانید ببینید. چون device ما با هیچ نوع خاصی از سخت افزار ارتباطی ندارد مقدار FILE_DEVICE_UNKNOWN را قرار می دهیم

۵) DeviceCharacteristics: یکسری خصوصیات دیگری که درایور ما می تواند داشته باشد

۶) Exclusive: این پارامتر را FALSE قرار می دهیم. در صورت TRUE بودن این پارامتر فقط یک هندل می توان از device مربوطه گرفت.

۷) DeviceObject: اینجا device ایجاد شده بر می گردد

توضیحات بیشتر تابع IoCreateDevice را در این لینک ببینید. 

ابزاری به نام WinObj وجود دارد که Device Name و Symbolic Name های تعریف شده در سیستم را نمایش می دهد. مثلا در شکل زیر من بعد از اینکه درایور را با OSR Loader لود کردم اطلاعاتی که در شکل آمده اضافه شده اند.

بررسی تابع DriverUnload

در این قسمت وقتی قرار است درایور unload شود باید یکسری تغییراتی که در سیستم داده ایم را به حالت قبل بر گردانیم در واقع Device و Symbolic Link ایجاد شده باید حذف گردد ما این کار را به این دو دستور انجام می دهیم

IoDeleteSymbolicLink( &uniWin32NameString );
IoDeleteDevice( deviceObject );

بررسی برنامه سطح کاربر با سورس فایل testapp.c 

بخش سطح کاربر به صورت کلی سه کار انجام می دهد.

اول) نصب درایور که ما در آموزش اول این کار را با ابزار OSR Loader انجام می داده ایم. ولی اینبار بد نیست ببینید بدون استفاده از ابزارهای جانبی و از طریق کدنویسی چگونه می شود این کار را انجام داد. API های استفاده شده مربوط به کار با سرویسها در ویندوز می باشد که به غیر از ایجاد سرویس از آنها برای لود درایور هم می توان استفاده کرد. بررسی توابع را به عهده ی خودتان می گذارم چون هدفم از این آموزش ها بیشتر اشنایی به مباحث کرنلی است. به هر حال

دوم) گرفتن هندل با استفاده از CreateFile که قبلا گفتم نامی که به عنوان پارامتر به این تابع می دهیم Symbolic Link ایجاد شده برای device ما است. به این صورت

if ((hDevice = CreateFile( "\\\\.\\IoctlTest",
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL)) == INVALID_HANDLE_VALUE) {
...
...
}

سوم) آماده کردن بافر خود و فرستادن دستورات IOCTL به درایور. برای فرستان یک دستور IOCTL به یک device از تابع DeviceIoControl باید استفاده کنیم. به غیر از اینکه IOCTL هایی که خودمان تعریف کرده ایم تا به device درایور خود بفرستیم. در ویندوز تعدادی IOCTL های عمومی نیز وجود دارد که کارهای خوب و بد زیادی می توان با آنها انجام داد. (به هر حالخنده)

ما به این صورت از تابع استفاده کرده ایم

bRc = DeviceIoControl ( hDevice,
(DWORD) IOCTL_SIOCTL_METHOD_BUFFERED,
&InputBuffer,
(DWORD) strlen ( InputBuffer )+1,
&OutputBuffer,
sizeof( OutputBuffer),
&bytesReturned,
NULL
);

توضیح پارامترها:

۱) hDevice: هندل خروجی که از CreateFile می گیرید را اینجا باید قرار دهید

۲) dwIoControlCode: کد IOCTL ما که پایین تر توضیح داده ام چگونه این کد تولید می شود

۳) lpInBuffer: بافری است که ما می خواهیم به درایور بفرستیم این بافر می تواند هر فرمتی مثلا یک رشته یا struct باشد

۴) nInBufferSize: طول بافر ورویدی

۵) lpOutBuffer: بافری که درایور قرار است اطلاعاتی در آن قرار دهد

۶) nOutBufferSize: طول بافر خروجی

۷) lpBytesReturned: اندازه اطلاعاتی که در بافر خروجی ریخته می شود. در واقع اندازه واقعی ممکن از کو چکتر از اندازه بافر باشد که این پارامتر مشخص می کند

۸) lpOverlapped: این پارامتر برای ما مهم نیست.

توضیحات بیشتر اینجا

ساختار IOCTL چیست؟

تا الان باید بدانید ioctl چیست. یک کد که در واقع یک عدد است و  ما در سمت سطح کاربر می خواهیم بسازیم و از طریق آن به درایور خود بگوییم فلان کار را انجام بدهد. ساختاری که این کد ۳۲ بیتی دارد در شکل زیر نمایش داده شده است.

 ioctl را با این ماکرو ایجاد می کنند. که IOCTL_Device_Function نامی اختیاری است.

#define IOCTL_Device_Function CTL_CODE(DeviceType, Function, Method, Access)

توضیح پارامترها

۱) DeviceType: از آنجایی که درایور ما برای سخت افزار خاصی نیست کاری با مقدارهای تعریف شده نداریم. مقادیر که می دهیم باید مقداری بیشتر از 0x8000 داشته باشد چون مقادیر زیر 0x8000 برای ماکروسافت رزرو شده اند

۲) FunctionCode: این مقدار در واقع عملیاتی است که قرار است اتفاق بیافتد خود ما مقدار آن را مشخص میکنم و این عدد باید بیشتر از 0x800 باشد چون مقادیر کمتر رزرو  شده اند

۳) TransferType: نوع دسترسی به حافظه داده که قبلتر در مورد آن صحبت کردیم و شامل این مقادیر می تواند باشد

METHOD_BUFFERED
METHOD_IN_DIRECT
METHOD_OUT_DIRECT
METHOD_NEITHER

۴) RequiredAccess: میزان دسترسی که در هنگام هندل گرفتن از device باید وارد کنیم ما در درایور خود همه دسترسی ها را گرفتیم یعنی هم خواندن و هم نوشتن

با استفاده از این کد ioctl را تعریف کرده ایم

#define IOCTL_SIOCTL_METHOD_BUFFERED \
CTL_CODE( SIOCTL_TYPE, 0x902, METHOD_BUFFERED, FILE_ANY_ACCESS )

مثال های تکمیلی

خوب بعد نوشتن این آموزش یکی از خواننده های سایت زحمت کشیدن و یک درایور نوشتن که می تونه در فهم بهتر این قسمت از آموزش کمک کنه. من توضیحاتی که خودشون دادن را اینجا نقل کردم به اضافه لینک دانلود درایور. امیدوارم مفید واقع بشود. در آینده هم اگر کسی فکر می کنه کاری انجام داده و به نظرش برای دیگران میتونه مفید باشه اطلاع بده و اینجا قرار می دم. :)

به نقل از احسان " یه درایور خیلی آماتور نوشتم درست اجرا میشه. اگه صلاح میدانید براتون ایمل کنم. تو یه بخش بذارید. عملکرد به این شکله که:

فایل sys. یک درایوره که پیام کاربر رو میگیره و تو سطح کرنل چاپ مکنه. ( یعنی تو دیباگ ویو)
فایل RDTest درایور رو لود میکنه و منتظر یه کلید از کاربر میشه تا سرویس رو خاتمه بده.
فایل comm هم پیامی رو با استفاده از WriteFile به درایور میفرسته. روش ارسال هم بافره.
فایل comm باید پس از اجرای RDTest و پیش از زدن کلید، به اجرا در بیاد."
نکته: فقط به این نکته توجه داشته باشید قسمت سطح کاربر از یک مسیر مشخص و ثابت برای بارگداری درایور استفاده کرده در نتیجه باید به مسیر درایور با سیستم خودتون تغییرش بدین.

پایان

رسیدیم به آخر مقاله که امیدوارم برایتان مفید واقع شده باشد. نکته ای که در پایان بد نیست به آن اشاره کنم این است که اگر می خواهید درایور نویسی را خوب یاد بگیرید فقط به این مقاله اکتفا نکرده و تا می تواند از منابع مختلف برای یادگیری استفاده کنید. 

اگر ایراد وجود داشت بگویید تا اصلاح کنیم. و طبق معمول اگر سوال برایتان به وجود آورده هم از طریق کامنت و هم از طریق ایمیلی می توانید مطرح کنید.

موفق باشید

نظرات (۱۷)

کد انتقال اطلاعات از یک داریو به درایو دیگر با برنامه نویسی ++C
  • پویا شاهین فر
  • سلام
    وبلاگتون خیلی خوبه. کلی استفاده .کردیم

    درایور نویسی برای ویندوز رو تا حالا رو سایتهای فارسی ندیده بودم.  امیدوارم این سری درایور نویسی ادامه پیدا کنه.

    موفق باشید
    سلام
    آقا لطفا ادامه بده
    من هرروز به سایت شما سرمی زنم ولی خبری نمیشه
    مطلب قبلی روش بهتر کار کرده بودید.  
    پاسخ:
    سلام
    ممنون بابت نظرتون
    خوب بود می گفتین کجاهای مطلب به نظرتون ضعیفتره. تا من بتونم در صورت توان بهترشون کنم
    ممنون
    امیدوارم از کامنت قبلی بد برداشت نشده باشد. شما بسیار کار ارزشمندی انجام داده اید. من یکی خودم خیلی استفاده کردم. اما چند نکته:
    1- دلیل مقدار صفر دادن به EXCLUSIVE چیه؟
    2- آیا در برنامه سطح کاربر نیاز به Incude کردن فایل خاصی نیست؟ ( البته به جز فایلهای معمول)
    3- در برنامه سطح کاربر موقع فراخوانی deviceiocontrol، آرگومانهای inputbuffer و outbuffer از چه نوع یا ساختار داده ای هستند؟
    اگر بشه یه نمونه برنامه سطح کاربر بذارید بعد از اجرا با دیباگ یا امثال آن نتیجه را ببینیم.
    باز هم از زحمتی که کشیدید کمال تشکر را دارم.
    پاسخ:
    نه برداشت خاصی نکردم، من انتهای مطلب هم نوشتم نظر ، انتقاد هر چی هست بدین، هدف همینه که یکی میخونه کم و کسری هست، ایرادی هست میگه منم حالا اگه بدونم رفع می کنم. :)
    ۱- این مورد تو وبلاگ اضافه کردم، این برای اینه که فقط یک هندل از device  بشه گرفت.
    ۲- نه همون فایل های معمول کافیه 
    ۳- من یه نمونه کد گذاشتم به آدرس زیر، خروجی قسمت کرنل این برنامه رو میشه از طریق ابزار DbgView که قبلا معرفی کردم مشاهده کرد، خروجی سمت کاربر هم تو کنسول نشون میده
    http://bayanbox.ir/id/4731276673593439958?download
    ۳- این دو پارامتر از نوع PVOID تعریف شدن یعنی هر چی میتونه باشه قالبا یه struct تعریف میشه. نمونه برنامه ای که لینک دادم هم حتما ببینید.

    در مورد اجرای یک برنامه سطح کاربر و نمایش خروجی، فعلا نه ولی بعد شاید اضافه کردم.

    ممنون بابت نکاتی که مطرح کردین، باز اگه هست بگین ما خیلی هم خوشحال میشیم.
    شاد باشید
    درود. آقا من خواستم از طریق تابع WriteFile با درایور ارتباط برقرار کنم. پیامی رو که میفرستم به دست درایور نمیرسه. همه چیز رو چک کردم. همش درسته حتی متد موردنظر رو هم فراخوانی میکنه. ولی IRP->associated.SystemBuffer خالیه. یعنی null ه. چیزی به ذهنتون نمیرسه. نکته ای که من بلد نبودم. پیشاپیش ممنون.
    پاسخ:
    با GetLastError ببینین WriteFile چه خطایی بر می گرداند. نه فقط این API بلکه باقی API  ها رو هم ببینید ارروری بر می گردونن.
    درود. کد خطا صفر برمیگردانه. 
    مشکل بر طرف شد. یه درایور خیلی آماتور نوشتم درست اجرا میشه. اگه صلاح میدانید براتون ایمل کنم. تو یه بخش بذارید. عملکرد به این شکله که:
    فایل sys. یک درایوره که پیام کاربر رو میگیره و تو سطح کرنل چاپ مکنه. ( یعنی تو دیباگ ویو)
    فایل RDTest درایور رو لود میکنه و منتظر یه کلید از کاربر میشه تا سرویس رو خاتمه بده.
    فایل comm هم پیامی رو با استفاده از WriteFile به درایور میفرسته. روش ارسال هم بافره.
    فایل comm باید پس از اجرای RDTest و پیش از زدن کلید، به اجرا در بیاد.
    پاسخ:
    اوه آفرین، این هفته اینقدر سرم شلوغ شد که نتونستم خیلی اینجا سر بزنم، الان کامنت شما رو دیدم کلی حال کردم :)
    خوب بده سورستو به اسم خودت تو آموزش دوم به عنوان مثال های تکمیلی قرار میدم، 
    آدرس ایمیل رو بذارید. فقط فایلای متن رو بفرستم خودتون اجرا میگیرید؟ یا اینکه اجراییشم بفرستم؟
    پاسخ:
    همون سورس ها را بدین، ایمیل هم binthought@gmail.com
    ممنون
    فرستادم. بگید حتما که رسیده یا نه!
    ایمیل دوم فایل هاش خواناترن.
    پاسخ:
    خوب فکر کنم شما کلا ایمیل چک نمیکنید :)، من ایمیل شما رو دریافت کردم منتظرم فایل های sources و makefile رو به من بدین
    فرستادم
    پاسخ:
    ممنون، درایورتون رو به پست اضافه کردم.
    شاد باشید
  • حسام الدین محمدی
  • سلام- من یه مشکل بسیار جدی بران به وجود اومده. برنامه ای قدیمی، متعلق به فرد دیگری، به من داده شده تا خطای اون رو بر طرف کنم. این برنامه داده ها یی را از یک دستگاه usb  می خونه. اما با کوچکترین برخورد به پورت برنامه هنگ می کنه. من متوجه شده که برنامه در این خط از کار میافته. هیچ error یا catch هم برگشت داده نمیشه!!

    bTemp = DeviceIoControl
    (
    _hEzUsb, dwIoControlCode,
    lpInBuffer, nInBufferSize,
    lpOutBuffer, nOutBufferSize,
    &nBytes,NULL
    );

      من نیاز دارم که از این خط رد بشم حالا به هر طریقی.!!! 
    خواهش می کنم کمکم کنید وقت ندارم.
    (من با c++ های قدیمی اون هم در این سطح خیلی اشنا نیستم!!!!)
    پاسخ:

    سلام


    1- ممکنه یک مشکل معتبر نبودن پارامترهای DeviceIoControl باشه،

    2-  از اونجایی که شما در این مرحله با یه درایور در ارتباط هستین ممکنه درایور به مشکل برخورده کرده باشه، در این صورت بهتره از یه دیباگر مثل WinDbg استفاده کنید.

    سلام
    داداش این درایوری که آقای "احسان" نوشتند
    رو می تونید این جا بذارین؟
    ممنونم
    پاسخ:
    سورس درایور که تو پست هست بیلد کنیدش دیگه :)، مشکلش چیه؟
    سلام
    اینی که نوشتین دقیقا تو کدوم و کجای فایل رو باید مسیر دهی کنیم:
    "لینک درایور 
    نکته: فقط به این نکته توجه داشته باشید قسمت سطح کاربر از یک مسیر مشخص و ثابت برای بارگداری درایور استفاده کرده در نتیجه باید به مسیر درایور با سیستم خودتون تغییرش بدین."
    من تو xp بیلد می کنم.
    ممنونم

    پاسخ:
    داخل فایل آرشیو یکسری سورس هست. یکی مربوط به سطح کاربره که مسیر درایور داخلشه و کارش لود کردندرایور است اون سورس رو تغییر بدین
    با سلام و عرض خسته نباشید و تشکر از وبلاگ و آموزشای خوبتون ...
    در مورد درایوری که در آخر لینکشو گذاشتید یه سوالی دارم.
    برنامه لود کننده درایور جوری نوشته شده که پس از پایان رسیدن برنامه درایور آنلود میشه.میشه برنامه رو جوری نوشت که پس از لود کردن درایور آنلود نشه و برنامه دیگری در سطح کاربر اجرا بشه که مسئولیت آنلود کردن درایور با او باشه ؟ اگه جواب بله هست ، ممنون میشم یه راهنمایی بکنید که برنامه آنلود کننده چطور باید آبجکت درایور خودشو پیدا کنه که متوقفش کنه .
    یه سوال دیگه ما تو osLoader اول درایور رو رجیستر میکردیم بعد استارتش میکردیم . تو این درایور که لینکشو این آخر دادین ، تو برنامه لود کننده ای که نوشتین هر دو کار با هم انجام میشه ؟ ینی کلید رجیستری هم به رجیستری ویندوز اضافه میکنه؟!
    با تشکر ...
    پاسخ:
    سلام
    خوشحالم که آموزش بدردتون خورده
    و در مورد سوالها:

    ۱. بله میشه برنامه را طوری نوشت که بعد از لود شدن درایور آنلود نشه. در واقع کافیه داخل کد این دو قسمت (لود و آنلود) جدا نوشته بشن یا حتی داخل یک برنامه دیگه عمل آنلود شدن انجام بشه. برای مثال پیشنهاد میکنم مسیر src/general/ioctl/wdm/exe داخل wdk را ببینید که یک مثال کامل داده و از همین کد می توان برای لود و آنلود هر درایوری استفاده کرد.

    ۲. در مورد اینکه چطور آبجکت درایور را می شود گرفت. کسی که درایور را نوشته درایور آبجکت را هم دارد. رجوع کنید به قسمتی از آموزش که توابع IoCreateDevice و IoCreateSymbolicLink استفاده شده. رشته ای که به تابع دوم یعنی IoCreateSymbolicLink داده میشه در واقع رشته ای است که داخل برنامه سطح کاربر قابل استفاده است (برای گرفتن هندل از درایور) و از تابعی مثل CreateFile برای دسترسی به آبجکت درایور می توان استفاده کرد (رجوع کنید به مثالی که از wdk دادم)

    ۳. در مورد اون سورس بطور دقیقتر تابع CreateService وظیفه ایجاد کلیدهای رجیستری را دارد. و تابع StartService هم درایور تعریف شده داخل آن کلید رجیستری را لود میکند.

    با سلام بنده از اطلاعاتی خوبی که در این صفحه هست خیلی سر در نیاوردم چون تخصصم برنامه نویسی نیست ولی مشکلی دارم که نیاز به خدمات افرادی دارم که از مطالب فوق سر در میاورند. دستگاهی دارم که هدفم خواندن اطلاعاتی از این دستگاه و ذخیره این اطلاعات در فایل برای آینده است که وقتی نیاز شد دئباره اطلاعات را داخل دستگاه بنویسم. با استفاده از serial port monitor توانستم log زیر را به عنوان نمونه و قسمتی از اطلاعات بگیرم و فردی را میخواهم که بتواند مثلا با دستورات IRP_MJ_READ و IRP_MJ_WRITE و یا ابزار و دستورات دیگر اطلاعات مورد نظر من را خوانده و در فایلی ذخیره نماید و دوباره داخل دستگاه لود کند.

     

    نمونه لاگ serial port monitor:

    Device software uses IRP_MJ_READ command to read info I want (see attached log). May be I can use IRP_MJ_READ and IRP_MJ_write commands to read and write info I want directly. Any advice in this?

    103 IRP_MJ_READ - Transfers data from a COM port to a client (COM4) - 349 bytes of 349
    STATUS_SUCCESS
    05 90 0c 00 e8 a1 50 01 00 00 5c 01 f6 00 04 00 ...è¡P...\.ö...
    00 00 60 01 f6 00 2c 01 00 00 8c 02 f6 00 04 00 ..`.ö.,...Œ.ö...
    00 00 90 02 f6 00 7c 00 00 00 0c 03 f6 00 a4 1f ...ö.|.....ö.¤.
    00 00 b0 22 f6 00 a4 1f 00 00 54 42 f6 00 94 11 ..°"ö.¤...TBö.”.
    00 00 e8 53 f6 00 20 0a 00 00 08 5e f6 00 08 07 ..èSö. ....^ö...
    00 00 10 65 f6 00 c0 12 00 00 d0 77 f6 00 b0 04 ...eö.À...Ðwö.°.
    00 00 80 7c f6 00 c4 09 00 00 44 86 f6 00 4c 04 ..€|ö.Ä...D†ö.L.
    00 00 90 8a f6 00 b0 04 00 00 40 8f f6 00 34 08 ..Šö.°...@ö.4.
    00 00 74 97 f6 00 4c 04 00 00 c0 9b f6 00 4c 04 ..t—ö.L...À›ö.L.
    00 00 0c a0 f6 00 b8 0b 00 00 c4 ab f6 00 48 00 ... ö.¸...Ä«ö.H.
    00 00 0c ac f6 00 0c 00 00 00 18 ac f6 00 10 0e ...¬ö......¬ö...
    00 00 28 ba f6 00 10 0e 00 00 38 c8 f6 00 10 0e ..(ºö.....8Èö...
    00 00 48 d6 f6 00 0c 00 00 00 54 d6 f6 00 80 04 ..HÖö.....TÖö.€.
    00 00 d4 da f6 00 e0 01 00 00 b4 dc f6 00 a4 1f ..ÔÚö.à...´Üö.¤.
    00 00 58 fc f6 00 e8 03 00 00 40 00 f7 00 80 00 ..Xüö.è...@.÷.€.
    00 00 c0 00 f7 00 10 00 00 00 d0 00 f7 00 0c 00 ..À.÷.....Ð.÷...
    00 00 dc 00 f7 00 0c 00 00 00 e8 00 f7 00 60 03 ..Ü.÷.....è.÷.`.
    00 00 48 04 f7 00 b0 01 00 00 f8 05 f7 00 a4 1f ..H.÷.°...ø.÷.¤.
    00 00 9c 25 f7 00 04 00 00 00 a0 25 f7 00 10 0e ..œ%÷..... %÷...
    00 00 b0 33 f7 00 0c 00 00 00 bc 33 f7 00 ac 0d ..°3÷.....¼3÷.¬.
    00 00 68 41 f7 00 a8 00 00 00 10 42 f7 00 a4 1f ..hA÷.¨....B÷.¤.
    00 00 b4 61 f7 00 8c 00 00 00 f0 60 92 ..´a÷.Œ...ð`’

     

    ارسال نظر آزاد است، اما اگر قبلا در بیان ثبت نام کرده اید می توانید ابتدا وارد شوید.
    شما میتوانید از این تگهای html استفاده کنید:
    <b> یا <strong>، <em> یا <i>، <u>، <strike> یا <s>، <sup>، <sub>، <blockquote>، <code>، <pre>، <hr>، <br>، <p>، <a href="" title="">، <span style="">، <div align="">
    تجدید کد امنیتی