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

【Windows】在任务管理器中隐藏进程

在此前的一篇,我们已经介绍过了注入Dll 阻止任务管理器结束进程 -- Win 10/11。本篇利用 hook  NtQuerySystemInformation 并进行断链的方法实现进程隐身,实测支持 taskmgr.exe 的任意多进程隐身。

任务管理器

代码:

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <detours/detours.h>
#include <winternl.h>
#include <string>
#include <iostream>
#include <stdio.h>
#include <vector>
#include <shared_mutex>#pragma comment(lib, "detours.lib")
#pragma comment(lib, "user32.lib")typedef struct _VM_COUNTERS
{SIZE_T        PeakVirtualSize;SIZE_T        VirtualSize;ULONG         PageFaultCount;SIZE_T        PeakWorkingSetSize;SIZE_T        WorkingSetSize;SIZE_T        QuotaPeakPagedPoolUsage;SIZE_T        QuotaPagedPoolUsage;SIZE_T        QuotaPeakNonPagedPoolUsage;SIZE_T        QuotaNonPagedPoolUsage;SIZE_T        PagefileUsage;SIZE_T        PeakPagefileUsage;
} VM_COUNTERS;// 线程信息结构体
typedef struct _MY_SYSTEM_THREAD_INFORMATION
{LARGE_INTEGER   KernelTime;LARGE_INTEGER   UserTime;LARGE_INTEGER   CreateTime;ULONG           WaitTime;PVOID           StartAddress;CLIENT_ID       ClientId;KPRIORITY       Priority;KPRIORITY       BasePriority;ULONG           ContextSwitchCount;LONG            State;// 状态,是THREAD_STATE枚举类型中的一个值LONG            WaitReason;//等待原因, KWAIT_REASON中的一个值
} MY_SYSTEM_THREAD_INFORMATION, * PMY_SYSTEM_THREAD_INFORMATION;typedef struct _MY_UNICODE_STRING
{USHORT Length;USHORT MaximumLength;PWSTR  Buffer;
} MY_UNICODE_STRING, * PMY_UNICODE_STRING;typedef struct _MY_SYSTEM_PROCESS_INFORMATION
{ULONG            NextEntryOffset; // 指向下一个结构体的指针ULONG            ThreadCount; // 本进程的总线程数ULONG            Reserved1[6]; // 保留LARGE_INTEGER    CreateTime; // 进程的创建时间LARGE_INTEGER    UserTime; // 在用户层的使用时间LARGE_INTEGER    KernelTime; // 在内核层的使用时间MY_UNICODE_STRING   ImageName; // 进程名KPRIORITY        BasePriority; // ULONG            ProcessId; // 进程IDULONG            InheritedFromProcessId;ULONG            HandleCount; // 进程的句柄总数ULONG            Reserved2[2]; // 保留VM_COUNTERS      VmCounters;IO_COUNTERS      IoCounters;SYSTEM_THREAD_INFORMATION Threads[5]; // 子线程信息数组
}MY_SYSTEM_PROCESS_INFORMATION, * PMY_SYSTEM_PROCESS_INFORMATION;// 定义一个指针函数类型
typedef NTSTATUS(WINAPI* __NtQuerySystemInformation)(SYSTEM_INFORMATION_CLASS SystemInformationClass,PVOID SystemInformation,ULONG SystemInformationLength,PULONG ReturnLength);// 定义一个存放原函数的指针
PVOID fpNtQuerySystemInformation = NULL;
// 读写锁
std::shared_mutex ppNameListMutex;
// 受保护进程名列表
std::vector<std::wstring> ppNameList;// 声明函数
extern "C" {__declspec(dllexport)void StartHookingFunction();__declspec(dllexport) void UnmappHookedFunction();__declspec(dllexport) bool SetProtectedProcessListFromBuffer(const wchar_t* buffer, size_t length);
}NTSTATUS WINAPI HookedNtQuerySystemInformation(SYSTEM_INFORMATION_CLASS SystemInformationClass,PVOID SystemInformation,ULONG SystemInformationLength,PULONG ReturnLength
);void OpenDebugConsole()
{AllocConsole();FILE* fDummy;freopen_s(&fDummy, "CONOUT$", "w", stdout);freopen_s(&fDummy, "CONOUT$", "w", stderr);freopen_s(&fDummy, "CONIN$", "r", stdin);std::wcout << L"Debug console opened.\n";
}BOOL APIENTRY DllMain(HMODULE hModule,DWORD  ul_reason_for_call,LPVOID lpReserved)
{// 禁用 DLL 模块的通知DisableThreadLibraryCalls(hModule);switch (ul_reason_for_call){case DLL_PROCESS_ATTACH:{OpenDebugConsole();  // 打开控制台进行调试std::wcout << L"DLL injected, setting up hooks.\n";// 设置受保护进程列表,此函数也可以从远程进程注入线程来调用const WCHAR ppName[] = L"cmd.exe;conhost.exe";if (SetProtectedProcessListFromBuffer(ppName, wcslen(ppName) + 1)){std::wcout << L"Protected process list set successfully.\n";}else{std::wcout << L"Failed to set protected process list.\n";}// 启用 HOOKStartHookingFunction();std::wcout << L"Hooking started.\n";}break;case DLL_THREAD_ATTACH:case DLL_THREAD_DETACH:break;case DLL_PROCESS_DETACH:UnmappHookedFunction();std::wcout << L"Hooking detached.\n";break;}return TRUE;
}extern "C"
__declspec(dllexport)
void StartHookingFunction()
{//开始事务DetourTransactionBegin();//更新线程信息  DetourUpdateThread(GetCurrentThread());fpNtQuerySystemInformation =DetourFindFunction("ntdll.dll","NtQuerySystemInformation");//将拦截的函数附加到原函数的地址上,这里可以拦截多个函数。DetourAttach(&(PVOID&)fpNtQuerySystemInformation,HookedNtQuerySystemInformation);//结束事务DetourTransactionCommit();
}extern "C"
__declspec(dllexport)
void UnmappHookedFunction()
{//开始事务DetourTransactionBegin();//更新线程信息 DetourUpdateThread(GetCurrentThread());//将拦截的函数从原函数的地址上解除,这里可以解除多个函数。DetourDetach(&(PVOID&)fpNtQuerySystemInformation,HookedNtQuerySystemInformation);//结束事务DetourTransactionCommit();
}// 从缓冲区解析多个进程名的函数
extern "C"
__declspec(dllexport)
bool SetProtectedProcessListFromBuffer(const wchar_t* buffer, size_t length) {if (buffer == nullptr || length == 0) {return false;  // 返回错误状态}std::unique_lock lock(ppNameListMutex);  // 写锁ppNameList.clear();  // 清空原列表std::wstring tempName;for (size_t i = 0; i < length; ++i) {if (buffer[i] == L';' || i == length - 1) {// 遇到分号或者到达缓冲区末尾,表示一个进程名结束if (i == length - 1 && buffer[i] != L';' && buffer[i] != L'\0') {tempName += buffer[i];  // 处理最后一个字符不是分号的情况}if (!tempName.empty()) {std::wcout << L"Parsed process name: " << tempName << L"\n";  // 输出调试信息std::wcout << L"Length: " << tempName.size() << L"\n";ppNameList.push_back(tempName);  // 将进程名存入列表tempName.clear();  // 清空临时字符串以解析下一个进程名}}else {// 继续读取进程名字符tempName += buffer[i];}}std::wcout << L"Total protected processes: " << ppNameList.size() << L"\n";  // 输出调试信息return !ppNameList.empty();  // 返回成功标志,如果解析后列表为空则返回false
}// 检查进程是否在受保护列表中的函数(带读锁)
static bool IsProcessProtected(const std::wstring& processName) {std::vector<std::wstring> localPpNameList;{std::shared_lock lock(ppNameListMutex);localPpNameList = ppNameList;  // 将受保护列表复制到局部变量}// 在局部变量中进行比较return std::find(localPpNameList.begin(), localPpNameList.end(), processName) != localPpNameList.end();
}static bool IsHandleValidate(const LPVOID lpAddress)
{MEMORY_BASIC_INFORMATION Buffer{};VirtualQuery(lpAddress, &Buffer, 0x30u);//std::wcout << L"HandleValidate Buffer.Protect: " << Buffer.Protect << L"\n";return Buffer.State == MEM_COMMIT && Buffer.Protect != PAGE_NOACCESS;
}static BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) {DWORD processId;GetWindowThreadProcessId(hwnd, &processId);// 检查窗口是否属于当前进程if (processId == GetCurrentProcessId()) {// 检索指向窗口句柄向量的指针std::vector<HWND>* pWindowHandles = reinterpret_cast<std::vector<HWND>*>(lParam);// 检查指针是否有效if (pWindowHandles && IsHandleValidate(pWindowHandles)) {pWindowHandles->push_back(hwnd);}else {// (可选)记录错误或处理无效指针情况std::cerr << "Invalid pointer passed to EnumWindowsProc." << std::endl;}}return TRUE; // 继续枚举
}static void FlushProcessWindows() {std::vector<HWND> windowHandles;// 枚举所有顶级窗口,通过lParam将指针传递给EnumWindowsProcEnumWindows(EnumWindowsProc, reinterpret_cast<LPARAM>(&windowHandles));// 向每一个窗口发送 F5 来刷新窗口for (HWND hwnd : windowHandles) {// 模拟 F5PostMessage(hwnd, WM_KEYDOWN, VK_F5, 0);Sleep(10);PostMessage(hwnd, WM_KEYUP, VK_F5, 0);}
}// NtQuerySystemInformation的Hook函数,用于隐藏受保护的进程
NTSTATUS WINAPI HookedNtQuerySystemInformation(SYSTEM_INFORMATION_CLASS SystemInformationClass,PVOID SystemInformation,ULONG SystemInformationLength,PULONG ReturnLength
) {//std::wcout << L"NtQuerySystemInformation hook called.\n";static bool isNotFirstHook;const size_t nodeSize = sizeof(MY_SYSTEM_PROCESS_INFORMATION);// 先调用原始的 NtQuerySystemInformationNTSTATUS status = ((__NtQuerySystemInformation)fpNtQuerySystemInformation)(SystemInformationClass, SystemInformation, SystemInformationLength, ReturnLength);//std::wcout << L"Original NtQuerySystemInformation returned: " << status << L"\n";// 只处理 SystemProcessInformation 类型的信息if (SystemInformationClass == SystemProcessInformation && NT_SUCCESS(status)) {//std::wcout << L"Processing SystemProcessInformation.\n";PMY_SYSTEM_PROCESS_INFORMATION pCurrentNode = (PMY_SYSTEM_PROCESS_INFORMATION)SystemInformation;PMY_SYSTEM_PROCESS_INFORMATION pPreviousNode = nullptr;bool isFirstNode = true;while (pCurrentNode != nullptr && IsHandleValidate(pCurrentNode)) {if (pCurrentNode->NextEntryOffset == 0) {  // 到达末尾break;}if (pCurrentNode->ImageName.Buffer == nullptr || pCurrentNode->ImageName.Length == 0) {// 跳过无效的进程名//std::wcout << L"Skipping invalid process name.\n";pCurrentNode = (PMY_SYSTEM_PROCESS_INFORMATION)((PUCHAR)pCurrentNode + pCurrentNode->NextEntryOffset);continue;}// 获取当前进程名std::wstring processName(pCurrentNode->ImageName.Buffer, pCurrentNode->ImageName.Length / sizeof(WCHAR));//std::wcout << L"Processing process: " << processName << L"\n";// 检查该进程名是否在受保护列表中if (IsProcessProtected(processName)) {std::wcout << L"Process is protected: " << processName << L"\n";// 如果在受保护列表中,则将该进程从链表中移除if (pPreviousNode) {pPreviousNode->NextEntryOffset += pCurrentNode->NextEntryOffset;std::wcout << L"Process removed from list: " << processName << L"\n";}else if (isFirstNode) {  // 第一个节点是受保护进程,// 替换为下一个节点的数据if (pCurrentNode->NextEntryOffset == 0) {// 如果没有下一个节点,表示列表中只有一个受保护// 将进程信息列表清空memset(pCurrentNode, 0, SystemInformationLength);std::wcout << L"Only one protected process, clearing list.\n";break;}else {PMY_SYSTEM_PROCESS_INFORMATION pNextNode =(PMY_SYSTEM_PROCESS_INFORMATION)((PUCHAR)pCurrentNode + pCurrentNode->NextEntryOffset);if(!IsHandleValidate(pNextNode)) {std::wcout << L"HandleValidate failed.\n";break;}// 将下一个节点的数据拷贝到当前节点memcpy(pCurrentNode, pNextNode, sizeof(MY_SYSTEM_PROCESS_INFORMATION));pCurrentNode->NextEntryOffset = pNextNode->NextEntryOffset;std::wcout << L"First process was protected, replaced with next process.\n";continue;  // 保持 pPreviousNode 不变,重新检查当前节点}}}else {// 如果没有被保护,移动到下一个节点pPreviousNode = pCurrentNode;}// 如果下一个节点超出缓冲区范围,停止处理if (((PUCHAR)pCurrentNode + pCurrentNode->NextEntryOffset + nodeSize) >(PUCHAR)SystemInformation + SystemInformationLength) {std::wcout << L"Reached end of buffer.\n";break;}// 继续下一个进程信息节点pCurrentNode = (PMY_SYSTEM_PROCESS_INFORMATION)((PUCHAR)pCurrentNode + pCurrentNode->NextEntryOffset);}}// 只在第一次调用 hook 函数后强制刷新窗口if (!isNotFirstHook) {isNotFirstHook = true;FlushProcessWindows();}return status;
}

可以删除 dllmain 里面的hook 函数以及所有输出字符串。从外部进程通过注入远程线程的方式来实现动态调整隐身策略。主要利用下面三个函数:

导出函数

执行效果:

隐身效果(调试输出模式)

本文出处链接:[https://blog.csdn.net/qq_59075481/article/details/142676712]。

本文发布于:2024.10.02。

相关文章:

【Windows】在任务管理器中隐藏进程

在此前的一篇&#xff0c;我们已经介绍过了注入Dll 阻止任务管理器结束进程 -- Win 10/11。本篇利用 hook NtQuerySystemInformation 并进行断链的方法实现进程隐身&#xff0c;实测支持 taskmgr.exe 的任意多进程隐身。 任务管理器 代码&#xff1a; // dllmain.cpp : 定义 …...

【TypeScript学习】TypeScript基础学习总结二

主要记录ts中的类、接口与泛型 1.类 无论是在哪种语言中&#xff0c;类都是面向对象编程(OOP)的一个主要实现方式。能够实现代码更加灵活&#xff0c;更具有结构化。类作用都是提供一个模板&#xff0c;通过类可以创建多个具有相同结构的对象。 // 类的定义&#xff0c;与对象…...

中国电信解锁万亿参数大模型:TeleAI的创新与突破

首个由万卡集群训练出来的万亿参数大模型&#xff0c;已被一家央企解锁。 具体而言&#xff0c;为了推动纯国产人工智能的探索&#xff0c;带来这条新路径的正是中国电信人工智能研究院&#xff08;TeleAI&#xff09;。 该研究院由中国电信集团的CTO、首席科学家兼院长李学龙…...

戴尔PowerEdge R840服务器亮黄灯 不开机

最近接修到一台东莞用户的DELL PowerEdge R840 服务器因为意外断电后&#xff0c;无法正常开机的问题&#xff0c; 大概故障现象是 插上电源线 按卡机按钮无响应&#xff0c;无法开机&#xff0c;无显示输出&#xff0c;工程师到现场检修&#xff0c;经过idrac中日志分析&#…...

【前端安全】js逆向之微信公众号登录密码

❤️博客主页&#xff1a; iknow181 &#x1f525;系列专栏&#xff1a; 网络安全、 Python、JavaSE、JavaWeb、CCNP &#x1f389;欢迎大家点赞&#x1f44d;收藏⭐评论✍ 随着发展&#xff0c;越来越多的登录页面添加了密码加密的措施&#xff0c;使得暴力破解变得不在简单&a…...

C# 泛型使用案例_C# 泛型使用整理

一、系统自带常用的泛型 1.字典&#xff0c;集合 //字典 Dictionary<string, int> dic new Dictionary<string, int>(); //泛型集合 List<int> list new List<int>(); 2.泛型委托&#xff0c;输入参数&#xff0c;输出参数 //泛型 委托---输出参…...

Docker 安装 Citus 单节点集群:全面指南与详细操作

Docker 安装 Citus 单节点集群&#xff1a;全面指南与详细操作 文章目录 Docker 安装 Citus 单节点集群&#xff1a;全面指南与详细操作一 服务器资源二 部署图三 安装部署1 创建网络2 运行脚本1&#xff09;docker-compose.cituscd1.yml2&#xff09;docker-compose.cituswk1.…...

Arthas redefine(加载外部的.class文件,redefine到JVM里 )

文章目录 二、命令列表2.2 class/classloader相关命令2.2.3 redefine&#xff08;加载外部的.class文件&#xff0c;redefine到JVM里 &#xff09;举例1&#xff1a;加载新的代码&#xff0c;jad/mc 命令使用举例2&#xff1a;上传 .class 文件到服务器的技巧 本人其他相关文章…...

C++教程(三):c++常用的配置文件类型

目录 1. INI 文件 2. JSON 文件 3. YAML 文件 4. XML 文件 5. TOML 文件 6. 二进制配置文件&#xff08;Protocol Buffers, MessagePack, Avro 等&#xff09; 总结 在 C 项目中&#xff0c;常用的配置文件类型有多种选择&#xff0c;具体选择取决于项目的复杂性、可读性…...

Arduino Uno控制雨滴传感器模块的设计方案

以下是Arduino Uno控制雨滴传感器模块的设计方案&#xff1a; 一、硬件准备&#xff1a; 1. Arduino Uno 开发板一块。 2. 雨滴传感器模块一个。 3. 杜邦线若干。 4. 9V直流电源一个。 二、硬件连接&#xff1a; 1. 将Arduino Uno板的Vin引脚、GND引脚分别连接到9V直流电…...

华为常见命令手册

常见命令 display ip interface brief —> 查看设备上的每个接口的IP地址【地址/掩码/状态】 display interface gi0/0/0 → 查看该接口的MAC 地址 ipconfig → 查看 PC 上面的 IP地址 display arp → 查看设备的 ARP 表 arp -a → 查看 PC 上面的 ARP 表 display m…...

TinyAP:使用TinyML对抗Wi-Fi攻击的智能接入点

论文标题&#xff1a; 英文&#xff1a;TinyAP: An intelligent Access Point to combat Wi-Fi attacks using TinyML中文&#xff1a;TinyAP&#xff1a;使用TinyML对抗Wi-Fi攻击的智能接入点 作者信息&#xff1a; Anand Agrawal 和 Rajib Ranjan Maiti&#xff0c;来自印…...

grafana频繁DataSourceError问题

背景 随着 Grafana 数据量的不断增加&#xff0c;逐渐暴露出以下问题&#xff1a; Grafana 页面加载缓慢&#xff1b;Grafana 告警频繁出现 DatasourceError 错误。 对于第一个问题&#xff0c;大家可以参考这篇文章&#xff1a;Grafana 加载缓慢的解决方案。 不过&#xf…...

python-ds:Python 中的数据结构库(适用于面试的数据结构和算法合集)

在软件开发中&#xff0c;数据结构是组织和存储数据的方式&#xff0c;对算法的效率和程序的性能至关重要。Python 提供了许多内置的数据结构&#xff0c;但在一些复杂的应用场景中&#xff0c;原生数据结构可能无法满足特定需求。这时&#xff0c;一个功能强大、易于使用的数据…...

AccessoriesqueryController

目录 1、 AccessoriesqueryController 1.1、 库存配件查询 1.2、 查询仓库 1.2.1、 //把数据库数据加到表格&#xff1a;跟默认然一行数据进行合并 1.3、 /// 查询 1.3.1、 配件编码查询 1.3.2、 配件名称查询 1.3.3、 配件类型查询 AccessoriesqueryControlle…...

M3u8视频由手机拷贝到电脑之后,通过potplayer播放报错找不到文件地址怎么解决?

该文章前面三节主要介绍M3u8视频是什么&#xff0c;视频播放错误(找不到地址)的解决方法在后面 M3U8是一种多媒体播放列表文件格式&#xff0c;主要用于流媒体播放。 一、文件格式特点 1. 文本文件&#xff1a;M3U8是一个采用 UTF-8 编码的文本文件&#xff0c;这意味着它可…...

【分布式微服务云原生】windows+docker+mysql5.7.44一主一从主从复制

目录 1. 主库设置2. 从库设置3. 验证主从复制内容汇总表格 摘要&#xff1a; 在Windows系统上通过Docker部署MySQL主从复制&#xff0c;以下是详细的步骤和命令&#xff0c;帮助你设置一主一从的MySQL复制环境。 1. 主库设置 步骤1&#xff1a;运行MySQL主库容器 docker run …...

鸿蒙NEXT开发-界面渲染(条件和循环)(基于最新api12稳定版)

注意&#xff1a;博主有个鸿蒙专栏&#xff0c;里面从上到下有关于鸿蒙next的教学文档&#xff0c;大家感兴趣可以学习下 如果大家觉得博主文章写的好的话&#xff0c;可以点下关注&#xff0c;博主会一直更新鸿蒙next相关知识 专栏地址: https://blog.csdn.net/qq_56760790/…...

TypeScript 设计模式之【状态模式】

文章目录 状态模式&#xff1a;优雅切换的交通信号灯状态模式的奥秘状态模式有什么利与弊?如何使用状态模式来优化你的系统代码实现案例状态模式的主要优点状态模式的主要缺点状态模式的适用场景总结 状态模式&#xff1a;优雅切换的交通信号灯 当你站在繁忙的十字路口&#…...

MongoDB 聚合管道

参考: 聚合管道 - MongoDB 手册 v7.0 介绍 聚合管道由一个或多个处理文档的阶段组成&#xff1a; 每个阶段对输入文档执行一个操作。例如&#xff0c;某个阶段可以过滤文档、对文档进行分组并计算值。 从一个阶段输出的文档将传递到下一阶段。 一个聚合管道可以返回针对文档…...

【Python】 -- 趣味代码 - 小恐龙游戏

文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...

stm32G473的flash模式是单bank还是双bank?

今天突然有人stm32G473的flash模式是单bank还是双bank&#xff1f;由于时间太久&#xff0c;我真忘记了。搜搜发现&#xff0c;还真有人和我一样。见下面的链接&#xff1a;https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...

Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误

HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误&#xff0c;它们的含义、原因和解决方法都有显著区别。以下是详细对比&#xff1a; 1. HTTP 406 (Not Acceptable) 含义&#xff1a; 客户端请求的内容类型与服务器支持的内容类型不匹…...

全球首个30米分辨率湿地数据集(2000—2022)

数据简介 今天我们分享的数据是全球30米分辨率湿地数据集&#xff0c;包含8种湿地亚类&#xff0c;该数据以0.5X0.5的瓦片存储&#xff0c;我们整理了所有属于中国的瓦片名称与其对应省份&#xff0c;方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...

对WWDC 2025 Keynote 内容的预测

借助我们以往对苹果公司发展路径的深入研究经验&#xff0c;以及大语言模型的分析能力&#xff0c;我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际&#xff0c;我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测&#xff0c;聊作存档。等到明…...

linux 错误码总结

1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...

ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放

简介 前面两期文章我们介绍了I2S的读取和写入&#xff0c;一个是通过INMP441麦克风模块采集音频&#xff0c;一个是通过PCM5102A模块播放音频&#xff0c;那如果我们将两者结合起来&#xff0c;将麦克风采集到的音频通过PCM5102A播放&#xff0c;是不是就可以做一个扩音器了呢…...

Java入门学习详细版(一)

大家好&#xff0c;Java 学习是一个系统学习的过程&#xff0c;核心原则就是“理论 实践 坚持”&#xff0c;并且需循序渐进&#xff0c;不可过于着急&#xff0c;本篇文章推出的这份详细入门学习资料将带大家从零基础开始&#xff0c;逐步掌握 Java 的核心概念和编程技能。 …...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包

文章目录 现象&#xff1a;mysql已经安装&#xff0c;但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时&#xff0c;可能是因为以下几个原因&#xff1a;1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...

Map相关知识

数据结构 二叉树 二叉树&#xff0c;顾名思义&#xff0c;每个节点最多有两个“叉”&#xff0c;也就是两个子节点&#xff0c;分别是左子 节点和右子节点。不过&#xff0c;二叉树并不要求每个节点都有两个子节点&#xff0c;有的节点只 有左子节点&#xff0c;有的节点只有…...