├── README.md ├── WinLogHunt.cpp └── compile.bat /README.md: -------------------------------------------------------------------------------- 1 | # Windows Event Log Extractor 2 | 3 | A C++ tool for searching Windows Event Logs — both live system channels and offline `.evtx` files — for specific keywords (process names, service names, commands, etc.). 4 | Useful for incident response, threat hunting, and forensic analysis. 5 | 6 | --- 7 | 8 | ## Features 9 | 10 | - **Live Log Scanning**: Enumerates all event log channels and searches their entries. 11 | - **Offline Analysis**: Recursively scans directories for `.evtx` files and searches them. 12 | - **Case-Insensitive Search**: Matches regardless of case. 13 | - **Flexible Search Term**: Search any substring or use regex patterns with the `--regex` option. 14 | - **Multiple Formats**: Supports `xml`, `txt`, `csv`, and `json`. 15 | 16 | --- 17 | 18 | ## Output Details 19 | 20 | For every matched event, the tool provides: 21 | 22 | * **Source** – The origin of the event, either a live log channel (e.g., `[Security]`) or the full path to an offline `.evtx` file. 23 | * **Event Content** – The complete content of the event in the chosen output format (`xml`, `txt`, `csv`, or `json`), including all relevant details such as timestamps, EventID, provider, and associated parameters. 24 | 25 | All results are displayed in the console as they are found and also written to the specified output file for later analysis. 26 | 27 | --- 28 | 29 | 30 | --- 31 | 32 | ## Prerequisites 33 | 34 | - Windows 10 / Windows 11 35 | - Visual Studio 2019+ (C++ toolset) or MSVC command-line (cl) 36 | - `wevtapi.lib` (part of Windows SDK) 37 | - Administrator privileges recommended for scanning live logs (particularly Security or other protected channels) 38 | 39 | --- 40 | 41 | ## Build 42 | 43 | ```cmd 44 | compile.bat 45 | ``` 46 | 47 | --- 48 | 49 | ## Usage 50 | 51 | **Search live logs for `services.exe`:** 52 | ```cmd 53 | WinLogHunt.exe -i services.exe --live 54 | ``` 55 | 56 | **Search live logs using regex (e.g., any `exe` in `C:\Windows\System32`):** 57 | ```cmd 58 | WinLogHunt.exe -i "C:\\Windows\\System32\\.*\.exe" --live --regex 59 | ``` 60 | 61 | **Scan offline logs:** 62 | ```cmd 63 | WinLogHunt.exe -i powershell -d "D:\DiskImage\Windows\System32\winevt\logs" 64 | ``` 65 | 66 | **Output in JSON format:** 67 | ```cmd 68 | WinLogHunt.exe -i cmd.exe --live -f json -o cmd_results.json 69 | ``` 70 | 71 | --- 72 | 73 | ## Example output (snippet) 74 | 75 | ``` 76 | [*] Scanning file: "D:\...\Security.evtx" 77 | [Security.evtx] 78 | 79 | ... 80 | 81 | C:\Windows\System32\services.exe 82 | ... 83 | 84 | 85 | 86 | Results saved to results.xml 87 | ``` 88 | 89 | --- 90 | 91 | ## Security considerations 92 | 93 | - Event XML may contain sensitive information (usernames, hostnames, file paths). 94 | - If running on production systems, get appropriate authorization and run in a controlled environment. 95 | - Avoid exposing output files to untrusted parties. 96 | 97 | --- 98 | 99 | ## Contributing 100 | 101 | Contributions, bug reports, and feature requests are welcome. Open an issue or submit a pull request with a clear description and test cases. 102 | 103 | --- 104 | 105 | ## License 106 | 107 | This tool is provided as-is for educational, forensic, and research purposes. Use responsibly and in accordance with applicable laws and organizational policies. 108 | 109 | --- 110 | 111 | -------------------------------------------------------------------------------- /WinLogHunt.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #pragma comment(lib, "wevtapi.lib") 12 | 13 | namespace fs = std::filesystem; 14 | 15 | std::wstring ToLower(const std::wstring &s) { 16 | std::wstring out = s; 17 | std::transform(out.begin(), out.end(), out.begin(), ::towlower); 18 | return out; 19 | } 20 | 21 | void SearchServicesInLog(const std::wstring &channelPath, const std::wstring &target, std::wofstream &outfile, const std::string &format, bool useRegex) { 22 | std::wregex pattern; 23 | if (useRegex) { 24 | try { 25 | pattern = std::wregex(target, std::regex_constants::icase); 26 | } catch (const std::regex_error&) { 27 | std::wcerr << L"[!] Invalid regex pattern: " << target << std::endl; 28 | return; 29 | } 30 | } 31 | 32 | DWORD flags = EvtQueryReverseDirection | EvtQueryTolerateQueryErrors; 33 | if (channelPath.size() > 5 && ToLower(channelPath).rfind(L".evtx") == channelPath.size() - 5) { 34 | flags |= EvtQueryFilePath; 35 | } 36 | 37 | EVT_HANDLE hQuery = EvtQuery(NULL, channelPath.c_str(), NULL, flags); 38 | if (!hQuery) { 39 | DWORD err = GetLastError(); 40 | if (err != ERROR_NOT_SUPPORTED) { 41 | std::wcerr << L"[!] Failed to open log: " << channelPath << L" Error: " << err << std::endl; 42 | } 43 | return; 44 | } 45 | 46 | EVT_HANDLE events[10]; 47 | DWORD returned = 0; 48 | 49 | while (EvtNext(hQuery, 10, events, INFINITE, 0, &returned)) { 50 | for (DWORD i = 0; i < returned; i++) { 51 | DWORD bufferSize = 0, bufferUsed = 0, propertyCount = 0; 52 | EvtRender(NULL, events[i], EvtRenderEventXml, 0, NULL, &bufferUsed, &propertyCount); 53 | 54 | if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { 55 | bufferSize = bufferUsed; 56 | WCHAR* buffer = new WCHAR[bufferSize / sizeof(WCHAR)]; 57 | 58 | if (EvtRender(NULL, events[i], EvtRenderEventXml, bufferSize, buffer, &bufferUsed, &propertyCount)) { 59 | std::wstring xml = ToLower(buffer); 60 | bool matched = false; 61 | 62 | if (useRegex) { 63 | matched = std::regex_search(xml, pattern); 64 | } else { 65 | matched = xml.find(target) != std::wstring::npos; 66 | } 67 | 68 | if (matched) { 69 | std::wstring header = L"[" + channelPath + L"]\n"; 70 | if (format == "xml" || format == "txt") { 71 | outfile << header << buffer << L"\n"; 72 | } else if (format == "csv") { 73 | std::wstring line = L"\"" + channelPath + L"\",\"" + buffer + L"\"\n"; 74 | outfile << line; 75 | } else if (format == "json") { 76 | std::wstring jsonEntry = L"{\"log\":\"" + channelPath + L"\",\"event\":\"" + buffer + L"\"}\n"; 77 | outfile << jsonEntry; 78 | } 79 | } 80 | } 81 | 82 | delete[] buffer; 83 | } 84 | 85 | EvtClose(events[i]); 86 | } 87 | } 88 | 89 | EvtClose(hQuery); 90 | } 91 | 92 | void ScanLiveLogs(const std::wstring &target, std::wofstream &outfile, const std::string &format, bool useRegex) { 93 | EVT_HANDLE hEnum = EvtOpenChannelEnum(NULL, 0); 94 | if (!hEnum) { 95 | std::wcerr << L"[!] EvtOpenChannelEnum failed: " << GetLastError() << std::endl; 96 | return; 97 | } 98 | 99 | WCHAR buf[512]; 100 | DWORD bufSize = sizeof(buf); 101 | DWORD used = 0; 102 | 103 | while (EvtNextChannelPath(hEnum, bufSize / sizeof(WCHAR), buf, &used)) { 104 | SearchServicesInLog(buf, target, outfile, format, useRegex); 105 | } 106 | 107 | DWORD err = GetLastError(); 108 | if (err != ERROR_NO_MORE_ITEMS) { 109 | std::wcerr << L"[!] EvtNextChannelPath failed: " << err << std::endl; 110 | } 111 | 112 | EvtClose(hEnum); 113 | } 114 | 115 | void ScanEvtxDirectory(const std::wstring &directory, const std::wstring &target, std::wofstream &outfile, const std::string &format, bool useRegex) { 116 | for (const auto &entry : fs::directory_iterator(directory)) { 117 | if (entry.path().extension() == L".evtx") { 118 | std::wcout << L"[*] Scanning file: " << entry.path() << std::endl; 119 | SearchServicesInLog(entry.path().wstring(), target, outfile, format, useRegex); 120 | } 121 | } 122 | } 123 | 124 | void PrintHelp() { 125 | std::wcout << L"Usage:\n" 126 | << L" WinLogHunt.exe -i [--live | -d ] [-o ] [-f ] [--regex] [-h]\n\n" 127 | << L"Options:\n" 128 | << L" -h : Show this help message\n" 129 | << L" -i keyword : Search keyword (service name) [REQUIRED]\n" 130 | 131 | << L" -d directory : Scan .evtx files from specified directory\n" 132 | << L" -o outputfile : Output file (default: results.xml)\n" 133 | << L" -f format : Output format: xml, json, csv, txt (default: xml)\n" 134 | << L" --regex : Treat keyword as a regex pattern\n" 135 | << L" --live : Scan live Windows event logs\n\n" 136 | 137 | << L"Notes:\n" 138 | << L" Either --live or -d must be specified.\n" 139 | << L" -i is required to perform a search.\n"; 140 | } 141 | 142 | int wmain(int argc, wchar_t* argv[]) { 143 | std::wstring target; 144 | std::wstring outputPath = L"results.xml"; 145 | std::string format = "xml"; 146 | std::wstring directory; 147 | bool live = false; 148 | bool useRegex = false; 149 | 150 | for (int i = 1; i < argc; i++) { 151 | std::wstring arg = argv[i]; 152 | if (arg == L"-i" && i + 1 < argc) { 153 | target = argv[++i]; 154 | } else if (arg == L"-o" && i + 1 < argc) { 155 | outputPath = argv[++i]; 156 | } else if (arg == L"-f" && i + 1 < argc) { 157 | std::wstring f = ToLower(argv[++i]); 158 | if (f == L"xml" || f == L"txt" || f == L"csv" || f == L"json") { 159 | format = std::string(f.begin(), f.end()); 160 | } else { 161 | std::wcerr << L"[!] Invalid format. Using xml." << std::endl; 162 | } 163 | } else if (arg == L"-d" && i + 1 < argc) { 164 | directory = argv[++i]; 165 | } else if (arg == L"--live") { 166 | live = true; 167 | } else if (arg == L"--regex") { 168 | useRegex = true; 169 | } else if (arg == L"-h") { 170 | PrintHelp(); 171 | return 0; 172 | } else { 173 | std::wcerr << L"[!] Unknown argument: " << arg << std::endl; 174 | PrintHelp(); 175 | return 1; 176 | } 177 | } 178 | 179 | if (target.empty()) { 180 | std::wcerr << L"[!] Must provide a search keyword using -i " << std::endl; 181 | PrintHelp(); 182 | return 1; 183 | } 184 | 185 | if (!live && directory.empty()) { 186 | std::wcerr << L"[!] Must provide --live or -d " << std::endl; 187 | return 1; 188 | } 189 | 190 | std::wofstream outfile(outputPath); 191 | if (!outfile.is_open()) { 192 | std::wcerr << L"[!] Failed to open " << outputPath << L" for writing" << std::endl; 193 | return 1; 194 | } 195 | 196 | if (live) { 197 | ScanLiveLogs(target, outfile, format, useRegex); 198 | } 199 | 200 | if (!directory.empty()) { 201 | if (fs::exists(directory) && fs::is_directory(directory)) { 202 | ScanEvtxDirectory(directory, target, outfile, format, useRegex); 203 | } else { 204 | std::wcerr << L"[!] Directory does not exist: " << directory << std::endl; 205 | } 206 | } 207 | 208 | outfile.close(); 209 | std::wcout << L"Results saved to " << outputPath << std::endl; 210 | return 0; 211 | } 212 | -------------------------------------------------------------------------------- /compile.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | echo Compiling Event Log Searcher... 3 | 4 | REM Try to find Visual Studio installation 5 | set "VSWHERE=%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" 6 | if exist "%VSWHERE%" ( 7 | for /f "usebackq tokens=*" %%i in (`"%VSWHERE%" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do ( 8 | set "VS_PATH=%%i" 9 | ) 10 | ) 11 | 12 | if defined VS_PATH ( 13 | echo Found Visual Studio at: %VS_PATH% 14 | call "%VS_PATH%\VC\Auxiliary\Build\vcvars64.bat" 15 | ) else ( 16 | echo Visual Studio not found, trying default paths... 17 | if exist "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat" ( 18 | call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat" 19 | ) else if exist "C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Auxiliary\Build\vcvars64.bat" ( 20 | call "C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Auxiliary\Build\vcvars64.bat" 21 | ) else if exist "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" ( 22 | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" 23 | ) else ( 24 | echo Error: Visual Studio not found. Please install Visual Studio with C++ development tools. 25 | pause 26 | exit /b 1 27 | ) 28 | ) 29 | 30 | echo Compiling with cl.exe... 31 | cl.exe /EHsc /std:c++17 /O2 /MT WinLogHunt.cpp /link psapi.lib advapi32.lib /OUT:WinLogHunt.exe 32 | 33 | if %ERRORLEVEL% EQU 0 ( 34 | echo. 35 | echo Compilation successful! Executable: WinLogHunt.exe 36 | ) else ( 37 | echo. 38 | echo Compilation failed! 39 | ) 40 | 41 | --------------------------------------------------------------------------------