首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Windows 驱动开发 - 链表的数据结构

Windows 驱动开发 - 链表的数据结构

作者头像
IBinary
发布2021-09-10 10:47:20
发布2021-09-10 10:47:20
1.4K00
代码可运行
举报
文章被收录于专栏:逆向技术逆向技术
运行总次数:0
代码可运行

目录

Windows 驱动开发2 链表的数据结构

一丶链表

1.1 简介

​ 链表在windows内核开发中是最最最常见的数据结构了。 主要分为单向链表和双向链表。 单向链表的链表节点只有一个链表节点指针。 双向则是两个。 分别是指向前链表节点和后链表节点。

双向链表指向了前后两个节点。所以链表在插入移除上面的操作比单向链表更为方便。

1.2 WDK中的链表结构

代码语言:javascript
代码运行次数:0
运行
复制
typedef struct _LIST_ENTRY {
   struct _LIST_ENTRY *Flink;
   struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;

在内核中链表就是一个结构体定义的数据结构。 而链表的使用就是将其放到 其它结构中。并且通过某种方法来进行链接起来。

1.2.1 链表的使用

首先我们先定义一个链表,作为Node节点。

然后我们定义一个我们自己的结构

如下:

代码语言:javascript
代码运行次数:0
运行
复制
typedef struct _MY_TEST_STRUCT {
	ULONG m_ulDataA;
	ULONG m_ulDataB;
	ULONG m_ulDataC;
	ULONG m_ulDataD;
}MY_TEST_STRUCT,*PMY_TEST_STRUCT;

结构定义好了我们如何使用链表哪? 我们可以在结构中定义一个Node节点。

如下:

代码语言:javascript
代码运行次数:0
运行
复制
typedef struct _MY_TEST_STRUCT {
	ULONG m_ulDataA;
	ULONG m_ulDataB;
	LIST_ENTRY m_listentry;
	ULONG m_ulDataC;
	ULONG m_ulDataD;
}MY_TEST_STRUCT,*PMY_TEST_STRUCT;

我们定义了一个链表节点 名字为 m_listentry 但是其实我们可以将这个成员定义在任何地方。

我们看下在内存的布局吧。

而如果我们多定义几个结构,并且让他们互相链接起来。那么在内存中表现形式就如下:

我们可以看到 节点A中的 Fink指向节点B中的Fink位置 好好品味我这句话 它并不是指向节点B的首地址, 也就是并不是指向m_ulDataA位置

而Bink则是指向前节点位置的Fink位置。

这里操作系统设计的很巧妙。 通过在任意结构中定义 Node节点 那么这个结构就是一个双向链表的节点。

那么可能有人问了。如果给我一个节点A。 那么我要遍历双向链表的时候要怎么遍历。 因为节点位置不固定,且Flink指向的位置并不是结构体的开头。

其实这个问题很简单。 我们算一下结构体的偏移。

假设 m_listentry节点在任意结构体位置处定义,那么我们只需要计算出 m_listentry结构距离 结构体首地址的偏移即可。

如下:

代码语言:javascript
代码运行次数:0
运行
复制
typedef struct _MY_TEST_STRUCT {
	ULONG m_ulDataA;						//设在内存中的地址为0位置
	ULONG m_ulDataB;						//+4
	LIST_ENTRY m_listentry;                 //+8
	ULONG m_ulDataC;                        
	ULONG m_ulDataD;
}MY_TEST_STRUCT,*PMY_TEST_STRUCT;

通过上面结构体可以看到 +8位置是m_listentry地址。 而我们通过遍历链表所得出的地址也是+8的位置。 那么我们只需要把的出来的地址-8即可得到结构体首地址

例如下:

代码语言:javascript
代码运行次数:0
运行
复制
PMY_TEST_STRUCT BNode = xxx.m_listentry -8 

而如果计算出-8这个偏移。 其实我们可以利用指针原理 来获取偏移量。

如下:

代码语言:javascript
代码运行次数:0
运行
复制
(&(MY_TEST_STRUCT*)0).m_listentry

这里利用了个小技巧,设0地址为结构体首地址。 并且强转为我们自己的结构体。然后再去访问成员变量m_listentry。 但是有人说了0地址直接访问变量肯定是错的。直接会蓝屏。因为0地址根本就不是我们自己的结构。 所以这里还有个设计的小技巧。 我们取的是0的地址。 这样一来我们并没有访问变量了。

所以上面的公式变成了

代码语言:javascript
代码运行次数:0
运行
复制
PMY_TEST_STRUCT BNode = xxx.m_listentry -((&(MY_TEST_STRUCT*)0).m_listentry)

是不是挺恶心的。不过别怕,操作系统给我们定义了一个宏。如下:

代码语言:javascript
代码运行次数:0
运行
复制
#define CONTAINING_RECORD(address, type, field) ((type *)( \
                                                  (PCHAR)(address) - \
                                                  (ULONG_PTR)(&((type *)0)->field)))

