当前位置: 首页 > news >正文

私有句柄表

私有句柄表

实验环境

  • win7 x86

什么是私有句柄表?

私有句柄表是操作系统内部的一种数据结构,用于存储一个进程所拥有的句柄(或称为句柄对象)的信息。在操作系统中,句柄是一个标识符,用于唯一标识一个对象,例如文件、套接字、管道等等。GPT是这样说的,
那我就举个例子让大家更简单的理解:比如我们使用OpenProcessAPI成功打开一个进程时,我们便会得到一个返回值,这个返回值就叫作句柄,我们可以通过这个句柄来间接操作我们OpenProcess打开的那个进程,句柄值会被放入我们打开者的私有句柄表里,我们用到的时候就会去私有句柄表找出来用,当我们不再使用的时候,比如调用CloseHandle便可以把刚才那个句柄值从私有句柄表中移除,随之我们也就无法以常规的方式去操作刚才打开的那个进程。

从内核中看私有句柄表

私有句柄表所在的位置如下:
EPROCESS—>ObjectTable[_HANDLE_TABLE]—>TableCode&0xFFFFFFF8—>第一层句柄表或者句柄表指针表的地址

nt!_EPROCESS+0x000 Pcb              : _KPROCESS+0x098 ProcessLock      : _EX_PUSH_LOCK+0x0a0 CreateTime       : _LARGE_INTEGER+0x0a8 ExitTime         : _LARGE_INTEGER+0x0b0 RundownProtect   : _EX_RUNDOWN_REF+0x0b4 UniqueProcessId  : Ptr32 Void+0x0b8 ActiveProcessLinks : _LIST_ENTRY+0x0c0 ProcessQuotaUsage : [2] Uint4B+0x0c8 ProcessQuotaPeak : [2] Uint4B+0x0d0 CommitCharge     : Uint4B+0x0d4 QuotaBlock       : Ptr32 _EPROCESS_QUOTA_BLOCK+0x0d8 CpuQuotaBlock    : Ptr32 _PS_CPU_QUOTA_BLOCK+0x0dc PeakVirtualSize  : Uint4B+0x0e0 VirtualSize      : Uint4B+0x0e4 SessionProcessLinks : _LIST_ENTRY+0x0ec DebugPort        : Ptr32 Void+0x0f0 ExceptionPortData : Ptr32 Void+0x0f0 ExceptionPortValue : Uint4B+0x0f0 ExceptionPortState : Pos 0, 3 Bits+0x0f4 ObjectTable      : Ptr32 _HANDLE_TABLE   //进程对应私有句柄表有关结构地址nt!_HANDLE_TABLE+0x000 TableCode        : Uint4B+0x004 QuotaProcess     : Ptr32 _EPROCESS+0x008 UniqueProcessId  : Ptr32 Void+0x00c HandleLock       : _EX_PUSH_LOCK+0x010 HandleTableList  : _LIST_ENTRY+0x018 HandleContentionEvent : _EX_PUSH_LOCK+0x01c DebugInfo        : Ptr32 _HANDLE_TRACE_DEBUG_INFO+0x020 ExtraInfoPages   : Int4B+0x024 Flags            : Uint4B+0x024 StrictFIFO       : Pos 0, 1 Bit+0x028 FirstFreeHandle  : Uint4B+0x02c LastFreeHandleEntry : Ptr32 _HANDLE_TABLE_ENTRY+0x030 HandleCount      : Uint4B+0x034 NextHandleNeedingPool : Uint4B+0x038 HandleCountHighWatermark : Uint4B

.TableCode的值的最后3bit位代表着这个私有句柄表有几层,如果最后3bit位为0则代表仅有一层,1的话为两层,2的话最多为三层,以此类推,每张句柄表存有4096/8个句柄值,但是我们日常使用的电脑中最多有两层就很够多了,原因如下:
当只有一层的时候那么TableCode直接指向了私有句柄表的首个值,每个值占8字节,一个表的内存大小为4096个字节,一个句柄值为8字节,所以此时能存下的最大句柄值个数为4096/8个;当为两层的时候,TableCode指向了一张句柄表指针的表,TableCode+4*(层数-1)存的就是对应句柄表的指针,这张表依旧是4096个字节,那也就是说总共有4096/4个句柄表指针,那么一个句柄表有4096/8个句柄值,那此时就有(4096/4)x(4096/8)个句柄值了,一次类推,有点类似呈现了短期的指数大爆炸。那通常情况下我们的常规进程是不会打开这么多对象的,很多常规进程基本上就只有1层句柄表。

