亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

如何運用Capstone實現(xiàn)64位進(jìn)程鉤子掃描

 更新時間:2024年08月14日 10:49:31   作者:微軟技術(shù)分享  
本章將通過Capstone引擎實現(xiàn)64位進(jìn)程鉤子的掃描,讀者可使用此段代碼檢測目標(biāo)進(jìn)程內(nèi)是否被掛了鉤子,感興趣的朋友跟隨小編一起看看吧

進(jìn)程鉤子掃描是一種安全技術(shù)和分析方法,用于檢測和分析進(jìn)程內(nèi)的指令是否被篡改或注入了惡意功能。鉤子(Hook)技術(shù)允許開發(fā)人員在執(zhí)行特定系統(tǒng)調(diào)用或函數(shù)時插入自定義代碼。雖然進(jìn)程鉤子在調(diào)試和軟件功能擴展中發(fā)揮了重要作用,但該技術(shù)也可以被惡意軟件用來攔截和修改程序行為,從而隱藏其活動或進(jìn)行其他惡意操作。本章將通過Capstone引擎實現(xiàn)64位進(jìn)程鉤子的掃描,讀者可使用此段代碼檢測目標(biāo)進(jìn)程內(nèi)是否被掛了鉤子。

通過進(jìn)程鉤子掃描,安全研究人員和開發(fā)人員可以檢測進(jìn)程中是否存在未授權(quán)的鉤子,并分析這些鉤子的行為。這有助于識別和防止惡意軟件的活動,確保系統(tǒng)和應(yīng)用程序的完整性和安全性。

在編寫代碼之前,讀者需要自行下載并配置Capstone反匯編引擎,配置參數(shù)如下所示;

在之前的PeView命令行解析工具中筆者介紹了如何掃描32位進(jìn)程內(nèi)的鉤子,由于32位進(jìn)程需要重定位所以在掃描時需要考慮到對內(nèi)存地址的修正,而64位進(jìn)程則無需考慮重定位的問題,其鉤子掃描原理與32位保持一致,均通過將磁盤和內(nèi)存中的代碼段進(jìn)行反匯編,并逐條比較它們的機器碼和反匯編結(jié)果。如果存在差異,則表示該代碼段在內(nèi)存中被篡改或掛鉤。

定義頭文件

首先引入capstone.h頭文件,并引用capstone64.lib靜態(tài)庫,通過定義PeTextInfo來存儲每個PE文件中節(jié)的文件偏移及大小信息,通過ModuleInfo用于存放進(jìn)程內(nèi)的模塊信息,而DisassemblyInfo則用來存放反匯編信息,底部則定義PE結(jié)構(gòu)的全局變量用于存儲頭指針。

#include <windows.h>
#include <TlHelp32.h>
#include <tchar.h>
#include <iostream>
#include <atlconv.h>
#include <vector>
#include <inttypes.h>
#include <capstone/capstone.h>
#pragma comment(lib,"capstone64.lib")
using namespace std;
// 存放PE信息段
struct PeTextInfo
{
	DWORD64 virtualAddress;    // 節(jié)區(qū)在內(nèi)存的偏移
	DWORD64 pointerToRawData;  // 節(jié)區(qū)在文件中的偏移
	DWORD64 size;              // 大小
};
// 存放進(jìn)程內(nèi)所有模塊信息
typedef struct
{
	char modulePath[256];      // 模塊路徑
	char moduleName[128];      // 模塊名
	long long moduleBase;      // 模塊基址
}ModuleInfo;
// 存放反匯編數(shù)據(jù)
typedef struct
{
	int opCodeSize;            // 機器碼長度
	int opStringSize;          // 反匯編長度
	unsigned long long address;// 相對地址
	unsigned char opCode[16];  // 機器碼
	char opString[256];        // 反匯編
}DisassemblyInfo;
// 全局PE結(jié)構(gòu)
IMAGE_DOS_HEADER* dosHeader;              // DOS頭
IMAGE_NT_HEADERS* ntHeader;               // NT頭
IMAGE_FILE_HEADER* fileHeader;            // 標(biāo)準(zhǔn)PE頭
IMAGE_OPTIONAL_HEADER64* optionalHeader;  // 可選PE頭
IMAGE_SECTION_HEADER* sectionHeader;      // 節(jié)表

