首页
社区
课程
招聘
27
[原创]X86内核笔记_2_驱动开发
发表于: 2021-7-6 19:53 34819

[原创]X86内核笔记_2_驱动开发

2021-7-6 19:53
34819

0707:更新驱动遍历、隐藏。更换图床
0712:更新驱动通信
0922:更新内存加载
内存加载驱动的代码放在评论区了

 

目录

0.创建驱动项目

创建项目

WDK和VS安装参考文章:X86内核笔记0配置双机调试环境

 

打开VS2017,新建项目选择Visual C++ -> Windwos Drivers -> Legacy -> Empty WDM Driver

 

image-20210707113559574

 

右键SourceFiles目录,新建项。创建一个扩展名为C的C++文件。(不要用cpp扩展名)。文件名随意起,不是非要和项目名一样。

 

image-20210707113710643

 

在.c文件中先引入头文件 ntifs.h

1
#include //这个头文件内包含了大量驱动相关的头文件,一次性包含,省时省力。

删除INF文件

 

image-20210707113737860

 

如果引入头文件出现找不到头文件,就如下图设置一下SDK版本。

 

image-20210707113757099

 

然后设置一些项目属性:

  • 取消警告视为错误。

    image-20210707113817359

  • DriverSetting中将驱动平台改为Win7.(我们要在win7上做实验。)

    image-20210707113916405

驱动分类简述

驱动大体分为三种,分别是:NT式驱动、WDM式驱动、WDF式驱动(KWDF内核驱动,UWDF用户驱动)。

NT式驱动

NT虚拟驱动,老式驱动,从WIN95开始使用NT式驱动。 若所开发的驱动不与硬件打交道,建议使用NT式驱动或WDM式驱动。如果NT式驱动出现了绑定设备的情况,该驱动将无法卸载。只能通过重启系统进行卸载。对于服务器来说重启很伤。

WDM式驱动

相对于NT式驱动来讲,WDM式驱动支持卸载(热拔插)。无需重启即可卸载。并且WDM式驱动对于NT式驱动进行了一些封装和优化。本质区别不大。

WDF式驱动

WDF式驱动相较前两种,其最大的意义是简化开发。不像NT与WDM驱动那么底层化。WDF式驱动将WDM式驱动进行了封装,做成了一套架构,使得开发驱动变得更简单。同时带来的弊端就是无法掌控底层。

 

由于开发简便,不容易蓝屏,所以公司开发驱动一般选用WDF式驱动。

 

想要学习WDF式驱动,需要了解COM相关知识。

 

只有系统中存在WDFLDR.sys驱动,我们编写的WDF驱动才可以跑起来。并且项目中需要一个inf文件,NT/WDM式驱动则不需要这个inf文件。

 

image-20210707114938747

1.编写驱动程序

驱动入口函数(DriverEntry)

1
2
3
4
5
#include "ntifs.h"
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject,PUNICODE_STRING RegistryPath){
    //代码
    return STATUS_UNSUCCESSFUL;
}

DriverEntry是我们写代码时的入口函数。其编译生成的sys文件真正的入口点并不是DriverEntry。在IDA中可以看到驱动真正的入口点函数是GsDriverEntry。其内部调用了我们的DriverEntry函数。

 

image-20210707113939582

指定入口函数

如果不想让编译器生成GsDriverEntry而是直接将入口函数设置为DriverEntry,可以按照下图设置。

 

image-20210707114010768

编写代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include "ntddk.h"
 
void UnloadDriver(PDRIVER_OBJECT driver);
 
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
    DbgBreakPoint();        //相当于 __asm{int 3}
    DbgPrint("驱动加载了。\r\n");    //驱动的打印函数,相当于3环的printf
    DriverObject->DriverUnload = UnloadDriver;    //为驱动指定卸载函数
    return STATUS_SUCCESS;
}
//驱动卸载函数
void UnloadDriver(PDRIVER_OBJECT driver) {
    DbgPrint("驱动停止了。\r\n");
}

打印字符串对象

如果想要打印字符串对象中的字符串,可以使用如下格式:

1
2
3
4
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING pReg) {
    DbgPrint("-------%wZ--------",pReg);//传入字符串对象指针。
    return STATUS_SUCCESS;
}

