如何運用Capstone實現(xiàn)64位進(jìn)程鉤子掃描
進(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
、GetProcessIDByName
和GetModuleInfoByProcessName
。GetProcessHandleByName
函數(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語言實現(xiàn)學(xué)生消費管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C語言實現(xiàn)學(xué)生消費管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-08-08C++使用模板實現(xiàn)單鏈表(類外實現(xiàn))
這篇文章主要為大家詳細(xì)介紹了C++使用模板實現(xiàn)單鏈表的相關(guān)資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-12-12C語言動態(tài)內(nèi)存管理的實現(xiàn)示例
動態(tài)內(nèi)存管理是一種允許程序在運行時根據(jù)需要動態(tài)申請和回收內(nèi)存的策略,它提供了四種重要的函數(shù),本文就來介紹一下,感興趣的可以了解一下2024-11-11