应用层和内核层通信

最近在看Windows内核安全与驱动开发,总结了一些东西,主要介绍一些自旋锁,事件,链表相关的东西。

LIST_ENTRY一个双向链表,在Windows中经常使用。在使用时只需在结构体中插入一个LIST_ENTRY结构,位置无所谓(最好在前面)。比如:

typedef	struct	{
	LIST_ENTRY	list_entry;
	UNICODE_STRING	file_name;
}MY_LIST_TEST;

在使用之前需要调用InitializeListHead来初始化:

#define	MEM_TAG  'test'
LIST_ENTRY my_list_head; 		//链表头(注意不是自己定义的链表结构体)
InitializeListHead(&my_list_head);	//初始化
MY_LIST_TEST *my_first_list = ExAllocatePoolWithTag(NonPagedPool, sizeof(CWK_STR_NODE), MEM_TAG);	//申请空间
my_first_list->file_name = .....	//填写数据成员
InsertHeadList(&my_list_head,(PLIST_ENTRY)&my_first_list); 	//添加节点


这样做非常不安全,因为多线程的情况下操作同一个链表会破坏这个链表,这个时候就要引进自旋锁的概念。使用自旋锁首先要初始化一个自旋锁:

KSPIN_LOCK g_cwk_lock;	
KeInitializeSpinLock(&g_cwk_lock);	//初始化一个自旋锁
ExInterlockedInsertHeadList(&my_list_head, (PLIST_ENTRY)&my_first_list, &g_cwk_lock);	//添加节点 并进行加锁
my_first_list = ExInterlockedRemoveHeadList(&my_list_first, &g_cwk_lock);	//移除一个节点 并返回到my_first_list中


下面说一下同步事件,当一个地方需要等待另一个事件完成后才能继续进行时需要设置同步事件,同步事件数据结构是KEVENT

 
  
KEVENT event;	//定义一个事件
KeInitializeEvent(&event,SynchronizationEvent,TRUE); //事件初始化
......
//这里设置事件等待,如果这个事件没有被设置,那么会一直在这里等待
KeWaitForSingleObject(&_event,Executive,KernelMode,0,0);
......
//这理设置事件,只有这里执行完了以后,上面的等待才能放行,去执行后续流程
KeSetEvent(&_event, 0, TRUE);


要实现用户层和内核层的通信,首先要生成一个设备对象,在驱动开发中设备对象和分发函数构成了整个内核体系的基本框架。生成设备可以使用函数IoCreateDevice函数,

NTSTATUS IoCreateDevice
(
IN PDRIVER_OBJECT DriverObject,		//从DriverEntry的参数获得
IN ULONG DeviceExtensionSize,		//设备扩展大小
IN PUNICODE_STRING DeviceName OPTIONAL,	//设备名
IN DEVICE_TYPE DeviceType,		//设备类型
IN ULONG DeviceCharacteristics,		//设备属性
IN BOOLEAN Exclusive,			//表示是否是一个独占设备
OUT PDEVICE_OBJECT *DeviceObject	//返回成成的设备对象指针
);