生成驱动

点击生成解决方案即可。若报一些格式错误,就删除一些特殊符号之类的东西。

加载驱动(部署-启动-停止-卸载)

使用InstDrv.exe加载驱动。使用DbgView.exe查看输出(必须选中监视核心,否则无法监视驱动层输出)。1

调试驱动

通过调用函数DbgBreakPoint为驱动增加一个断点。这个函数相当于int 3指令。这样我们就可以在windbg中断下。并且Windbg会自动识别PE结构中的PDB路径,自动加载PDB文件识别出我们的源码。

 

image-20210707114036228

解决刷屏

如果windbg调试过程中,出现了刷屏的情况,执行以下命令可以关闭刷屏。

1
kd> ed nt!Kd_SXS_Mask 0;ed nt!Kd_FUSION_Mask 0

2.驱动对象PDRIVER_OBJECT初识

在成功断在我们的代码中后,在Windbg中查看驱动对象结构。

 

image-20210707114100203

  • Type:驱动对象类型。

  • Size:驱动对象大小

  • DeviceObject:设备对象,我们这里没添加设备,因此是null

  • DriverStart:驱动文件基址,也就是PE格式中的ImageBase。通过db命令可以看到4D 5A。

  • DriverSize:驱动模块大小,也就是PE格式中的SizeOfImage。

  • DriverExtension:驱动扩展对象。使用dt命令查看该对象

    1
    2
    3
    4
    5
    6
    7
    8
    kd> dt _DRIVER_EXTENSION 0x8831b790
    ntdll!_DRIVER_EXTENSION
       +0x000 DriverObject     : 0x8831b6e8 _DRIVER_OBJECT
       +0x004 AddDevice        : (null)
       +0x008 Count            : 0
       +0x00c ServiceKeyName   : _UNICODE_STRING "hellodriver"
       +0x014 ClientDriverExtension : (null)
       +0x018 FsFilterCallbacks : (null)
    • DriverObject:指向当前驱动对象首地址。
    • ServiceKeyName:驱动服务注册表文件夹名。
  • DriverName:驱动名,也就是驱动的文件名前面加个\Driver\。这个名字是个字符串结构体。

    查看该字符串结构:

    1
    2
    3
    4
    5
    6
    kd> dt _UNICODE_STRING 8831b6e8 +1c
    ntdll!_UNICODE_STRING
     "\Driver\hellodriver"
       +0x000 Length           : 0x26        //字符串长度
       +0x002 MaximumLength    : 0x26        //字符串最大长度
       +0x004 Buffer           : 0x87fc36c8  "\Driver\hellodriver"    //字符串内容
  • HardwareDatabase:驱动服务注册表路径。前往注册表查看该路径,可以发现一个名为“hellodriver”的文件夹,这就是我们的驱动。

    image-20210707114126468

    • DisplayName:驱动名
    • ErrorControl:当驱动加载失败时会设置这个值。
    • ImagePath:驱动文件路径。\??\是设备路径,我们平时访问各种文件夹其实都带这个\??\,只是windows底层帮我们补充了。
    • Start:驱动加载类型。手动启动为3,开机自启为2,BIOS自启为1。
    • Type:服务类型。1为驱动。
  • DriverInit :驱动入口点,也就是PE文件的AddressOfEntryPoint。

  • DriverUnload:驱动卸载函数地址。

3.驱动加载方法

加载驱动大体分为两种:服务加载和直接加载。实际应用中可以将两种方法都利用上。

服务加载

  1. 调用OpenSCManager打开服务控制。
  2. 调用CreateService创建服务。实际上就是创建注册表相关键值。在执行完该API后,驱动已经被注册为服务了。这时我们通过CMD执行net start XXXX也可以加载我们的驱动。
  3. 调用OpenService打开现有服务。
  4. 调用StartService启动服务

这种方式实际上加载该驱动的进程,并不是调用API的进程。而是通过API向系统通知我要加载一个驱动。系统进程接收到通知后加入到系统中的一个队列。并由系统进程在某时某刻加载该驱动。

 

也就是这种方式是通知系统进程来进行加载。

直接加载

调用ZwLoadDriver或NtLoadDriver加载一个已被正确注册的驱动。

 

