6.5 Windows驱动开发:内核枚举PspCidTable句柄表

在 Windows 操作系统内核中,PspCidTable 通常是与进程(Process)管理相关的数据结构之一。它与进程的标识和管理有关,每个进程都有一个唯一的标识符,称为进程 ID(PID)。与之相关的是客户端 ID,它是一个结构,其中包含唯一标识进程的信息。这样的标识符在进程管理、线程管理和内核对象的创建等方面都起到关键作用。

PspCidTable 可用于快速查找和管理进程信息,以便在系统中追踪、查询和操作进程。该表格可能包含有关每个进程的关键信息,例如 PID、客户端 ID、进程状态等。

在上一篇文章《内核枚举DpcTimer定时器》中我们通过枚举特征码的方式找到了DPC定时器基址并输出了内核中存在的定时器列表,本章将学习如何通过特征码定位的方式寻找Windows 10系统下面的PspCidTable内核句柄表地址。

首先引入一段基础概念;

  • 1.在windows下所有的资源都是用对象的方式进行管理的(文件、进程、设备等都是对象),当要访问一个对象时,如打开一个文件,系统就会创建一个对象句柄,通过这个句柄可以对这个文件进行各种操作。
  • 2.句柄和对象的联系是通过句柄表来进行的,准确来说一个句柄就是它所对应的对象在句柄表中的索引。
  • 3.通过句柄可以在句柄表中找到对象的指针,通过指针就可以对,对象进行操作。

PspCidTable 就是这样的一种表(内核句柄表),表的内部存放的是进程EPROCESS线程ETHREAD的内核对象,并通过进程PID线程TID进行索引,ID号以4递增,内核句柄表不属于任何进程,也不连接在系统的句柄表上,通过它可以返回系统的任何对象。

内核句柄表与普通句柄表完全一样,但它与每个进程私有的句柄表有以下不同;

  • 1.PspCidTable 中存放的对象是系统中所有的进程线程对象,其索引就是PIDTID
  • 2.PspCidTable 中存放的直接是对象体EPROCESS和ETHREAD,而每个进程私有的句柄表则存放的是对象头OBJECT_HEADER
  • 3.PspCidTable 是一个独立的句柄表,而每个进程私有的句柄表以一个双链连接起来。
  • 4.PspCidTable 访问对象时要掩掉低三位,每个进程私有的句柄表是双链连接起来的。

那么在Windows10系统中该如何枚举句柄表;

  • 1.首先找到PsLookupProcessByProcessId函数地址,该函数是被导出的可以动态拿到。
  • 2.其次在PsLookupProcessByProcessId地址中搜索PspReferenceCidTableEntry函数。
  • 3.最后在PspReferenceCidTableEntry地址中找到PspCidTable函数。

首先第一步先要得到PspCidTable函数内存地址,输入dp PspCidTable即可得到,如果在程序中则是调用MmGetSystemRoutineAddress取到。

PspCidTable是一个HANDLE_TALBE结构,当新建一个进程时,对应的会在PspCidTable存在一个该进程和线程对应的HANDLE_TABLE_ENTRY项。在windows10中依然采用动态扩展的方法,当句柄数少的时候就采用下层表,多的时候才启用中层表或上层表。

接着我们解析ffffdc88-79605dc0这个内存地址,执行dt _HANDLE_TABLE 0xffffdc8879605dc0得到规范化结构体。

内核句柄表分为三层如下;

  • 下层表:是一个HANDLE_TABLE_ENTRY项的索引,整个表共有256个元素,每个元素是一个8个字节长的HANDLE_TABLE_ENTRY项及索引,HANDLE_TABLE_ENTRY项中保存着指向对象的指针,下层表可以看成是进程和线程的稠密索引。
  • 中层表:共有256个元素,每个元素是4个字节长的指向下层表的入口指针及索引,中层表可以看成是进程和线程的稀疏索引。
  • 上层表:共有256个元素,每个元素是4个字节长的指向中层表的入口指针及索引,上层表可以看成是中层表的稀疏索引。

