Windows7下驱动开发与调试体系构建——2.R3与R0的通信示例
阅读原文时间:2023年07月10日阅读:3

目录/参考资料:https://www.cnblogs.com/railgunRG/p/14412321.html

在阅读本节前,建议先阅读《Windows内核安全与驱动开发》第五章内容,并自行了解相关背景知识。


R0部分

创建项目,打开:项目属性->链接器->输入->附加依赖项(点开后选择【编辑】),添加:

%(AdditionalDependencies)
$(DDK_LIB_PATH)\wdmsec.lib

参考代码:

#include
#include

static PDEVICE_OBJECT g_cdo = NULL;

const GUID CWK_GUID_CLASS_MYCDO =
{ 0x17a0d1e0L, 0x3249, 0x12e1, {0x92,0x16, 0x45, 0x1a, 0x21, 0x30, 0x29, 0x06} };

#define CWK_CDO_SYB_NAME L"\\??\\slbkcdo_3948d33e"

// 从应用层给驱动发送一个字符串。
#define CWK_DVC_SEND_STR \
(ULONG)CTL_CODE( \
FILE_DEVICE_UNKNOWN, \
0x911,METHOD_BUFFERED, \
FILE_WRITE_DATA)

// 从驱动读取一个字符串
#define CWK_DVC_RECV_STR \
(ULONG)CTL_CODE( \
FILE_DEVICE_UNKNOWN, \
0x912,METHOD_BUFFERED, \
FILE_READ_DATA)

// 定义一个链表用来保存字符串
#define CWK_STR_LEN_MAX 512
typedef struct {
LIST_ENTRY list_entry;
char buf[CWK_STR_LEN_MAX];
} CWK_STR_NODE;

// 还必须有一把自旋锁来保证链表操作的安全性
KSPIN_LOCK g_cwk_lock;
// 一个事件来标识是否有字符串可以取
KEVENT g_cwk_event;
// 必须有个链表头
LIST_ENTRY g_cwk_str_list;

#define MEM_TAG 'cwkr'

// 分配内存并初始化一个链表节点
CWK_STR_NODE *cwkMallocStrNode()
{
CWK_STR_NODE *ret = ExAllocatePoolWithTag(
NonPagedPool, sizeof(CWK_STR_NODE), MEM_TAG);
if (ret == NULL)
return NULL;
return ret;
}

void cwkUnload(PDRIVER_OBJECT driver)
{
DbgPrint("\r\n准备卸载\r\n");
UNICODE_STRING cdo_syb = RTL_CONSTANT_STRING(CWK_CDO_SYB_NAME);
CWK_STR_NODE *str_node;
//ASSERT(g_cdo != NULL);//bug崩溃?
DbgPrint("准备删除符号链接\r\n");
IoDeleteSymbolicLink(&cdo_syb);
DbgPrint("准备删除驱动对象\r\n");
if(g_cdo!=NULL)IoDeleteDevice(g_cdo);
else {
DbgPrint("\r\n发生严重错误:g_cdo==NULL\r\n");
}

DbgPrint("\\r\\n准备释放内存\\r\\n");  
// 负责的编程态度:释放分配过的所有内核内存。  
while (TRUE)  
{  
    str\_node = (CWK\_STR\_NODE \*)ExInterlockedRemoveHeadList(  
        &g\_cwk\_str\_list, &g\_cwk\_lock);  
    // str\_node = RemoveHeadList(&g\_cwk\_str\_list);  
    if (str\_node != NULL)  
        ExFreePool(str\_node);  
    else  
        break;  
};

DbgPrint("\\r\\n卸载完成\\r\\n");  

}