進(jìn)程與線程

在進(jìn)程與線程處理模塊中,我們定義了三個函數(shù):GetProcessHandleByName、GetProcessIDByNameGetModuleInfoByProcessNameGetProcessHandleByName函數(shù)接收一個進(jìn)程名并返回該進(jìn)程的句柄,方便后續(xù)的進(jìn)程操作;GetProcessIDByName函數(shù)通過進(jìn)程名獲取其對應(yīng)的PID(進(jìn)程標(biāo)識符),用于標(biāo)識特定進(jìn)程;GetModuleInfoByProcessName函數(shù)接收一個進(jìn)程名并返回該進(jìn)程內(nèi)所有模塊的信息,包括模塊路徑、模塊名和模塊基址,便于對進(jìn)程內(nèi)的模塊進(jìn)行分析和處理。

// -----------------------------------------------------------------------------------
// 進(jìn)程線程部分
// -----------------------------------------------------------------------------------
// 通過進(jìn)程名獲取進(jìn)程句柄
// 參數(shù): 
//   processName - 進(jìn)程名
// 返回值:
//   進(jìn)程句柄
HANDLE GetProcessHandleByName(PCHAR processName)
{
	// 初始化進(jìn)程快照
	PROCESSENTRY32 processEntry;
	processEntry.dwSize = sizeof(PROCESSENTRY32);
	// 獲得快照句柄
	HANDLE processSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	// 獲取第一個進(jìn)程
	Process32First(processSnap, &processEntry);
	do
	{
		USES_CONVERSION;
		if (strcmp(processName, W2A(processEntry.szExeFile)) == 0)
		{
			// 關(guān)閉快照句柄,避免內(nèi)存泄漏
			CloseHandle(processSnap);
			// 返回句柄
			return OpenProcess(PROCESS_ALL_ACCESS, FALSE, processEntry.th32ProcessID);
		}
	} while (Process32Next(processSnap, &processEntry));
	// 關(guān)閉快照句柄,避免內(nèi)存泄漏
	CloseHandle(processSnap);
	return (HANDLE)NULL;
}
// 根據(jù)進(jìn)程名獲取PID
// 參數(shù): 
//   processName - 進(jìn)程名
// 返回值:
//   進(jìn)程ID
DWORD64 GetProcessIDByName(LPCTSTR processName)
{
	DWORD64 processID = 0xFFFFFFFF;
	HANDLE snapshot = INVALID_HANDLE_VALUE;
	PROCESSENTRY32 processEntry;
	processEntry.dwSize = sizeof(PROCESSENTRY32);
	snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, NULL);
	Process32First(snapshot, &processEntry);
	do
	{
		if (!_tcsicmp(processName, (LPCTSTR)processEntry.szExeFile))
		{
			processID = processEntry.th32ProcessID;
			break;
		}
	} while (Process32Next(snapshot, &processEntry));
	CloseHandle(snapshot);
	return processID;
}
// 獲取進(jìn)程內(nèi)所有模塊信息
// 參數(shù): 
//   processName - 進(jìn)程名
// 返回值:
//   包含模塊信息的向量
std::vector<ModuleInfo> GetModuleInfoByProcessName(CHAR* processName)
{
	// 讀取進(jìn)程中的模塊信息
	MODULEENTRY32 moduleEntry;
	USES_CONVERSION;
	DWORD64 processID = GetProcessIDByName(A2W(processName));
	// 存放模塊路徑
	std::vector<ModuleInfo> moduleInfos = {};
	// 在使用這個結(jié)構(gòu)前,先設(shè)置它的大小
	moduleEntry.dwSize = sizeof(MODULEENTRY32);
	// 獲取模塊快照
	HANDLE moduleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, processID);
	// INVALID_HANDLE_VALUE表示無效的句柄
	if (moduleSnap == INVALID_HANDLE_VALUE)
	{
		return{};
	}
	BOOL hasMoreModules = Module32First(moduleSnap, &moduleEntry);   // 獲取第一個模塊信息
	char* modulePath = NULL;                                         // 模塊路徑
	char* moduleName = NULL;                                         // 模塊名
	DWORD64 moduleBase = NULL;                                       // 模塊基址
	while (hasMoreModules)
	{
		ModuleInfo moduleInfo;
		USES_CONVERSION;
		// W2A 將wchar轉(zhuǎn)ascii
		modulePath = W2A(moduleEntry.szExePath);
		moduleBase = (DWORD64)moduleEntry.modBaseAddr;
		moduleName = W2A(moduleEntry.szModule);
		// printf("模塊路徑: %s -> 模塊基地址: %x -> 模塊名: %s \n", ModulePath, ModuleBase, ModuleName);
		strcpy_s(moduleInfo.modulePath, modulePath);
		strcpy_s(moduleInfo.moduleName, moduleName);
		moduleInfo.moduleBase = moduleBase;
		// 放入容器內(nèi)
		moduleInfos.push_back(moduleInfo);
		hasMoreModules = Module32Next(moduleSnap, &moduleEntry);
	}
	CloseHandle(moduleSnap);
	return moduleInfos;
}