但是这个生成的设备具有默认的安全属性,必须用管理员权限的进程才能打开,当用户是普通用户就没办法打开,所以使用IoCreateDeviceSecure函数创建设备
NTSTATUS IoCreateDeviceSecure
(
IN PDRIVER_OBJECT DriverObject,		//从DriverEntry的参数获得
IN ULONG DeviceExtensionSize,		//设备扩展大小
IN PUNICODE_STRING DeviceName OPTIONAL,	//设备名
IN DEVICE_TYPE DeviceType,		//设备类型
IN ULONG DeviceCharacteristics,		//设备属性
IN BOOLEAN Exclusive,			//表示是否是一个独占设备
IN PUCNICODE_STRING DefaultSDDLString,	//设置设备安全
IN LPCGUID DeviceClassGuid,		//微软提供CoCreateGuid生成,第一次生成就一直使用,不要每次启动都生成一个新的
OUT PDEVICE_OBJECT *DeviceObject	//返回成成的设备对象指针
);
可以发现只有两个地方不一样,其中DefaultSDDLString这个参数传入L"D:P(A;;GA;;;WD)"即表示允许任何用户访问该设备的万能安全设置字符串,使用如下:
//全局参数
PDEVICE_OBJECT g_cdo = NULL;
const GUID  CWK_GUID_CLASS_MYCDO = {0x17a0d1e0L, 0x3249, 0x12e1, {0x92,0x16, 0x45, 0x1a, 0x21, 0x30, 0x29, 0x06}};//GUID
//......
NTSTATUS DriverEntry(PDRIVER_OBJECT driver,PUNICODE_STRING reg_path){
	UNICODE_STRING sddl = RTL_CONSTANT_STRING(L"D:P(A;;GA;;;WD)"); 
	UNICODE_STRING cdo_name = RTL_CONSTANT_STRING(L"\\Device\\cwk_3948d33e"); //设备名
	IoCreateDeviceSecure(
			driver,
			0,
			&cdo_name,
			FILE_DEVICE_UNKNOWN,
			FILE_DEVICE_SECURE_OPEN,
			FALSE,
			&sddl,
			(LPCGUID)&CWK_GUID_CLASS_MYCDO,//guid
			&g_cdo);
}
但是应用层无法直接通过设备的名字来打开设备,必须要建立一个暴露给应用层的符号链接,符号链接就是记录一个字符串对应到另一个字符串的一种简单结构,通过函数IoCreateSymbolicLink生成,一般都会成功,除非符号链接名冲突。
NTSTATUS   IoCreateSymbolicLink( 
  IN PUNICODE_STRING  SymbolicLinkName,	//符号链接名
  IN PUNICODE_STRING  DeviceName 	//设备名   
);
设备大部分都位于\Device\这个路径下,符号链接位于\??\这个路径下(的确是两个问号)
#define CWK_CDO_SYB_NAME    L"\\??\\slbkcdo_3948d33e"
UNICODE_STRING cdo_syb = RTL_CONSTANT_STRING(CWK_CDO_SYB_NAME);
IoCreateSymbolicLink(&cdo_syb,&cdo_name);
在卸载驱动的时候应该删除它们,否则符号链接一直存在,使用如下代码:

IoDeleteSymbolicLink(&cdo_syb);
IoDeleteDevice(g_cdo);
之后要介绍的是分发函数,所为分发函数就是用来处理发送给设备对象的请求的函数,使用如下结构:

NTSTATUS cwkDispatch(IN PDEVICE_OBJECT dev,IN PIRP irp){
	.......//处理请求
}
NTSTATUS DriverEntry(PDRIVER_OBJECT driver,PUNICODE_STRING reg_path){
	ULONG i;
	for(i=0;iMajorFunction[i] = cwkDispatch;
	}
}

 其中>MajorFunction是一个函数指针,保存着所有分发函数的指针,将所有分发函数都设置成同一个函数处理。在准备处理请求的时候第一步是获取请求的当前栈空间,我们发现cwkDispatch的参数有irp,查看irp可以发现它是一个大的结构体,结构如下: 
  

