4.3 Windows驱动开发:监控进程与线程对象操作

news2024/10/5 17:28:09

在内核中,可以使用ObRegisterCallbacks这个内核回调函数来实现监控进程和线程对象操作。通过注册一个OB_CALLBACK_REGISTRATION回调结构体,可以指定所需的回调函数和回调的监控类型。这个回调结构体包含了回调函数和监控的对象类型,还有一个Altitude字段,用于指定回调函数的优先级。优先级越高的回调函数会先被调用,如果某个回调函数返回了一个非NULL值,后续的回调函数就不会被调用。

当有进程或线程对象创建、删除、复制或重命名时,内核会调用注册的回调函数。回调函数可以访问被监控对象的信息,如句柄、进程ID等,并可以采取相应的操作,如打印日志、记录信息等。

首先我们先来解释一下OB_CALLBACK_REGISTRATION结构体,OB_CALLBACK_REGISTRATION结构体是用于向内核注册回调函数的结构体,其中包含了回调函数和监控的对象类型等信息。

OB_CALLBACK_REGISTRATION结构体的定义:

typedef struct _OB_CALLBACK_REGISTRATION {
    PVOID RegistrationContext;
    POB_OPERATION_REGISTRATION OperationRegistration;
} OB_CALLBACK_REGISTRATION, *POB_CALLBACK_REGISTRATION;

其中,RegistrationContext是一个指针,指向一个可以在回调函数中访问的上下文数据结构,可以用来传递一些参数或状态信息。OperationRegistration是一个指向OB_OPERATION_REGISTRATION结构体的指针,该结构体指定了回调函数的相关信息,包括回调函数的地址、监控的对象类型、回调函数的优先级等。

OB_OPERATION_REGISTRATION结构体的定义如下:

typedef struct _OB_OPERATION_REGISTRATION {
    POBJECT_TYPE ObjectType;
    OB_OPERATION Operations;
    POB_PRE_OPERATION_CALLBACK PreOperation;
    POB_POST_OPERATION_CALLBACK PostOperation;
} OB_OPERATION_REGISTRATION, *POB_OPERATION_REGISTRATION;

其中,ObjectType是一个指针,指向要监控的对象类型的OBJECT_TYPE结构体。

Operations是一个枚举类型,表示要监控的操作类型,可以是如下之一:

  • OB_OPERATION_HANDLE_CREATE:创建对象句柄
  • OB_OPERATION_HANDLE_DUPLICATE:复制对象句柄
  • OB_OPERATION_HANDLE_CLOSE:关闭对象句柄
  • OB_OPERATION_HANDLE_WAIT:等待对象句柄
  • OB_OPERATION_HANDLE_SET_INFORMATION:设置对象句柄信息
  • OB_OPERATION_HANDLE_QUERY_INFORMATION:查询对象句柄信息
  • OB_OPERATION_HANDLE_OPERATION:其他操作

PreOperation和PostOperation分别是指向回调函数的指针,用于指定在进行指定操作之前和之后要执行的回调函数。这两个回调函数的参数和返回值等信息可以参考Microsoft官方文档。

我们以创建一个简单的监控进程对象为例,实现一个自己的进程回调函数MyObjectCallBack()当有新进程被加载时,自动路由到我们自己的回调中来;

首先在驱动程序入口处,定义Base结构,并初始化Base.ObjectTypePsProcessType标志着用于监控进程,接着填充Base.Operations并初始化为OB_OPERATION_HANDLE_CREATE表示当有进程句柄操作时触发回调,最后填充Base.PreOperation在回调前触发执行MyObjectCallBack自己的回调,当结构体被填充好以后,直接调用ObRegisterCallbacks()向内核申请回调事件即可,这段代码实现如下所示;

#include <ntddk.h>
#include <ntstrsafe.h>

PVOID Globle_Object_Handle;

OB_PREOP_CALLBACK_STATUS MyObjectCallBack(PVOID RegistrationContext, POB_PRE_OPERATION_INFORMATION OperationInformation)
{
    DbgPrint("执行了我们的回调函数...");
    return STATUS_SUCCESS;
}

