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

在内核中,可以使用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 1PVOID 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);           // 取出当前调用函数的PIDstrcpy(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非零表示加载DLLif (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,c0000022hret*/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非零表示加载DLLif (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.rhkb.cn/news/199687.html

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

相关文章

Camtasia2024年破解版安装包如何下载?

作为一个互联网人&#xff0c;没少在录屏软件这个坑里摸爬滚打。培训、学习、游戏、影视解说……都得用它。这时候没个拿得出手的私藏软件&#xff0c;还怎么混&#xff1f;说实话&#xff0c;录屏软件这两年也用了不少&#xff0c;基本功能是有但总觉得缺点什么&#xff0c;直…

C进阶---文件操作

我们在日常使用电脑保存文件时&#xff0c;其目的就是为了便于以后查看、修改、更新等操作&#xff1b;保存在文件中可以使数据持久化&#xff0c;所以今天我们家里学习文件的相关操作。 一、文件 1.1什么是文件 磁盘上的文件是文件。 在程序设计中&#xff0c;文件一般分…

某60区块链安全之51%攻击实战学习记录

区块链安全 文章目录 区块链安全51%攻击实战实验目的实验环境实验工具实验原理攻击过程 51%攻击实战 实验目的 1.理解并掌握区块链基本概念及区块链原理 2.理解区块链分又问题 3.理解掌握区块链51%算力攻击原理与利用 4.找到题目漏洞进行分析并形成利用 实验环境 1.Ubuntu1…

基于RK3588全高端智能终端机器人主板

一、小尺寸板型设计 该款主板为小型板&#xff0c;尺寸仅为125*85mm&#xff0c;更小更紧凑&#xff0c;可完美适应各类高端智能自助终端&#xff1b; 二、八核高端处理器 采用RK3588S八核64位处理器&#xff0c;8nm LP制程&#xff0c;主频最高达2.4GHz&#xff0c;搭载Andr…

解决requests 2.28.x版本SSL错误:证书验证失败

1、问题背景 在使用requests 2.28.1版本时&#xff0c;我进行HTTP post传输报告负载时&#xff0c;由于SSL验证设置为True&#xff0c;请求失败&#xff0c;错误如下&#xff1a;(Caused by SSLError(SSLCertVerificationError(1, ‘[SSL: CERTIFICATE_VERIFY_FAILED] certifi…

对OpenAI CEO奥特曼突然被解雇事件的一些分析

今天也来凑个热闹&#xff0c;说说OpenAI的事。本来不想写的&#xff0c;但是看到自媒体又开始胡说八道&#xff0c;所以根据我自己得到的消息和理解说一说我的看法&#xff0c;这篇文章要是有个小姐姐解说录成视频&#xff0c;那肯定火了&#xff0c;但是我现在没资源&#xf…

Oauth2认证及Spring Security Oauth2授权码模式

Oauth2认证 Oauth2简介 简介 第三方认证技术方案最主要是解决认证协议的通用标准问题&#xff0c;因为要实现跨系统认证&#xff0c;各系统之间要遵循一定的接口协议。 OAUTH协议为用户资源的授权提供了一个安全的、开放而又简易的标准。同时&#xff0c;任何第三方都可以使…

本地/笔记本/纯 cpu 部署、使用类 gpt 大模型

文章目录 1. 安装 web UI1.1. 下载代码库1.2. 创建 conda 环境1.3. 安装 pytorch1.4. 安装 pip 库 2. 下载大模型3. 使用 web UI3.1. 运行 UI 界面3.2. 加载模型3.3. 进行对话 使用 web UI 大模型文件&#xff0c;即可在笔记本上部署、使用类 gpt 大模型。 1. 安装 web UI 1…

php一句话木马免杀

php一句话木马免杀 针对于php一句话木马做免杀&#xff1a; 利用php动态函数的特性&#xff0c;将危险函数拆分成字符&#xff0c;最终使用字符串拼接的方式&#xff0c;然后重新拼接&#xff0c;后加括号执行代码&#xff0c;并且可以使用花指令进行包装&#xff0c;如无限i…

C++ DAY06 c++多态

简介 一个事物的多种形态 , 简称多态 物的多态 同一个人在不同人面前展现是不同的 如 : 在我面前 在对象面前 在朋友面前 在父母面前 事的多态 吃饭 中国人 筷子 熟食 美国人 刀叉 7 分熟 日本人 筷子 生食 印度人 手 睡觉 中国人 床上 日本人 榻榻米 侧卧 平躺…

