3.5 Windows驱动开发:应用层与内核层内存映射
在上一篇博文《内核通过PEB得到进程参数》
中我们通过使用KeStackAttachProcess
附加进程的方式得到了该进程的PEB结构信息,本篇文章同样需要使用进程附加功能,但这次我们将实现一个更加有趣的功能,在某些情况下应用层与内核层需要共享一片内存区域通过这片区域可打通内核与应用层的隔离,此类功能的实现依附于MDL内存映射机制实现。
3.5.1 应用层映射到内核层
先来实现将R3内存数据拷贝到R0中,功能实现所调用的API如下:
- 调用
IoAllocateMdl
创建一个MDL
结构体。这个结构体描述了一个要锁定的内存页的位置和大小。 - 调用
MmProbeAndLockPages
用于锁定创建的地址其中UserMode
代表用户层,IoReadAccess
以读取的方式锁定 - 调用
MmGetSystemAddressForMdlSafe
用于从MDL
中得到映射内存地址 - 调用
RtlCopyMemory
用于内存拷贝,将DstAddr
应用层中的数据拷贝到pMappedSrc
中 - 调用
MmUnlockPages
拷贝结束后解锁pSrcMdl
- 调用
IoFreeMdl
释放之前创建的MDL
结构体。
如上则是应用层数据映射到内核中的流程,我们将该流程封装成SafeCopyMemory_R3_to_R0
方便后期的使用,函数对数据的复制进行了分块操作,因此可以处理更大的内存块。
下面是对该函数的分析:
-
1.首先进行一些参数的检查,如果有任何一个参数为0,那么函数就会返回
STATUS_UNSUCCESSFUL
。 -
2.使用一个
while
循环来分块复制数据,每个块的大小为PAGE_SIZE
(通常是4KB)。这个循环在整个内存范围内循环,每次复制一个内存页的大小,直到复制完整个内存范围。 -
3.在循环内部,首先根据起始地址和当前要复制的大小来确定本次要复制的大小。如果剩余的内存不足一页大小,则只复制剩余的内存。
-
4.调用
IoAllocateMdl
创建一个MDL
,表示要锁定和复制的内存页。这里使用了(PVOID)(SrcAddr & 0xFFFFFFFFFFFFF000)
来确定页的起始地址。因为页的大小为0x1000
,因此在计算页的起始地址时,将SrcAddr
向下舍入到最接近的0x1000
的倍数。 -
5.如果
IoAllocateMdl
成功,则调用MmProbeAndLockPages
来锁定页面。这个函数将页面锁定到物理内存中,并返回一个虚拟地址,该虚拟地址指向已锁定页面的内核地址。 -
6.使用
MmGetSystemAddressForMdlSafe
函数获取一个映射到内核空间的地址,该地址可以直接访问锁定的内存页。 -
6.如果获取到了映射地址,则使用
RtlCopyMemory
函数将要复制的数据从应用层内存拷贝到映射到内核空间的地址。在复制结束后,使用MmUnlockPages
函数解锁内存页,释放对页面的访问权限。
最后,释放 MDL
并更新 SrcAddr
和 DstAddr
以复制下一个内存块。如果复制过程中发生任何异常,函数将返回 STATUS_UNSUCCESSFUL
。
总的来说,这个函数是一个很好的实现,它遵循了内核驱动程序中的最佳实践,包括对内存的安全处理、分块复制、错误处理等。
#include <ntifs.h>
#include <windef.h>// 分配内存
void* RtlAllocateMemory(BOOLEAN InZeroMemory, SIZE_T InSize)
{void* Result = ExAllocatePoolWithTag(NonPagedPool, InSize, 'lysh');if (InZeroMemory && (Result != NULL))RtlZeroMemory(Result, InSize);return Result;
}// 释放内存
void RtlFreeMemory(void* InPointer)
{ExFreePool(InPointer);
}/*
将应用层中的内存复制到内核变量中SrcAddr r3地址要复制
DstAddr R0申请的地址
Size 拷贝长度
*/
NTSTATUS SafeCopyMemory_R3_to_R0(ULONG_PTR SrcAddr, ULONG_PTR DstAddr, ULONG Size)
{NTSTATUS status = STATUS_UNSUCCESSFUL;ULONG nRemainSize = PAGE_SIZE - (SrcAddr & 0xFFF);ULONG nCopyedSize = 0;if (!SrcAddr || !DstAddr || !Size){return status;}while (nCopyedSize < Size){PMDL pSrcMdl = NULL;PVOID pMappedSrc = NULL;if (Size - nCopyedSize < nRemainSize){nRemainSize = Size - nCopyedSize;}// 创建MDLpSrcMdl = IoAllocateMdl((PVOID)(SrcAddr & 0xFFFFFFFFFFFFF000), PAGE_SIZE, FALSE, FALSE, NULL);if (pSrcMdl){__try{// 锁定内存页面(UserMode代表应用层)MmProbeAndLockPages(pSrcMdl, UserMode, IoReadAccess);// 从MDL中得到映射内存地址pMappedSrc = MmGetSystemAddressForMdlSafe(pSrcMdl, NormalPagePriority);}__except (EXCEPTION_EXECUTE_HANDLER){}}if (pMappedSrc){__try{// 将MDL中的映射拷贝到pMappedSrc内存中RtlCopyMemory((PVOID)DstAddr, (PVOID)((ULONG_PTR)pMappedSrc + (SrcAddr & 0xFFF)), nRemainSize);}__except (1){// 拷贝内存异常}// 释放锁MmUnlockPages(pSrcMdl);}if (pSrcMdl){// 释放MDLIoFreeMdl(pSrcMdl);}if (nCopyedSize){nRemainSize = PAGE_SIZE;}nCopyedSize += nRemainSize;SrcAddr += nRemainSize;DstAddr += nRemainSize;}status = STATUS_SUCCESS;return status;
}
有了封装好的SafeCopyMemory_R3_to_R0
函数,那么接下来就是使用该函数实现应用层到内核层中的拷贝,为了能实现拷贝我们需要做以下几个准备工作;
- 1.使用
PsLookupProcessByProcessId
函数通过进程ID查找到对应的EProcess
结构体,以获取该进程在内核中的信息。 - 2.使用
KeStackAttachProcess
函数将当前进程的执行上下文切换到指定进程的上下文,以便能够访问该进程的内存。 - 3.使用
RtlAllocateMemory
函数在当前进程的内存空间中分配一块缓冲区,用于存储从指定进程中读取的数据。 - 4.调用
SafeCopyMemory_R3_to_R0
函数将指定进程的内存数据拷贝到分配的缓冲区中。 - 5.将缓冲区中的数据转换为
BYTE
类型的指针,并将其输出。
PsLookupProcessByProcessId函数用于通过进程ID查找到对应的EProcess结构体,这个结构体是Windows操作系统内核中用于表示一个进程的数据结构。
NTSTATUS status = PsLookupProcessByProcessId(ProcessId, &ProcessObject);
if (!NT_SUCCESS(status)) {return status;
}
KeStackAttachProcess函数将当前进程的执行上下文切换到指定进程的上下文,以便能够访问该进程的内存。这个函数也只能在内核态中调用。
KeStackAttachProcess(ProcessObject, &ApcState);
RtlAllocateMemory函数在当前进程的内存空间中分配一块缓冲区,用于存储从指定进程中读取的数据。这个函数是Windows操作系统内核中用于动态分配内存的函数,其中第一个参数TRUE表示允许操作系统在分配内存时进行页面合并,以减少内存碎片的产生。第二个参数nSize表示需要分配的内存空间的大小。如果分配失败,就需要将之前的操作撤销并返回错误状态。
PVOID pTempBuffer = RtlAllocateMemory(TRUE, nSize);
if (pTempBuffer == NULL) {KeUnstackDetachProcess(&ApcState);ObDereferenceObject(ProcessObject);return STATUS_NO_MEMORY;
}
SafeCopyMemory_R3_to_R0函数将指定进程的内存数据拷贝到分配的缓冲区中。
if (!SafeCopyMemory_R3_to_R0(ModuleBase, pTempBuffer, nSize)) {RtlFreeMemory(pTempBuffer);KeUnstackDetachProcess(&ApcState);ObDereferenceObject(ProcessObject);return STATUS_UNSUCCESSFUL;
}
最后,将缓冲区中的数据转换为BYTE类型的指针,并将其输出。需要注意的是,在返回之前需要先将当前进程的执行上下文切换回原先的上下文。
BYTE* data = (BYTE*)pTempBuffer;
KeUnstackDetachProcess(&ApcState);
ObDereferenceObject(ProcessObject);
return data;
如上实现细节用一段话总结,首先PsLookupProcessByProcessId
得到进程EProcess
结构,并KeStackAttachProcess
附加进程,声明pTempBuffer
指针用于存储RtlAllocateMemory
开辟的内存空间,nSize
则代表读取应用层进程数据长度,ModuleBase
则是读入进程基址,调用SafeCopyMemory_R3_to_R0
即可将应用层数据拷贝到内核空间,并最终BYTE* data
转为BYTE字节的方式输出。这样就完成了将指定进程的内存数据拷贝到当前进程中的操作。
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{DbgPrint("hello lyshark.com \n");NTSTATUS status = STATUS_UNSUCCESSFUL;PEPROCESS eproc = NULL;KAPC_STATE kpc = { 0 };__try{// HANDLE 进程PIDstatus = PsLookupProcessByProcessId((HANDLE)4556, &eproc);if (NT_SUCCESS(status)){// 附加进程KeStackAttachProcess(eproc, &kpc);// -------------------------------------------------------------------// 开始映射// -------------------------------------------------------------------// 将用户空间内存映射到内核空间PVOID pTempBuffer = NULL;ULONG nSize = 0x1024;ULONG_PTR ModuleBase = 0x0000000140001000;// 分配内存pTempBuffer = RtlAllocateMemory(TRUE, nSize);if (pTempBuffer){// 拷贝数据到R0status = SafeCopyMemory_R3_to_R0(ModuleBase, (ULONG_PTR)pTempBuffer, nSize);if (NT_SUCCESS(status)){DbgPrint("[*] 拷贝应用层数据到内核里 \n");}// 转成BYTE方便读取BYTE* data = pTempBuffer;for (size_t i = 0; i < 10; i++){DbgPrint("%02X \n", data[i]);}}// 释放空间RtlFreeMemory(pTempBuffer);// 脱离进程KeUnstackDetachProcess(&kpc);}}__except (EXCEPTION_EXECUTE_HANDLER){Driver->DriverUnload = UnDriver;return STATUS_SUCCESS;}Driver->DriverUnload = UnDriver;return STATUS_SUCCESS;
}
代码运行后即可将进程中0x0000000140001000
处的数据读入内核空间并输出:
3.5.2 内核层映射到应用层
与上方功能实现相反SafeCopyMemory_R0_to_R3
函数则用于将一个内核层中的缓冲区写出到应用层中,SafeCopyMemory_R0_to_R3
函数接收源地址SrcAddr、要复制的数据长度Length
以及目标地址DstAddr
作为参数,其写出流程可总结为如下步骤:
-
1.使用
IoAllocateMdl
函数分别为源地址SrcAddr
和目标地址DstAddr
创建两个内存描述列表(MDL)。 -
2.使用
MmBuildMdlForNonPagedPool
函数,将源地址的MDL
更新为描述非分页池的虚拟内存缓冲区,并更新MDL
以描述底层物理页。 -
3.通过两次调用
MmGetSystemAddressForMdlSafe
函数,分别获取源地址和目标地址的指针,即pSrcMdl
和pDstMdl
。 -
4.使用
MmProbeAndLockPages
函数以写入方式锁定用户空间中pDstMdl
指向的地址,并将它的虚拟地址映射到物理内存页,从而确保该内存页在复制期间不会被交换出去或被释放掉。 -
5.然后使用
MmMapLockedPagesSpecifyCache
函数将锁定的用户空间内存页映射到内核空间,并返回内核空间中的虚拟地址。 -
6.最后使用
RtlCopyMemory
函数将源地址的数据复制到目标地址。 -
7.使用
MmUnlockPages
函数解除用户空间内存页的锁定,并使用MmUnmapLockedPages
函数取消内核空间与用户空间之间的内存映射。 -
8.最后释放源地址和目标地址的
MDL
,使用IoFreeMdl
函数进行释放。
内存拷贝SafeCopyMemory_R0_to_R3
函数,函数首先分配源地址和目标地址的MDL
结构,然后获取它们的虚拟地址,并以写入方式锁定目标地址的MDL
,最后使用RtlCopyMemory
函数将源地址的内存数据拷贝到目标地址。拷贝完成后,函数解锁目标地址的MDL
,并返回操作状态。
封装代码SafeCopyMemory_R0_to_R3()
功能如下:
// 分配内存
void* RtlAllocateMemory(BOOLEAN InZeroMemory, SIZE_T InSize)
{void* Result = ExAllocatePoolWithTag(NonPagedPool, InSize, 'lysh');if (InZeroMemory && (Result != NULL))RtlZeroMemory(Result, InSize);return Result;
}// 释放内存
void RtlFreeMemory(void* InPointer)
{ExFreePool(InPointer);
}/*
将内存中的数据复制到R3中SrcAddr R0要复制的地址
DstAddr 返回R3的地址
Size 拷贝长度
*/
NTSTATUS SafeCopyMemory_R0_to_R3(PVOID SrcAddr, PVOID DstAddr, ULONG Size)
{PMDL pSrcMdl = NULL, pDstMdl = NULL;PUCHAR pSrcAddress = NULL, pDstAddress = NULL;NTSTATUS st = STATUS_UNSUCCESSFUL;// 分配MDL 源地址pSrcMdl = IoAllocateMdl(SrcAddr, Size, FALSE, FALSE, NULL);if (!pSrcMdl){return st;}// 该 MDL 指定非分页虚拟内存缓冲区,并对其进行更新以描述基础物理页。MmBuildMdlForNonPagedPool(pSrcMdl);// 获取源地址MDL地址pSrcAddress = MmGetSystemAddressForMdlSafe(pSrcMdl, NormalPagePriority);if (!pSrcAddress){IoFreeMdl(pSrcMdl);return st;}// 分配MDL 目标地址pDstMdl = IoAllocateMdl(DstAddr, Size, FALSE, FALSE, NULL);if (!pDstMdl){IoFreeMdl(pSrcMdl);return st;}__try{// 以写入的方式锁定目标MDLMmProbeAndLockPages(pDstMdl, UserMode, IoWriteAccess);// 获取目标地址MDL地址pDstAddress = MmGetSystemAddressForMdlSafe(pDstMdl, NormalPagePriority);}__except (EXCEPTION_EXECUTE_HANDLER){}if (pDstAddress){__try{// 将源地址拷贝到目标地址RtlCopyMemory(pDstAddress, pSrcAddress, Size);}__except (1){// 拷贝内存异常}MmUnlockPages(pDstMdl);st = STATUS_SUCCESS;}IoFreeMdl(pDstMdl);IoFreeMdl(pSrcMdl);return st;
}
调用该函数实现拷贝,此处除去附加进程以外,在拷贝之前调用了ZwAllocateVirtualMemory
将内存属性设置为PAGE_EXECUTE_READWRITE
可读可写可执行状态,然后在向该内存中写出pTempBuffer
变量中的内容,此变量中的数据是0x90
填充的区域。
此处的ZwAllocateVirtualMemory
函数,用于在进程的虚拟地址空间中分配一块连续的内存区域,以供进程使用。它属于Windows内核API的一种,与用户态的VirtualAlloc
函数相似,但是它运行于内核态,可以分配不受用户空间地址限制的虚拟内存,并且可以用于在驱动程序中为自己或其他进程分配内存。
函数的原型为:
NTSYSAPI NTSTATUS NTAPI ZwAllocateVirtualMemory(_In_ HANDLE ProcessHandle,_Inout_ PVOID *BaseAddress,_In_ ULONG_PTR ZeroBits,_Inout_ PSIZE_T RegionSize,_In_ ULONG AllocationType,_In_ ULONG Protect
);
其中ProcessHandle
参数是进程句柄,BaseAddress
是分配到的内存区域的起始地址,ZeroBits
指定保留的高位,RegionSize
是分配内存大小,AllocationType
和Protect
分别表示内存分配类型和内存保护属性。
ZwAllocateVirtualMemory函数成功返回NT_SUCCESS
,返回值为0,否则返回相应的错误代码。如果函数成功调用,会将BaseAddress
参数指向分配到的内存区域的起始地址,同时将RegionSize
指向的值修改为实际分配到的内存大小。
当内存属性被设置为PAGE_EXECUTE_READWRITE
之后,则下一步直接调用SafeCopyMemory_R0_to_R3
进行映射即可,其调用完整案例如下所示;
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{DbgPrint("hello lyshark.com \n");NTSTATUS status = STATUS_UNSUCCESSFUL;PEPROCESS eproc = NULL;KAPC_STATE kpc = { 0 };__try{// HANDLE 进程PIDstatus = PsLookupProcessByProcessId((HANDLE)4556, &eproc);if (NT_SUCCESS(status)){// 附加进程KeStackAttachProcess(eproc, &kpc);// -------------------------------------------------------------------// 开始映射// -------------------------------------------------------------------// 将用户空间内存映射到内核空间PVOID pTempBuffer = NULL;ULONG nSize = 0x1024;PVOID ModuleBase = 0x0000000140001000;// 分配内存pTempBuffer = RtlAllocateMemory(TRUE, nSize);if (pTempBuffer){memset(pTempBuffer, 0x90, nSize);// 设置内存属性 PAGE_EXECUTE_READWRITEZwAllocateVirtualMemory(NtCurrentProcess(), &ModuleBase, 0, &nSize, MEM_RESERVE, PAGE_EXECUTE_READWRITE);ZwAllocateVirtualMemory(NtCurrentProcess(), &ModuleBase, 0, &nSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);// 将数据拷贝到R3中status = SafeCopyMemory_R0_to_R3(pTempBuffer, &ModuleBase, nSize);if (NT_SUCCESS(status)){DbgPrint("[*] 拷贝内核数据到应用层 \n");}}// 释放空间RtlFreeMemory(pTempBuffer);// 脱离进程KeUnstackDetachProcess(&kpc);}}__except (EXCEPTION_EXECUTE_HANDLER){Driver->DriverUnload = UnDriver;return STATUS_SUCCESS;}Driver->DriverUnload = UnDriver;return STATUS_SUCCESS;
}
拷贝成功后,应用层进程内将会被填充为Nop
指令。
相关文章:

3.5 Windows驱动开发:应用层与内核层内存映射
在上一篇博文《内核通过PEB得到进程参数》中我们通过使用KeStackAttachProcess附加进程的方式得到了该进程的PEB结构信息,本篇文章同样需要使用进程附加功能,但这次我们将实现一个更加有趣的功能,在某些情况下应用层与内核层需要共享一片内存…...

【小黑送书—第八期】>>别再吐槽大学教材了,来看看这些网友强推的数学神作!
导读:关于大学数学教材的吐槽似乎从来没停止过。有人慨叹:数学教材晦涩难懂。错!难懂,起码还可以读懂。数学教材你根本读不懂;也有人说:数学教材简直就是天书。 数学教材有好有坏,这话不假&…...

MatLab的下载、安装与使用(亲测有效)
1、概述 MatLab是由MathWorks公司开发并发布的,支持线性代数、矩阵运算、绘制函数和数据、信号处理、图像处理以及视频处理等功能。广泛用于算法开发、数据可视化、数据分析以及数值计算等。 Matlab 的主要特性包括: 简单易用的语法,使得程…...

无人智能货柜:引领便捷购物新体验
无人智能货柜:引领便捷购物新体验 无人智能货柜利用人工智能技术,将传统货架与电子商务相结合,形成智能销售终端。其采用先拿货后付款的购物模式,用户只需扫码、拿货、关门三个简洁流畅的步骤,极大地提升了消费者的购物…...

4.6 Windows驱动开发:内核遍历进程VAD结构体
在上一篇文章《内核中实现Dump进程转储》中我们实现了ARK工具的转存功能,本篇文章继续以内存为出发点介绍VAD结构,该结构的全程是Virtual Address Descriptor即虚拟地址描述符,VAD是一个AVL自平衡二叉树,树的每一个节点代表一段虚…...

基于世界杯算法优化概率神经网络PNN的分类预测 - 附代码
基于世界杯算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于世界杯算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于世界杯优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要:针对PNN神经网络…...

NPM 与 XUI 共存!Nginx Proxy Manager 搭配 X-UI 实现 Vless+WS+TLS 教程!
之前分享过搭建可以与宝塔共存的一个 “魔法” 服务器状态监控应用 ——xui,支持 VmessWSTLS。 最近 Docker 视频出的比较多,前阵子又出现了宝塔国内版存在隐私泄露的问题,很多小伙伴其实都不用宝塔了,那么,在我们现在…...

【网络奇遇记】那年我与计算机网络的浅相知
🌈个人主页:聆风吟 🔥系列专栏:网络奇遇记、数据结构 🔖少年有梦不应止于心动,更要付诸行动。 文章目录 一. 计算机网络的定义1.1 计算机早期的一个最简单的定义1.2 现阶段计算机网络的一个较好的定义 二. …...
LeetCode26.删除有序数组中的重复项(双指针法)
LeetCode26.删除有序数组中的重复项 1.问题描述2.解题思路3.代码 1.问题描述 给你一个 非严格递增排列 的数组 nums ,请你** 原地** 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然…...

原型网络Prototypical Network的python代码逐行解释,新手小白也可学会!!-----系列8
文章目录 前言一、原始代码二、对每一行代码的解释:总结 前言 这是该系列原型网络的最后一段代码及其详细解释,感谢各位的阅读! 一、原始代码 if __name__ __main__:##载入数据labels_trainData, labels_testData load_data() # labels_…...

黑马点评回顾 redis实现共享session
文章目录 传统session缺点整体访问流程代码实现生成验证码登录 问题具体思路 传统session缺点 传统单体项目一般是把session存入tomcat,但是每个tomcat中都有一份属于自己的session,假设用户第一次访问第一台tomcat,并且把自己的信息存放到第一台服务器…...

Redis篇---第八篇
系列文章目录 文章目录 系列文章目录前言一、说说 Redis 哈希槽的概念?二、Redis 常见性能问题和解决方案有哪些?三、假如 Redis 里面有 1 亿个 key,其中有 10w 个 key 是以某个固定的已知的前缀开头的,如果将它们全部找出来?前言 前些天发现了一个巨牛的人工智能学习网站…...

Unity使用Visual Studio Code 调试
Unity 使用Visual Studio Code 调试C# PackageManager安装Visual Studio EditorVisual Studio Code安装Unity 插件修改Unity配置调试 PackageManager安装Visual Studio Editor 打开 Window->PackageManger卸载 Visual Studio Code Editor ,这个已经被官方废弃安…...

【Linux】进程替换|exec系列函数
文章目录 一、看一看单进程版的进程替换二、进程替换的原理三、多进程版——验证各种程序替换接口exec系列函数execlexeclpexecvexecvp tipsexecleexecve 四、总结 一、看一看单进程版的进程替换 #include<stdio.h> #include<unistd.h> #include<stdlib.h>i…...
Java编程技巧:将图片导出成pdf文件
目录 一、pom依赖二、代码三、测试链接 一、pom依赖 <!-- pdf插件 start --> <dependency><groupId>com.itextpdf</groupId><artifactId>itextpdf</artifactId><version>5.5.3</version> </dependency> <dependency…...
二项分布和泊松分布
一、二项分布 1.1 n重伯努利试验 若是二项分布,则必是n重伯努利试验概型。即:每次试验只有两种结果 与 ,且在每次试验中A发生的概率相等,即P(A)p,将这种试验独立重复n次,则称这种试验为n重伯努利试验&#…...

【飞控调试】DJIF450机架+Pixhawk6c mini+v1.13.3固件+好盈Platinium 40A电调无人机调试
1 背景 由于使用了一种新的航电设备组合,在调试无人机起飞的时候遇到了之前没有遇到的问题。之前用的飞控(Pixhawk 6c)和电调(Hobbywing X-Rotor 40A),在QGC里按默认参数配置来基本就能平稳飞行࿰…...

Android studio配置Flutter开发环境报错问题解决
博主前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住也分享一下给大家 👉点击跳转到教程 报错问题截图 报错原因已经给出: You need Java 11 or higher to build your app with this version of G…...
2023.11.18 -自用hadoop高可用环境搭建命令
启动hadoop高可用环境 # 1.先恢复快照到高可用环境 # 2.三台服务器启动zookeeper服务 [rootnode1 ~]# zkServer.sh start [rootnode2 ~]# zkServer.sh start [rootnode3 ~]# zkServer.sh start 查看服务状态: [rootnode]# zkServer.sh status 关闭zk服务的命令是: [rootnode]# …...
【Linux】常用系统工作命令
一、Linux文档目录结构 在Linux系统中,目录、字符设备、套接字、硬盘、光驱、打印机等都被抽象成文件形式,“Linux系统中一切都是文件”。Linux系统中的一切文件都是从"根"目录(/)开始的,并按照文件系统层次…...

VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...

2025年能源电力系统与流体力学国际会议 (EPSFD 2025)
2025年能源电力系统与流体力学国际会议(EPSFD 2025)将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会,EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
leetcodeSQL解题:3564. 季节性销售分析
leetcodeSQL解题:3564. 季节性销售分析 题目: 表:sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...

蓝桥杯3498 01串的熵
问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798, 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...

使用 SymPy 进行向量和矩阵的高级操作
在科学计算和工程领域,向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能,能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作,并通过具体…...

莫兰迪高级灰总结计划简约商务通用PPT模版
莫兰迪高级灰总结计划简约商务通用PPT模版,莫兰迪调色板清新简约工作汇报PPT模版,莫兰迪时尚风极简设计PPT模版,大学生毕业论文答辩PPT模版,莫兰迪配色总结计划简约商务通用PPT模版,莫兰迪商务汇报PPT模版,…...
Redis:现代应用开发的高效内存数据存储利器
一、Redis的起源与发展 Redis最初由意大利程序员Salvatore Sanfilippo在2009年开发,其初衷是为了满足他自己的一个项目需求,即需要一个高性能的键值存储系统来解决传统数据库在高并发场景下的性能瓶颈。随着项目的开源,Redis凭借其简单易用、…...

Golang——9、反射和文件操作
反射和文件操作 1、反射1.1、reflect.TypeOf()获取任意值的类型对象1.2、reflect.ValueOf()1.3、结构体反射 2、文件操作2.1、os.Open()打开文件2.2、方式一:使用Read()读取文件2.3、方式二:bufio读取文件2.4、方式三:os.ReadFile读取2.5、写…...

C++_哈希表
本篇文章是对C学习的哈希表部分的学习分享 相信一定会对你有所帮助~ 那咱们废话不多说,直接开始吧! 一、基础概念 1. 哈希核心思想: 哈希函数的作用:通过此函数建立一个Key与存储位置之间的映射关系。理想目标:实现…...