那句柄和对象的关系是啥呢?
在内核里,私有句柄的句柄值&0xFFFFFFF8后便指向了一个_OBJECT_HEADER结构体,结构体内存布局如下

nt!_OBJECT_HEADER+0x000 PointerCount     : Int4B+0x004 HandleCount      : Int4B+0x004 NextToFree       : Ptr32 Void+0x008 Lock             : _EX_PUSH_LOCK+0x00c TypeIndex        : UChar       //对象类型+0x00d TraceFlags       : UChar+0x00e InfoMask         : UChar+0x00f Flags            : UChar+0x010 ObjectCreateInfo : Ptr32 _OBJECT_CREATE_INFORMATION+0x010 QuotaBlockCharged : Ptr32 Void+0x014 SecurityDescriptor : Ptr32 Void+0x018 Body             : _QUAD                //对象地址

其中,0xc的位置代表了该句柄对应的对象是一个什么类型的对象,比如文件、进程、线程等,0x18的位置便指向了该句柄对应的对象结构。若句柄对应的对象是一个进程对象那么0x18的位置存的就是对应进程对象的_EPROCESS的结构,可以从这个结构便获得进程名、进程ID等等信息

有什么用

既然只要进程对象被打开,那么打开者就会获得打开进程对象的句柄值,并且放入私有句柄表,进而以一定权限操作打开进程,那当我们想知道我们的进程是否被打开或者有哪些进程打开了,我们是否可以通过遍历所有进程的私有句柄表来判断我们进程是否被打开操作呢?进而检测是否有非法内存操作的风险

逆向思路

第一:我们需要的是私有句柄表,那我们就需要找到对应的TableCode;
第二:TableCode存在ObjectTable中,那我们就需要找到任意一个进程的ObjectTable,随后通过该结构[HANDLE_TABLE]的HandleTableList 成员遍历得到所有进程的ObjectTable地址,ObjectTable对象的HANDLE_TABLE结构如下:

nt!_HANDLE_TABLE+0x000 TableCode        : 0x968e8000                          +0x004 QuotaProcess     : 0x864075e0 _EPROCESS+0x008 UniqueProcessId  : 0x00000a98 Void+0x00c HandleLock       : _EX_PUSH_LOCK// 所有进程私有句柄表的双向链表的某个节点+0x010 HandleTableList  : _LIST_ENTRY [ 0x83f50e68 - 0x8bec8960 ] +0x018 HandleContentionEvent : _EX_PUSH_LOCK+0x01c DebugInfo        : (null) +0x020 ExtraInfoPages   : 0n0 +0x024 Flags            : 0+0x024 StrictFIFO       : 0y0+0x028 FirstFreeHandle  : 0xc4+0x02c LastFreeHandleEntry : 0x968e8ff8 _HANDLE_TABLE_ENTRY+0x030 HandleCount      : 0x30+0x034 NextHandleNeedingPool : 0x800+0x038 HandleCountHighWatermark : 0x31

第三:要想获得任意一个进程ObjectTable的话我们就获取System进程的EPROCESS结构来获取System的ObjectTable地址;

用代码来叙说细节

1 获取System进程的EPROCESS,驱动所属进程就是System

PEPROCESS pEprocess = PsGetCurrentProcess();

2 获取指定进程的私有句柄表地址ObjectTable

PULONG pHanldeForSystem = *(PULONG)((PUCHAR)pEprocess + 0xf4);

3 获取进程ObjectTable的HandleTableList的地址,这个成员是双向链表的某个节点,所有正常进程的ObjectTable+0x10的位置都在这个链表上

PLIST_ENTRY pPriListForSys = (PLIST_ENTRY)((PUCHAR)pHanldeForSystem + 0x10);

