Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,13 @@ I heard that the original dev wasn't planning to make a Windows version for it.
He was probably right.
Oh it is such a pain to work with the windows kernel.
Windows is the most popular and the most user friendly and organized looking OS but the SECOND you peek inside it is such a garbling mess of WEIRD stuff. It's so weird.
Some quirks I've noticed:
- In Linux, when a parent process dies (such as, a process spawns a child process and then ends itself), the kernel politely adopts the child process to avoid it from floating as an orphan. Windows, on the other hand, is a merciless masochistic psychopath and will leave processes connected to a ghost PID. Sometimes, Windows frees the parent PID up and something immediately snatches it, so a parent PID can be deceiving.
Some quirks I've noticed since I started working on this:
- In Linux, when a parent process dies (such as, a process spawns a child process and then ends itself), the kernel politely adopts the child process to avoid it from floating as an orphan. Windows, on the other hand, is a merciless sadistic psychopath and will leave processes connected to a ghost PID. Sometimes, Windows frees the parent PID up and something immediately snatches it, so a parent PID can be deceiving.
- The kernel stuff is complicated as heck for no reason. Like everything is so separated and needs very weirdly specific workarounds that it's almost eerie.
- To get the uptime of a process, the Windows kernel politely tells you the raw FILETIME of a process. But that isn't exactly... readable. In fact, it represents the amount of 100-nanosecond intervals since January 1, 1601, in the UTC timezone. Microsoft, WHAT? Literally every other piece of software uses Unix time, which is the number of **seconds** since January 1, 1970, in the UTC timezone. In Linux, you can just read the /proc/PID/stat file and get the uptime in seconds.
- Everything in Windows that's stupid like this is simply because of legacy reasons. While newer versions of Windows look like a shiny new (although heavily bloated) OS with a nice user-friendly UI and a whole crap ton of WebView2's and RAM hogs, the kernel is still the same mess of code that was written like 30 years ago. To cut them some slack, the Windows NT kernel was worked on by a bunch of people simultaneously which can lead to conflicting ideas, decisions, and other stuff. Linux, on the other hand, was written by one person (Linus Torvalds) and a small team of volunteers, so it has a much more consistent design than whatever this monstrosity is. Anyways, the other reason is compatibility. Even if Microsoft _wants_ to change something, millions of facilities use custom-written applications that rely on old behavior, especially things like hospitals and other infrastructure. (I bet banks are still written in COBOL or FORTRAN tho lol)


I could've definitely done this in one sitting if I only had like 5 more hours but I decided i will stop right here and this is pretty good for the initial commit. 300-400 lines of C++ ain't that bad for 3 hours, and I want to spend the remaining 2 hours of this day to spend New Years with my family.


## AI assistance disclaimer
Expand Down
97 changes: 93 additions & 4 deletions main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@
#include <chrono>
#include <thread>
#include <filesystem>
#include <iomanip>
#include <sstream>
#include <ctime>

#define windows_time_to_unix_epoch(x) ((x) - 116444736000000000LL) / 10000000LL
// The above macro converts Windows FILETIME to Unix epoch time in seconds.
// I explain more about why this is needed below and in the README.
// TLDR: FILETIME is an arbitrary number and we need math to convert it into something useful.
// I took this macro from https://stackoverflow.com/a/74650247
// Thanks!

