当前位置: 首页 > 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;。 含有无穷上限/下…...

永磁同步电机控制算法--模糊PI转速控制器

一、原理介绍 在常规的PID控制系统的基础上提出了一种模糊PID以及矢量变换方法相结合的控制系统&#xff0c;经过仿真分析对比证明&#xff1a; 模糊PID控制系统能够有效的提高永磁同步电机的转速响应速度&#xff0c;降低转矩脉动&#xff0c;增强了整体控制系统的抗干扰能力…...

使用API网关Kong配置反向代理和负载均衡

简介 Kong 是一个微服务API网关。 Kong是一个云原生&#xff0c;快速&#xff0c;可扩展和分布式微服务抽象层&#xff08;也称为API网关&#xff0c;API中间件或在某些情况下为Service Mesh&#xff09;。 作为2015年的开源项目&#xff0c;其核心价值在于高性能和可扩展性。…...

学习 React【Plan - June - Week 1】

一、使用 JSX 书写标签语言 JSX 是一种 JavaScript 的语法扩展&#xff0c;React 使用它来描述用户界面。 什么是 JSX&#xff1f; JSX 是 JavaScript 的一种语法扩展。看起来像 HTML&#xff0c;但它实际上是在 JavaScript 代码中写 XML/HTML。浏览器并不能直接运行 JSX&…...

Kerberos面试内容整理-在 Linux/Windows 中的 Kerberos 实践

Windows 实践: 在Windows环境中,Kerberos 几乎是无形融合的。用户使用域账号登录计算机时,实际上就完成了Kerberos的AS认证并获取TGT;此后的资源访问(如共享文件夹、打印机、数据库等)都会自动使用Kerberos进行验证,而无需用户干预。Windows通过LSASS进程维护和缓存用户…...

【科研绘图系列】R语言绘制和弦图(Chord diagram plot)

禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍加载R包数据下载导入数据数据预处理相关性计算和弦图系统信息介绍 本文介绍了一个基于R语言的数据分析和可视化流程,主要用于生成和弦图(Chord Diagram)。和弦图是一种用于展示…...

在uni-app中如何从Options API迁移到Composition API?

uni-app 从 Options API 迁移到 Composition API 的详细指南 一、迁移前的准备 升级环境&#xff1a; 确保 HBuilderX 版本 ≥ 3.2.0项目 uni-app 版本 ≥ 3.0.0 了解 Composition API 基础&#xff1a; 响应式系统&#xff1a;ref、reactive生命周期钩子&#xff1a;onMount…...

每日算法 -【Swift 算法】三数之和

Swift&#xff5c;三数之和&#xff08;3Sum&#xff09;详细题解 注释 拓展&#xff08;LeetCode 15&#xff09; ✨题目描述 给你一个包含 n 个整数的数组 nums&#xff0c;判断 nums 中是否存在三个元素 a, b, c&#xff0c;使得 a b c 0。请你找出所有和为 0 且不重…...

CppCon 2015 学习:C++ in the audio industry

实时编程&#xff08;real-time programming&#xff09;&#xff1a;音频处理对延迟极度敏感&#xff0c;要求代码必须非常高效且稳定。无锁线程同步&#xff08;lock-free thread synchronization&#xff09;&#xff1a;避免阻塞&#xff0c;提高性能&#xff0c;尤其是在多…...

go语言的锁

本篇文章主要讲锁&#xff0c;主要会涉及go的sync.Mutex和sync.RWMutex。 一.锁的概念和发展 1.1 锁的概念 所谓的加锁和解锁其实就是指一个数据是否被占用了&#xff0c;通过Mutex内的一个状态来表示。 例如&#xff0c;取 0 表示未加锁&#xff0c;1 表示已加锁&#xff…...

CppCon 2015 学习:Functional Design Explained

这两个 C 程序 不完全相同。它们的差异在于对 std::cout 的使用和代码格式。 程序 1&#xff1a; #include <iostream> int main(int argc, char** argv) {std::cout << "Hello World\n"; }解释&#xff1a;这个程序是 正确的。std::cout 是 C 标准库中…...