動(dòng)實(shí)例分析系列: HidHide驅(qū)動(dòng)分析-HidHideCLI 篇(四))
HidHide 驅(qū)動(dòng)分析 - HidHideCLI 篇四日志系統(tǒng)與異常處理一、日志系統(tǒng)的分層架構(gòu)HidHideCLI 的日志系統(tǒng)與內(nèi)核驅(qū)動(dòng)共享相同的 ETW 提供者結(jié)構(gòu)但在用戶態(tài)實(shí)現(xiàn)了獨(dú)立的日志后端。日志系統(tǒng)分為三個(gè)層次1.1 宏定義層Logging.h// 三種追蹤級(jí)別#defineTRACE_DETAILED(message){TraceEvent(...,EtwEventTraceDetailed,message,);...}#defineTRACE_PERFORMANCE(message){TraceEvent(...,EtwEventTracePerformance,message,);...}#defineTRACE_ALWAYS(message){TraceEvent(...,EtwEventTraceAlways,message,);...}// 日志事件宏簡(jiǎn)化參數(shù)傳遞#defineETW(eventDescriptor)__FILE__[ProjectDirLength],__LINE__,__FUNCTION__,EtwEventLog##eventDescriptor// 日志與返回宏#defineLOG_AND_RETURN_NTSTATUS(message,result){LogEvent(ETW(Exception),L%s reports NT status 0x%08X,message,result);return(result);}ProjectDirLength是一個(gè)編譯期定義的常量用于裁剪__FILE__中的絕對(duì)路徑前綴使日志輸出更簡(jiǎn)潔。1.2 核心日志函數(shù)Logging.cppLogEvent格式化并寫入事件日志 追蹤日志NTSTATUSLogEvent(LPCSTR fileName,UINT32 lineNumber,LPCSTR functionName,PCEVENT_DESCRIPTOR event,LPCWSTR format,...){va_list args;va_start(args,format);std::vectorWCHARmessageW(LOGGING_MESSAGE_MAXIMUM_SIZE);std::vswprintf(messageW.data(),messageW.size(),format,args);UpdateEventLog(false,fileName,lineNumber,functionName,event,messageW.data(),);returnSTATUS_SUCCESS;}UpdateEventLog函數(shù)封裝了EventWriteTransfer調(diào)用將日志事件同時(shí)寫入日志提供者和追蹤提供者。TraceEvent僅寫入追蹤日志不寫入事件日志用于高性能場(chǎng)景NTSTATUSTraceEvent(LPCSTR fileName,UINT32 lineNumber,LPCSTR functionName,PCEVENT_DESCRIPTOR event,LPCWSTR messageW,LPCSTR messageA){UpdateEventLog(true,fileName,lineNumber,functionName,event,messageW,messageA);returnSTATUS_SUCCESS;}DbgPrintEx 模擬在用戶態(tài)模擬內(nèi)核態(tài)DbgPrintEx行為輸出到調(diào)試器NTSTATUSDbgPrintEx(ULONG componentId,ULONG level,LPCSTR format,...){va_list args;va_start(args,format);std::vectorCHARbuffer(LOGGING_MESSAGE_MAXIMUM_SIZE);std::vsnprintf(buffer.data(),buffer.size(),format,args);OutputDebugStringA(buffer.data());returnSTATUS_SUCCESS;}1.3 ETW 提供者注冊(cè)/注銷NTSTATUS WINAPILogRegisterProviders()noexcept{EventRegisterNefarius_Hid_Hide_CLI();EventRegisterNefarius_Drivers_HidHideCLI();LogEvent(ETW(Started),L%s,_L(BldProductVersion));returnSTATUS_SUCCESS;}兩個(gè)提供者分別對(duì)應(yīng)日志Logging和追蹤Tracing方便管理員根據(jù)需要啟用不同級(jí)別的診斷信息。二、異常處理框架2.1 異常編碼與傳遞LogException 類LogException是一個(gè)輔助類用于將不同來(lái)源的錯(cuò)誤碼CONFIGRET、HRESULT、NTSTATUS、WIN32統(tǒng)一編碼為字符串便于日志記錄和異常傳遞。編碼函數(shù)staticstd::stringEncodeWIN32(LPCSTR fileName,UINT32 lineNumber,LPCSTR functionName,PCEVENT_DESCRIPTOR event,DWORD result)noexcept{std::ostringstream os;FormatMessageA(...);// 獲取錯(cuò)誤描述osPrefixWIN32 0xstd::hexstd::setw(8)result at fileName(lineNumber) functionName: buffer.data();returnos.str();}輸出格式示例Error code 0x00000002 at HID.cpp(123) HidModelInfo: The system cannot find the file specified.解碼與轉(zhuǎn)換CONFIGRETLogException::ToCONFIGRET()constnoexcept{autoconstdecodedDecodeExceptionData();switch(decoded.encoding){caseEncoding::configret:returndecoded.configret;caseEncoding::hresult:returnSUCCEEDED(decoded.hresult)?CR_SUCCESS:CR_FAILURE;// ...}}支持在異常處理中將錯(cuò)誤碼靈活轉(zhuǎn)換為所需類型。2.2 異常拋出宏#defineTHROW_WIN32(result){throwstd::runtime_error(LogException::EncodeWIN32(ETW(Exception),result));}#defineTHROW_WIN32_LAST_ERROR{throwstd::runtime_error(LogException::EncodeWIN32(ETW(Exception),GetLastError()));}#defineTHROW_CONFIGRET(result){throwstd::runtime_error(LogException::EncodeCONFIGRET(ETW(Exception),result));}所有 Windows API 調(diào)用失敗時(shí)通過(guò)宏拋出包含文件名、行號(hào)和錯(cuò)誤描述的std::runtime_error。2.3 異常捕獲與日志記錄宏#defineLOGEXC_AND_RETURN_WIN32{\LogException(LogException::ExceptionMessage()).Log(ETW(Exception)).ToWIN32();\}在catch塊中使用將異常信息記錄到 ETW然后返回對(duì)應(yīng)的 WIN32 錯(cuò)誤碼。2.4 頂層異常處理MainApplication函數(shù)使用三層異常捕獲DWORDMainApplication()noexcept{try{HidHide::CommandInterpreter(false).Start(HidHide::CommandLineArguments());returnERROR_SUCCESS;}catch(std::exceptionconstexc){std::wcerrexc.what()std::endl;LOGEXC_AND_RETURN_WIN32;}catch(...){std::wcerrLUnhandled exceptionstd::endl;LOGEXC_AND_RETURN_WIN32;}}第一層std::exception捕獲所有標(biāo)準(zhǔn) C 異常包括通過(guò)THROW_WIN32拋出的std::runtime_error。第二層...捕獲所有非標(biāo)準(zhǔn)異常如結(jié)構(gòu)化異常作為最后的兜底。錯(cuò)誤信息同時(shí)輸出到std::wcerr便于腳本捕獲錯(cuò)誤輸出和 ETW。三、配置管理提交與取消3.1 配置提交ApplyConfigurationChanges交互模式下用戶退出時(shí)調(diào)用ApplyConfigurationChangesm_FilterDriverProxy.ApplyConfigurationChanges();此方法比對(duì)緩存與驅(qū)動(dòng)狀態(tài)僅提交有變更的配置。3.2 取消操作–cancel--cancel命令設(shè)置m_Cancel true在Start方法的主循環(huán)中檢查此標(biāo)志if(m_Cancel)return;取消后配置變更不會(huì)被提交所有修改在退出時(shí)丟棄。四、實(shí)用工具函數(shù)4.1 字符串表加載StringTable(UINT resourceId)從資源文件中加載本地化字符串std::wstringStringTable(UINT stringTableResourceId){autoconsthInstanceGetModuleHandleW(nullptr);std::vectorWCHARbuffer(UNICODE_STRING_MAX_CHARS);if(0LoadStringW(hInstance,stringTableResourceId,buffer.data(),buffer.size())ERROR_SUCCESS!GetLastError())THROW_WIN32_LAST_ERROR;returnbuffer.data();}所有用戶可見的文本均通過(guò)此函數(shù)加載支持國(guó)際化。4.2 多字符串轉(zhuǎn)換Windows 注冊(cè)表REG_MULTI_SZ格式與std::vectorstd::wstring的相互轉(zhuǎn)換std::vectorstd::wstringMultiStringToStringList(std::vectorWCHARconstmultiString){for(size_t index0,start0;indexmultiString.size();index){if(0multiString.at(index)){std::wstringstring(multiString.at(start),0,index-start);if(!string.empty())result.emplace_back(string);startindex1;}}returnresult;}注意處理雙空終止符空字符串被跳過(guò)!string.empty()判斷避免將列表終止符誤認(rèn)為有效條目。4.3 命令行參數(shù)提取CommandLineArguments()從完整的命令行字符串中提取參數(shù)部分std::wstringCommandLineArguments(){std::wstring commandLineGetCommandLineW();if(LcommandLine.at(0)){autoindexcommandLine.find(L,1);returnTrim(commandLine.erase(0,index1));}else{autoindexcommandLine.find(L );returnTrim(commandLine.erase(0,index));}}先跳過(guò)可執(zhí)行文件路徑可能帶引號(hào)返回剩余的命令行參數(shù)。五、CLI 與內(nèi)核驅(qū)動(dòng)的完整交互鏈路用戶輸入命令如--app-reg C:\Tools\mapper.exe命令解釋器解析提取命令名app-reg和參數(shù)C:\Tools\mapper.exe參數(shù)驗(yàn)證ValOneFullyQualifiedExecutablePath檢查文件存在性和可執(zhí)行性路徑轉(zhuǎn)換FileNameToFullImageName將C:\Tools\mapper.exe轉(zhuǎn)換為\Device\HarddiskVolume1\Tools\mapper.exe代理調(diào)用FilterDriverProxy.WhitelistAddEntry(fullImageName)更新內(nèi)存緩存提交交互退出時(shí)ApplyConfigurationChanges調(diào)用DeviceIoControl(IOCTL_SET_WHITELIST)內(nèi)核處理驅(qū)動(dòng)更新注冊(cè)表和運(yùn)行時(shí)集合新配置生效反饋輸出命令執(zhí)行結(jié)果輸出到 stdout/stderr成功返回 ERROR_LEVEL 0失敗返回非 0這一完整鏈路實(shí)現(xiàn)了從用戶輸入到內(nèi)核生效的全流程自動(dòng)化使得 HidHideCLI 成為自動(dòng)化部署和高級(jí)管理的理想工具。