总结起来一个句柄表有一个上层表,一个上层表最多可以有256个中层表的入口指针,每个中层表最多可以有256个下层表的入口指针,每个下层表最多可以有256个进程和线程对象的指针。PspCidTable表可以看成是HANDLE_TBALE_ENTRY项的多级索引。

如上图所示TableCode是指向句柄表的指针,低二位(二进制)记录句柄表的等级:0(00)表示一级表,1(01)表示二级表,2(10)表示三级表。这里的 0xffffdc88-7d09b001 就说名它是一个二级表。

一级表里存放的就是进程和线程对象(加密过的,需要一些计算来解密),二级表里存放的是指向某个一级表的指针,同理三级表存放的是指向二级表的指针。

x64 系统中,每张表的大小是 0x1000(4096),一级表中存放的是 _handle_table_entry 结构(大小 = 16),二级表和三级表存放的是指针(大小 = 8)

我们对 0xffffdc88-7d09b001 抹去低二位,输入dp 0xffffdc887d09b000 输出的结果就是一张二级表,里面存储的就是一级表指针。

继续查看第一张一级表,输入dp 0xffffdc887962a000命令,我们知道一级句柄表是根据进程或线程ID来索引的,且以4累加,所以第一行对应id = 0,第二行对应id = 4。根据尝试,PID = 4的进程是System

所以此处的第二行0xb281de28-3300ffa7就是加密后的System进程的EPROCESS结构,对于Win10系统来说解密算法(value >> 0x10) & 0xfffffffffffffff0是这样的,我们通过代码计算出来。

#include <Windows.h>
#include <iostream>int _tmain(int argc, _TCHAR* argv[])
{std::cout << "hello lyshark" << std::endl;ULONG64 ul_recode = 0xb281de283300ffa7;ULONG64 ul_decode = (LONG64)ul_recode >> 0x10;ul_decode &= 0xfffffffffffffff0;std::cout << "解密后地址: " << std::hex << ul_decode << std::endl;getchar();return 0;
}

运行程序得到如下输出,即可知道System系统进程解密后的EPROCESS结构地址是0xffffb281de283300

回到WinDBG调试器,输入命令dt _EPROCESS 0xffffb281de283300解析以下这个结构,输出结果是System进程。

理论知识总结已经结束了,接下来就是如何实现枚举进程线程了,枚举流程如下:

  • 1.首先找到PspCidTable的地址。
  • 2.然后找到HANDLE_TBALE的地址。
  • 3.根据TableCode来判断层次结构。
  • 4.遍历层次结构来获取对象地址。
  • 5.判断对象类型是否为进程对象。
  • 6.判断进程是否有效。

这里先来实现获取PspCidTable函数的动态地址,代码如下。