这种方法需要我们自己手动去注册表内注册该驱动的相关信息。这样该驱动才可以被加载。

 

直接加载的方式在调用API后就会直接加载该驱动,所以该驱动的加载者就是调用该API的进程。相比于服务加载会留下痕迹。

4.第一个练习

编写两个驱动A和B,在A中定义全局变量值为100,打印A的地址pA。在B中打印pA的数据,观察是否与A中定义的相同。

 

A代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include
 
void UnloadDriver(PDRIVER_OBJECT driver);
 
UINT32 i = 100;
 
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
    DbgPrint("i addr = %08x\r\n", &i);
    DriverObject->DriverUnload = UnloadDriver;
    return STATUS_SUCCESS;
}
//驱动卸载函数
void UnloadDriver(PDRIVER_OBJECT driver) {
    DbgPrint("驱动停止了。\r\n");
}

B代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include
 
void UnloadDriver(PDRIVER_OBJECT driver);
 
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
    PUINT32 p = (PUINT32)0x8e315000;
    DbgPrint("i value = %d\r\n", *p);
    DriverObject->DriverUnload = UnloadDriver;
    return STATUS_SUCCESS;
}
//驱动卸载函数
void UnloadDriver(PDRIVER_OBJECT driver) {
    DbgPrint("驱动停止了。\r\n");
}

5.驱动常用类型及API

在驱动中写代码与3环不同,一些数据类型及常用API也最好使用驱动开发专用的版本。这算是一种代码规范。

基本数据类型

在驱动中,原数据类型int char等均被封装、重定义。在驱动开发中应使用如下数据类型:

1
2
3
4
5
6
7
8
9
10
11
UINT8,PUINT8 -> unsigned char
UINT16,PUINT16 -> unsigned short 
UINT32,PUINT32 -> unsigned int
UINT64,PUINT64 -> unsigned __int64
INT8,PINT8 -> char
INT16,PINT16 -> short
INT32,PINT32 -> int
INT64,PINT64 -> __int64
LONG32,PLONG32 -> int
ULONG32,PULONG32 -> unsigned int
DWORD32,PDWRD32 -> int

错误码返回值

绝大多数内核函数都会有一个返回值,类型为NTSTATUS。该类型本质就是一个LONG。

 

如GetLastError这种取错误码的函数,取到的值其实就是NTSTATUS转化后的错误码。

 

常用的NTSTATUS宏如下,负数(大于0X80000000)的返回值为错误,大于等于0为成功

1
2
3
4
5
6
STATUS_SEVERITY_SUCCESS          0x0
STATUS_SEVERITY_INFORMATIONAL    0x1
STATUS_SEVERITY_WARNING          0x2
STATUS_SEVERITY_ERROR            0x3
STATUS_UNSUCCESSFUL              0xC0000001
STATUS_ACCESS_VIOLATION             0xC0000005

同时有一个宏用于判断返回值是成功还是失败:

1
NT_SUCCESS(NTSATUS类型参数)    //#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)

字符串相关

在内核开发中,字符串不要定义为char* x = "xx",WDK为我们准备了一些字符串相关的API。

定义字符串

1
2
3
UNICODE_STRING uStr = {0};    //定义一个unicode字符串,类型为UNICODE_STRING
STRING aStr = {0};    //定义一个ascii字符串,类型为STRING
ANSI_STRING aStr = {0};  //所有ANSI与直接STRING 作用相同

初始化字符串

1
2
3
RtlInitUnicodeString(&uStr,L"unicode string");//初始化unicode字符串,为其赋值。不会申请内存。
RtlInitString(&aStr,"ascii string"); //初始化ascii字符串,为其赋值,不会申请内存。
RtlInitAnsiString(&aStr,"ascii string"); //初始化ascii字符串,为其赋值,不会申请内存。

字符串转化

1
2
RtlAnsiStringToUnicodeString(&uStr,&aStr,true);//将ascii字符串转为unicode字符串,无需为unicode字符串做初始化,第三个参数为true则自动申请内存。为false则不申请,仅修改unicode现有空间。若为true,则需要手动释放字符串内存。
RtlUnicodeStringToAnsiString();//unicode字符串转为ascii字符串,用法与上面相同。

释放字符串