VOID UnDriver(PDRIVER_OBJECT driver)
{
    ObUnRegisterCallbacks(Globle_Object_Handle);
    DbgPrint("回调卸载完成...");
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
    OB_OPERATION_REGISTRATION Base;                          // 回调函数结构体(你所填的结构都在这里)
    OB_CALLBACK_REGISTRATION CallbackReg;

    CallbackReg.RegistrationContext = NULL;                  // 注册上下文(你回调函数返回参数)
    CallbackReg.Version = OB_FLT_REGISTRATION_VERSION;       // 注册回调版本
    CallbackReg.OperationRegistration = &Base;
    CallbackReg.OperationRegistrationCount = 1;               // 操作计数(下钩数量)
    RtlUnicodeStringInit(&CallbackReg.Altitude, L"600000");   // 长度
    
  Base.ObjectType = PsProcessType;                          // 进程操作类型.此处为进程操作
    Base.Operations = OB_OPERATION_HANDLE_CREATE;             // 操作句柄创建
    Base.PreOperation = MyObjectCallBack;                     // 你自己的回调函数
    Base.PostOperation = NULL;

  // 注册回调
    if (ObRegisterCallbacks(&CallbackReg, &Globle_Object_Handle))
  {
        DbgPrint("回调注册成功...");
  }
    
  Driver->DriverUnload = UnDriver;
    return STATUS_SUCCESS;
}

上方代码运行后,我们可以打开Xuetr扫描一下内核Object钩子,可以看到已经成功挂钩了,输出效果如下图所示;

4.3.1 实现监控进程打开与关闭

接下来我们实现一个简单的需求,通过编写一个自定义MyObjectCallBack回调函数实现保护win32calc.exe进程不被关闭,本功能实现的关键在于如何获取到监控进程的进程名GetProcessImageNameByProcessID函数就是用来实现转换的,通过向此函数内传入一个进程PID则会通过PsGetProcessImageFileName输出该进程的进程名。

而当回调函数内接收到此进程名时,则可以通过strstr(ProcName, "win32calc.exe")对进程名进行判断,如果匹配到结果,则直接通过Operation->Parameters->CreateHandleInformation.DesiredAccess = ~THREAD_TERMINATE去掉TERMINATE_PROCESSTERMINATE_THREAD权限即可,此时的进程将会被保护而无法被关闭,这段代码实现如下所示;

#include <ntddk.h>
#include <wdm.h>
#include <ntstrsafe.h>
#define PROCESS_TERMINATE 1

PVOID Globle_Object_Handle;
NTKERNELAPI UCHAR * PsGetProcessImageFileName(__in PEPROCESS Process);

char* GetProcessImageNameByProcessID(ULONG ulProcessID)
{
    NTSTATUS  Status;
    PEPROCESS  EProcess = NULL;
    Status = PsLookupProcessByProcessId((HANDLE)ulProcessID, &EProcess);
    if (!NT_SUCCESS(Status))
        return FALSE;
    ObDereferenceObject(EProcess);
    return (char*)PsGetProcessImageFileName(EProcess);
}

OB_PREOP_CALLBACK_STATUS MyObjectCallBack(PVOID RegistrationContext, POB_PRE_OPERATION_INFORMATION Operation)
{
    char ProcName[256] = { 0 };
    HANDLE pid = PsGetProcessId((PEPROCESS)Operation->Object);           // 取出当前调用函数的PID
    strcpy(ProcName, GetProcessImageNameByProcessID((ULONG)pid));        // 通过PID取出进程名,然后直接拷贝内存
    //DbgPrint("当前进程的名字是:%s", ProcName);

    if (strstr(ProcName, "win32calc.exe"))
    {
        if (Operation->Operation == OB_OPERATION_HANDLE_CREATE)
        {
            if ((Operation->Parameters->CreateHandleInformation.OriginalDesiredAccess & PROCESS_TERMINATE) == PROCESS_TERMINATE)
            {
                DbgPrint("你想结束进程?");

                // 如果是计算器,则去掉它的结束权限,在Win10上无效
                Operation->Parameters->CreateHandleInformation.DesiredAccess = ~THREAD_TERMINATE;
                return STATUS_UNSUCCESSFUL;
            }
        }
    }
    return STATUS_SUCCESS;
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
    ObUnRegisterCallbacks(Globle_Object_Handle);
    DbgPrint("回调卸载完成...");
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
    NTSTATUS obst = 0;
    OB_CALLBACK_REGISTRATION obReg;
    OB_OPERATION_REGISTRATION opReg;

    memset(&obReg, 0, sizeof(obReg));
    obReg.Version = ObGetFilterVersion();
    obReg.OperationRegistrationCount = 1;
    obReg.RegistrationContext = NULL;
    RtlInitUnicodeString(&obReg.Altitude, L"321125");
    obReg.OperationRegistration = &opReg;

    memset(&opReg, 0, sizeof(opReg));
    opReg.ObjectType = PsProcessType;
    opReg.Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE;
    opReg.PreOperation = (POB_PRE_OPERATION_CALLBACK)&MyObjectCallBack;
    obst = ObRegisterCallbacks(&obReg, &Globle_Object_Handle);

    Driver->DriverUnload = UnDriver;
    return STATUS_SUCCESS;
}