4 遍历HandleTableList链表获得所有进程的ObjectTable,并保存下来

PLIST_ENTRY pTmp = pPriListForSys;
int cout = 0;
do 
{pTmp = pTmp->Flink;cout++;
} while (pTmp != pPriListForSys);
pTmp = pPriListForSys;
PULONG pHandleTable = ExAllocatePool(NonPagedPool, cout*sizeof(PULONG));
RtlZeroMemory(pHandleTable, cout*sizeof(PULONG));
//保存ObjectTable
for (int i = 0; i < cout;i++)
{pHandleTable[i] = (PULONG)((PUCHAR)(pTmp->Flink) - 0x10);pTmp = pTmp->Flink;
}

5 获取所有进程的ObjectTable–>TableCode,并保存下来

PULONG pTableCode = ExAllocatePool(NonPagedPool, cout*sizeof(PULONG));
RtlZeroMemory(pTableCode, cout*sizeof(PULONG));
for (int i = 0; i < cout;i++)
{pTableCode[i] = *(PULONG)pHandleTable[i];DbgPrintEx(77, 0, "[db]tablecode地址为:%p\n", pTableCode[i]);
}

6 筛选出只有一层的TableCode,因为超过两层的通常都是系统进程之类的,恶意进程的通常只有一层

ULONG uTmpCode = 0;
int count_2 = 0;
for (int i = 0; i < cout; i++)
{uTmpCode =pTableCode[i] & 0x00000007;if (uTmpCode>0){continue;}count_2++;
}
count_2 = count_2-1;
PULONG pOneTableCode = ExAllocatePool(NonPagedPool, count_2*sizeof(PULONG));
RtlZeroMemory(pOneTableCode, count_2*sizeof(PULONG));
count_2 = 0;
for (int i = 0; i < cout; i++)
{uTmpCode = pTableCode[i] & 0x00000007;if (uTmpCode>0){continue;}pOneTableCode[count_2] = pTableCode[i] & 0xFFFFFFF8;//此时count_2是一层句柄TableCode的数量count_2++;
}

7 遍历出每个只有一层句柄表的表内句柄值,并打印出句柄值(上面忘记说了,句柄值总共64位,低32位才是我们目前说的句柄值),

类型值(我的系统上进程对象的类型是7,你们可以通过取System的进程对应的类型值作比较,只要类型值一样就是进程,我为了方便直接就写了调试出来的0x7),进程名(前提是句柄对应的对象是进程)

count_2 = count_2 - 1;
ULONG64 pTmpTableValue = 0;
ULONG uHight32 = 0;
ULONG uLow32 = 0;
UCHAR uType = 0;
PUCHAR pProcessName = NULL;
for (int i = 0; i < count_2-2;i++)
{DbgPrintEx(77, 0, "[db]第%d个表内容,地址为:%p:\n", i, pOneTableCode[i]);for (int j = 0; j < 512;j++){if (pOneTableCode[i]==0){break;}pTmpTableValue = *((PULONG64)((PUCHAR)pOneTableCode[i] + j * 8));uHight32 = pTmpTableValue >> 32;uLow32 = pTmpTableValue & 0x00000000ffffffff;uLow32 = uLow32 & 0xFFFFFFF8;if (uLow32 != 0){DbgPrintEx(77, 0, "[db]第%d个句柄表的第%d个值为%lx`%lx\n", i, j,uHight32, uLow32);uType = *(PUCHAR)((PUCHAR)uLow32 + 0xc);DbgPrintEx(77, 0, "[db]Type is %d\n", uType);if (uType == 0x7){pProcessName = (PUCHAR)((PUCHAR)uLow32 + 0x18 + 0x16c);DbgPrintEx(77, 0, "[db][exeinfor]:Processname:%s\n", pProcessName);}}}
}

8 判断是否被打开的话我们直接添加一个全局变量FLAG来计数我们进程对应的句柄在其他进程中出现的次数,比如超过指定次便判断为风险,或者也可以查看是那个进程打开的,获取进程名,我这里就只判断了打开次数,接下来看一下完整代码,以判断123.exe是否被打开1次以上为风险来判断提示,我用了两个exe去OpenProcess 123.exe去实验

