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

浅谈进程隐藏技术

前言

在之前几篇文章已经学习了解了几种钩取的方法

  • 浅谈调试模式钩取
  • 浅谈热补丁
  • 浅谈内联钩取原理与实现
  • 导入地址表钩取技术

这篇文章就利用钩取方式完成进程隐藏的效果。

进程遍历方法

在实现进程隐藏时,首先需要明确遍历进程的方法。

CreateToolhelp32Snapshot

CreateToolhelp32Snapshot函数用于创建进程的镜像,当第二个参数为0时则是创建所有进程的镜像,那么就可以达到遍历所有进程的效果。

#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>int main()
{//设置编码,便于后面能够输出中文setlocale(LC_ALL, "zh_CN.UTF-8");//创建进程镜像,参数0代表创建所有进程的镜像HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);if (hSnapshot == INVALID_HANDLE_VALUE){std::cout << "Create Error" << std::endl;exit(-1);}/** typedef struct tagPROCESSENTRY32 { * DWORD dwSize;               进程信息结构体大小,首次调用之前必须初始化* DWORD cntUsage;              引用进程的次数,引用次数为0时,则进程结束* DWORD th32ProcessID;           进程的ID* ULONG_PTR th32DefaultHeapID;       进程默认堆的标识符,除工具使用对我们没用* DWORD th32ModuleID;                  进程模块的标识符* DWORD cntThreads;             进程启动的执行线程数* DWORD th32ParentProcessID;           父进程ID* LONG  pcPriClassBase;          进程线程的基本优先级* DWORD dwFlags;              保留* TCHAR szExeFile[MAX_PATH];          进程的路径* } PROCESSENTRY32; * typedef PROCESSENTRY32 *PPROCESSENTRY32; */PROCESSENTRY32 pi;pi.dwSize = sizeof(PROCESSENTRY32);//取出第一个进程BOOL bRet = Process32First(hSnapshot, &pi);while (bRet){wprintf(L"进程路径:%s\t进程号:%d\n", pi.szExeFile, pi.th32ProcessID);//取出下一个进程bRet = Process32Next(hSnapshot, &pi);}
}

EnumProcesses

EnumProcesses用于将所有进程号的收集。

#include <iostream>
#include <Windows.h>
#include <Psapi.h>int main()
{setlocale(LC_ALL, "zh_CN.UTF-8");DWORD processes[1024], dwResult, size;unsigned int i;//收集所有进程的进程号if (!EnumProcesses(processes, sizeof(processes), &dwResult)){std::cout << "Enum Error" << std::endl;}//进程数量size = dwResult / sizeof(DWORD);for (i = 0; i < size; i++){//判断进程号是否为0if (processes[i] != 0){//用于存储进程路径TCHAR szProcessName[MAX_PATH] = { 0 };//使用查询权限打开进程HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION |PROCESS_VM_READ,FALSE,processes[i]);if (hProcess != NULL){HMODULE hMod;DWORD dwNeeded;//收集该进程的所有模块句柄,第一个句柄则为文件路径if (EnumProcessModules(hProcess, &hMod, sizeof(hMod),&dwNeeded)){//根据句柄获取文件路径GetModuleBaseName(hProcess, hMod, szProcessName,sizeof(szProcessName) / sizeof(TCHAR));}wprintf(L"进程路径:%s\t进程号:%d\n", szProcessName, processes[i]);}}   }
}

ZwQuerySystemInfomation

ZwQuerySystemInfomation函数是CreateToolhelp32Snapshot函数与EnumProcesses函数底层调用的函数,也用于遍历进程信息。代码参考https://cloud.tencent.com/developer/article/1454933