这里的宏就是我们上面所说的意思。

看下如何使用吧。

伪代码:

代码语言:javascript
代码运行次数:0
运行
复制
MY_TEST_STRUCT testA;
MY_TEST_STRUCT testB;
PMY_TEST_STRUCT testB = CONTAINING_RECORD(&testB.m_listentry, MY_TEST_STRUCT, m_listentry);

参数一就是我们双向链表得出的地址。 比如 节点A的双向链表位置是+8位置。 那么参数1就是+8地址。

参数二就是结构体类型

参数三就是结构体成员。

看如下图应该更能明白。

1.3 链表API 之初始化节点

链表的头节点是不携带任何内容的,只是表示链表的头部,对链表的所有操作都是从头部开始的。

如果链表只有一个头节点,那么这个链表就是一个空的链表。 头节点的Flink Blink都是指向自身.

如下:

代码语言:javascript
代码运行次数:0
运行
复制
LIST_ENTRY list = { 0 };
InitializeListHead(&list);

其初始化函数的代码实现如下:

代码语言:javascript
代码运行次数:0
运行
复制
VOID InitializeListHead(_Out_ PLIST_ENTRY listHead)
{
   listHead->flink = listHead->Blink = ListHead;
   return;
}

1.4 链表操作API 节点的插入

常见的节点插入操作有两种方式,一种是插入到尾部,一种是放到头。 而注意一下头节点是没有body

数据的一个基础的listentry类型。 所以我们所说的插入到头其实是插入到头节点指向的下一个节点的位置

如A -> B-> C 我们插入D 变成了 A->D->B-C

代码语言:javascript
代码运行次数:0
运行
复制
VOID InsertHeadList(_Inout_ PLIST_ENTRY ListHead,_Out_ PLIST_ENTRY Entry) //插入节点到头部
VOID  InsertTailList(_Inout_ PLIST_ENTRY ListHead,_Out_ PLIST_ENTRY Entry)//插入节点到尾部

例子:

代码语言:javascript
代码运行次数:0
运行
复制
//初始化头节点
	LIST_ENTRY list = { 0 };
	InitializeListHead(&list);
	//定义自己的结构体
	MY_TEST_STRUCT A = { 0 };
	MY_TEST_STRUCT B = { 0 };
	MY_TEST_STRUCT C = { 0 };
	//给头节点插入数据
	InsertTailList(&list, &A.m_listentry);
	InsertTailList(&list, &B.m_listentry);
	InsertHeadList(&list, &C.m_listentry);

注意:

​ 我是在堆栈中使用的API来进行插入的,插入的节点其实是结构体中的LIST_ENTRY成员位置。

因为我是在堆栈中使用的,所以你的链表存储的数据都是基于堆栈的。 所以出了函数就没法使用了。

看一个错误的例子,我把链表定义为了全局。

