Skip to content

Commit d8e5820

Browse files
Merge pull request #52 from supervoidcoder/better-error-handling
feat: enhanced error handing when inspecting processes. Added an intenal lookup table (map) of certain error codes that might show up on win-witr. also changed "Process Ancestry" to "Why It Exists" to match the original witr. etc
2 parents 687febc + 69fc08b commit d8e5820

1 file changed

Lines changed: 172 additions & 9 deletions

File tree

main.cpp

Lines changed: 172 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
#include <sstream>
1717
#include <ctime>
1818
#include <algorithm>
19-
19+
#include <conio.h>
20+
2021
#define windows_time_to_unix_epoch(x) ((x) - 116444736000000000LL) / 10000000LL
2122
// The above macro converts Windows FILETIME to Unix epoch time in seconds.
2223
// I explain more about why this is needed below and in the README.
@@ -53,6 +54,45 @@ Less words to type ;)
5354
*/
5455
std::string forkAuthor = ""; // if this is a fork of this project, put your name here! Please be nice and leave my name too :)
5556
std::string version = "v0.1.0"; // Version of this Windows port
57+
thread_local std::string currentParentExe = ""; // to store the name of our own parent process for error hints
58+
59+
std::string WideToString(const std::wstring& wstr);
60+
61+
void EnsureCurrentParentExe() {
62+
if (!currentParentExe.empty()) return;
63+
64+
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
65+
if (hSnapshot == INVALID_HANDLE_VALUE) return;
66+
67+
PROCESSENTRY32 pe32{};
68+
pe32.dwSize = sizeof(PROCESSENTRY32);
69+
70+
DWORD currentProcessId = GetCurrentProcessId();
71+
DWORD parentPid = 0;
72+
73+
if (Process32First(hSnapshot, &pe32)) {
74+
do {
75+
if (pe32.th32ProcessID == currentProcessId) {
76+
parentPid = pe32.th32ParentProcessID;
77+
break;
78+
}
79+
} while (Process32Next(hSnapshot, &pe32));
80+
}
81+
82+
if (parentPid != 0) {
83+
pe32.dwSize = sizeof(PROCESSENTRY32);
84+
if (Process32First(hSnapshot, &pe32)) {
85+
do {
86+
if (pe32.th32ProcessID == parentPid) {
87+
currentParentExe = WideToString(pe32.szExeFile);
88+
break;
89+
}
90+
} while (Process32Next(hSnapshot, &pe32));
91+
}
92+
}
93+
94+
CloseHandle(hSnapshot);
95+
}
5696

5797