首先运行计算器,然后启动驱动保护,此时我们在任务管理器中就无法结束计算器进程了。

4.3.2 实现监控进程中的模块加载

系统中的模块加载包括用户层模块DLL和内核模块SYS的加载,在内核环境下我们可以调用PsSetLoadImageNotifyRoutine内核函数来设置一个映像加载通告例程,当有驱动或者DLL被加载时,回调函数就会被调用从而执行我们自己的回调例程。

PsSetLoadImageNotifyRoutine 函数用来设置一个映像加载通告例程。该函数需要传入一个回调函数的指针,该回调函数会在系统中有驱动程序或 DLL 被加载时被调用。

该函数函数的原型为:

NTKERNELAPI
NTSTATUS
PsSetLoadImageNotifyRoutine(
  PLOAD_IMAGE_NOTIFY_ROUTINE NotifyRoutine
);

其中,NotifyRoutine 参数是一个指向映像加载通告例程的函数指针。该函数将在系统中有驱动程序或 DLL 被加载时被调用。

当一个映像被加载时,Windows 内核会检查是否已注册了映像加载通告例程。如果已注册,则内核会调用该例程,将被加载的模块的信息作为参数传递给该例程。通常,该例程会记录或处理这些信息。

需要注意的是,映像加载通告例程应该尽可能地简短,不要执行复杂的操作,以避免影响系统性能。同时,该例程应该是线程安全的,以免发生竞态条件或死锁。

如上函数中PLOAD_IMAGE_NOTIFY_ROUTINE用于接收一个自定义函数,该自定义函数需要声明成如下原型;

VOID MyLoadImageNotifyRoutine(
  PUNICODE_STRING FullImageName,
  HANDLE ProcessId,
  PIMAGE_INFO ImageInfo
);
  • 参数FullImageName参数是指被加载的模块的完整路径名;
  • 参数ProcessId参数是指加载该模块的进程ID;
  • 参数ImageInfo参数是指与该模块相关的信息,包括其基地址、大小等。在回调函数中,可以对这些信息进行处理,以实现对模块加载的监控。

有了如上知识体系,实现监控的目的就会变得简单,其监控的实现重点是实现自己的MyLoadImageNotifyRoutine如下代码中简单实现了当有新的DLL被装载到内存是,则通过DbgView输出该模块的具体信息;

#include <ntddk.h>
#include <ntimage.h>

// 用与获取特定基地址的模块入口
PVOID GetDriverEntryByImageBase(PVOID ImageBase)
{
    PIMAGE_DOS_HEADER pDOSHeader;
    PIMAGE_NT_HEADERS64 pNTHeader;
    PVOID pEntryPoint;
    pDOSHeader = (PIMAGE_DOS_HEADER)ImageBase;
    pNTHeader = (PIMAGE_NT_HEADERS64)((ULONG64)ImageBase + pDOSHeader->e_lfanew);
    pEntryPoint = (PVOID)((ULONG64)ImageBase + pNTHeader->OptionalHeader.AddressOfEntryPoint);
    return pEntryPoint;
}