PE文件操作

如下代碼實現(xiàn)了PE(Portable Executable)文件的讀取、解析和擴展功能。我們定義了三個主要函數(shù):ReadPEFile用于從磁盤讀取PE文件數(shù)據(jù),ParsePEHeaders用于解析PE文件的頭信息,ExpandPEImageBuffer用于將PE文件擴展為內(nèi)存中加載后的形式,并復(fù)制文件中的各個節(jié)(section)到內(nèi)存中。最后,GetCodeSectionInfo函數(shù)獲取了PE文件中代碼段的起始地址和大小信息。

// -----------------------------------------------------------------------------------
// PE文件讀寫部分
// -----------------------------------------------------------------------------------
// 讀取硬盤PE文件數(shù)據(jù)
// 參數(shù): 
//   filePath - 文件路徑
//   fileBuffer - 文件緩沖區(qū)指針
// 返回值:
//   文件大小
DWORD64 ReadPEFile(LPSTR filePath, LPVOID* fileBuffer)
{
	FILE* file = NULL;
	fopen_s(&file, filePath, "rb");
	if (file == NULL)
	{
		return 0;
	}
	else
	{
		// 計算文件大小
		fseek(file, 0, SEEK_END);
		long long fileSize = ftell(file);
		fseek(file, 0, SEEK_SET);
		// 開辟指定大小的內(nèi)存
		LPVOID buffer = malloc(sizeof(char) * fileSize);
		if (buffer == NULL)
		{
			fclose(file);
			return 0;
		}
		// 將文件數(shù)據(jù)拷貝到緩沖區(qū)
		size_t bytesRead = fread(buffer, sizeof(char), fileSize, file);
		if (!bytesRead)
		{
			free(buffer);
			fclose(file);
			return 0;
		}
		*fileBuffer = buffer;
		buffer = NULL;
		fclose(file);
		return fileSize;
	}
	return 0;
}
// 讀取PE頭信息
// 參數(shù): 
//   fileBuffer - 文件緩沖區(qū)指針
// 返回值:
//   成功返回1,失敗返回0
DWORD64 ParsePEHeaders(LPVOID fileBuffer)
{
	if (fileBuffer == NULL)
	{
		// 緩沖區(qū)指針無效
		return 0;
	}
	// 判斷是否是有效的MZ標(biāo)記
	if (*((PWORD)fileBuffer) != IMAGE_DOS_SIGNATURE)
	{
		return 0;
	}
	dosHeader = (IMAGE_DOS_HEADER*)fileBuffer;
	// 判斷是否是有效的pe標(biāo)志
	if (*((PDWORD)((DWORD64)fileBuffer + dosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE)
	{
		return 0;
	}
	ntHeader = (IMAGE_NT_HEADERS*)((DWORD64)fileBuffer + dosHeader->e_lfanew);                                       // NT頭賦值
	fileHeader = (IMAGE_FILE_HEADER*)((DWORD64)ntHeader + 4);                                                        // 標(biāo)準(zhǔn)PE頭賦值
	optionalHeader = (IMAGE_OPTIONAL_HEADER64*)((DWORD64)fileHeader + IMAGE_SIZEOF_FILE_HEADER);                     // 可選PE頭賦值,標(biāo)準(zhǔn)PE頭地址+標(biāo)準(zhǔn)PE頭大小
	sectionHeader = (IMAGE_SECTION_HEADER*)((DWORD64)optionalHeader + fileHeader->SizeOfOptionalHeader);             // 第一個節(jié)表 可選PE頭地址+可選PE頭大小
	return 1;
}
// 拉伸PE結(jié)構(gòu)
// 參數(shù): 
//   fileBuffer - 硬盤狀態(tài)的PE數(shù)據(jù)指針
//   imageBuffer - 用來存放拉伸后的PE數(shù)據(jù)的指針
// 返回值:
//   PE鏡像大小
DWORD64 ExpandPEImageBuffer(LPVOID fileBuffer, LPVOID* imageBuffer)
{
	if (fileBuffer == NULL)
	{
		return 0;
	}
	// 申請ImageBuffer所需的內(nèi)存空間
	LPVOID buffer = malloc(sizeof(char) * optionalHeader->SizeOfImage);
	if (buffer == NULL)
	{
		return 0;
	}
	memset(buffer, 0, optionalHeader->SizeOfImage);              // 將空間初始化為0
	memcpy(buffer, fileBuffer, optionalHeader->SizeOfHeaders);  // 把頭+節(jié)表+對齊的內(nèi)存復(fù)制過去
	// 復(fù)制節(jié)
	for (int i = 0; i < fileHeader->NumberOfSections; i++)
	{
		buffer = (LPVOID)((DWORD64)buffer + (sectionHeader + i)->VirtualAddress);                     // 定位這個節(jié)內(nèi)存中的偏移
		fileBuffer = (LPVOID)((DWORD64)fileBuffer + (sectionHeader + i)->PointerToRawData);           // 定位這個節(jié)在文件中的偏移
		memcpy(buffer, fileBuffer, (sectionHeader + i)->SizeOfRawData);                               // 復(fù)制節(jié)在文件中所占的內(nèi)存過去
		buffer = (LPVOID)((DWORD64)buffer - (sectionHeader + i)->VirtualAddress);                     // 恢復(fù)到起始位置
		fileBuffer = (LPVOID)((DWORD64)fileBuffer - (sectionHeader + i)->PointerToRawData);           // 恢復(fù)到起始位置
	}
	*imageBuffer = buffer;
	buffer = NULL;
	return optionalHeader->SizeOfImage;
}
// 獲取本程序代碼段在內(nèi)存中的起始地址和大小
// 參數(shù): 
//   textInfo - 存放代碼段信息的結(jié)構(gòu)體指針
// 返回值:
//   代碼段的數(shù)量
DWORD64 GetCodeSectionInfo(PeTextInfo* textInfo)
{
	int length = 0;
	for (int i = 0; i < fileHeader->NumberOfSections; i++)
	{
		// 判斷是否是可執(zhí)行的代碼
		if (((sectionHeader + i)->Characteristics & 0x20000000) == 0x20000000)
		{
			(textInfo + length)->virtualAddress = (sectionHeader + i)->VirtualAddress;
			(textInfo + length)->pointerToRawData = (sectionHeader + i)->PointerToRawData;
			(textInfo + length)->size = (sectionHeader + i)->SizeOfRawData;
			length++;
		}
	}
	return length;
}

反匯編與掃描

反匯編部分通過定義DisassembleCode函數(shù),該函數(shù)接收一個起始地址及代碼長度,當(dāng)執(zhí)行結(jié)束后會將反匯編結(jié)果放入到DisassemblyInfo容器內(nèi)返回給用戶,具體的反匯編實現(xiàn)細(xì)節(jié)可自行參考代碼學(xué)習(xí)。

// -----------------------------------------------------------------------------------
// 反匯編部分
// -----------------------------------------------------------------------------------
// 反匯編字符串
// 參數(shù): 
//   startOffset - 起始地址
//   size - 代碼大小
// 返回值:
//   包含反匯編信息的向量
std::vector<DisassemblyInfo> DisassembleCode(unsigned char *startOffset, int size)
{
	std::vector<DisassemblyInfo> disassemblyInfos = {};
	csh handle;
	cs_insn *insn;
	size_t count;
	// 打開句柄
	if (cs_open(CS_ARCH_X86, CS_MODE_64, &handle) != CS_ERR_OK)
	{
		return{};
	}
	// 反匯編代碼,地址從0x0開始,返回總條數(shù)
	count = cs_disasm(handle, (unsigned char *)startOffset, size, 0x0, 0, &insn);
	if (count > 0)
	{
		DWORD index;
		// 循環(huán)反匯編代碼
		for (index = 0; index < count; index++)
		{
			// 清空
			DisassemblyInfo disasmInfo;
			memset(&disasmInfo, 0, sizeof(DisassemblyInfo));
			// 循環(huán)拷貝機器碼
			for (int x = 0; x < insn[index].size; x++)
			{
				disasmInfo.opCode[x] = insn[index].bytes[x];
			}
			// 拷貝地址長度
			disasmInfo.address = insn[index].address;
			disasmInfo.opCodeSize = insn[index].size;
			// 拷貝反匯編指令
			strcpy_s(disasmInfo.opString, insn[index].mnemonic);
			strcat_s(disasmInfo.opString, " ");
			strcat_s(disasmInfo.opString, insn[index].op_str);
			// 得到反匯編長度
			disasmInfo.opStringSize = (int)strlen(disasmInfo.opString);
			disassemblyInfos.push_back(disasmInfo);
		}
		cs_free(insn, count);
	}
	else
	{
		return{};
	}
	cs_close(&handle);
	return disassemblyInfos;
}

最后我們在主函數(shù)中來實現(xiàn)反匯編比對邏輯,首先我們分別指定一個磁盤文件路徑并將其放入到fullPath變量內(nèi),然后通過GetModuleInfoByProcessName得到進(jìn)程內(nèi)的所有加載模塊信息,并對比進(jìn)程內(nèi)模塊是否為Win32Project.exe也就是進(jìn)程自身,當(dāng)然此處也可被替換為例如user32.dll等模塊,當(dāng)磁盤與內(nèi)存被讀入后,通過ParsePEHeaders解析PE頭信息,并將PE文件通過ExpandPEImageBuffer拉伸到內(nèi)存中模擬加載后的狀態(tài)。

隨后,通過GetCodeSectionInfo獲取代碼節(jié)的地址和大小,將磁盤和內(nèi)存中的代碼段數(shù)據(jù)分別讀取到緩沖區(qū)中。最后,通過Capstone反匯編庫對磁盤和內(nèi)存中的代碼段進(jìn)行反匯編,并逐條memcmp對比反匯編指令,以檢測代碼是否被篡改。整個過程包括文件讀取、內(nèi)存解析、反匯編和數(shù)據(jù)對比,最后輸出檢測結(jié)果并釋放分配的內(nèi)存資源。

int main(int argc, char *argv[])
{
	DWORD64 fileSize = 0;
	LPVOID fileBuffer = NULL;
	// 從完整路徑中獲取文件名
	CHAR fullPath[256] = { 0 };
	CHAR fileName[64] = { 0 }, *p = NULL;
	strcpy_s(fullPath, "d:\\Win32Project.exe");
	strcpy_s(fileName, (p = strrchr(fullPath, '\\')) ? p + 1 : fullPath);
	// 打開進(jìn)程
	HANDLE processHandle = GetProcessHandleByName(fileName);
	// 循環(huán)輸出所有模塊信息
	std::vector<ModuleInfo> moduleInfos = GetModuleInfoByProcessName(fileName);
	for (int i = 0; i < moduleInfos.size(); i++)
	{
		if (strcmp(moduleInfos[i].moduleName, "Win32Project.exe") == 0)
		{
			printf("[*] 模塊基地址: 0x%I64X | 模塊路徑: %s \n", moduleInfos[i].moduleBase, moduleInfos[i].modulePath);
			// 讀取磁盤PE文件
			fileSize = ReadPEFile(moduleInfos[i].modulePath, &fileBuffer);
			// 解析PE頭
			DWORD64 ref = ParsePEHeaders(fileBuffer);
			// 拉伸PE
			LPVOID imageBuffer = NULL;
			DWORD64 sizeOfImage = ExpandPEImageBuffer(fileBuffer, &imageBuffer);
			// 獲取.text節(jié)地址
			PeTextInfo textInfo;
			DWORD64 textSectionCount = GetCodeSectionInfo(&textInfo);
			// 讀入磁盤數(shù)據(jù)
			unsigned char *fileTextBuffer = NULL;
			fileTextBuffer = (unsigned char *)malloc((textInfo.size));
			memcpy(fileTextBuffer, (unsigned char *)((DWORD64)imageBuffer + textInfo.virtualAddress), textInfo.size);
			// 讀入內(nèi)存數(shù)據(jù)
			unsigned char *memoryTextBuffer = NULL;
			DWORD64 protectTemp = NULL;
			DWORD64 moduleBase = moduleInfos[i].moduleBase;
			memoryTextBuffer = (unsigned char *)malloc(textInfo.size);
			for (int j = 0; j < textInfo.size; j++)
			{
				ReadProcessMemory(processHandle, (LPVOID)(moduleBase + textInfo.virtualAddress), memoryTextBuffer, sizeof(char) * textInfo.size, NULL);
			}
			// 開始反匯編
			std::vector<DisassemblyInfo> fileDisassembly = DisassembleCode(fileTextBuffer, textInfo.size);
			std::vector<DisassemblyInfo> memoryDisassembly = DisassembleCode(memoryTextBuffer, textInfo.size);
			for (int k = 0; k < fileDisassembly.size(); k++)
			{
				printf("0x%I64X | ", moduleBase + memoryDisassembly[k].address);
				printf("文件匯編: %-45s | ", fileDisassembly[k].opString);
				printf("內(nèi)存匯編: %-45s | ", memoryDisassembly[k].opString);
				// 開始對比
				if (memcmp(fileDisassembly[k].opCode, memoryDisassembly[k].opCode, fileDisassembly[k].opCodeSize) != 0)
				{
					// 被掛鉤
					printf("文件=> ");
					for (int l = 0; l < fileDisassembly[k].opCodeSize; l++)
					{
						printf("0x%02X ", fileDisassembly[k].opCode[l]);
					}
					printf(" 內(nèi)存=> ");
					for (int m = 0; m < memoryDisassembly[k].opCodeSize; m++)
					{
						printf("0x%02X ", memoryDisassembly[k].opCode[m]);
					}
				}
				printf("\n");
			}
			// 釋放
			imageBuffer = NULL;
			free(fileBuffer);
			free(fileTextBuffer);
			free(memoryTextBuffer);
		}
	}
	system("pause");
	return 0;
}

為了測試掃描效果,我們可以啟動一個64位應(yīng)用程序,此處為Win32Project.exe進(jìn)程,通過x64dbg附加,并跳轉(zhuǎn)到Win32Project.exe的程序領(lǐng)空,如下圖所示;

此時我們隨意找一處位置,這里就選擇00007FF6973110E6處,并將其原始代碼由int3修改為nop長度為6字節(jié),如下圖所示;

至此,我們編譯并運行lyshark.exe程序,此時則可輸出Win32Project.exe進(jìn)程中的第一個模塊也就是Win32project.exe的掛鉤情況,輸出效果如下圖所示;

到此這篇關(guān)于運用Capstone實現(xiàn)64位進(jìn)程鉤子掃描的文章就介紹到這了,更多相關(guān)Capstone 64位鉤子掃描內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

您可能感興趣的文章:

相關(guān)文章

  • C語言 字符串首字母轉(zhuǎn)換成大寫簡單實例

    C語言 字符串首字母轉(zhuǎn)換成大寫簡單實例

    這篇文章主要介紹了C語言 字符串首字母轉(zhuǎn)換成大寫簡單實例的相關(guān)資料,需要的朋友可以參考下
    2017-05-05
  • C++模擬實現(xiàn)list功能

    C++模擬實現(xiàn)list功能

    list的底層是一個循環(huán)雙向鏈表結(jié)構(gòu),雙向鏈表中每個元素存儲在互不相關(guān)的獨立節(jié)點中,在節(jié)點中通過指針指向其前一個元素和后一個元素,接下來通過本文給大家分享C++模擬實現(xiàn)list的示例代碼,需要的朋友可以參考下
    2021-08-08
  • C++ 實現(xiàn)L2-002 鏈表去重

    C++ 實現(xiàn)L2-002 鏈表去重

    這篇文章主要介紹了C++ 實現(xiàn)L2-002 鏈表去重,本文通過簡要的案例,解題思路講解了該項技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-07-07
  • 淺談關(guān)于C語言中#define的副作用

    淺談關(guān)于C語言中#define的副作用

    這篇文章主要介紹了關(guān)于C語言中#define的副作用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-03-03
  • C語言實現(xiàn)學(xué)生消費管理系統(tǒng)

    C語言實現(xiàn)學(xué)生消費管理系統(tǒng)

    這篇文章主要為大家詳細(xì)介紹了C語言實現(xiàn)學(xué)生消費管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-08-08
  • C語言中字符串處理函數(shù)sscanf的用法

    C語言中字符串處理函數(shù)sscanf的用法

    一直對于一些日期字符串中數(shù)字的提取比較頭疼,現(xiàn)看到 sscanf 對于字符串中的內(nèi)容提取較方便,本文主要介紹了C語言中字符串處理函數(shù)sscanf的用法,具有一定參考價值,感興趣的可以了解一下
    2023-08-08
  • 深入了解C++函數(shù)重載解析策略

    深入了解C++函數(shù)重載解析策略

    這篇文章主要為大家詳細(xì)介紹了C++中函數(shù)重載的解析策略,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)C++有一定幫助,感興趣的小伙伴可以了解一下
    2022-10-10
  • C++使用模板實現(xiàn)單鏈表(類外實現(xiàn))

    C++使用模板實現(xiàn)單鏈表(類外實現(xiàn))

    這篇文章主要為大家詳細(xì)介紹了C++使用模板實現(xiàn)單鏈表的相關(guān)資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-12-12
  • C++實現(xiàn)簡易的五子棋游戲

    C++實現(xiàn)簡易的五子棋游戲

    這篇文章主要為大家詳細(xì)介紹了C++實現(xiàn)簡易的五子棋游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-05-05
  • C語言動態(tài)內(nèi)存管理的實現(xiàn)示例

    C語言動態(tài)內(nèi)存管理的實現(xiàn)示例

    動態(tài)內(nèi)存管理是一種允許程序在運行時根據(jù)需要動態(tài)申請和回收內(nèi)存的策略,它提供了四種重要的函數(shù),本文就來介紹一下,感興趣的可以了解一下
    2024-11-11

最新評論