typedef struct DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT) _IRP {
    CSHORT Type;
    USHORT Size;
    /*
    MdlAddress(PMDL)域指向一个内存描述符表(MDL),该表描述了一个与该请求关联的用户模式缓冲区。
    http://www.cnblogs.com/guanlaiy/archive/2012/09/06/2673375.html
    这个博客讲解的比较详细
    */
    PMDL MdlAddress;
    ULONG Flags; //包含一些对驱动程序只读的标志。
	
    union {
        struct _IRP *MasterIrp;
        __volatile LONG IrpCount;
        PVOID SystemBuffer; //指针指向一个数据缓冲区,该缓冲区位于内核模式的非分页内存中
    } AssociatedIrp;
	
    // 用来将 IRP挂入某个线程的 IrpList队列
    LIST_ENTRY ThreadListEntry;
	
    // 是一个仅包含两个域的结构,驱动程序在最终完成请求时设置这个结构   
	//IoStatus.Status域将收到一个NTSTATUS代码
	//IoStatus.Information的类型为ULONG_PTR,其中保存的信息要取决于具体的IRP类型和请求完成的状态。
    IO_STATUS_BLOCK IoStatus;
	
	//指定原始I/O请求的来源。驱动程序有时需要查看这个值来决定是否要信任某些参数
    KPROCESSOR_MODE RequestorMode;
	//如果为TRUE,则表明处理该IRP的最低级派遣例程返回了STATUS_PENDING。
    BOOLEAN PendingReturned;
	
    /*
	其实数据结构 IRP 只是"I/O 请求包"IRP的头部,在 IRP 数据结构的后面还有一个IO_STACK_LOCATION 数据结
	构的数组,数组的大小则取决于 IRP 数据结构中的StackCount,其数值来自堆叠中顶层设备对象的 StackSize
	字段。这样,就在 IRP 中为目标设备对象堆叠中的每一层即每个模块都准备好了一个 IO_STACK_LOCATION 数
	据结构。而CurrentLocation,则是用于该数组的下标,说明目前是在堆叠中的哪一层,因而正在使用哪一个 
	IO_STACK_LOCATION 数据结构。
	分层驱动中使用CurrentLocation来记录IRP到达了哪一层,在不同的层有对应的处理函数
	(通过IO_STACK_LOCATION关联),对IRP进行特定的处理。
    */
    CHAR StackCount;
    CHAR CurrentLocation;
	//如果为TRUE,则表明IoCancelIrp已被调用,否则表明没有调用IoCancelIrp函数
    BOOLEAN Cancel;
	//是一个IRQL值,当你在取消例程中释放自旋锁时应参考这个域
    KIRQL CancelIrql;
    CCHAR ApcEnvironment;
    UCHAR AllocationFlags;
    PIO_STATUS_BLOCK UserIosb;
    PKEVENT UserEvent;
    union {
        struct {
            union {
                PIO_APC_ROUTINE UserApcRoutine;
                PVOID IssuingProcess;
            };
            PVOID UserApcContext;
        } AsynchronousParameters;
        LARGE_INTEGER AllocationSize;
    } Overlay;
    __volatile PDRIVER_CANCEL CancelRoutine;
	
    PVOID UserBuffer;
    union {

        struct {

            union {
                KDEVICE_QUEUE_ENTRY DeviceQueueEntry;
                struct {
                    PVOID DriverContext[4];
                } ;

            } ;
            PETHREAD Thread;

            PCHAR AuxiliaryBuffer;
			
            struct {

                LIST_ENTRY ListEntry;

                union {
                    // 当前栈的位置 - 包含一个在IRP栈中指向当前IO_STACK_LOCATION结构,
                    struct _IO_STACK_LOCATION *CurrentStackLocation;
                    ULONG PacketType;
                };
            };
            PFILE_OBJECT OriginalFileObject;

        } Overlay;

        KAPC Apc;

        PVOID CompletionKey;

    } Tail;

} IRP;

typedef IRP *PIRP;
//http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=69879&id=2680607 这个博客讲的很不错

 任何内核模式程序在创建一个IRP时,同时还创建了一个与之关联的 IO_STACK_LOCATION 结构数组:数组中的每个堆栈单元都对应一个将处理该IRP的驱动程序,堆栈单元中包含该IRP的类型代码和参数信息以及完成函数的地址。查看IO_STACK_LOCATION结构 
  