1
2
RtlFreeUnicodeString();//释放unicode字符串内存,当字符串初始化中为其分配了内存时,需要释放内存。
RtlFreeAnsiString(); //释放ascii字符串内存,当字符串初始化中为其分配了内存时,需要释放内存。

字符串格式化

1
2
3
4
5
#include //使用格式化API需要引入此头文件
char aStr[0x1000]= {0};
RtlStringCbPrintfA(aStr, 0x1000, "%d---%s", 123, "test");//参数1: Ascii字符串指针
wchar uStr[0x1000] = {0};
RtlStringCbPrintfW(uStr, 0x1000, L"%d---%s", 123, L"test");//参数1Unicode字符串指针

字符串比较

1
2
RtlCompareUnicodeString(&uStr1,&uStr2,TRUE);//比较两个unicode字符串是否相等,true忽略大小写
RtlCompareString    //比较两个ascii字符串是否相等

内存相关

申请内存

1
2
3
4
ExAllocatePool(type,size);//type:内存类型,PagePool和NonPagePool,分别为分页内存和非分页内存。
//分页内存:后面章节会详细说,暂时理解为不可执行的内存
//非分页内存:后面章节会详细说,暂时理解为可执行的内存   通常填NonPagePool,对应属性为PTE的XD/NX位。
ExAllocatePoolWithTag(type,size,tag);//tag:内存标志,四个字节最多,如'test',为申请的内存起个名字。用单引号包含,内部最终转为16进制数据。

拷贝、设置、比较内存

1
2
3
4
5
RtlFillMemory(pointer,length,value);//相当于memset
RtlEqualMemory(pointer,Source,Length)//相当于memcmp结果取反
RtlMoveMemory(pointer,Source,Length) //相当于memmove
RtlCopyMemory(pointer,Source,Length) //相当于memcpy
RtlZeroMemory(pointer,Length) //相当于memset第二参数为0.

释放内存

1
ExFreePool(pointer);//释放内存

延迟

1
2
3
4
5
6
7
//驱动代码中的延迟不可以使用Sleep,而是KeDelayExecutionThread
LARGE_INTEGER li = { 0 };    //时长结构。
li.QuadPart = -10000 * 5000;    //时间单位 负数代表相对时间  正数代表绝对时间。 5000代表5秒。
KeDelayExecutionThread(KernelMode,FALSE,&li);
//第一个参数:延迟模式,我们这里选内核模式
//第二个参数:强制唤醒。如果为FALSE,那么休眠时间未结束前,不会被唤醒。
//第三个参数:延迟时长。

创建线程

1
2
3
4
5
6
7
8
9
10
11
//线程函数
VOID myThreadFun(_In_ PVOID StartContext) {
    //线程函数代码
}
 
HANDLE tHandle = NULL;
NTSTATUS tRet = PsCreateSystemThread(&tHandle,THREAD_ALL_ACCESS,NULL,NULL,NULL, myThreadFun,NULL);
//最后一个参数是线程函数启动参数。
if(NT_SUCCESS(tRet)){
    ZwClose(tHandle);//相当于CloseHandle
}

内核链表API


[注意]看雪招聘,专注安全领域的专业人才平台!