#include <ntifs.h>
#include <string.h>INT openflag = 0;VOID UnloadDriver(PDRIVER_OBJECT pDriver)
{}NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pRegPath)
{//获取当前进程systemPEPROCESS pEprocess = PsGetCurrentProcess();//获取指定进程的私有句柄表地址handle_tablePULONG pHanldeForSystem = *(PULONG)((PUCHAR)pEprocess + 0xf4);//获取handle_table中的Table_code PULONG pTableCodeForSystem = *pHanldeForSystem;//获取所有进程私有句柄表的地址PLIST_ENTRY pPriListForSys = (PLIST_ENTRY)((PUCHAR)pHanldeForSystem + 0x10);//打印测试DbgPrintEx(77, 0, "[db]地址:\npEprocess:%p\npHanldeForSystem:%p,\nTableCodeForSystem:%p\npPriListForSys:%p\n", pEprocess,pHanldeForSystem,pTableCodeForSystem,pPriListForSys);/*循环所有进程的私有句柄表定义一个tmp去遍历把所有私有handle_table的地址保存下来*/PLIST_ENTRY pTmp = pPriListForSys;int cout = 0;do {DbgPrintEx(77, 0, "[db]第%d个handle_table list地址为:%p\n", cout, pTmp->Flink);pTmp = pTmp->Flink;cout++;} while (pTmp != pPriListForSys);pTmp = pPriListForSys;PULONG pHandleTable = ExAllocatePool(NonPagedPool, cout*sizeof(PULONG));RtlZeroMemory(pHandleTable, cout*sizeof(PULONG));//保存handle_tablefor (int i = 0; i < cout;i++){pHandleTable[i] = (PULONG)((PUCHAR)(pTmp->Flink) - 0x10);DbgPrintEx(77, 0, "[db]handle_table地址为:%p\n", pHandleTable[i]);pTmp = pTmp->Flink;}//获取所有的tablecodePULONG pTableCode = ExAllocatePool(NonPagedPool, cout*sizeof(PULONG));RtlZeroMemory(pTableCode, cout*sizeof(PULONG));for (int i = 0; i < cout;i++){pTableCode[i] = *(PULONG)pHandleTable[i];DbgPrintEx(77, 0, "[db]tablecode地址为:%p\n", pTableCode[i]);}//筛选出只有一层的TableCodeULONG uTmpCode = 0;int count_2 = 0;for (int i = 0; i < cout; i++){uTmpCode =pTableCode[i] & 0x00000007;if (uTmpCode>0){continue;}count_2++;}count_2 = count_2-1;PULONG pOneTableCode = ExAllocatePool(NonPagedPool, count_2*sizeof(PULONG));RtlZeroMemory(pOneTableCode, count_2*sizeof(PULONG));count_2 = 0;for (int i = 0; i < cout; i++){uTmpCode = pTableCode[i] & 0x00000007;if (uTmpCode>0){continue;}pOneTableCode[count_2] = pTableCode[i] & 0xFFFFFFF8;DbgPrintEx(77, 0, "[db]一层的tablecode:%p\n", pOneTableCode[count_2]);//此时count_2是一层句柄TableCode的数量count_2++;}//遍历出每个一层句柄表内的值count_2 = count_2 - 1;ULONG64 pTmpTableValue = 0;ULONG uHight32 = 0;ULONG uLow32 = 0;UCHAR uType = 0;PUCHAR pProcessName = NULL;ULONG uFindNum = 0;for (int i = 0; i < count_2-2;i++){DbgPrintEx(77, 0, "[db]第%d个表内容,地址为:%p:\n", i, pOneTableCode[i]);for (int j = 0; j < 512;j++){if (pOneTableCode[i]==0){break;}pTmpTableValue = *((PULONG64)((PUCHAR)pOneTableCode[i] + j * 8));uHight32 = pTmpTableValue >> 32;uLow32 = pTmpTableValue & 0x00000000ffffffff;uLow32 = uLow32 & 0xFFFFFFF8;if (uLow32 != 0){DbgPrintEx(77, 0, "[db]第%d个句柄表的第%d个值为%lx`%lx\n", i, j,uHight32, uLow32);uType = *(PUCHAR)((PUCHAR)uLow32 + 0xc);DbgPrintEx(77, 0, "[db]Type is %d\n", uType);if (uType == 0x7){pProcessName = (PUCHAR)((PUCHAR)uLow32 + 0x18 + 0x16c);if (strcmp(pProcessName,"123.exe")==0){openflag++;}DbgPrintEx(77, 0, "[db][exeinfor]:Processname:%s\n", pProcessName);}}uFindNum++;}}DbgPrintEx(77, 0, "[db]总共翻越了%d坐山\n", uFindNum);if (openflag>1){DbgPrintEx(77, 0, "[db]很遗憾,我们翻山越岭找到了它,它出现了%d次,有风险\n", openflag);}pDriver->DriverUnload = UnloadDriver;return STATUS_SUCCESS;
}