5898
bool IsVirtualTerminalModeEnabled() {
@@ -71,6 +111,26 @@ bool IsVirtualTerminalModeEnabled() {
71111
// but I'm sure there's someone out there using some ANCIENT old version of Windows that doesn't support it, and we want to support this for all versions.
72112
// Who knows, I might even test this on windows XP hahahahahaha...
73113

114+
115+
116+
// Here, we will create an unordered map, essentially a lookup table filled with useful stuff like hints for specific errors
117+
std::unordered_map<int, std::string> errorHints = {
118+
{5, "Access is denied."},
119+
{87, "The process or PID doesn't exist."},
120+
{31, "This error indicates a driver error, but in win-witr, it often means you are calling a pseudo-process, such as System, Registry, or other processes that only exist in RAM as a kernel process. It is often easy to tell them apart if they lack a .exe extension."}
121+
// So far, these are the only error codes I myself have encountered while using win-witr.
122+
// Something funny about this tool is that the error descriptions in Windows documentation are sometimes
123+
// Completely unrelated to what the actual error means in the context of win-witr.
124+
// For example, ERROR_INVALID_PARAMETER (87) is described as "The parameter is incorrect.", but in win-witr,
125+
// it usually means that the process or PID you're trying to query doesn't exist.
126+
// Same for 31, which is described as "A device attached to the system is not functioning.", but in win-witr,
127+
// I've only gotten it when calling processes like System, Registry, etc.
128+
129+
// If you want the full list of win32 error codes, you can find them here: https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
130+
131+
};
132+
133+
74134
bool EnableDebugPrivilege() {
75135
HANDLE hToken;
76136
LUID luid;
@@ -197,6 +257,52 @@ std::string GetReadableFileTime(DWORD pid) {
197257
return oss.str();
198258
}
199259

260+
void PrintErrorHints(int errorCode) {
261+
EnsureCurrentParentExe();
262+
// Use our little lookup table to give hints for specific errors
263+
if (errorHints.find(errorCode) != errorHints.end()) {
264+
if (IsVirtualTerminalModeEnabled()) {
265+
std::cerr << "\033[1;33mHint:\033[0m " << errorHints[errorCode] << std::endl;
266+
} else {
267+
std::cerr << "Hint: " << errorHints[errorCode] << std::endl;
268+
269+
}
270+
// if it's 5 specifically, we can add an extra hint
271+
if (errorCode == 5) {
272+
if (currentParentExe == "powershell.exe" || currentParentExe == "pwsh.exe" || currentParentExe == "PowerShell.exe") {
273+
std::cerr << "Try reopening Powershell as Administrator and running win-witr again." << std::endl;
274+
} else if (currentParentExe == "cmd.exe") {
275+
std::cerr << "Try reopening Command Prompt as Administrator and running win-witr again." << std::endl;
276+
}
277+
else if (currentParentExe == "WindowsTerminal.exe") {
278+
std::cerr << "Try reopening Windows Terminal as Administrator and running win-witr again." << std::endl;
279+
} else if (currentParentExe == "explorer.exe") {
280+
std::cerr << "It seems you might be opening this as a shortcut with flags from Explorer. For best results, try running win-witr from an elevated Command Prompt or Powershell. " << std::endl;
281+
std::cout << "\nPress any key to exit...";
282+
_getch();
283+
// the process will automatically exit since if you open a terminal based script from explorer, the terminal will close immediately after execution is over
284+
// either way we want to give the user a chance to read the error message even if they did something as weird as this
285+
// it's crazy I even thought of this scenario lol
286+
287+
} else if (currentParentExe == "wsl.exe" || currentParentExe == "wslhost.exe") {
288+
std::cerr << "Hah, you're running this in Windows Subsystem for Linux. Run wsl as Admin!" << std::endl;
289+
290+
291+
} else if (currentParentExe == "git-bash.exe") {
292+
std::cerr << "Uh, you're running this from Git Bash. Try running this program from an elevated Command Prompt or Powershell." << std::endl;
293+
}
294+
else {
295+
std::cerr << "Try reopening this program (or more likely, the shell you're running this in, currently " << currentParentExe << ") as Administrator and running win-witr again." << std::endl;
296+
}
297+
298+
299+
300+
}
301+
302+
303+
}
304+
}
305+
200306

201307
void PrintAncestry(DWORD pid) {
202308

@@ -218,6 +324,21 @@ UPDATE: This is done now!!
218324
DWORD parentPid = 0;
219325
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
220326
if (hSnapshot == INVALID_HANDLE_VALUE) return;
327+
328+
329+
DWORD currentProcessId = GetCurrentProcessId(); // checking our own process
330+
DWORD currentParentPid = 0;
331+
332+
if (Process32First(hSnapshot, &pe32)) { // here, we're gonna use the existing snapshot so it doesn't use another
333+
do {
334+
// it shouldn't harm performance, but even if it does, I want to get
335+
// the features done first before optimizing anything
336+
if (pe32.th32ProcessID == currentProcessId) {
337+
break;
338+
}
339+
} while (Process32Next(hSnapshot, &pe32));
340+
}
341+
221342
DWORD targetpid = pid; // the function already passes pid into us, but
222343
// just to be safe that pid doesn't get overwritten in the loop below
223344
std::string exeName = "Unknown/Dead Process";
@@ -227,7 +348,7 @@ UPDATE: This is done now!!
227348
std::vector<DWORD> parentPids;
228349
bool found = false;
229350
while (pid != 0 && pid != 4) {
230-
found = false;
351+
found = false;
231352
if (Process32First(hSnapshot, &pe32)) {
232353
do {
233354
if (pe32.th32ProcessID == pid) {
@@ -374,35 +495,77 @@ void PIDinspect(DWORD pid) { // ooh guys look i'm in the void
374495
// This lets us know if the error was denied specifically for access reasons. THis will initiate our little fallback.
375496
hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid); // poor little guy getting limited of his full power
376497
// This has been tested and it does let us get info about lsass.exe and even System! Woohoo!
498+
// But of course, you need to be running as admin for this to work.
377499
}
500+
int errorCode = 0;
501+
bool queryError = false;
378502
if (!hProcess) {
503+
errorCode = GetLastError();
504+
379505
if (IsVirtualTerminalModeEnabled()) {
506+
507+
queryError = true;
380508
std::cerr << "\033[1;31mError:\033[0m Could not open process with PID "
381-
<< pid << ". Error code: " << GetLastError()
509+
<< pid << ". Error code: " << errorCode
382510
<< "\nMaybe it doesn't exist or access is denied." << std::endl;
511+
383512
} else {
513+
queryError = true;
384514
std::cerr << "Error: Could not open process with PID "
385-
<< pid << ". Error code: " << GetLastError()
515+
<< pid << ". Error code: " << errorCode
386516
<< "\nMaybe it doesn't exist or access is denied." << std::endl;
517+
387518
}
388-
return;
519+
if (queryError) {
520+
PrintErrorHints(errorCode);
521+
}
522+
523+
389524
}
390525

391526

392527
char exePath[MAX_PATH] = {0};
393528
DWORD size = MAX_PATH;
529+
394530
if (QueryFullProcessImageNameA(hProcess, 0, exePath, &size)) {
531+
395532
std::cout << "Executable Path: " << exePath << std::endl;
396533
} else {
397-
std::cerr << "Error: Unable to query executable path. Error code: "
398-
<< GetLastError()
399-
<< "\n Maybe Access is Denied or the process is living in RAM." << std::endl;
534+
535+
errorCode = GetLastError();
536+
if (IsVirtualTerminalModeEnabled()) {
537+
queryError = true;
538+
std::cerr << "\033[1;31mError:\033[0m Unable to query executable path. Error code: "
539+
<< errorCode
540+
<< "\n Maybe Access is Denied or the process is running entirely in RAM." << std::endl;
541+
} else {
542+
queryError = true;
543+
std::cerr << "Error: Unable to query executable path. Error code: "
544+
<< errorCode
545+
<< "\n Maybe Access is Denied or the process is running entirely in RAM." << std::endl;
546+
}
547+
if (queryError) {
548+
PrintErrorHints(errorCode);
549+
// it might seem like overkill to call the function every time there's an error,
550+
// but if you remember we have a fallback for opening processes, so there are multiple
551+
// places where an error can occur.
552+
// for example, when testing this, the hint for error 5 (access denied) didn't show up
553+
// since immediately after it was overwritten by error code 6 (valid but insufficient permissions) created by the fallback
554+
// with the limited process info
555+
}
556+
557+
400558
}
401559

560+
// Use our little lookup table to give hints for specific errors
561+
562+
563+
564+
402565

403566
// TODO: add color text
404567

405-
std::cout << "\nProcess Ancestry:\n";
568+
std::cout << "\nWhy It Exists:\n";
406569
PrintAncestry(pid);
407570

408571
std::cout << "\nStarted: " << GetReadableFileTime(pid) << std::endl;

0 commit comments

Comments
 (0)