最后于 2021-9-22 17:11 被SSH山水画编辑 ,原因: 更新驱动通信
收藏
免费 27
支持
分享
赞赏记录
参与人
雪币
留言
时间
一路南寻
为你点赞!
2025-1-3 05:14
NoHeart
+3
感谢你的积极参与,期待更多精彩内容!
2024-12-23 13:25
逐梦旅人
+1
谢谢你的细致分析,受益匪浅!
2024-12-22 14:44
嫉妒的死远点
感谢你的贡献,论坛因你而更加精彩!
2024-9-17 04:03
zhczf
为你点赞~
2024-3-31 22:39
治愈ckt
为你点赞~
2024-1-4 15:35
shishichen
为你点赞~
2022-12-24 18:26
七夜大大
为你点赞~
2022-11-3 22:25
PLEBFE
为你点赞~
2022-7-27 01:04
心游尘世外
为你点赞~
2022-7-26 22:52
飘零丶
为你点赞~
2022-7-17 02:30
北门观雪
为你点赞~
2022-5-29 09:13
34r7hm4n
为你点赞~
2021-12-9 23:55
他的猫豆
为你点赞~
2021-11-30 16:38
byfhs
为你点赞~
2021-10-15 16:02
ᕦ(笨ㅂ鸟)ᕤ
为你点赞~
2021-10-1 22:47
yeyeshun
为你点赞~
2021-9-29 16:26
ZENGKEFU
为你点赞~
2021-9-25 19:49
sinkay
为你点赞~
2021-9-23 14:57
~时光荏苒
为你点赞~
2021-8-10 22:24
谖草
为你点赞~
2021-7-9 10:00
zzage
为你点赞~
2021-7-7 14:53
SSH山水画
为你点赞~
2021-7-7 10:03
0xC5
为你点赞~
2021-7-7 08:28
chuxue
为你点赞~
2021-7-7 01:32
skzhe
为你点赞~
2021-7-6 22:37
ookkaa
为你点赞~
2021-7-6 20:09
最新回复 (38)
雪    币: 9014
活跃值: (5598)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
2
关注中
2021-7-7 09:18
0
雪    币: 23352
活跃值: (3472)
能力值: (RANK:648 )
在线值:
发帖
回帖
粉丝
3
期待楼主更新完成
2021-7-7 09:43
0
雪    币: 1222
活跃值: (1880)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
4
写的不错
2021-7-7 10:23
0
雪    币: 3984
活跃值: (6126)
能力值: ( LV9,RANK:140 )
在线值:
发帖
回帖
粉丝
5
感谢分享 另外此处: 由于开发简便,不容易蓝屏,所以公司开发驱动一般选用MDF式驱动。   此处应该是WDF式驱动?
有几张图片看不到
我们要在win7上做实验  下面的图片看不到
如果不想让编译器生成GsDriverEntry而是直接将入口函数设置为DriverEntry,可以按照下图设置。  下面的图片看不到
在成功断在我们的代码中后,在Windbg中查看驱动对象结构。  下面的图片看不到
2021-7-7 10:39
1
雪    币: 1498
活跃值: (14683)
能力值: ( LV12,RANK:380 )
在线值:
发帖
回帖
粉丝
6
0346954 感谢分享 另外此处: 由于开发简便,不容易蓝屏,所以公司开发驱动一般选用MDF式驱动。 此处应该是WDF式驱动? 有几张图片看不到 我们要在win7上做实验 下面的图片看不到 如果不想让编 ...
嗯  是WDF驱动   下次我改一下。  我图床用的是github的  总挂,难受
2021-7-7 11:13
0
雪    币: 4574
活跃值: (6598)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
7
有什么参考 学习资料吗?
2021-7-7 14:55
0
雪    币: 1498
活跃值: (14683)
能力值: ( LV12,RANK:380 )
在线值:
发帖
回帖
粉丝
8
Max_hhg 有什么参考 学习资料吗?
各种内核书籍,Windows 内核情景分析  Windows内核原理与实现  这些都可以
2021-7-7 15:24
0
雪    币: 71
活跃值: (930)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
make
2021-7-7 15:31
0
雪    币: 3940
活跃值: (3761)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
10
可以可以,加油,就搞通信,我就弄了三天才捣鼓出来
2021-7-9 08:05
0
雪    币: 1498
活跃值: (14683)
能力值: ( LV12,RANK:380 )
在线值:
发帖
回帖
粉丝
11
APT_华生 可以可以,加油,就搞通信,我就弄了三天才捣鼓出来
2021-7-9 09:06
0
雪    币: 8565
活跃值: (5364)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
感谢大牛的基础文章, 比着做测试成功了第一个驱动,现在发现两个问题,希望大牛有时间的时候帮忙解惑
1 DbgBreakPoint();  这行代码不注释掉的话,win7 32 加载驱动会蓝屏 ,注释掉就没问题了 ,debugview输出也正常 。  
2 编译x64的驱动后 debugview 捕获不到打印内容
2021-7-9 12:19
0
雪    币: 1498
活跃值: (14683)
能力值: ( LV12,RANK:380 )
在线值:
发帖
回帖
粉丝
13
romobin 感谢大牛的基础文章, 比着做测试成功了第一个驱动,现在发现两个问题,希望大牛有时间的时候帮忙解惑 1 DbgBreakPoint(); 这行代码不注释掉的话,win7 32 加载驱动会蓝屏 ,注释 ...
1.   DbgBreakPoint只能在连接上调试后才能有效果,否则没有调试器接管int3会蓝屏。
2.   没遇到过,可能不支持64位的?
2021-7-9 15:42
0
雪    币: 30
活跃值: (772)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
romobin 感谢大牛的基础文章, 比着做测试成功了第一个驱动,现在发现两个问题,希望大牛有时间的时候帮忙解惑 1 DbgBreakPoint(); 这行代码不注释掉的话,win7 32 加载驱动会蓝屏 ,注释 ...
这是因为你没有配注册表信息吧,具体给你一个地址,你配一下,然后重启就可以了:http://www.pnpon.com/article/detail-119.html
2021-7-9 16:39
0
雪    币: 8565
活跃值: (5364)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
tsingchen 这是因为你没有配注册表信息吧,具体给你一个地址,你配一下,然后重启就可以了:http://www.pnpon.com/article/detail-119.html
感谢,看雪也有一篇解决这个问题的,已经解决了
2021-7-9 20:34
0
雪    币: 8565
活跃值: (5364)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
SSH山水画 1. DbgBreakPoint只能在连接上调试后才能有效果,否则没有调试器接管int3会蓝屏。 2. 没遇到过,可能不支持64位的?
了解了 ,已经解决了,感谢 
2021-7-9 20:34
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
17