在这里插入图片描述

经过实验得到123.exe进程对象句柄被至少3个其他进程拥有,其中两个是我自己写的exe,另外一个应该是某个系统进程,此时我们看到总过遍历了判断18944次内存区域,打印了接近几分钟,这也许就是翻山越岭的爱吧!(主要是我打印费时了)

相关文章:

私有句柄表

私有句柄表 实验环境 win7 x86 什么是私有句柄表&#xff1f; 私有句柄表是操作系统内部的一种数据结构&#xff0c;用于存储一个进程所拥有的句柄&#xff08;或称为句柄对象&#xff09;的信息。在操作系统中&#xff0c;句柄是一个标识符&#xff0c;用于唯一标识一个对…...

Vue——类与样式绑定

目录 Class 与 Style 绑定​ 绑定 HTML class​ 绑定对象​ 绑定数组​ 在组件上使用​ 绑定内联样式​ 绑定对象​ 绑定数组​ 自动前缀​ 样式多值​ Class 与 Style 绑定​ 数据绑定的一个常见需求场景是操纵元素的 CSS class 列表和内联样式。因为 class 和 styl…...

软考中项计算题总结

计算题在下午的考试属于重中之重&#xff0c;可以说得计算题得天下&#xff0c;先把计算题搞定&#xff0c;再看案例找错题&#xff0c;这2个是最容易得分的&#xff0c;所以对于进度、成本类的计算题一定要搞懂&#xff1a; 所属项目过程计算计算公式说明进度管理三点估算&am…...

如何使用基于GPT-4的Cursor编辑器提升开发效率

程序员最恨两件事情&#xff1a;一是别人代码不写文档&#xff0c;二是要让自己写文档。随着 GPT-4 的到来这些都不是问题了&#xff0c;顺带可能连程序员都解决了。。。 之前一直觉得 AI 生成的代码也就写个面试题的水平&#xff0c;小打小闹&#xff0c;现在时代可变了。Curs…...

压箱底教程分享,手把手教会你如何注册target账号和下单

喜欢套利的朋友肯定都认识target这个平台吧&#xff0c;它是美国热门的综合性海淘网站之一。东哥近日收到私信有朋友向我请教在注册target账号时遇到的一些问题&#xff0c;所以今天东哥想跟大家分享的就是就是target账号注册教程和下单流程&#xff0c;让也想注册target账号的…...

一次性搞懂dBSPL、dBm、dBu、dBV、dBFS的区别!

相信学习音乐制作的同学在混音阶段经常会碰到各种关于声音的单位&#xff0c;其中最具代表性的可能就是分贝家族的单位了&#xff0c;如dBSPL、dBm、dBu、dBV、dBFS等。 那么&#xff0c;这些单位分别表示什么&#xff0c;又有什么区别呢&#xff1f; 描述声音信号强弱的单位…...

漂亮实用的15个脑图模板,你知道哪些是AI做的吗?

对于很多第一次接触到思维导图的朋友&#xff0c;看到软件的时候往往找不到方向&#xff0c;不知道如何创作&#xff1f; 今天大家的好助手来了。 一是有大量的思维导图模板&#xff0c;大家看着模板做&#xff0c;慢慢就会做了。 二是ProcessOn 思维导图已经可以用AI 做思维…...