VOID MyLoadImageNotifyRoutine(PUNICODE_STRING FullImageName,HANDLE ProcessId,PIMAGE_INFO ImageInfo)
{
    PVOID pDrvEntry;

  // MmIsAddress 验证地址可用性
    if (FullImageName != NULL && MmIsAddressValid(FullImageName))
    {
        if (ProcessId == 0)
        {
            pDrvEntry = GetDriverEntryByImageBase(ImageInfo->ImageBase);
            DbgPrint("模块名称:%wZ --> 装载基址:%p --> 镜像长度: %d", FullImageName, pDrvEntry,ImageInfo->ImageSize);
        }
    }
}

VOID UnDriver(PDRIVER_OBJECT driver)
{
    PsRemoveLoadImageNotifyRoutine((PLOAD_IMAGE_NOTIFY_ROUTINE)MyLoadImageNotifyRoutine);
    DbgPrint("驱动卸载完成...");
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
    PsSetLoadImageNotifyRoutine((PLOAD_IMAGE_NOTIFY_ROUTINE)MyLoadImageNotifyRoutine);
    DbgPrint("驱动加载完成...");
    Driver->DriverUnload = UnDriver;
    return STATUS_SUCCESS;
}

输出效果图如下所示:

通过上方代码的学习,相信读者已经学会了如何监视系统内加载驱动与DLL功能,接着我们给上方的代码加上判断,只需在上方代码的基础上进行一定的改进,但需要注意MyLoadImageNotifyRoutine回调函数中的ModuleStyle参数,该参数用于判断加载模块的类型,如果返回值为零则表示加载SYS,如果返回非零则表示加载的是DLL模块。

VOID UnicodeToChar(PUNICODE_STRING dst, char *src)
{
    ANSI_STRING string;
    RtlUnicodeStringToAnsiString(&string, dst, TRUE);
    strcpy(src, string.Buffer);
    RtlFreeAnsiString(&string);
}

VOID MyLoadImageNotifyRoutine(PUNICODE_STRING FullImageName,HANDLE ModuleStyle,PIMAGE_INFO ImageInfo)
{
    PVOID pDrvEntry;
    char szFullImageName[256] = { 0 };

  // MmIsAddress 验证地址可用性
    if (FullImageName != NULL && MmIsAddressValid(FullImageName))
    {
    // ModuleStyle为零表示加载sys非零表示加载DLL
        if (ModuleStyle == 0)
        {
            pDrvEntry = GetDriverEntryByImageBase(ImageInfo->ImageBase);
            UnicodeToChar(FullImageName, szFullImageName);
            if (strstr(_strlwr(szFullImageName), "hook.sys"))
            {
                DbgPrint("准备拦截SYS内核模块:%s", _strlwr(szFullImageName));
            }
        }
    }
}

输出效果图如下所示:

如上方代码中所示,MyLoadImageNotifyRoutine函数只能接收全局模块加载动作,但是此模式无法判断到底是哪个进程加载的hook.sys驱动,因为回调函数本就很底层了,到了一定的深度之后就无法判断到底是谁主动引发的行为,一切回调行为都会变成系统的行为,而某些驱动过滤软件,通常会使用特征扫描等方式来判断驱动是否是我们所需;

当判断特定模块是我们所要拦截的驱动时,则下一步就要进行驱动的屏蔽工作,对于驱动屏蔽来说最直接的办法就是在程序的入口位置写入Mov eax,c0000022h;ret这两条汇编指令从而让模块无法被执行,此时模块虽然被加载了但却无法执行功能,本质上来说已经起到了拒绝加载的效果;

通过ImageInfo->ImageBase 来获取被加载驱动程序hook.sys的映像基址,然后找到NT头的OptionalHeader节点,该节点里面就是被加载驱动入口的地址,通过汇编在驱动头部写入ret返回指令,即可实现屏蔽加载特定驱动文件,这段代码完整实现如下所示;

#include <ntddk.h>
#include <intrin.h>
#include <ntimage.h>

PVOID GetDriverEntryByImageBase(PVOID ImageBase)
{
    PIMAGE_DOS_HEADER pDOSHeader;
    PIMAGE_NT_HEADERS64 pNTHeader;
    PVOID pEntryPoint;
    pDOSHeader = (PIMAGE_DOS_HEADER)ImageBase;
    pNTHeader = (PIMAGE_NT_HEADERS64)((ULONG64)ImageBase + pDOSHeader->e_lfanew);
    pEntryPoint = (PVOID)((ULONG64)ImageBase + pNTHeader->OptionalHeader.AddressOfEntryPoint);
    return pEntryPoint;
}