【U8+】用友U8账套引入/还原,提示:逻辑文件‘UFModel’不是数据库的一部分。

【问题描述】 用友U8+账套引入(恢复账套)的时候,提示: 逻辑文件‘UFModel’不是数据库‘UFDATA_001_2015’的一部分。 请使用RESTORE FILELISTONLY来列出逻辑文件名。-2147217900 【解决方法】 查看用友U8+账套库正确的逻辑名称为【UFMODEL】和【UFMODEL_log】。 【案例…

​分享mfc140u.dll丢失的解决方法,针对原因解决mfc140u.dll丢失的问题

作为电脑小白&#xff0c;如果电脑中出现了mfc140u.dll丢失的问题&#xff0c;肯定会比较的慌乱。但是出现mfc140u.dll丢失的问题&#xff0c;其实也有很简单的办法&#xff0c;所以大家不用慌张&#xff0c;接下来就教大家解决办法&#xff0c;能够有效的解决mfc140u.dll丢失的…

MFC 对话框

目录 一、对话款基本认识 二、对话框项目创建 三、控件操作 四、对话框创建和显示 模态对话框 非模态对话框 五、动态创建按钮 六、访问控件 控件添加控制变量 访问对话框 操作对话框 SendMessage() 七、对话框伸缩功能实现 八、对话框小项目-逃跑按钮 九、小项…

C#中的is和as的使用和区别

目录 概述一、is操作符1. is操作符的语法2. is操作符的用途3. is操作符的使用示例4. is操作符与typeof操作符的区别 二、as操作符1. as操作符的语法2. as操作符的用途3. as操作符的使用示例4. as操作符与is操作符的区别和联系5. as操作符与is操作符的区别总结 概述 在C#编程语…

探寻欧洲市场的机遇:深度剖析欧洲跨境电商

随着全球化的不断推进&#xff0c;欧洲作为一个经济发达、多元文化共存的大陆&#xff0c;成为跨境电商发展的重要目标。本文将深入剖析欧洲跨境电商的机遇&#xff0c;分析欧洲市场的特点、挑战与前景&#xff0c;为企业提供在这个充满潜力的市场中蓬勃发展的指导。 欧洲市场的…

【前端学java】java中的Object类(8)

往期回顾&#xff1a; 【前端学java】JAVA开发的依赖安装与环境配置 &#xff08;0&#xff09;【前端学 java】java的基础语法&#xff08;1&#xff09;【前端学java】JAVA中的packge与import&#xff08;2&#xff09;【前端学java】面向对象编程基础-类的使用 &#xff08…

YOLOv8-Seg改进:位置信息的轴线压缩增强注意力Sea_Attention| ICLR2023 SeaFormer,轻量级语义分割算法,复旦大学和腾讯

🚀🚀🚀本文改进:位置信息的轴线压缩增强注意力Sea_Attention,一方面将QKV特征进行轴线压缩后再注意力增强,另一方面将QKV特征使用卷积网络提升局部信息,最后将二者融合,输出增强特征 🚀🚀🚀Sea_Attention小目标分割首选,暴力涨点 🚀🚀🚀YOLOv8-seg创新…

LeetCode【76】最小覆盖子串

题目&#xff1a; 思路&#xff1a; https://segmentfault.com/a/1190000021815411 代码&#xff1a; public String minWindow(String s, String t) { Map<Character, Integer> map new HashMap<>();//遍历字符串 t&#xff0c;初始化每个字母的次数for (int…

redis 非关系型数据库

redis 非关系型数据库&#xff0c;缓存型数据库。 关系型数据库和非关系型数据库的区别 关系型数据库是一个机构化的数据库&#xff0c;行和列。 列&#xff1a;声明对象 行&#xff1a;记录对象属性。 表与表之间是有关联&#xff0c;使用sql语句&#xff0c;来对指定的表…

【Linux】指令详解(二)

目录 1. 前言2. 重新认识指令2.1 指令的本质2.1.1 which2.1.2 alias 3. 常见指令3.1 whoami3.2 cd3.2.1 cd -3.2.2 cd ~ 3.3 touch3.3.1 文件创建时间 3.4 stat3.5 mkdir3.5.1 创建一个目录3.5.2 创建路径 3.6 tree3.7 rm3.7.1 rm -f3.7.2 rm -r 3.8 man3.9 cp3.10 mv 1. 前言 …