历代程序员都无法逃脱的诅咒 -- 低代码

1764年5月4日星期四 愤怒的纺织工人 纵火烧毁了哈格里夫斯的家 因为他发明的珍妮纺织机 让很多当地的手工纺织工人失业了 这也被认为是第一次工业革命的开端 由于事发的星期四 所以这一事件也被称作疯狂星期四 类似的变革 也一次次的出现在软件行业 他是历代程序员都无法逃脱的…...

14Exceptional Control Flow Exceptions and Process(异常控制流,异常和进程)

异常控制流 异常控制流出现的地方&#xff1a; 异常控制流&#xff08;Exceptional Control Flow&#xff0c;ECF&#xff09;是程序执行过程中由于某些特殊事件或条件而导致的控制流的改变。异常控制流通常出现在以下几种情况&#xff1a; 硬件异常和中断&#xff1a;硬件异…...

LeetCode - 两数之和

题目信息 源地址&#xff1a;两数之和 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出和为目标值 target 的那两个整数&#xff0c;并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同一个元素在答案里不…...

Python 小型项目大全 31~35

三十一、猜数字 原文&#xff1a;http://inventwithpython.com/bigbookpython/project31.html 猜数字是初学者练习基本编程技术的经典游戏。在这个游戏中&#xff0c;电脑会想到一个介于 1 到 100 之间的随机数。玩家有 10 次机会猜出数字。每次猜中后&#xff0c;电脑会告诉玩…...

他又赚了一万美金

有一些学员真的挺能干的&#xff0c;收了一万刀&#xff0c;感到欣慰&#xff0c;毕竟在国外lead这条路&#xff0c;有很多人被骗&#xff0c;也有很多人赚钱。 但是大部分人跟着某一些所谓的大佬&#xff0c;最后自己却不动手操作。 ​ 从一开始怕跟我学习&#xff0c;到最后选…...

企业工程项目管理系统+spring cloud 系统管理+java 系统设置+二次开发

工程项目各模块及其功能点清单 一、系统管理 1、数据字典&#xff1a;实现对数据字典标签的增删改查操作 2、编码管理&#xff1a;实现对系统编码的增删改查操作 3、用户管理&#xff1a;管理和查看用户角色 4、菜单管理&#xff1a;实现对系统菜单的增删改查操…...

教你使用Apache搭建Http

Apache2默认采用的是80端口号&#xff0c;因此直接通过公网ip或域名就能访问。现实中&#xff0c;很多服务器本身就部署了许多其它服务&#xff0c;80端口号往往被占用&#xff0c;因此就需要将Apache2改成其它访问端口。 修改端口&#xff0c;首先需要修改/etc/apache2/ports…...

ZooKeeper+Kafka+ELK+Filebeat集群搭建实现大批量日志收集和展示

文章目录一、集群环境准备二、搭建 ZooKeeper 集群和配置三、搭建 Kafka 集群对接zk四、搭建 ES 集群和配置五、部署 Logstash 消费 Kafka数据写入至ES六、部署 Filebeat 收集日志七、安装 Kibana 展示日志信息一、集群环境准备 1.1 因为资源原因这里我就暂时先一台机器部署多…...

数据结构初阶 - 总结

-0- 数据结构前言 什么是数据结构 什么是算法 数据结构和算法的重要性-1- 时间复杂度和空间复杂度 &#x1f449;数据结构 -1- 时间复杂度和空间复杂度 | C 算法效率 时间复杂度大O的渐进表示法eg 空间复杂度 常见复杂度对比OJ 消失的数组 轮转数组-2- 顺序表 与 链表 &am…...

代码随想录算法训练营第四十四天-动态规划6|518. 零钱兑换 II ,377. 组合总和 Ⅳ (遍历顺序决定是排列还是组合)

如果求组合数就是外层for循环遍历物品&#xff0c;内层for遍历背包。 如果求排列数就是外层for遍历背包&#xff0c;内层for循环遍历物品。 求物品可以重复使用时&#xff0c;最好是用一维数组&#xff0c;会比较方便。二维数组不想思考了&#xff0c;二维还是用在01背吧吧。…...