VOID UnicodeToChar(PUNICODE_STRING dst, char *src)
{
    ANSI_STRING string;
    RtlUnicodeStringToAnsiString(&string, dst, TRUE);
    strcpy(src, string.Buffer);
    RtlFreeAnsiString(&string);
}

// 使用开关写保护需要在C/C++优化中启用内部函数

// 关闭写保护
KIRQL WPOFFx64()
{
    KIRQL irql = KeRaiseIrqlToDpcLevel();
    UINT64 cr0 = __readcr0();
    cr0 &= 0xfffffffffffeffff;
    _disable();
    __writecr0(cr0);
    return irql;
}

// 开启写保护
void WPONx64(KIRQL irql)
{
    UINT64 cr0 = __readcr0();
    cr0 |= 0x10000;
    _enable();
    __writecr0(cr0);
    KeLowerIrql(irql);
}

BOOLEAN DenyLoadDriver(PVOID DriverEntry)
{
    UCHAR fuck[] = "\xB8\x22\x00\x00\xC0\xC3";
    KIRQL kirql;
    /* 在模块开头写入以下汇编指令
    Mov eax,c0000022h
    ret
    */

    if (DriverEntry == NULL)
  {
    return FALSE;
  }

    kirql = WPOFFx64();
    memcpy(DriverEntry, fuck,sizeof(fuck) / sizeof(fuck[0]));
    WPONx64(kirql);
    return TRUE;
}

VOID MyLoadImageNotifyRoutine(PUNICODE_STRING FullImageName, HANDLE ModuleStyle, PIMAGE_INFO ImageInfo)
{
    PVOID pDrvEntry;
    char szFullImageName[256] = { 0 };

  // MmIsAddress 验证地址可用性
    if (FullImageName != NULL && MmIsAddressValid(FullImageName))
    {
    // ModuleStyle为零表示加载sys非零表示加载DLL
        if (ModuleStyle == 0)
        {
            pDrvEntry = GetDriverEntryByImageBase(ImageInfo->ImageBase);
            UnicodeToChar(FullImageName, szFullImageName);
            if (strstr(_strlwr(szFullImageName), "hook.sys"))
            {
                DbgPrint("拦截SYS内核模块:%s", szFullImageName);
                DenyLoadDriver(pDrvEntry);
            }
        }
    }
}

VOID UnDriver(PDRIVER_OBJECT driver)
{
    PsRemoveLoadImageNotifyRoutine((PLOAD_IMAGE_NOTIFY_ROUTINE)MyLoadImageNotifyRoutine);
    DbgPrint("驱动卸载完成...");
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
    PsSetLoadImageNotifyRoutine((PLOAD_IMAGE_NOTIFY_ROUTINE)MyLoadImageNotifyRoutine);
    DbgPrint("驱动加载完成...");
    Driver->DriverUnload = UnDriver;
    return STATUS_SUCCESS;
}

而对于屏蔽DLL模块加载同样如此,仅仅只是在判断ModuleStyle参数时将非零作为过滤条件即可,要实现该功能只需要在上面的代码上稍微修改一下即可;

char *UnicodeToLongString(PUNICODE_STRING uString)
{
    ANSI_STRING asStr;
    char *Buffer = NULL;;
    RtlUnicodeStringToAnsiString(&asStr, uString, TRUE);
    Buffer = ExAllocatePoolWithTag(NonPagedPool, uString->MaximumLength * sizeof(wchar_t), 0);
    if (Buffer == NULL)
  {
        return NULL;
  }

    RtlCopyMemory(Buffer, asStr.Buffer, asStr.Length);
    return Buffer;
}

VOID MyLoadImageNotifyRoutine(PUNICODE_STRING FullImageName, HANDLE ModuleStyle, PIMAGE_INFO ImageInfo)
{
    PVOID pDrvEntry;
    char *PareString = NULL;

    if (MmIsAddressValid(FullImageName))
    {
    // 非零则监控DLL加载
        if (ModuleStyle != 0)
        {
            PareString = UnicodeToLongString(FullImageName);
            if (PareString != NULL)
            {
                if (strstr(PareString, "hook.dll"))
                {
                    pDrvEntry = GetDriverEntryByImageBase(ImageInfo->ImageBase);
                    if (pDrvEntry != NULL)
                        DenyLoadDriver(pDrvEntry);
                }
            }
        }
    }
}

