├── 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 |
--------------------------------------------------------------------------------