#include <ntifs.h>
#include <windef.h>// 获取 PspCidTable
BOOLEAN get_PspCidTable(ULONG64* tableAddr)
{// 获取 PsLookupProcessByProcessId 地址UNICODE_STRING uc_funcName;RtlInitUnicodeString(&uc_funcName, L"PsLookupProcessByProcessId");ULONG64 ul_funcAddr = MmGetSystemRoutineAddress(&uc_funcName);if (ul_funcAddr == NULL){return FALSE;}DbgPrint("PsLookupProcessByProcessId addr = %p \n", ul_funcAddr);// 前 40 字节有 call(PspReferenceCidTableEntry)/*0: kd> uf PsLookupProcessByProcessIdnt!PsLookupProcessByProcessId:fffff802`0841cfe0 48895c2418      mov     qword ptr [rsp+18h],rbxfffff802`0841cfe5 56              push    rsifffff802`0841cfe6 4883ec20        sub     rsp,20hfffff802`0841cfea 48897c2438      mov     qword ptr [rsp+38h],rdifffff802`0841cfef 488bf2          mov     rsi,rdxfffff802`0841cff2 65488b3c2588010000 mov   rdi,qword ptr gs:[188h]fffff802`0841cffb 66ff8fe6010000  dec     word ptr [rdi+1E6h]fffff802`0841d002 b203            mov     dl,3fffff802`0841d004 e887000000      call    nt!PspReferenceCidTableEntry (fffff802`0841d090)fffff802`0841d009 488bd8          mov     rbx,raxfffff802`0841d00c 4885c0          test    rax,raxfffff802`0841d00f 7435            je      nt!PsLookupProcessByProcessId+0x66 (fffff802`0841d046)  Branch*/ULONG64 ul_entry = 0;for (INT i = 0; i < 100; i++){// fffff802`0841d004 e8 87 00 00 00      call    nt!PspReferenceCidTableEntry (fffff802`0841d090)if (*(PUCHAR)(ul_funcAddr + i) == 0xe8){ul_entry = ul_funcAddr + i;break;}}if (ul_entry != 0){// 解析 call 地址INT i_callCode = *(INT*)(ul_entry + 1);DbgPrint("i_callCode = %p \n", i_callCode);ULONG64 ul_callJmp = ul_entry + i_callCode + 5;DbgPrint("ul_callJmp = %p \n", ul_callJmp);// 来到 call(PspReferenceCidTableEntry) 内找 PspCidTable/*0: kd> uf PspReferenceCidTableEntrynt!PspReferenceCidTableEntry+0x115:fffff802`0841d1a5 488b0d8473f5ff  mov     rcx,qword ptr [nt!PspCidTable (fffff802`08374530)]fffff802`0841d1ac b801000000      mov     eax,1fffff802`0841d1b1 f0480fc107      lock xadd qword ptr [rdi],raxfffff802`0841d1b6 4883c130        add     rcx,30hfffff802`0841d1ba f0830c2400      lock or dword ptr [rsp],0fffff802`0841d1bf 48833900        cmp     qword ptr [rcx],0fffff802`0841d1c3 0f843fffffff    je      nt!PspReferenceCidTableEntry+0x78 (fffff802`0841d108)  Branch*/for (INT i = 0; i < 0x120; i++){// fffff802`0841d1a5 48 8b 0d 84 73 f5 ff  mov     rcx,qword ptr [nt!PspCidTable (fffff802`08374530)]if (*(PUCHAR)(ul_callJmp + i) == 0x48 && *(PUCHAR)(ul_callJmp + i + 1) == 0x8b && *(PUCHAR)(ul_callJmp + i + 2) == 0x0d){// 解析 mov 地址INT i_movCode = *(INT*)(ul_callJmp + i + 3);DbgPrint("i_movCode = %p \n", i_movCode);ULONG64 ul_movJmp = ul_callJmp + i + i_movCode + 7;DbgPrint("ul_movJmp = %p \n", ul_movJmp);// 得到 PspCidTable*tableAddr = ul_movJmp;return TRUE;}}}return FALSE;
}VOID UnDriver(PDRIVER_OBJECT driver)
{DbgPrint(("Uninstall Driver Is OK \n"));
}NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{DbgPrint(("hello lyshark \n"));ULONG64 tableAddr = 0;get_PspCidTable(&tableAddr);DbgPrint("PspCidTable Address = %p \n", tableAddr);Driver->DriverUnload = UnDriver;return STATUS_SUCCESS;
}

运行后即可得到动态地址,我们可以验证一下是否一致:

继续增加对与三级表的动态解析代码,最终代码如下所示:

#include <ntifs.h>
#include <windef.h>// 获取 PspCidTable
BOOLEAN get_PspCidTable(ULONG64* tableAddr)
{// 获取 PsLookupProcessByProcessId 地址UNICODE_STRING uc_funcName;RtlInitUnicodeString(&uc_funcName, L"PsLookupProcessByProcessId");ULONG64 ul_funcAddr = MmGetSystemRoutineAddress(&uc_funcName);if (ul_funcAddr == NULL){return FALSE;}DbgPrint("PsLookupProcessByProcessId addr = %p \n", ul_funcAddr);// 前 40 字节有 call(PspReferenceCidTableEntry)/*0: kd> uf PsLookupProcessByProcessIdnt!PsLookupProcessByProcessId:fffff802`0841cfe0 48895c2418      mov     qword ptr [rsp+18h],rbxfffff802`0841cfe5 56              push    rsifffff802`0841cfe6 4883ec20        sub     rsp,20hfffff802`0841cfea 48897c2438      mov     qword ptr [rsp+38h],rdifffff802`0841cfef 488bf2          mov     rsi,rdxfffff802`0841cff2 65488b3c2588010000 mov   rdi,qword ptr gs:[188h]fffff802`0841cffb 66ff8fe6010000  dec     word ptr [rdi+1E6h]fffff802`0841d002 b203            mov     dl,3fffff802`0841d004 e887000000      call    nt!PspReferenceCidTableEntry (fffff802`0841d090)fffff802`0841d009 488bd8          mov     rbx,raxfffff802`0841d00c 4885c0          test    rax,raxfffff802`0841d00f 7435            je      nt!PsLookupProcessByProcessId+0x66 (fffff802`0841d046)  Branch*/ULONG64 ul_entry = 0;for (INT i = 0; i < 100; i++){// fffff802`0841d004 e8 87 00 00 00      call    nt!PspReferenceCidTableEntry (fffff802`0841d090)if (*(PUCHAR)(ul_funcAddr + i) == 0xe8){ul_entry = ul_funcAddr + i;break;}}if (ul_entry != 0){// 解析 call 地址INT i_callCode = *(INT*)(ul_entry + 1);DbgPrint("i_callCode = %p \n", i_callCode);ULONG64 ul_callJmp = ul_entry + i_callCode + 5;DbgPrint("ul_callJmp = %p \n", ul_callJmp);// 来到 call(PspReferenceCidTableEntry) 内找 PspCidTable/*0: kd> uf PspReferenceCidTableEntrynt!PspReferenceCidTableEntry+0x115:fffff802`0841d1a5 488b0d8473f5ff  mov     rcx,qword ptr [nt!PspCidTable (fffff802`08374530)]fffff802`0841d1ac b801000000      mov     eax,1fffff802`0841d1b1 f0480fc107      lock xadd qword ptr [rdi],raxfffff802`0841d1b6 4883c130        add     rcx,30hfffff802`0841d1ba f0830c2400      lock or dword ptr [rsp],0fffff802`0841d1bf 48833900        cmp     qword ptr [rcx],0fffff802`0841d1c3 0f843fffffff    je      nt!PspReferenceCidTableEntry+0x78 (fffff802`0841d108)  Branch*/for (INT i = 0; i < 0x120; i++){// fffff802`0841d1a5 48 8b 0d 84 73 f5 ff  mov     rcx,qword ptr [nt!PspCidTable (fffff802`08374530)]if (*(PUCHAR)(ul_callJmp + i) == 0x48 && *(PUCHAR)(ul_callJmp + i + 1) == 0x8b && *(PUCHAR)(ul_callJmp + i + 2) == 0x0d){// 解析 mov 地址INT i_movCode = *(INT*)(ul_callJmp + i + 3);DbgPrint("i_movCode = %p \n", i_movCode);ULONG64 ul_movJmp = ul_callJmp + i + i_movCode + 7;DbgPrint("ul_movJmp = %p \n", ul_movJmp);// 得到 PspCidTable*tableAddr = ul_movJmp;return TRUE;}}}return FALSE;
}/* 解析一级表
BaseAddr:一级表的基地址
index1:第几个一级表
index2:第几个二级表
*/
VOID parse_table_1(ULONG64 BaseAddr, INT index1, INT index2)
{// 遍历一级表(每个表项大小 16 ),表大小 4k,所以遍历 4096/16 = 526 次PEPROCESS p_eprocess = NULL;PETHREAD p_ethread = NULL;INT i_id = 0;for (INT i = 0; i < 256; i++){if (!MmIsAddressValid((PVOID64)(BaseAddr + i * 16))){DbgPrint("非法地址= %p \n", BaseAddr + i * 16);continue;}ULONG64 ul_recode = *(PULONG64)(BaseAddr + i * 16);// 解密ULONG64 ul_decode = (LONG64)ul_recode >> 0x10;ul_decode &= 0xfffffffffffffff0;// 判断是进程还是线程i_id = i * 4 + 1024 * index1 + 512 * index2 * 1024;if (PsLookupProcessByProcessId(i_id, &p_eprocess) == STATUS_SUCCESS){DbgPrint("进程PID: %d | ID: %d | 内存地址: %p | 对象: %p \n", i_id, i, BaseAddr + i * 0x10, ul_decode);}else if (PsLookupThreadByThreadId(i_id, &p_ethread) == STATUS_SUCCESS){DbgPrint("线程TID: %d | ID: %d | 内存地址: %p | 对象: %p \n", i_id, i, BaseAddr + i * 0x10, ul_decode);}}
}/* 解析二级表
BaseAddr:二级表基地址
index2:第几个二级表
*/
VOID parse_table_2(ULONG64 BaseAddr, INT index2)
{// 遍历二级表(每个表项大小 8),表大小 4k,所以遍历 4096/8 = 512 次ULONG64 ul_baseAddr_1 = 0;for (INT i = 0; i < 512; i++){if (!MmIsAddressValid((PVOID64)(BaseAddr + i * 8))){DbgPrint("非法二级表指针(1):%p \n", BaseAddr + i * 8);continue;}if (!MmIsAddressValid((PVOID64)*(PULONG64)(BaseAddr + i * 8))){DbgPrint("非法二级表指针(2):%p \n", BaseAddr + i * 8);continue;}ul_baseAddr_1 = *(PULONG64)(BaseAddr + i * 8);parse_table_1(ul_baseAddr_1, i, index2);}
}/* 解析三级表
BaseAddr:三级表基地址
*/
VOID parse_table_3(ULONG64 BaseAddr)
{// 遍历三级表(每个表项大小 8),表大小 4k,所以遍历 4096/8 = 512 次ULONG64 ul_baseAddr_2 = 0;for (INT i = 0; i < 512; i++){if (!MmIsAddressValid((PVOID64)(BaseAddr + i * 8))){continue;}if (!MmIsAddressValid((PVOID64)* (PULONG64)(BaseAddr + i * 8))){continue;}ul_baseAddr_2 = *(PULONG64)(BaseAddr + i * 8);parse_table_2(ul_baseAddr_2, i);}
}VOID UnDriver(PDRIVER_OBJECT driver)
{DbgPrint(("Uninstall Driver Is OK \n"));
}NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{DbgPrint(("hello lyshark \n"));ULONG64 tableAddr = 0;get_PspCidTable(&tableAddr);DbgPrint("PspCidTable Address = %p \n", tableAddr);// 获取 _HANDLE_TABLE 的 TableCodeULONG64 ul_tableCode = *(PULONG64)(((ULONG64)*(PULONG64)tableAddr) + 8);DbgPrint("ul_tableCode = %p \n", ul_tableCode);// 取低 2位(二级制11 = 3)INT i_low2 = ul_tableCode & 3;DbgPrint("i_low2 = %X \n", i_low2);// 一级表if (i_low2 == 0){// TableCode 低 2位抹零(二级制11 = 3)parse_table_1(ul_tableCode & (~3), 0, 0);}// 二级表else if (i_low2 == 1){// TableCode 低 2位抹零(二级制11 = 3)parse_table_2(ul_tableCode & (~3), 0);}// 三级表else if (i_low2 == 2){// TableCode 低 2位抹零(二级制11 = 3)parse_table_3(ul_tableCode & (~3));}else{DbgPrint("LyShark提示: 错误,非法! ");return FALSE;}Driver->DriverUnload = UnDriver;return STATUS_SUCCESS;
}