NTSTATUS cwkDispatch(
IN PDEVICE_OBJECT dev,
IN PIRP irp)
{
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);
NTSTATUS status = STATUS_SUCCESS;
ULONG ret_len = 0;
while (1)
{
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;  
        CWK\_STR\_NODE \*str\_node;  
        switch (irpsp->Parameters.DeviceIoControl.IoControlCode)  
        {  
        case CWK\_DVC\_SEND\_STR:

            ASSERT(buffer != NULL);  
            ASSERT(outlen == 0);  
            if (inlen > CWK\_STR\_LEN\_MAX)  
            {  
                status = STATUS\_INVALID\_PARAMETER;  
                break;  
            }

            DbgPrint("\\r\\n接收到来自r3的信息:%s\\r\\n", (char \*)buffer);  
            str\_node = cwkMallocStrNode();  
            if (str\_node == NULL)  
            {  
                status = STATUS\_INSUFFICIENT\_RESOURCES;  
                break;  
            }

            //char\* tmpBuffer= RTL\_CONSTANT\_STRING(L"QWQ");//字符缓冲  
            //strncpy(tmpBuffer, (char \*)buffer, CWK\_STR\_LEN\_MAX);

            strncpy(str\_node->buf, (char \*)buffer, CWK\_STR\_LEN\_MAX);

            strncpy(str\_node->buf, (char \*)buffer, CWK\_STR\_LEN\_MAX);

            char tmp1\[512\] = "Hello,Ring3!";  
            strncpy(str\_node->buf, tmp1, CWK\_STR\_LEN\_MAX);  
            // 插入到链表末尾。用锁来保证安全性。  
            ExInterlockedInsertTailList(&g\_cwk\_str\_list, (PLIST\_ENTRY)str\_node, &g\_cwk\_lock);  
            // InsertTailList(&g\_cwk\_str\_list, (PLIST\_ENTRY)str\_node);  
            // 打印  
            // DbgPrint((char \*)buffer);  
            // 那么现在就可以认为这个请求已经成功。因为刚刚已经插入了一  
            // 个,那么可以设置事件来表明队列中已经有元素了。  
            KeSetEvent(&g\_cwk\_event, 0, FALSE);//设为TRUE就是等死  
            //DbgPrint("\\r\\n信息处理完成!\\r\\n");  
            break;  
        case CWK\_DVC\_RECV\_STR:

            //ASSERT(buffer != NULL);  
            //ASSERT(inlen == 0);  
            if (outlen < CWK\_STR\_LEN\_MAX)  
            {  
                DbgPrint("\\r\\n长度太短..\\r\\n");  
                status = STATUS\_INVALID\_PARAMETER;  
                break;  
            }  
            while (1)  
            {  
                // 插入到链表末尾。用锁来保证安全性。  
                str\_node = (CWK\_STR\_NODE \*)ExInterlockedRemoveHeadList(&g\_cwk\_str\_list, &g\_cwk\_lock);  
                // str\_node = RemoveHeadList(&g\_cwk\_str\_list);  
                if (str\_node != NULL)  
                {  
                    // 这种情况下,取得了字符串。那就拷贝到输出缓冲中。然后  
                    // 整个请求就返回了成功。  
                    strncpy((char \*)buffer, str\_node->buf, CWK\_STR\_LEN\_MAX);  
                    ret\_len = strnlen(str\_node->buf, CWK\_STR\_LEN\_MAX) + 1;  
                    DbgPrint("\\r\\n准备发送信息,%s\\r\\n", str\_node->buf);  
                    ExFreePool(str\_node);  
                    break;  
                }  
                else  
                {  
                    // 对于合法的要求,在缓冲链表为空的情况下,等待事件进行  
                    // 阻塞。也就是说,如果缓冲区中没有字符串,就停下来等待  
                    // 。这样应用程序也会被阻塞住,DeviceIoControl是不会返回  
                    // 的。但是一旦有就会返回。等于驱动“主动”通知了应用。  
                    KeWaitForSingleObject(&g\_cwk\_event, Executive, KernelMode, 0, 0);  
                }  
            }  
            break;  
        default:  
            // 到这里的请求都是不接受的请求。未知的请求一律返回非法参数错误。  
            status = STATUS\_INVALID\_PARAMETER;

            DbgPrint("\\r\\n出现未知请求!!\\r\\n");  
            break;  
        }  
    }  
    break;  
}  
// 返回结果  
irp->IoStatus.Information = ret\_len;  
irp->IoStatus.Status = status;  
IoCompleteRequest(irp, IO\_NO\_INCREMENT);  
//DbgPrint("\\r\\n成功返回结果!\\r\\n");  
return status;  

}

NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path)
{
NTSTATUS status;
ULONG i;
UCHAR mem[256] = { 0 };

// 生成一个控制设备。然后生成符号链接。  
UNICODE\_STRING sddl = RTL\_CONSTANT\_STRING(L"D:P(A;;GA;;;WD)");  
UNICODE\_STRING cdo\_name = RTL\_CONSTANT\_STRING(L"\\\\Device\\\\cwk\_3948d33e");  
UNICODE\_STRING cdo\_syb = RTL\_CONSTANT\_STRING(CWK\_CDO\_SYB\_NAME);

//KdBreakPoint();  
// 生成一个控制设备对象。  
status = IoCreateDeviceSecure(  
    driver,  
    0, &cdo\_name,  
    FILE\_DEVICE\_UNKNOWN,  
    FILE\_DEVICE\_SECURE\_OPEN,  
    FALSE, &sddl,  
    (LPCGUID)&CWK\_GUID\_CLASS\_MYCDO,  
    &g\_cdo);  
if (!NT\_SUCCESS(status))  
    return status;

// 生成符号链接.  
IoDeleteSymbolicLink(&cdo\_syb);  
status = IoCreateSymbolicLink(&cdo\_syb, &cdo\_name);  
if (!NT\_SUCCESS(status))  
{  
    IoDeleteDevice(g\_cdo);  
    return status;  
}

// 初始化事件、锁、链表头。  
KeInitializeEvent(&g\_cwk\_event, SynchronizationEvent, TRUE);  
KeInitializeSpinLock(&g\_cwk\_lock);  
InitializeListHead(&g\_cwk\_str\_list);

// 所有的分发函数都设置成一样的。  
for (i = 0; i < IRP\_MJ\_MAXIMUM\_FUNCTION; i++)  
{  
    driver->MajorFunction\[i\] = cwkDispatch;  
}  
//DbgPrint("test1\\r\\n");

// 支持动态卸载。  
driver->DriverUnload = cwkUnload;  
//DbgPrint("test2\\r\\n");  
// 清除控制设备的初始化标记。  
//g\_cdo->Flags &= ~DO\_DEVICE\_INITIALIZING;  
//DbgPrint("test3\\r\\n");  
return STATUS\_SUCCESS;  

}

R3部分

参考代码:

#include
#include
#include

const char* CWK_DEV_SYM = "\\\\.\\slbkcdo_3948d33e";

// 从应用层给驱动发送一个字符串。
#define CWK_DVC_SEND_STR \
(ULONG)CTL_CODE( \
FILE_DEVICE_UNKNOWN, \
0x911,METHOD_BUFFERED, \
FILE_WRITE_DATA)

// 从驱动读取一个字符串
#define CWK_DVC_RECV_STR \
(ULONG)CTL_CODE( \
FILE_DEVICE_UNKNOWN, \
0x912,METHOD_BUFFERED, \
FILE_READ_DATA)

int _tmain(int argc, _TCHAR* argv[])
{
HANDLE device = NULL;
ULONG ret_len;
int ret = 0;
char tmp[] = "Hello driver, this is a message from app.\r\n";
char* msg = tmp;
char tst_msg[1024] = { 0 };

// 打开设备.每次要操作驱动的时候,先以此为例子打开设备  
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");

    int VAKb;  
    scanf\_s("%d", &VAKb);  
    return -1;  
}  
else  
    printf("coworker demo: Open device successfully.\\r\\n");  
///////

// 这里开始,其实是对驱动的一系列测试。分配3个字符串:  
// 1.长度为0.应该可以正常输入。  
// 2.长度为511字节,应该可以正常输入。  
// 3.长度为512字节,应该返回失败。  
// 4.长度为1024字节的字符串,但声明缓冲区长度为128,应该返回失败。  
// 5.第一次读取,应该读出msg的内容。  
// 5.第一次读取,应该读出长度为511字节的字符串。  
// 6.第二次读取,应该读出长度为0的字符串。  
do {  
    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("发送信息:%s\\n", msg);

    if (DeviceIoControl(device, CWK\_DVC\_RECV\_STR, NULL, 0, tst\_msg, 1024, &ret\_len, 0) == 0)  
    {  
        ret = -6;  
        break;  
    }  
    else  
    {  
        printf("收到的信息:%s\\r\\n",tst\_msg);  
    }  
} while (0);

///////  
CloseHandle(device);  
int VAKb;  
scanf\_s("%d", &VAKb);  
return ret;  

}

虚拟机内可能无法运行。解决方法:选择静态编译。项目属性-配置属性-C/C+±代码生成-运行库-多线程调试(/MTd)。


测试图例:

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章