我们以屏蔽SYS内核模块为例,当驱动文件WinDDK.sys被加载后,尝试加载hook.sys会提示拒绝访问,说明我们的驱动保护生效了;

关键的内核进程操作已经分享,该功能作用使用非常广泛,例如杀软的主动防御系统,游戏的保护系统等都会用到这些东西,以QQ电脑管家为例,默认进程在运行后都会挂钩进程保护钩子用于监控系统内进程与线程操作;

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

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

相关文章

RK3568驱动指南|第七篇 设备树-第67章 of操作函数实验:获取属性

瑞芯微RK3568芯片是一款定位中高端的通用型SOC&#xff0c;采用22nm制程工艺&#xff0c;搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码&#xff0c;支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU&#xff0c;可用于轻量级人工…

笔记54:门控循环单元 GRU

本地笔记地址&#xff1a;D:\work_file\DeepLearning_Learning\03_个人笔记\3.循环神经网络\第9章&#xff1a;动手学深度学习~现代循环神经网络 a a a a a a a

第八篇 基于JSP 技术的网上购书系统——商品信息查看、我的购物车、结算中心功能实现(网上商城、仿淘宝、当当、亚马逊)

目录 1.商品信息查看 1.1功能说明 1.2界面设计 1.3 处理流程 1.4数据来源和算法 1.4.1数据来源 1.4.2查询条件 1.4.3表间关系 1.4.4相关sql实例 2.我的购物车 2.1功能说明 2.2界面设计 2.3处理流程 2.4 数据来源和算法 3.结算中心 3.1功能说明 3.2界…

企业服务器中了babyk勒索病毒怎么办,babyk勒索病毒解密数据集恢复

网络技术的不断发展应用&#xff0c;为企业的生产生活提供了强有力帮助&#xff0c;企业也不断走向数字化办公模式&#xff0c;而对于企业来说&#xff0c;企业计算机存储的数据至关重要&#xff0c;如果不加以保护很容易造成数据丢失&#xff0c;近期&#xff0c;云天数据恢复…

JAXB的XmlElement注解

依赖 如果基于JAX-WS开发&#xff0c;可以在maven工程的pom.xml文件中增加如下依赖&#xff0c;会将依赖的JAXB库也下载下来&#xff1a; <dependency><groupId>jakarta.xml.ws</groupId><artifactId>jakarta.xml.ws-api</artifactId><vers…

网络工程师沦为IT行业里的“二等公民”了?

大家好&#xff0c;我是老杨。 都说网工难&#xff0c;都说网工苦&#xff0c;都说网工行业已经不行了、网工成为最底层、“二等公民”&#xff0c;已经彻底没落了…… 这些在互联网里持续不断散发出来的负能量&#xff0c;有没有让你在深夜耍手机的时候一次又一次的扎心过&a…

【HarmonyOS开发】配置开发工具DevEco Studio

1、下载 注意&#xff1a; 1、安装过程中&#xff0c;一定要自定义安装位置&#xff0c;包比较大&#xff0c;包比较大&#xff0c;包比较大&#xff01;&#xff01;&#xff01; 2、可以将该工具添加到右键中&#xff0c;否则&#xff0c;如果你的项目不是HarmonyOS&#xff…

数组两种初始化方法

1.数组的静态初始化 静态初始化即在初始化数组时即规定了数组的大小以及数组中每个元素的值 有三种静态初始化的方法&#xff1a; 以初始化一个int类型的数组为例&#xff1a; 1.数组类型[] 数组名 new 数组类型[]{元素1,元素2...元素n}; int[] a new int[]{1,3,5}; 2.数…

Vue3 动态设置 ref

介绍 在一些场景&#xff0c;ref设置是未知的需要根据动态数据来决定&#xff0c;如表格中的input框需要我们主动聚焦&#xff0c;就需要给每一个input设置一个ref&#xff0c;进而进行聚焦操作。 Demo 点击下面截图中的编辑按钮&#xff0c;自动聚焦到相应的输入框中。 &…