代码语言:javascript
代码运行次数:0
运行
复制
NTSTATUS use_headinfo() 
{
	//初始化头节点
	LIST_ENTRY list = { 0 };
	InitializeListHead(&list);
	//定义自己的结构体
	MY_TEST_STRUCT A = { 0 };
	MY_TEST_STRUCT B = { 0 };
	MY_TEST_STRUCT C = { 0 };
	//给头节点插入数据
	InsertTailList(&g_HeadInfo, &A.m_listentry);
	InsertTailList(&g_HeadInfo, &B.m_listentry);
	InsertHeadList(&g_HeadInfo, &C.m_listentry);
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pCurobj, PUNICODE_STRING pReg)
{
	use_headinfo();
	PLIST_ENTRY pNextNode = g_HeadInfo.Flink;
	while (pNextNode != &g_HeadInfo)
	{
		PMY_TEST_STRUCT pcur = CONTAINING_RECORD(pNextNode, MY_TEST_STRUCT, m_listentry);
		if (pcur != nullptr)
		{
			//
			pcur->m_ulDataA = 1;   //会出错的位置
		}
		pNextNode = pNextNode->Flink;
	}
}

其实这也是C语言以及C++程序员容易反的低级错误。 首先我们使用了use_headinfo 将节点插入,这个没问题。 出问题的是我们在DriverEntry里面

遍历链表,并且取出值来将其修改。 遍历链表也没错,错就错在 我们的链表数据存储的是 栈内存呀。 也就是 A BC三个结构体在出了use_headinfo 就已经

释放了。 你存在链表中的数据就是错误的。此时继续使用肯定会蓝屏。

解决方法我们也知道,就是将A B C 结构体都变成堆内存。 这样出了作用域也不会释放。

1.5 链表操作API 节点的遍历

​ 节点遍历我们接触过了,其实就是 判断 Next的指针是否与原地址相等。 相等了就说明遍历完了。

思路就是建立一个节点指向你想遍历的任意节点的值。 然后取出 next几点。 然后判断是否与原节点一致。 不一致就循环,循环完了之后请注意

我们要更新Next节点的值,也就是让其指向下一个节点位置。 就这样一层一层遍历直到Next节点指向了原节点就退出循环。

代码语言:javascript
代码运行次数:0
运行
复制
PLIST_ENTRY pNext = xxx->Flink;  
while(pNext != xxx)
//xxx如果是指针那么直接判断是否 == xxx即可。 如果不是那么就取出他的地址来 所以这里是 xxx 因为xxx使用的是->符合。 如果是.那么就是&xxx
{
  //逻辑
  auto value =   CONTAINING_RECORD(PNext,type,filed);
  pNext = pNext->Flink;
}

1.6 链表操作API 节点的删除

删除节点有三种方式,分别是 从头部删除 从尾部删除 以及删除特定节点。

代码语言:javascript
代码运行次数:0
运行
复制
PLIST_ENTRY RemoveHeadList(_Inout_ PLIST_ENTRY ListHead); 
PLIST_ENTRY RemoveTailList(_Inout_ PLIST_ENTRY ListHead); 
BOOLEAN RemoveEntryList( _In_ PLIST_ENTRY Entry);          
代码语言:javascript
代码运行次数:0
运行
复制
前两个函数类似, 参数分别是给定头尾节点进行删除,删除后返回删除的节点,如果没有则返回NULL
删除特定节点的参数则是给定一个节点然后删除,如果删除后链表变成了空链表则是返回true否则就是FALSE

1.7 链表操作API 链表判断是否为空

代码语言:javascript
代码运行次数:0
运行
复制
BOOLEAN IsListEmpty(_In_ const LIST_ENTRY * ListHead);

函数很简单,如果头节点等于自身代表就是空链表。返回TRUE

否则就是FALSE

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-09-04 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Windows 驱动开发2 链表的数据结构
    • 一丶链表
      • 1.1 简介
      • 1.2 WDK中的链表结构
      • 1.2.1 链表的使用
      • 1.3 链表API 之初始化节点
      • 1.4 链表操作API 节点的插入
      • 1.5 链表操作API 节点的遍历
      • 1.6 链表操作API 节点的删除
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档