wma格式怎么转换mp3,4种方法超快学

其实我们在任何电子设备上所获取的音频文件都具有自己的格式&#xff0c;每种格式又对应着自己的属性特点。比如wma就是一种音质优于MP3的音频格式&#xff0c;虽然很多小伙伴比较青睐于wma所具有的音质效果&#xff0c;但也不得不去考虑因wma自身兼容性而引起很多播放器不能支…...

【数据结构与算法】判定给定的字符向量是否为回文算法

题目&#xff1a; Qestion: 试写一个算法判定给定的字符向量是否为回文。   回文解释: 回文是指正读反读均相同的字符序列&#xff0c;如“abba”和“abdba”均是回文&#xff0c;但“good”不是回文。 主要思路&#xff1a; 因为数据要求不是很严格并且是一个比较简单的…...

考研数二第十七讲 反常积分与反常积分之欧拉-泊松(Euler-Poisson)积分

反常积分 反常积分又叫广义积分&#xff0c;是对普通定积分的推广&#xff0c;指含有无穷上限/下限&#xff0c;或者被积函数含有瑕点的积分&#xff0c;前者称为无穷限广义积分&#xff0c;后者称为瑕积分&#xff08;又称无界函数的反常积分&#xff09;。 含有无穷上限/下…...

Python爬虫实战:研究MechanicalSoup库相关技术

一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...

Docker 离线安装指南

参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性&#xff0c;不同版本的Docker对内核版本有不同要求。例如&#xff0c;Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本&#xff0c;Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

idea大量爆红问题解决

问题描述 在学习和工作中&#xff0c;idea是程序员不可缺少的一个工具&#xff0c;但是突然在有些时候就会出现大量爆红的问题&#xff0c;发现无法跳转&#xff0c;无论是关机重启或者是替换root都无法解决 就是如上所展示的问题&#xff0c;但是程序依然可以启动。 问题解决…...

Cursor实现用excel数据填充word模版的方法

cursor主页&#xff1a;https://www.cursor.com/ 任务目标&#xff1a;把excel格式的数据里的单元格&#xff0c;按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例&#xff0c;…...

DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径

目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...

基于Flask实现的医疗保险欺诈识别监测模型

基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施&#xff0c;由雇主和个人按一定比例缴纳保险费&#xff0c;建立社会医疗保险基金&#xff0c;支付雇员医疗费用的一种医疗保险制度&#xff0c; 它是促进社会文明和进步的…...

Linux离线(zip方式)安装docker

目录 基础信息操作系统信息docker信息 安装实例安装步骤示例 遇到的问题问题1&#xff1a;修改默认工作路径启动失败问题2 找不到对应组 基础信息 操作系统信息 OS版本&#xff1a;CentOS 7 64位 内核版本&#xff1a;3.10.0 相关命令&#xff1a; uname -rcat /etc/os-rele…...

保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek

文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama&#xff08;有网络的电脑&#xff09;2.2.3 安装Ollama&#xff08;无网络的电脑&#xff09;2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...

安宝特案例丨Vuzix AR智能眼镜集成专业软件,助力卢森堡医院药房转型,赢得辉瑞创新奖

在Vuzix M400 AR智能眼镜的助力下&#xff0c;卢森堡罗伯特舒曼医院&#xff08;the Robert Schuman Hospitals, HRS&#xff09;凭借在无菌制剂生产流程中引入增强现实技术&#xff08;AR&#xff09;创新项目&#xff0c;荣获了2024年6月7日由卢森堡医院药剂师协会&#xff0…...

Webpack性能优化:构建速度与体积优化策略

一、构建速度优化 1、​​升级Webpack和Node.js​​ ​​优化效果​​&#xff1a;Webpack 4比Webpack 3构建时间降低60%-98%。​​原因​​&#xff1a; V8引擎优化&#xff08;for of替代forEach、Map/Set替代Object&#xff09;。默认使用更快的md4哈希算法。AST直接从Loa…...