#pragma comment(lib, "advapi32.lib") // For Security/Registry (Elevation check)
#pragma comment(lib, "iphlpapi.lib") // For Network stuff (Port to PID mapping)
Expand Down Expand Up @@ -53,6 +63,12 @@ bool IsVirtualTerminalModeEnabled() {

return (dwMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0;
}
// The above function checks if Virtual Terminal mode is enabled.
// This means that the terminal can render things like ANSI escape codes for colors and stuff.
// If we tried spitting out escape codes in a terminal that doesn't support it, it would look like unreadable garbage,
// and that's probably not very pleasant to the user. This is very rare and I have yet to encounter a terminal that doesn't support it,
// 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.
// Who knows, I might even test this on windows XP hahahahahaha...

bool EnableDebugPrivilege() {
HANDLE hToken;
Expand Down Expand Up @@ -128,7 +144,7 @@ std::string WideToString(const std::wstring& wstr) {
}
// The above stupid function is to convert wide strings (used by Windows API) to normal strings (used by C++ standard library) because cout chokes on wide strings.

// Helper to get creation time of a PID

ULONGLONG GetProcessCreationTime(DWORD pid) {
HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
if (!hProcess) return 0;
Expand All @@ -144,8 +160,54 @@ ULONGLONG GetProcessCreationTime(DWORD pid) {
CloseHandle(hProcess);
return 0;
}
// Process uptime helper
// Reference: https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getprocesstimes
// While this does indeed give you the time since the process was created,
// it actually returns a raw FILETIME, meaning it is an unsigned 64-bit integer that
// shows the amount of 100-nanosecond intervals since January 1, 1601 (UTC), which is just straight up not readable, and
// in my opinion INSANELY arbritrary, but I guess Microsoft is Microsoft...
// In short, we need to do some more math on this.

std::string GetReadableFileTime(DWORD pid) {
ULONGLONG creationTime = GetProcessCreationTime(pid);
if (creationTime == 0) return "N/A";
time_t unixTime = windows_time_to_unix_epoch(creationTime);
// Here's the macro we defined earlier! Now we finally have something useful to work with.
time_t now = std::time(nullptr);
double diffSeconds = std::difftime(now, unixTime);


std::string ago;
if (diffSeconds < 60) ago = std::to_string((int)diffSeconds) + " seconds ago";
else if (diffSeconds < 3600) ago = std::to_string((int)diffSeconds / 60) + " minutes ago";
else if (diffSeconds < 86400) ago = std::to_string((int)diffSeconds / 3600) + " hours ago";
else ago = std::to_string((int)diffSeconds / 86400) + " days ago";


std::tm bt{};
localtime_s(&bt, &unixTime);

std::ostringstream oss;
oss << ago << " (" << std::put_time(&bt, "%a %Y-%m-%d %H:%M:%S %z") << ")";

// All this shenanginanny stuff we do with the timestamp is to make it look just like witr's output, which I quote from the README in that repo:
// Started : 2 days ago (Mon 2025-02-02 11:42:10 +05:30)

return oss.str();
}


void PrintAncestry(DWORD pid, int depth = 0) {

/*
TODO: This tree is flipped. The output should be like this, as shown in the original witr:
systemd (pid 1)
└─ PM2 v5.3.1: God (pid 1481580)
└─ python (pid 1482060)


*/

if (pid == 0 || pid == 4) return;

HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
Expand Down Expand Up @@ -178,7 +240,7 @@ void PrintAncestry(DWORD pid, int depth = 0) {
ULONGLONG parentTime = GetProcessCreationTime(parentPid);

// If parentTime is 0, the parent is dead.
// If parentTime > childTime, the parent is an impostor (recycled PID).
// If parentTime > childTime, the parent is an impostor among us (recycled PID).
if (parentTime != 0 && parentTime < childTime) {
PrintAncestry(parentPid, depth + 1);
} else {
Expand All @@ -205,7 +267,7 @@ void PIDinspect(DWORD pid) { // ooh guys look i'm in the void
return;
}

// Query executable path
char exePath[MAX_PATH] = {0};
DWORD size = MAX_PATH;
if (QueryFullProcessImageNameA(hProcess, 0, exePath, &size)) {
Expand All @@ -216,10 +278,37 @@ void PIDinspect(DWORD pid) { // ooh guys look i'm in the void
<< "\n Maybe Access is Denied or the process is living in RAM." << std::endl;
}

// Print ancestry chain

// TODO: add color text

std::cout << "\nProcess Ancestry:\n";
PrintAncestry(pid);

std::cout << "\nStarted: " << GetReadableFileTime(pid) << std::endl;
/*
TODO:
This definitely needs a lot more details to be complete like witr. Unfortunately, windows needs even more shenanigans and a whole
lotta more code and admin access to get the same details. I will explain this some other day.

This is the output from witr for reference:
Target : node

Process : node (pid 14233)
User : pm2
Command : node index.js
Started : 2 days ago (Mon 2025-02-02 11:42:10 +05:30)
Restarts : 1

Why It Exists :
systemd (pid 1) → pm2 (pid 5034) → node (pid 14233)

Source : pm2

Working Dir : /opt/apps/expense-manager
Git Repo : expense-manager (main)
Listening : 127.0.0.1:5001
*/

CloseHandle(hProcess);
}

Expand Down
Binary file modified main.exe
Binary file not shown.
Binary file modified main.obj
Binary file not shown.
Loading