)
HidHide 驅動分析 - HidHideCLI 篇四日志系統與異常處理一、日志系統的分層架構HidHideCLI 的日志系統與內核驅動共享相同的 ETW 提供者結構但在用戶態實現了獨立的日志后端。日志系統分為三個層次1.1 宏定義層Logging.h// 三種追蹤級別#defineTRACE_DETAILED(message){TraceEvent(...,EtwEventTraceDetailed,message,);...}#defineTRACE_PERFORMANCE(message){TraceEvent(...,EtwEventTracePerformance,message,);...}#defineTRACE_ALWAYS(message){TraceEvent(...,EtwEventTraceAlways,message,);...}// 日志事件宏簡化參數傳遞#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是一個編譯期定義的常量用于裁剪__FILE__中的絕對路徑前綴使日志輸出更簡潔。1.2 核心日志函數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函數封裝了EventWriteTransfer調用將日志事件同時寫入日志提供者和追蹤提供者。TraceEvent僅寫入追蹤日志不寫入事件日志用于高性能場景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 模擬在用戶態模擬內核態DbgPrintEx行為輸出到調試器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 提供者注冊/注銷NTSTATUS WINAPILogRegisterProviders()noexcept{EventRegisterNefarius_Hid_Hide_CLI();EventRegisterNefarius_Drivers_HidHideCLI();LogEvent(ETW(Started),L%s,_L(BldProductVersion));returnSTATUS_SUCCESS;}兩個提供者分別對應日志Logging和追蹤Tracing方便管理員根據需要啟用不同級別的診斷信息。二、異常處理框架2.1 異常編碼與傳遞LogException 類LogException是一個輔助類用于將不同來源的錯誤碼CONFIGRET、HRESULT、NTSTATUS、WIN32統一編碼為字符串便于日志記錄和異常傳遞。編碼函數staticstd::stringEncodeWIN32(LPCSTR fileName,UINT32 lineNumber,LPCSTR functionName,PCEVENT_DESCRIPTOR event,DWORD result)noexcept{std::ostringstream os;FormatMessageA(...);// 獲取錯誤描述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.解碼與轉換CONFIGRETLogException::ToCONFIGRET()constnoexcept{autoconstdecodedDecodeExceptionData();switch(decoded.encoding){caseEncoding::configret:returndecoded.configret;caseEncoding::hresult:returnSUCCEEDED(decoded.hresult)?CR_SUCCESS:CR_FAILURE;// ...}}支持在異常處理中將錯誤碼靈活轉換為所需類型。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 調用失敗時通過宏拋出包含文件名、行號和錯誤描述的std::runtime_error。2.3 異常捕獲與日志記錄宏#defineLOGEXC_AND_RETURN_WIN32{\LogException(LogException::ExceptionMessage()).Log(ETW(Exception)).ToWIN32();\}在catch塊中使用將異常信息記錄到 ETW然后返回對應的 WIN32 錯誤碼。2.4 頂層異常處理MainApplication函數使用三層異常捕獲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捕獲所有標準 C 異常包括通過THROW_WIN32拋出的std::runtime_error。第二層...捕獲所有非標準異常如結構化異常作為最后的兜底。錯誤信息同時輸出到std::wcerr便于腳本捕獲錯誤輸出和 ETW。三、配置管理提交與取消3.1 配置提交ApplyConfigurationChanges交互模式下用戶退出時調用ApplyConfigurationChangesm_FilterDriverProxy.ApplyConfigurationChanges();此方法比對緩存與驅動狀態僅提交有變更的配置。3.2 取消操作–cancel--cancel命令設置m_Cancel true在Start方法的主循環中檢查此標志if(m_Cancel)return;取消后配置變更不會被提交所有修改在退出時丟棄。四、實用工具函數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();}所有用戶可見的文本均通過此函數加載支持國際化。4.2 多字符串轉換Windows 注冊表REG_MULTI_SZ格式與std::vectorstd::wstring的相互轉換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;}注意處理雙空終止符空字符串被跳過!string.empty()判斷避免將列表終止符誤認為有效條目。4.3 命令行參數提取CommandLineArguments()從完整的命令行字符串中提取參數部分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));}}先跳過可執行文件路徑可能帶引號返回剩余的命令行參數。五、CLI 與內核驅動的完整交互鏈路用戶輸入命令如--app-reg C:\Tools\mapper.exe命令解釋器解析提取命令名app-reg和參數C:\Tools\mapper.exe參數驗證ValOneFullyQualifiedExecutablePath檢查文件存在性和可執行性路徑轉換FileNameToFullImageName將C:\Tools\mapper.exe轉換為\Device\HarddiskVolume1\Tools\mapper.exe代理調用FilterDriverProxy.WhitelistAddEntry(fullImageName)更新內存緩存提交交互退出時ApplyConfigurationChanges調用DeviceIoControl(IOCTL_SET_WHITELIST)內核處理驅動更新注冊表和運行時集合新配置生效反饋輸出命令執行結果輸出到 stdout/stderr成功返回 ERROR_LEVEL 0失敗返回非 0這一完整鏈路實現了從用戶輸入到內核生效的全流程自動化使得 HidHideCLI 成為自動化部署和高級管理的理想工具。