驱动开发还是挺难的,先看看吧 不是很擅长C 与 C++...

2021-7-22 10:34
0
雪    币: 1498
活跃值: (14683)
能力值: ( LV12,RANK:380 )
在线值:
发帖
回帖
粉丝
18
mb_cigdritw 驱动开发还是挺难的,先看看吧 不是很擅长C 与 C++...
熟悉了就不难了
2021-7-22 17:33
0
雪    币: 2219
活跃值: (5495)
能力值: ( LV12,RANK:210 )
在线值:
发帖
回帖
粉丝
19
大佬,我想问一下,我按照您的代码编写测试文件,入口函数 第二个参数是PUNICODE_STRING就会报错,: fatal error LNK1169: 找到一个或多个多重定义的符号,改成UNICODE_STRING就可以生成.sys文件。但是把驱动文件放到win7虚拟机中 安装驱动成功,启动就失败,这是为什么啊 ??诚信提问!!
2021-9-9 14:54
0
雪    币: 1498
活跃值: (14683)
能力值: ( LV12,RANK:380 )
在线值:
发帖
回帖
粉丝
20
pyikaaaa 大佬,我想问一下,我按照您的代码编写测试文件,入口函数 第二个参数是PUNICODE_STRING就会报错,: fatal error LNK1169: 找到一个或多个多重定义的符号,改成UNICOD ...
启动失败是因为你参数应该传结构体指针,结果你传了个结构体。至于重定义,不晓得,可能是环境问题吧,可以百度下。
2021-9-9 15:25
0
雪    币: 2219
活跃值: (5495)
能力值: ( LV12,RANK:210 )
在线值:
发帖
回帖
粉丝
21
SSH山水画 启动失败是因为你参数应该传结构体指针,结果你传了个结构体。至于重定义,不晓得,可能是环境问题吧,可以百度下。
感谢大佬
2021-9-9 18:31
0
雪    币: 1498
活跃值: (14683)
能力值: ( LV12,RANK:380 )
在线值:
发帖
回帖
粉丝
22
驱动内存加载源码:https://wwi.lanzoui.com/iOVxCucxifi
2021-9-22 17:11
0
雪    币: 172
活跃值: (281)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
23
mark
2021-9-27 20:39
0
雪    币: 9
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
24
有无大佬知道驱动断链之后卸载的时候怎么还原吗
2021-10-13 16:02
0
雪    币: 1498
活跃值: (14683)
能力值: ( LV12,RANK:380 )
在线值:
发帖
回帖
粉丝
25
mb_itmemoyh 有无大佬知道驱动断链之后卸载的时候怎么还原吗
恢复清除的属性,再把自己的驱动加到链表里
2021-10-13 16:31
0
游客
登录 | 注册 方可回帖
返回

账号登录
验证码登录

忘记密码?
没有账号?立即免费注册