#include <iostream>
#include <Windows.h>
#include <ntstatus.h>
#include <winternl.h> 
#pragma comment(lib, "ntdll.lib") //定义函数指针
typedef NTSTATUS(WINAPI* NTQUERYSYSTEMINFORMATION)(IN      SYSTEM_INFORMATION_CLASS SystemInformationClass,IN OUT   PVOID                    SystemInformation,IN      ULONG                    SystemInformationLength,OUT PULONG                   ReturnLength);int main()
{//设置编码setlocale(LC_ALL, "zh_CN.UTF-8");//获取模块地址HINSTANCE ntdll_dll = GetModuleHandle(L"ntdll.dll");if (ntdll_dll == NULL) {std::cout << "Get Module Error" << std::endl;exit(-1);}NTQUERYSYSTEMINFORMATION ZwQuerySystemInformation = NULL;//获取函数地址ZwQuerySystemInformation = (NTQUERYSYSTEMINFORMATION)GetProcAddress(ntdll_dll, "ZwQuerySystemInformation");if (ZwQuerySystemInformation != NULL){SYSTEM_BASIC_INFORMATION sbi = { 0 };//查询系统基本信息NTSTATUS status = ZwQuerySystemInformation(SystemBasicInformation, (PVOID)&sbi, sizeof(sbi), NULL);if (status == STATUS_SUCCESS){wprintf(L"处理器个数:%d\r\n", sbi.NumberOfProcessors);}else{wprintf(L"ZwQuerySystemInfomation Error\n");}DWORD dwNeedSize = 0;BYTE* pBuffer = NULL;wprintf(L"\t----所有进程信息----\t\n");PSYSTEM_PROCESS_INFORMATION psp = NULL;//查询进程数量status = ZwQuerySystemInformation(SystemProcessInformation, NULL, 0, &dwNeedSize);if (status == STATUS_INFO_LENGTH_MISMATCH){pBuffer = new BYTE[dwNeedSize];//查询进程信息status = ZwQuerySystemInformation(SystemProcessInformation, (PVOID)pBuffer, dwNeedSize, NULL);if (status == STATUS_SUCCESS){psp = (PSYSTEM_PROCESS_INFORMATION)pBuffer;wprintf(L"\tPID\t线程数\t工作集大小\t进程名\n");do {//获取进程号wprintf(L"\t%d", psp->UniqueProcessId);//获取线程数量wprintf(L"\t%d", psp->NumberOfThreads);//获取工作集大小wprintf(L"\t%d", psp->WorkingSetSize / 1024);//获取路径wprintf(L"\t%s\n", psp->ImageName.Buffer);//移动psp = (PSYSTEM_PROCESS_INFORMATION)((PBYTE)psp + psp->NextEntryOffset);} while (psp->NextEntryOffset != 0);delete[]pBuffer;pBuffer = NULL;}else if (status == STATUS_UNSUCCESSFUL) {wprintf(L"\n STATUS_UNSUCCESSFUL");}else if (status == STATUS_NOT_IMPLEMENTED) {wprintf(L"\n STATUS_NOT_IMPLEMENTED");}else if (status == STATUS_INVALID_INFO_CLASS) {wprintf(L"\n STATUS_INVALID_INFO_CLASS");}else if (status == STATUS_INFO_LENGTH_MISMATCH) {wprintf(L"\n STATUS_INFO_LENGTH_MISMATCH");}}}
}

进程隐藏

通过上述分析可以知道遍历进程的方式有三种,分别是利用CreateToolhelp32SnapshotEnumProcesses以及ZwQuerySystemInfomation函数

但是CreateToolhelp32SnapshotEnumProcesses函数底层都是调用了ZwQuerySystemInfomation函数,因此我们只需要钩取该函数即可。

由于测试环境是Win11,因此需要判断在Win11情况下底层是否还是调用了ZwQuerySystemInfomation函数。

可以看到在Win11下还是会调用ZwQuerySystemInfomation函数,在用户态下该函数的名称为NtQuerySystemInformation函数。

这里采用内联钩取的方式对ZwQuerySystemInfomation进行钩取处理,具体怎么钩取在浅谈内联钩取原理与实现已经介绍过了,这里就不详细说明了。这里对自定义的ZwQuerySystemInfomation函数进行说明。

首先第一步需要进行脱钩处理,因为后续需要用到初始的ZwQuerySystemInfomation函数,紧接着获取待钩取函数的地址即可。