运行如上完整代码,我们可以在WinDBG中捕捉到枚举到的进程信息:

线程信息在进程信息的下面,枚举效果如下:

至此文章就结束了,这里多说一句,实际上ZwQuerySystemInformation枚举系统句柄时就是走的这条双链,枚举系统进程如果使用的是这个API函数,那么不出意外它也是在这些内核表中做的解析。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/190072.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【蓝桥杯软件赛 零基础备赛20周】第6周——栈

文章目录 1. 基本数据结构概述1.1 数据结构和算法的关系1.2 线性数据结构概述1.3 二叉树简介 2. 栈2.1 手写栈2.2 CSTL栈2.3 Java 栈2.4 Python栈 3 习题 1. 基本数据结构概述 很多计算机教材提到&#xff1a;程序 数据结构 算法。 “以数据结构为弓&#xff0c;以算法为箭”…

uniapp uview u-input在app(运行在安卓基座上)上不能动态控制type类型(显隐密码)

开发密码显隐功能时&#xff0c;在浏览器h5上功能是没问题的 <view class"login-item-input"><u-input:type"showPassWord ? password : text"style"background: #ecf0f8"placeholder"请输入密码"border"surround&quo…

麻吉POS集成:如何无代码开发实现电商平台和CRM系统的高效连接