typedef struct _IO_STACK_LOCATION {
    UCHAR MajorFunction;	//指示驱动程序应该使用哪个函数来处理IO请求。
    UCHAR MinorFunction;	//进一步指出该IRP属于哪个主功能类
    UCHAR Flags;			//表明IO请求类型。
    UCHAR Control;
    union {
        // 系统服务参数 :NtCreateFile
        struct {
            PIO_SECURITY_CONTEXT SecurityContext;
            ULONG Options;
            USHORT POINTER_ALIGNMENT FileAttributes;
            USHORT ShareAccess;
            ULONG POINTER_ALIGNMENT EaLength;
        } Create;
        struct {
            PIO_SECURITY_CONTEXT SecurityContext;
            ULONG Options;
            USHORT POINTER_ALIGNMENT Reserved;
            USHORT ShareAccess;
            PNAMED_PIPE_CREATE_PARAMETERS Parameters;
        } CreatePipe;
        struct {
            PIO_SECURITY_CONTEXT SecurityContext;
            ULONG Options;
            USHORT POINTER_ALIGNMENT Reserved;
            USHORT ShareAccess;
            PMAILSLOT_CREATE_PARAMETERS Parameters;
        } CreateMailslot;
        struct {
            ULONG Length;
            ULONG POINTER_ALIGNMENT Key;
            LARGE_INTEGER ByteOffset;
        } Read;
        struct {
            ULONG Length;
            ULONG POINTER_ALIGNMENT Key;
            LARGE_INTEGER ByteOffset;
        } Write;
        struct {
            ULONG Length;
            PUNICODE_STRING FileName;
            FILE_INFORMATION_CLASS FileInformationClass;
            ULONG POINTER_ALIGNMENT FileIndex;
        } QueryDirectory;
        struct {
            ULONG Length;
            ULONG POINTER_ALIGNMENT CompletionFilter;
        } NotifyDirectory;
        struct {
            ULONG Length;
            FILE_INFORMATION_CLASS POINTER_ALIGNMENT FileInformationClass;
        } QueryFile;
		
        struct {
            ULONG Length;
            FILE_INFORMATION_CLASS POINTER_ALIGNMENT FileInformationClass;
            PFILE_OBJECT FileObject;
            union {
                struct {
                    BOOLEAN ReplaceIfExists;
                    BOOLEAN AdvanceOnly;
                };
                ULONG ClusterCount;
                HANDLE DeleteHandle;
            };
        } SetFile;
		
        struct {
            ULONG Length;
            PVOID EaList;
            ULONG EaListLength;
            ULONG POINTER_ALIGNMENT EaIndex;
        } QueryEa;
        struct {
            ULONG Length;
        } SetEa;
        struct {
            ULONG Length;
            FS_INFORMATION_CLASS POINTER_ALIGNMENT FsInformationClass;
        } QueryVolume;
        struct {
            ULONG Length;
            FS_INFORMATION_CLASS POINTER_ALIGNMENT FsInformationClass;
        } SetVolume;
        struct {
            ULONG OutputBufferLength;
            ULONG POINTER_ALIGNMENT InputBufferLength;
            ULONG POINTER_ALIGNMENT FsControlCode;
            PVOID Type3InputBuffer;
        } FileSystemControl;
        struct {
            PLARGE_INTEGER Length;
            ULONG POINTER_ALIGNMENT Key;
            LARGE_INTEGER ByteOffset;
        } LockControl;
        struct {
            ULONG OutputBufferLength;
            ULONG POINTER_ALIGNMENT InputBufferLength;
            ULONG POINTER_ALIGNMENT IoControlCode;
            PVOID Type3InputBuffer;
        } DeviceIoControl; //IO通信
        struct {
            SECURITY_INFORMATION SecurityInformation;
            ULONG POINTER_ALIGNMENT Length;
        } QuerySecurity;
        struct {
            SECURITY_INFORMATION SecurityInformation;
            PSECURITY_DESCRIPTOR SecurityDescriptor;
        } SetSecurity;
        struct {
            PVPB Vpb;
            PDEVICE_OBJECT DeviceObject;
        } MountVolume;
        struct {
            PVPB Vpb;
            PDEVICE_OBJECT DeviceObject;
        } VerifyVolume;
        struct {
            struct _SCSI_REQUEST_BLOCK *Srb;
        } Scsi;
        struct {
            ULONG Length;
            PSID StartSid;
            PFILE_GET_QUOTA_INFORMATION SidList;
            ULONG SidListLength;
        } QueryQuota;
        struct {
            ULONG Length;
        } SetQuota;
        struct {
            DEVICE_RELATION_TYPE Type;
        } QueryDeviceRelations;
        struct {
            CONST GUID *InterfaceType;
            USHORT Size;
            USHORT Version;
            PINTERFACE Interface;
            PVOID InterfaceSpecificData;
        } QueryInterface;
        struct {
            PDEVICE_CAPABILITIES Capabilities;
        } DeviceCapabilities;
        struct {
            PIO_RESOURCE_REQUIREMENTS_LIST IoResourceRequirementList;
        } FilterResourceRequirements;
        struct {
            ULONG WhichSpace;
            PVOID Buffer;
            ULONG Offset;
            ULONG POINTER_ALIGNMENT Length;
        } ReadWriteConfig;
        struct {
            BOOLEAN Lock;
        } SetLock;
        struct {
            BUS_QUERY_ID_TYPE IdType;
        } QueryId;
        struct {
            DEVICE_TEXT_TYPE DeviceTextType;
            LCID POINTER_ALIGNMENT LocaleId;
        } QueryDeviceText;
        struct {
            BOOLEAN InPath;
            BOOLEAN Reserved[3];
            DEVICE_USAGE_NOTIFICATION_TYPE POINTER_ALIGNMENT Type;
        } UsageNotification;
        struct {
            SYSTEM_POWER_STATE PowerState;
        } WaitWake;
        struct {
            PPOWER_SEQUENCE PowerSequence;
        } PowerSequence;
		
#if (NTDDI_VERSION >= NTDDI_VISTA)
        struct {
            union {
                ULONG SystemContext;
                SYSTEM_POWER_STATE_CONTEXT SystemPowerStateContext;
            };
            POWER_STATE_TYPE POINTER_ALIGNMENT Type;
            POWER_STATE POINTER_ALIGNMENT State;
            POWER_ACTION POINTER_ALIGNMENT ShutdownType;
        } Power;
#else
        struct {
            ULONG SystemContext;
            POWER_STATE_TYPE POINTER_ALIGNMENT Type;
            POWER_STATE POINTER_ALIGNMENT State;
            POWER_ACTION POINTER_ALIGNMENT ShutdownType;
        } Power;
#endif // (NTDDI_VERSION >= NTDDI_VISTA)
        struct {
            PCM_RESOURCE_LIST AllocatedResources;
            PCM_RESOURCE_LIST AllocatedResourcesTranslated;
        } StartDevice;
        struct {
            ULONG_PTR ProviderId;
            PVOID DataPath;
            ULONG BufferSize;
            PVOID Buffer;
        } WMI;
        struct {
            PVOID Argument1;
            PVOID Argument2;
            PVOID Argument3;
            PVOID Argument4;
        } Others;

    } Parameters;
	
	/*
	CompletionRoutine是一个I/O完成例程的地址,该地址是由与这个堆栈单元对应的驱动程序的更上一层驱动程
	序设置的。通过调用IoSetCompletionRoutine函数来设置。设备堆栈的最低一级驱动程序并不需要完成例程,
	因为它们必须直接完成请求。然而,请求的发起者有时确实需要一个完成例程,但通常没有自己的堆栈单元。
	这就是为什么每一级驱动程序都使用下一级驱动程序的堆栈单元保存自己完成例程指针的原因。
	*/	
    PIO_COMPLETION_ROUTINE CompletionRoutine;

    PVOID Context;
} IO_STACK_LOCATION, *PIO_STACK_LOCATION;
可以看到这个结构体和许多操作有联系,我们可以通过函数IoGetCurrentIrpStackLocation获取当前栈位置
// 这其实是一个宏定义:#define IoGetCurrentIrpStackLocation (Irp) ((Irp)->Tail.Overlay.CurrentStackLocation)
PIO_STACK_LOCATION  irpsp = IoGetCurrentIrpStackLocation(irp);
下面粘上驱动程序(出自Windows内核安全与驱动开发)主代码,没有进行安全检查
NTSTATUS cwkDispatch(IN PDEVICE_OBJECT dev, IN PIRP irp)
{
    PIO_STACK_LOCATION  irpsp = IoGetCurrentIrpStackLocation(irp);
    NTSTATUS status = STATUS_SUCCESS;
    ULONG ret_len = 0;
    while(dev == g_cdo) 
    {
        // 如果这个请求不是发给g_cdo的,那就非常奇怪了。
        // 因为这个驱动只生成过这一个设备。所以可以直接
        // 返回失败。
		//IRP_MJ_CREATE 打开请求的主功能号
		//IRP_MJ_CLOSE  关闭请求的主功能号
	if(irpsp->MajorFunction == IRP_MJ_CREATE || irpsp->MajorFunction == IRP_MJ_CLOSE)
	    {
            // 生成和关闭请求,这个一律简单地返回成功就可以
            // 了。就是无论何时打开和关闭都可以成功。
            break;
	    }
        if(irpsp->MajorFunction == IRP_MJ_DEVICE_CONTROL)
	    {
	// 处理DeviceIoControl。
	//获取缓冲区
	PVOID buffer = irp->AssociatedIrp.SystemBuffer;  
	//获取输入缓冲区长度
        ULONG inlen = irpsp->Parameters.DeviceIoControl.InputBufferLength;
	//获取输出缓冲区长度
        ULONG outlen = irpsp->Parameters.DeviceIoControl.OutputBufferLength;
	ULONG len;
	switch(irpsp->Parameters.DeviceIoControl.IoControlCode)
	{
            case CWK_DVC_SEND_STR:
                ASSERT(buffer != NULL);
                ASSERT(inlen > 0);
                ASSERT(outlen == 0);
                DbgPrint((char *)buffer);
                // 已经打印过了,那么现在就可以认为这个请求已经成功。
                break;
            case CWK_DVC_RECV_STR:
            default:
                // 到这里的请求都是不接受的请求。未知的请求一律返回非法参数错误。
                status = STATUS_INVALID_PARAMETER;
                break;
            }
        }
        break;
    }
    // 到这里的请求都是不接受的请求。未知的请求一律返回非法参数错误。
	//输出长度
	irp->IoStatus.Information = ret_len;
	//记录请求完成情况
	irp->IoStatus.Status = status;
	//结束请求
	IoCompleteRequest(irp,IO_NO_INCREMENT);
	return status;
}
在应用层只需要使用CreateFile打开设备,使用函数DeviceIoControl向内核发送就可以代码如下:
int _tmain(int argc, _TCHAR* argv[])
{
	HANDLE device = NULL;
	ULONG ret_len;
    int ret = 0;
    char *msg = {"Hello driver, this is a message from app.\r\n"};

	// 打开设备.每次要操作驱动的时候,先以此为例子打开设备
	device=CreateFile(CWK_DEV_SYM,GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_SYSTEM,0);
	if (device == INVALID_HANDLE_VALUE)
	{
		printf("coworker demo: Open device failed.\r\n");
		return -1;
	}
	else
		printf("coworker demo: Open device successfully.\r\n");

	//发送控制代码直接到指定的设备驱动程序
    if(!DeviceIoControl(device, CWK_DVC_SEND_STR, msg, strlen(msg) + 1, NULL, 0, &ret_len, 0))
    {
        printf("coworker demo: Send message failed.\r\n");
        ret = -2;
    }
    else
        printf("coworker demo: Send message successfully.\r\n");

    CloseHandle(device);
	system("pause"); 
	return ret;
}

以上均为自己的一些看法,如有不对的地方还希望能及时给予批评指正。


你可能感兴趣的:(PC安全)