...//脱钩UnHook("ntdll.dll", "ZwQuerySystemInformation", g_pOrgBytes);HMODULE hModule = GetModuleHandleA("ntdll.dll");//获取待钩取函数的地址PROC    pfnOld = GetProcAddress(hModule, "ZwQuerySystemInformation");//调用原始的ZwQuerySystemInfomation函数NTSTATUS status = ((NTQUERYSYSTEMINFORMATION)pfnOld)(SystemInformationClass, SystemInformation, SystemInformationLength, ReturnLength);
...

为了隐藏指定进程,我们需要遍历进程信息,找到目标进程并且删除该进程信息实现隐藏的效果。这里需要知道的是进程信息都存储在SYSTEM_PROCESS_INFORMATION结构体中,该结构体是通过单链表对进程信息进行链接。因此我们通过匹配进程名称找到对应的SYSTEM_PROCESS_INFORMATION结构体,然后进行删除即可,效果如下图。

通过单链表中删除节点的操作,取出目标进程的结构体。代码如下

...pCur = (PSYSTEM_PROCESS_INFORMATION)(SystemInformation);while (true){if (!lstrcmpi(pCur->ImageName.Buffer, L"test.exe")){//需要隐藏的进程是最后一个节点if (pCur->NextEntryOffset == 0)pPrev->NextEntryOffset = 0;//不是最后一个节点,则将该节点取出elsepPrev->NextEntryOffset += pCur->NextEntryOffset;}//不是需要隐藏的节点,则继续遍历elsepPrev = pCur;//链表遍历完毕if (pCur->NextEntryOffset == 0)break;pCur = (PSYSTEM_PROCESS_INFORMATION)((PBYTE)pCur + pCur->NextEntryOffset);}
...

完整代码:https://github.com/h0pe-ay/HookTechnology/blob/main/ProcessHidden/inlineHook.c

但是采用内联钩取的方法去钩取任务管理器就会出现一个问题,这里将断点取消,利用内联钩取的方式去隐藏进程。

首先利用bl命令查看断点

紧着利用 bc [ID]删除断点

在注入之后任务管理器会在拷贝的时候发生异常

在经过一番调试后发现,由于多线程共同执行导致原本需要可写权限的段被修改为只读权限

windbg可以用使用!vprot + address查看指定地址的权限,可以看到由于程序往只读权限的地址进行拷贝处理,所以导致了异常。

但是在执行拷贝阶段是先修改了该地址为可写权限,那么导致该原因的情况就是其他线程执行了权限恢复后切换到该线程中进行写,所以导致了这个问题。

因此内联钩取是存在多线程安全的问题,此时可以使用微软自己构建的钩取库Detours,可以在钩取过程中确保线程安全。

帮助网安学习,全套资料S信免费领取:
① 网安学习成长路径思维导图
② 60+网安经典常用工具包
③ 100+SRC分析报告
④ 150+网安攻防实战技术电子书
⑤ 最权威CISSP 认证考试指南+题库
⑥ 超1800页CTF实战技巧手册
⑦ 最新网安大厂面试题合集(含答案)
⑧ APP客户端安全检测指南(安卓+IOS)

Detours

项目地址:https://github.com/microsoft/Detours

环境配置

参考:https://www.cnblogs.com/linxmouse/p/14168712.html

使用vcpkg下载

vcpkg.exe install detours:x86-windows
vcpkg.exe install detours:x64-windows
vcpkg.exe integrate install

实例

挂钩

利用Detours挂钩非常简单,只需要根据下列顺序,并且将自定义函数的地址与被挂钩的地址即可完成挂钩处理。

...//用于确保在 DLL 注入或加载时,恢复被 Detours 修改的进程镜像,保持稳定性DetourRestoreAfterWith();//开始一个新的事务来附加或分离DetourTransactionBegin();//进行线程上下文的更新DetourUpdateThread(GetCurrentThread());//挂钩DetourAttach(&(PVOID&)TrueZwQuerySystemInformation, ZwQuerySystemInformationEx);//提交事务error = DetourTransactionCommit();
...

脱钩

然后根据顺序完成脱钩即可。

...//开始一个新的事务来附加或分离DetourTransactionBegin();//进行线程上下文的更新DetourUpdateThread(GetCurrentThread());//脱钩DetourDetach(&(PVOID&)TrueZwQuerySystemInformation, ZwQuerySystemInformationEx);//提交事务error = DetourTransactionCommit();
...

挂钩的原理

从上述可以看到,Detours是通过事务确保了在DLL加载与卸载时后的原子性,但是如何确保多线程安全呢?后续通过调试去发现。

可以利用x ntdl!ZwQuerySystemInformation查看函数地址,可以看到函数的未被挂钩前的情况如下图。

挂钩之后原始的指令被修改为一个跳转指令把前八个字节覆盖掉,剩余的3字节用垃圾指令填充。

该地址里面又是一个jmp指令,并且完成间接寻址的跳转。

该地址是自定义函数ZwQuerySystemInformationEx,因此该间接跳转是跳转到的自定义函数内部。

跳转到TrueZwQuerySystemInformation内部发现ZwQuerySystemInformation函数内部的八字节指令被移动到该函数内部。紧接着又完成一个跳转。

该跳转到ZwQuerySystemInformation函数内部紧接着完成ZwQuerySystemInformation函数的调用。

综上所述,整体流程如下图。实际上Detours实际上使用的是热补丁的思路,但是Detours并不是直接在原始的函数空间中进行补丁,而是开辟了一段临时空间,将指令存储在里面。因此在挂钩后不需要进行脱钩处理就可以调用原始函数。因此就不存在多线程中挂钩与脱钩的冲突。

完整代码:https://github.com/h0pe-ay/HookTechnology/blob/main/ProcessHidden/detoursHook.c

相关文章:

浅谈进程隐藏技术

前言 在之前几篇文章已经学习了解了几种钩取的方法 浅谈调试模式钩取浅谈热补丁浅谈内联钩取原理与实现导入地址表钩取技术 这篇文章就利用钩取方式完成进程隐藏的效果。 进程遍历方法 在实现进程隐藏时&#xff0c;首先需要明确遍历进程的方法。 CreateToolhelp32Snapsh…...

【C++】Google Test(gtest)单元测试

文章目录 Google Test&#xff08;gtest&#xff09;单元测试使用示例更多用法测试夹具 Google Test&#xff08;gtest&#xff09;单元测试 单元测试是一种软件测试方法&#xff0c;它旨在将应用程序的各个部分&#xff08;通常是方法或函数&#xff09;分离出来并独立测试&a…...

水箱高低水位浮球液位开关

水箱高低水位浮球液位开关概述 水箱高低水位浮球液位开关是一种用于监测和控制水箱中液位的自动化设备&#xff0c;它能够在水箱液位达到预设的高低限制时&#xff0c;输出开关信号&#xff0c;以控制水泵或电磁阀的开闭&#xff0c;从而维持水箱液位在一个安全的范围内。这类设…...

Autoware内容学习与初步探索(一)

0. 简介 之前作者主要是基于ROS2&#xff0c;CyberRT还有AutoSar等中间件完成搭建的。有一说一&#xff0c;这种从头开发当然有从头开发的好处&#xff0c;但是如果说绝大多数的公司还是基于现成的Apollo以及Autoware来完成的。这些现成的框架中也有很多非常好的方法。目前作者…...

【手写数据库内核组件】01 解析树的结构,不同类型的数据结构组多层的链表树,抽象类型统一引用格式

不同类型的链表 ​专栏内容&#xff1a; postgresql使用入门基础手写数据库toadb并发编程 个人主页&#xff1a;我的主页 管理社区&#xff1a;开源数据库 座右铭&#xff1a;天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物. 文章目录 不同类型…...

Pandas 进阶 —— 数据转换、聚合与可视化

引言 在数据分析的旅程中&#xff0c;Pandas 库提供了从数据转换到聚合再到可视化的全面解决方案。上篇我们掌握了数据的导入和清洗&#xff0c;本篇我们将探索如何通过 Pandas 对数据进行更高级的处理&#xff0c;包括数据转换、聚合分析以及可视化展示。 数据转换 数据转换…...

华为OD机试 - 来自异国的客人(Java 2024 D卷 100分)

华为OD机试 2024D卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;D卷C卷A卷B卷&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;每一题都有详细的答题思路、详细的代码注释、样例测…...

期末上分站——计组(3)

复习题21-42 21、指令周期是指__C_。 A. CPU从主存取出一条指令的时间 B. CPU执行一条指令的时间 C. CPU从主存取出一条指令的时间加上执行这条指令的时间。 D. 时钟周期时间 22、微型机系统中外设通过适配器与主板的系统总线相连接&#xff0c;其功能是__D_。 A. 数据缓冲和…...

IDA*——AcWing 180. 排书

IDA* 定义 IDA*&#xff08;Iterative Deepening A*&#xff09;是一种结合了深度优先搜索&#xff08;DFS&#xff09;的递归深度限制特性和A搜索的启发式估价函数的搜索算法。它主要用于解决启发式搜索问题&#xff0c;尤其是当搜索空间很大或者搜索成本不确定时。 IDA* 是…...

【云计算】公有云、私有云、混合云、社区云、多云

公有云、私有云、混合云、社区云、多云 1.云计算的形态1.1 公有云1.2 私有云1.3 混合云1.4 社区云1.5 多云1.5.1 多云和混合云之间的关系1.5.2 多云的用途1.5.3 影子 IT 和多云1.5.4 优缺点 2.不同云形态的对比 1.云计算的形态 张三⾃⼰在家做饭吃&#xff0c;这是 私有云&…...

MySQL中的MVCC解析

MySQL中的MVCC解析 多版本并发控制是MySQL中实现高并发的一种关键技术。通过对数据进行多版本的管理&#xff0c;MVCC能够在保证数据一致性的同时&#xff0c;提高数据库的并发性能。本文将深入探讨MySQL中的MVCC机制&#xff0c;包括其原理、实现方式以及优势。 MVCC的原理 …...

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] LYA的生日聚会(100分) - 三语言AC题解(Python/Java/Cpp)

&#x1f36d; 大家好这里是清隆学长 &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f497; &#x1f…...

初识STM32:芯片基本信息

STM32简介 STM32是ST公司基于ARM公司的Cortex-M内核开发的32位微控制器。 ARM公司是全球领先的半导体知识产权&#xff08;IP&#xff09;提供商&#xff0c;全世界超过95%的智能手机和平板电脑都采用ARM架构。 ST公司于1987年由意大利的SGS微电子与法国的Thomson半导体合并…...

Zabbix 配置PING监控

Zabbix PING监控介绍 如果需要判断机房的网络或者主机是否正常&#xff0c;这就需要使用zabbix ping&#xff0c;Zabbix使用外部命令fping处理ICMP ping的请求&#xff0c;在基于ubuntu APT方式安装zabbix后默认已存在fping程序。另外zabinx_server配置文件参数FpingLocation默…...

异常解决(三)-- Wandb fails with ServiceStartProcessError

原文链接&#xff1a;https://github.com/wandb/wandb/issues/5765 我的环境配置&#xff1a; Python3.8.16 Wandb0.17.4 在使用Wandb记录实验数据时&#xff0c; 报以下错误&#xff1a; ServiceStartProcessError: The wandb service process exited with 1. Ensure that s…...

Qt调用Matlab(一)

目录 1 概述2 创建Qt工程2.1 增加Matlab支持3 调用Matlab3.1 widget.h3.2 widget.cpp4 运行4.1 配置4.2 运行1 概述 MATLAB是MathWorks公司出品的商业数学软件,用于数据分析、无线通信、深度学习、图像处理与计算机视觉、信号处理、量化金融与风险管理、机器人,控制系统等领域…...

网络爬虫(二) 哔哩哔哩热榜高频词按照图片形状排列

我们有时候需要爬取结果生成为自定义的词云图 生成自定义的词云图通常需要以下步骤&#xff1a; 1. 爬取数据&#xff1a;使用爬虫工具或库&#xff0c;如requests、BeautifulSoup等&#xff0c;可以爬取网页、论坛、社交媒体等平台上的文本数据。 2. 数据预处理&#xff1a…...

MySQL 常见错误及解决方案

1. Too many connections 运行环境&#xff1a;Winows11、Phpstudy V8.1.1.3、MySQL 5.7.26 同一时间 MySQL 的连接数量有限制&#xff0c;当超过上限时将提示下面错误信息&#xff1a; 1040 - Too many connections 查看当前最大连接数 mysql> show variables like %max_…...

STM32 - 内存分区与OTA

最近搞MCU&#xff0c;发现它与SOC之间存在诸多差异&#xff0c;不能沿用SOC上一些技术理论。本文以STM L4为例&#xff0c;总结了一些STM32 小白入门指南。 标题MCU没有DDR&#xff1f; 是的。MCU并没有DDR&#xff0c;而是让代码存储在nor flash上&#xff0c;临时变量和栈…...

RAG理论:ES混合搜索BM25+kNN(cosine)以及归一化

接前一篇:RAG实践:ES混合搜索BM25+kNN(cosine) https://blog.csdn.net/Xin_101/article/details/140230948 本文主要讲解混合搜索相关理论以及计算推导过程, 包括BM25、kNN以及ES中使用混合搜索分数计算过程。 详细讲解: (1)ES中如何通过BM25计算关键词搜索分数; (2)…...

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周&#xff0c;有很多同学在写期末Java web作业时&#xff0c;运行tomcat出现乱码问题&#xff0c;经过多次解决与研究&#xff0c;我做了如下整理&#xff1a; 原因&#xff1a; IDEA本身编码与tomcat的编码与Windows编码不同导致&#xff0c;Windows 系统控制台…...

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...

云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?

大家好&#xff0c;欢迎来到《云原生核心技术》系列的第七篇&#xff01; 在上一篇&#xff0c;我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在&#xff0c;我们就像一个拥有了一块崭新数字土地的农场主&#xff0c;是时…...

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:

一、属性动画概述NETX 作用&#xff1a;实现组件通用属性的渐变过渡效果&#xff0c;提升用户体验。支持属性&#xff1a;width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项&#xff1a; 布局类属性&#xff08;如宽高&#xff09;变化时&#…...

基于ASP.NET+ SQL Server实现(Web)医院信息管理系统

医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上&#xff0c;开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识&#xff0c;在 vs 2017 平台上&#xff0c;进行 ASP.NET 应用程序和简易网站的开发&#xff1b;初步熟悉开发一…...

【入坑系列】TiDB 强制索引在不同库下不生效问题

文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端

&#x1f31f; 什么是 MCP&#xff1f; 模型控制协议 (MCP) 是一种创新的协议&#xff0c;旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议&#xff0c;它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

LeetCode - 394. 字符串解码

题目 394. 字符串解码 - 力扣&#xff08;LeetCode&#xff09; 思路 使用两个栈&#xff1a;一个存储重复次数&#xff0c;一个存储字符串 遍历输入字符串&#xff1a; 数字处理&#xff1a;遇到数字时&#xff0c;累积计算重复次数左括号处理&#xff1a;保存当前状态&a…...

Opencv中的addweighted函数

一.addweighted函数作用 addweighted&#xff08;&#xff09;是OpenCV库中用于图像处理的函数&#xff0c;主要功能是将两个输入图像&#xff08;尺寸和类型相同&#xff09;按照指定的权重进行加权叠加&#xff08;图像融合&#xff09;&#xff0c;并添加一个标量值&#x…...

基于当前项目通过npm包形式暴露公共组件

1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹&#xff0c;并新增内容 3.创建package文件夹...