C 语言字符串

C 语言字符串 在本教程中&#xff0c;您将学习C语言编程中的字符串。您将在示例的帮助下学习声明它们&#xff0c;对其进行初始化以及将它们用于各种 I / O&#xff08;输入/输出&#xff09;操作。 在C语言编程中&#xff0c;字符串是以null字符\0结束的字符序列。例如: ch…

基于单片机温湿度PM2.5报警系统

**单片机设计介绍&#xff0c; 基于单片机温湿度PM2.5报警设置系统 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 单片机温湿度PM2.5报警设置系统是一种智能化的环境检测与报警系统。它主要由单片机、传感器、液晶显示屏、蜂鸣器…

Flume学习笔记(4)—— Flume数据流监控

前置知识&#xff1a; Flume学习笔记&#xff08;1&#xff09;—— Flume入门-CSDN博客 Flume学习笔记&#xff08;2&#xff09;—— Flume进阶-CSDN博客 Flume 数据流监控 Ganglia 的安装与部署 Ganglia 由 gmond、gmetad 和 gweb 三部分组成。 gmond&#xff08;Ganglia …

三十一、W5100S/W5500+RP2040树莓派Pico<TCP_Server多路socket>

文章目录 1 前言2 简介2. 1 使用多路socket的优点2.2 多路socket数据交互原理2.3 多路socket应用场景 3 WIZnet以太网芯片4 多路socket设置示例概述以及使用4.1 流程图4.2 准备工作核心4.3 连接方式4.4 主要代码概述4.5 结果演示 5 注意事项6 相关链接 1 前言 W5100S/W5500是一…

centos虚拟机无法接受消息(防火墙)

1.利用wireshark抓包&#xff0c; 发现发送信息后&#xff0c; 虚拟机返回 :host administratively prohibited 2.发现是centos虚拟机未关闭防火墙 &#xff08;关闭后可正常接收消息&#xff09;

uni-app 使用vscode开发uni-app

安装插件 uni-create-view 用于快速创建页面 配置插件 创建页面 输入页面名称&#xff0c;空格&#xff0c;顶部导航的标题&#xff0c;回车 自动生成页面并在pages.json中注册了路由 pages\login\login.vue <template><div class"login">login</d…

【漏洞复现】​金和OA存在任意文件读取漏洞

漏洞描述 金和OA协同办公管理系统C6软件(简称金和OA),本着简单、适用、高效的原则,贴合企事业单位的实际需求,实行通用化、标准化、智能化、人性化的产品设计,充分体现企事业单位规范管理、提高办公效率的核心思想,为用户提供一整套标准的办公自动化解决方案,以帮助企…

手把手教你搭建Maven私服

Java全能学习面试指南&#xff1a;https://javaxiaobear.cn 1. Maven私服简介 ①私服简介 Maven 私服是一种特殊的Maven远程仓库&#xff0c;它是架设在局域网内的仓库服务&#xff0c;用来代理位于外部的远程仓库&#xff08;中央仓库、其他远程公共仓库&#xff09;。 当然…

UE4动作游戏实例RPG Action解析一:角色移动,旋转,动画创建,创建武器,及武器配置

文末有git地址 一、角色移动,摄像机旋转 1.1、官方RPGAction Demo下载地址: ​ 1.2、在场景中创建一个空的角色 创建一个Character蓝图和一个PlayerController蓝图,添加弹簧臂组件和摄像机,并为网格体添加上一个骨骼网格体 ​ 1.3、如何让这个角色出现在场景中, 创建一…

混沌系统在图像加密中的应用(基于哈密顿能量函数的混沌系统构造1.4)

混沌系统在图像加密中的应用&#xff08;基于哈密顿能量函数的混沌系统构造1.4&#xff09; 前言一、逆时间对称性分析二、具有逆时间对称性的单晶格状混沌与拟周期流1.逆时间对称性及哈密顿能量函数2.数值仿真 python代码 前言 续接混沌系统在图像加密中的应用&#xff08;基…

cvf_使用lora方法增强能力

cvf_使用lora方法增强能力 实验对比图最终代码简介详细解析实验对比图 最终代码 import paddle import numpy as np import pandas as pd from tqdm import tqdmclass FeedFroward(paddle.nn.Layer)