麻吉POS集成的前沿技术&#xff1a;无代码开发 在竞争激烈的电商市场中&#xff0c;商家们急需一种高效且易于操作的技术手段来实现系统间的快速连接与集成。麻吉POS以其前沿的无代码开发技术&#xff0c;让这一需求成为可能。无代码开发是一种允许用户通过图形用户界面进行编…

LiteOS内存管理:TLSF算法

问题背景 TLSF算法主要是面向实时操作系统提出的&#xff0c;对于RTOS而言&#xff0c;执行时间的确定性是最根本的&#xff0c;然而传统的动态内存分配器&#xff08;DMA&#xff0c;Dynamic Memory Allocator&#xff09;存在两个主要问题&#xff1a; 最坏情况执行时间不确…

【LeetCode】栈和队列OJ题---C语言版

栈和队列OJ题 1.括号匹配问题&#xff08;1&#xff09;题目描述&#xff1a;&#xff08;2&#xff09;思路表述&#xff1a;&#xff08;3&#xff09;代码实现&#xff1a; 2.用队列实现栈&#xff08;1&#xff09;题目描述&#xff1a;&#xff08;2&#xff09;思路表述&…

Python学习路线 - Python语言基础入门 - 准备工作

Python学习路线 - Python语言基础入门 - 准备工作 初识PythonPython的优点 什么是编程语言Python环境安装Windows系统Python安装Python验证 MacOS系统Linux系统 第一个Python程序常见问题 Python解释器Python解释器概念Python解释器存放位置Python解释器运行".py"文件…

vue3请求代理proxy中pathRewrite失效

问题引入 在vue3配置请求代理proxy的时候pathRewrite失效。 有这样一个例子&#xff0c;作用是为了把所有以/api开头的请求代理到后端的路径和端口上&#xff0c;在vue.config.js配置文件中 设置了代理跨域和默认端口。但是重新运行之后发现端口是改了&#xff0c;但是路径仍然…

【工作生活】汽车ECU开发内容简介

目录 1. 目标 2. 要分享什么 3.1 行业知识 3.1.1车载行业知识&#xff1a; 3.1.2项目&#xff1a; 3.1.3开发测试工具&#xff1a; 3.2 硬件平台 3.3 基础知识 3.4 工作生活 3. 我们是谁 1. 目标 随着新能源汽车的快速崛起&#xff0c;汽车电子行业开始快速发展&…

Redis数据结构之跳表

跳表是一种有序的数据结构&#xff0c;它通过在每个节点中维持多个指向其他节点的指针&#xff0c;从而达到快速访问节点的目的。其核心思想就是通过建立多级索引来实现空间换时间。 在Redis中&#xff0c;使用跳表作为Zset的一种底层实现之一&#xff0c;这也是跳表在Redis中的…

SpringBoot 集成 ChatGPT,实战附源码

1 前言 在本文中&#xff0c;我们将探索在 Spring Boot 应用程序中调用 OpenAI ChatGPT API 的过程。我们的目标是开发一个 Spring Boot 应用程序&#xff0c;能够利用 OpenAI ChatGPT API 生成对给定提示的响应。 您可能熟悉 ChatGPT 中的术语“提示”。在 ChatGPT 或类似语…

如何本地搭建个人hMailServer邮件服务并实现远程发送邮件

