原创 | 驱动病毒那些事(一)—— 基础
这是一个系列文章,由于本人水平有限,文章如有叙述错误,请大佬斧正,在评论区交流。
内核模式驱动级病毒的特征是运行在内核驱动级、能与安全软件抗衡、可能不会对系统进行直接破坏但为别的病毒提供下载支持。内核模式驱动级病毒本身虽然不具备盗号功能,但它具备对抗安全软件及系统自带的安全工具的功能。
这类病毒在病毒产业链中起着核心作用,它一般先会对安全软件进行劫持、关闭防火墙、阻止系统升级,当成功后再下载大量其它木马病毒,进行下一阶段的攻击。
1.VS219驱动开发环境+ debugview
2.Windbg+VirtualKD双机调试环境
3.PCHunter分析工具
4.WRK源代码一份+Vscode代码阅读工具
5.InstDrv或KmdManager等驱动加载工具
这些工具和安装流程网上都有详细介绍,在此不再赘述。
如果大家在安装完成VS后,创建新项目时没有KMDF项目。安装的WDK 的文件夹下选择Vsix文件夹。
下面给大家介绍驱动中常见的数据结构,我们只有熟悉了驱动的数据结构,无论是在IDA静态分析结构体识别,还是windbg调试dt命令查看结构体信息,才能得心应手。
1.驱动对象DRIVER_OBJECT
typedef struct _DRIVER_OBJECT {
CSHORT Type;
CSHORT Size;
PDEVICE_OBJECT DeviceObject; //指向Driver创建的设备对象的指针。
ULONG Flags;
PVOID DriverStart; //驱动对象的起始地址
ULONG DriverSize; //驱动对象的大小
PVOID DriverSection;
//驱动对象结构.可以解析为_LDR_DATA_TABLE_ENTRY 是一个链表存储着下一个驱动对象
PDRIVER_EXTENSION DriverExtension;
UNICODE_STRING DriverName; // //驱动对象的名字
PUNICODE_STRING HardwareDatabase; //指向一个NICODE_STRING字符串,此字符串指向的注册表路径包含了硬件配置信息。
PFAST_IO_DISPATCH FastIoDispatch; //文件驱动用到的派遣函数
PDRIVER_INITIALIZE DriverInit; //驱动程序的DriverEntry 例程入口点。
PDRIVER_STARTIO DriverStartIo;
PDRIVER_UNLOAD DriverUnload; //卸载驱动的时候的回调函数。
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1]; //该成员是一个PDRIVER_DISPATCH结构体组数指针,每个指针指向一个处理IRP的派遣函数。
} DRIVER_OBJECT, *PDRIVER_OBJECT;
我们也可以通过windbg调试,找到该结构体地址,用dt命令查看具体结构体信息。
2.设备对象DEVICE_OBJECT
typedef struct _DEVICE_OBJECT {
CSHORT Type;
USHORT Size;
LONG ReferenceCount; //引用计数
struct _DRIVER_OBJECT *DriverObject; // 该设备所属的驱动对象
struct _DEVICE_OBJECT *NextDevice; //指向下一个设备对象的指针(如果存在)
struct _DEVICE_OBJECT *AttachedDevice;
struct _IRP *CurrentIrp; //当前IRP。
PIO_TIMER Timer;
ULONG Flags;
ULONG Characteristics;
__volatile PVPB Vpb;
PVOID DeviceExtension; //设备扩展结构指针,指向设备扩展对象。
DEVICE_TYPE DeviceType;
CCHAR StackSize; //指定发送给该驱动的IRP中stacklocation 的大小(最小值)
union {
LIST_ENTRY ListEntry;
WAIT_CONTEXT_BLOCK Wcb;
} Queue;
ULONG AlignmentRequirement;
KDEVICE_QUEUE DeviceQueue;// 设备对象的队列。驱动队列中包含了与驱动对象相应的等待驱动处理的IRP。
KDPC Dpc;
ULONG ActiveThreadCount;
PSECURITY_DESCRIPTOR SecurityDescriptor;
KEVENT DeviceLock;// 由I/O管理器创建的同步事件对象。
USHORT SectorSize;
USHORT Spare1;
struct _DEVOBJ_EXTENSION *DeviceObjectExtension;
PVOID Reserved;
} DEVICE_OBJECT, *PDEVICE_OBJECT;
设备对象(DREVICE_OBJECT)是唯一可以接收请求的实体,任何一个请求(IRP)都是发送给某个设备对象的,一个设备对象总是属于一个驱动对象。
3. 驱动入口DriverEntry
NTSTATUS DriverEntry(
_In_ PDRIVER_OBJECT DriverObject, //指向驱动程序对象结构的指针,该结构表示驱动程序的 WDM 驱动程序对象。
_In_ PUNICODE_STRING RegistryPath //指向 UNICODE字符串结构的指针,该字符串结构指定注册表中驱动程序的 Parameters 项的路径。
);
4.派遣函数IRP
NTSTATUS Dispatch(PDEVICE_OBJECT deivce,PIRP irp);
第一个参数为请求的目标设备,第二个参数为请求的指针。
IRP类型
IRP_MJ_CREATE创建设备,CreatFile会产生此IRP
IRP_MJ_CLOSE关闭设备,CloseHandle会产生此IRP
IRP_MJ_CLEANUP清除工作,CloseHandle会产生此IRP
IRP_MJ_DEVICE_CONTROL DeviceIoControl函数会产生此IRP
IRP_MJ_PNP即插即用消息,NT式驱动不支持此种IRP,只有WDM 驱动才支持此种IRP
IRP_MJ_POWER在操作系统处理电源消息时,产生此IRP
IRP_MJ_QUERY_INFORMATION获取文件长度,GetFileSize会产生此IRP
IRP_MJ_READ读取设备内容,ReadFile会产生此IRP
IRP_MJ_SET_INFORMATION设置文件长度,GetFileSize会产生此IRP
IRP_MJ_SHUTDOWN关闭系统前会产生此IRP
IRP_MJ_SYSTEM_CONTROL系统内部产生的控制信息,类似于内核调用DeviceIoControl函数
IRP_MJ_WRITE对设备进行WriteFile时会产生此IRP
到设备栈中的设备对象,设备对象通过结构体中DriverObject成员找到驱动对象,驱动对象通过检查结构中MajorFunction字段,确定执行什么操作。若创建成功,则应用层会收到一个此文件对象的句柄 。
内核模块并不生成一个进程,只是填写一组回调函数让Windows调用
具体调用细节可以参考:https://wenku.baidu.com/view/117ca249336c1eb91a375d1f.html
5.双向链表LIST_ENTRY
typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
}LIST_ENTRY,*PLIST_ENTRY
其中的成员Flink为指向下一个节点的指针,成员Blink为指向前一个节点的指针
6.字符串UNICODE_STRING
typedef struct _UNICODE_STRING {
USHORT Length; //字符串的所占的字节数
USHORT MaximumLength;// 字符串所能占的最大字节数字符串的指针
PWCH Buffer; //指向宽字符串的指针
} UNICODE_STRING;
1.常量内存,调用RtlInitUnicodeString 函数进行初始化。
UNICODE_STRING v1;
RtlInitUnicodeString(&v1, L"HelloWorld");
DbgPrint("%wZ\r\n", &v1);
2.手工赋值
UNICODE_STRING v1;
WCHAR Data[] = L"HelloWorld";
v1.Buffer = Data;
v1.Length = wcslen(Data)*sizeof(WCHAR);
v1.MaximumLength = (wcslen(Data)+1)*sizeof(WCHAR);
DbgPrint("%wZ\r\n", &v1);
3.动态内存ExAllocatePool函数动态分配。
UNICODE_STRING v1;
WCHAR Data[] = L"HelloWorld";
v1.Length = wcslen(Data) * sizeof(WCHAR);
v1.MaximumLength = (wcslen(Data) + 1) * sizeof(WCHAR);
v1.Buffer = ExAllocatePool(PagedPool, v1.MaximumLength);
这里有一点需要注意当应用程序与驱动通信时,一般应用程序传入的字符串为ANSI,所以在驱动中应先定义ANSI_STRING,然后再使用RtlAnsiStringToUnicodeString 将其转换成UNICODE_STRING,作为后用,否则会有蓝屏的危险。
7. 设备链接名称与设备名称关联 IoCreateSymbolicLink
NTSTATUS IoCreateSymbolicLink(
PUNICODE_STRING SymbolicLinkName,//符号链接名用于ring3通信
PUNICODE_STRING DeviceName //设备对象的名字
);
需要注意的是设备对象名称需要用UNICODE字符串指定,并且字符串必须是L"\\Device\\ [设备名]”这种形式。
驱动中符号链接的命名规则为L"\\??\\ [设备名]”或L"\\DosDevices\\[设备名]"
在应用层中用L”\\.\[设备名]”来标识符号链接名(""是C/C+中转义符, "\\.\"相当于\.)
下面用一个实例来向大家展示:
Ring 0:
先用IoCreateDevice函数创建设备对象,再用IoCreateSymbolicLink将符号链接名与设备对象名称关联
#define DRV_DEVICEL"\\Device\\IOname"//设备名称
#define DRV_SYSLNKL"\\??\\IOname "//符号链接
UNICODE_STRING devName;
RtlInitUnicodeString(&devName, DRV_DEVICE);
status = IoCreateDevice(pDriverObject, sizeof(DEVICE_EXTENSION),&devName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &pDevObj); //创建设备
UNICODE_STRING symLinkName;
RtlInitUnicodeString(&symLinkName, DRV_SYSLNK);
status = IoCreateSymbolicLink(&symLinkName, &devName);//设备链接名与设备名关联
Ring 3:
应用层通过符号链接名调用CreateFile函数获取到设备句柄DeviceHandle。再调用DeviceIoControl函数就可以通过这个DeviceHandle发送控制码了。
#define DEVICE_LINK_NAME L"\\\\.\\IOname "
HANDLE hDevice = CreateFile(
DEVICE_LINK_NAME,
GENERIC_WRITE | GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
0,
NULL);
设置符号路径后,可以直接使用命令bu 驱动名称!DriverEntry,windbg就会在驱动入口断下。
源代码调试效果如下:
uf nt!IopLoadDriver
64位下:
这里有一点需要注意,也是我学习时候的一个误区,认为所有内核代码都运行在系统进程内。事实上只有DriverEntry函数被调用时,一般位于系统进程中,Windows一般都用系统进程来加载内核模块,并不是说内核模块始终运行在System进程中。
1.《Windows内核安全与驱动开发》
2.https://www.cnblogs.com/lsh123/p/7354573.html
3. https://bbs.pediy.com/thread-228575.htm
4. https://wenku.baidu.com/view/117ca249336c1eb91a375d1f.html
5. https://www.cnblogs.com/zudn/archive/2010/12/30/1921457.html
6. https://www.cnblogs.com/hgy413/p/3693361.html
7. https://www.cnblogs.com/lsh123/p/7354573.html
8. https://www.cnblogs.com/zudn/archive/2010/12/30/1921457.html
往期推荐