文章目录 前言1. 安装hMailServer2. 设置hMailServer3. 客户端安装添加账号4. 测试发送邮件5. 安装cpolar6. 创建公网地址7. 测试远程发送邮件8. 固定连接公网地址9. 测试固定远程地址发送邮件 前言 hMailServer 是一个邮件服务器,通过它我们可以搭建自己的邮件服务,通过cpola…

VLAN间路由详细讲解

本次实验拓扑的主要概述以及设计到的相关技术 VLAN技术&#xff1a; VLAN&#xff08;Virtual Local Area Network&#xff09;即虚拟局域网&#xff0c;是将一个物理的LAN在逻辑上划分成多个广播域的通信技术。 每个VLAN是一个广播域&#xff0c;VLAN内的主机间可以直…

YOLOv8改进 | 2023 | SCConv空间和通道重构卷积(精细化检测,又轻量又提点)

一、本文介绍 本文给大家带来的改进内容是SCConv&#xff0c;即空间和通道重构卷积&#xff0c;是一种发布于2023.9月份的一个新的改进机制。它的核心创新在于能够同时处理图像的空间&#xff08;形状、结构&#xff09;和通道&#xff08;色彩、深度&#xff09;信息&#xf…

数字图像处理(实践篇) 十六 基于分水岭算法的图像分割

目录 一 分水岭算法 二 利用OpenCV实现分水岭算法的过程 三 实践 一 分水岭算法 基于任何灰度图像都可以视为地形表面&#xff0c;其中高强度表示山峰和山丘&#xff0c;而低强度表示山谷。首先&#xff0c;开始用不同颜色的水&#xff08;标签&#xff09;填充每个孤立的山…

医院智能导诊小程序源码 智能导诊源码

医院智能导诊系统、AI智能导诊、现有的ai模型做医院智能导诊、智能就医引导系统、人工智能挂号、医院AI全流程智能导诊系统。 智能导诊 可以根据用户症状描述精准推荐科室及医生智能学习医院历史数据及自动进行科室对照,与医院的系统连接后,患者可直接完成预约。 一、系统概述…

flutter-一个可以输入的数字增减器

效果 参考文章 代码 在参考文章上边&#xff0c;主要是改了一下样式&#xff0c;逻辑也比较清楚&#xff0c;对左右两边添加增减方法。 我在此基础上加了_numcontroller 输入框的监听。 加了数字输入框的控制 keyboardType: TextInputType.number, //设置键盘为数字 inputF…

JavaScript 数据结构

JavaScript 数据结构 目录 JavaScript 数据结构 一、标识符 二、关键字 三、常量 四、变量 每一种计算机编程语言都有自己的数据结构&#xff0c;JavaScript脚本语言的数据结构包括&#xff1a;标识符、常量、变量、保留字等。 一、标识符 标识符&#xff0c;说白了&…

Flutter学习(七)GetX offAllNamed使用的问题

背景 使用GetX开发应用的时候&#xff0c;也可能有人调用过offAllNamed&#xff0c;会发现所有controller的都被销毁了 环境 win10 getx 4.6.5 as 4 现象 从A页面&#xff0c;跳转到B页面&#xff0c;然后调用offAllNamed进行回到A页面&#xff0c;观察controller声明周期…

如何从 Android 手机恢复已删除的视频

您是否曾经丢失过手机中的任何数据&#xff1f;如今&#xff0c;由于 Android 上的应用程序崩溃、根进程停止、Android 更新失败等等&#xff0c;数据丢失很普遍。错误删除是丢失视频、录音和音乐副本的另一种可能的方式。 丢失包含有关新完成的项目的重要信息的视频或婚礼、周…

零基础OpenAi应用商店开发

在本月OpenAi开发者大会上&#xff0c;OpenAI宣布推出了GPTs功能&#xff0c;也就是GPT Store&#xff0c;类似App Store的应用商店&#xff0c;任何用户都可以去参与创建应用。通过该功能&#xff0c;用户可以定制化打造自己的GPT&#xff0c;并公开分享至OpenAI的应用商店。定…