Skip to content

Commit bcaa650

Browse files
Adding comments.
1 parent 8d43dd1 commit bcaa650

File tree

2 files changed

+134
-20
lines changed

2 files changed

+134
-20
lines changed

MinimalProcessManager/ProcessManager.cpp

Lines changed: 124 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,24 @@ typedef NTSTATUS(*ptrNtResumeProcess)(HANDLE hProcess);
77
void* ProcessManager::ptrResumeProcess = nullptr;
88
void* ProcessManager::ptrSuspendProcess = nullptr;
99

10+
// (1). Because we use not fully documented functions such as SuspendProcess and ResumeProcess
11+
// I have implemented a setup funcionality in the begining of the constructor
12+
// Basically, we get those functions from the ntdll.dll module which is loaded by default
13+
// in every windows executable. Because of that, we can obtain their addresses with GetProcAddres.
14+
// Of course, we need to provide proper prototyping so we can use them in code.
15+
16+
// (2). Here, I try, if a process id has been provided, to acquire some process information.
17+
// The MOST IMPORTANT member variable here is "hProcess" because it is a handle to the process
18+
// obtained through the specified process id and it is used basically for EVERY windows api process
19+
// related functions.
20+
// We also try to get the full path of the executable used to create the process specified by the user.
21+
// Finally, we trunk imagePathName so we can store just the executable name, without its full path.
1022
ProcessManager::ProcessManager(const DWORD pid) : processId(pid),
1123
hProcess(nullptr),
1224
imagePathName(L"(none)"),
1325
processName(L"(none)"), peParser(imagePathName) {
1426

15-
// Set up suspend/resume function ptrs
27+
// (1)
1628
if (ptrSuspendProcess == nullptr || ptrResumeProcess == nullptr) {
1729
HMODULE hNtdll = ::GetModuleHandle(L"ntdll.dll");
1830
if (!hNtdll) {
@@ -24,6 +36,7 @@ ProcessManager::ProcessManager(const DWORD pid) : processId(pid),
2436
ProcessManager::ptrResumeProcess = ::GetProcAddress(hNtdll, "NtResumeProcess");
2537
}
2638

39+
// (2)
2740
if (pid == 0)
2841
return;
2942

@@ -33,20 +46,33 @@ ProcessManager::ProcessManager(const DWORD pid) : processId(pid),
3346

3447
}
3548

36-
49+
// (1). The destructor of the class uses a private Cleanup function to guarantee that
50+
// it frees every resource allocated properly
3751
ProcessManager::~ProcessManager() {
52+
// (1)
3853
this->Cleanup();
3954
}
4055

41-
56+
// (1). Just function just exists to enable the programmer who is using our class
57+
// to update the current process based on a process name instead of a process id
58+
// giving the ProcessManager class more versatility.
4259
void ProcessManager::UpdateProcess(const std::wstring& procName) {
43-
// Just a wrapper
60+
// (1)
4461
this->UpdateProcess(this->GetProcessIdByName(procName));
4562
}
4663

4764

65+
// (1). First, we call the cleanup function to release every resource previously allocated.
66+
// So we can properly update the current process information without having memory leaks and
67+
// memory related bugs in general.
68+
// (2). We update each member variable and do some error checking.
69+
// (3). We also reload the PEParser class instance so it refers to the executable currently
70+
// specified.
4871
void ProcessManager::UpdateProcess(const DWORD pid) {
72+
// (1)
4973
this->Cleanup();
74+
75+
// (2)
5076
this->processId = pid;
5177
this->hProcess = this->getHandleFromPid(pid);
5278
if (!this->hProcess)
@@ -55,11 +81,15 @@ void ProcessManager::UpdateProcess(const DWORD pid) {
5581
if (!this->imagePathName.compare(L"(none)"))
5682
return;
5783
this->processName.assign(imagePathName.substr(imagePathName.find_last_of('\\') + 1));
84+
85+
// (3)
5886
this->peParser.Reload(this->imagePathName);
5987
}
6088

61-
89+
// (1). Function that returns a handle to a process object
90+
// using the process identifier (pid).
6291
HANDLE ProcessManager::getHandleFromPid(const DWORD pid) {
92+
// (1)
6393
HANDLE hTemp = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
6494
if (!hTemp) {
6595
printf("[-] Failed acquiring handle to process\n");
@@ -68,8 +98,10 @@ HANDLE ProcessManager::getHandleFromPid(const DWORD pid) {
6898
return hTemp;
6999
}
70100

71-
101+
// (1). We obtain the full path of the executable file associated with the
102+
// loaded process image.
72103
const std::wstring ProcessManager::getImagePathName(const HANDLE _hProcess) {
104+
// (1)
73105
DWORD pathSize{ MAX_PATH };
74106
std::wstring _imagePathName;
75107
_imagePathName.resize(pathSize + 1);
@@ -83,8 +115,11 @@ const std::wstring ProcessManager::getImagePathName(const HANDLE _hProcess) {
83115
return _imagePathName;
84116
}
85117

86-
118+
// (1). We close the process handle IF IT IS OPEN.
119+
// We set variables to zero, nullptr or "(none)" depending on their types
120+
// We also call a cleaning function from the PEParser class
87121
void ProcessManager::Cleanup(void) {
122+
// (1)
88123
this->processId = 0;
89124
if (this->hProcess)
90125
::CloseHandle(this->hProcess);
@@ -94,8 +129,21 @@ void ProcessManager::Cleanup(void) {
94129
this->peParser.clean();
95130
}
96131

97-
// Functionality
132+
// (1). This is probably the most important function for the MinimalProcessManager solution.
133+
// I first start initializing some variables properly following the microsoft documentation.
134+
// Here, we are using th32help.h library to list all existing process on the system.
135+
// This function can be used for the following purposes: display process list and
136+
// search for a particular process (based on its name) returing its PID.
137+
// (2). Stores a handle to an object that represents a snapshot on the system.
138+
// Also, the program does some error handling (its probably going to be updated to use exceptions in the future)
139+
// (3). Finally, all the logic is implemented in this third section. The function receiveis a boolean variable
140+
// that tells if the function is to be used as a filter or should simply list all processes.
141+
// In case the filter is off, we just print all the process information in a formatted way.
142+
// In case the filter is on AND the user has specified a valid process name (we never know),
143+
// then we compare with the current process name being displayed and, if they match, we return
144+
// the process id of that particular process beign searched.
98145
DWORD ProcessManager::DisplayProcessList(const bool filter, const std::wstring& targetProcName) const {
146+
// (1)
99147
PROCESS_INFORMATION procInfo{ 0 };
100148
PROCESSENTRY32W procEntry{ 0 };
101149
HANDLE hSnapshot{ nullptr };
@@ -104,13 +152,14 @@ DWORD ProcessManager::DisplayProcessList(const bool filter, const std::wstring&
104152
::ZeroMemory(&procEntry, sizeof(PROCESSENTRY32W));
105153
procEntry.dwSize = sizeof(PROCESSENTRY32W);
106154

107-
155+
// (2)
108156
hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
109157
if (!hSnapshot) {
110158
printf("[-] Failed acquiring snapshot for process list\n");
111159
return 0;
112160
}
113161

162+
// (3)
114163
::Process32First(hSnapshot, &procEntry);
115164

116165
if (!filter)
@@ -134,17 +183,16 @@ DWORD ProcessManager::DisplayProcessList(const bool filter, const std::wstring&
134183

135184
}
136185

137-
186+
// (1). We display some process information. The use of all the windows API functions used here
187+
// should be very straightforward to learn with the microsoft documentation.
138188
void ProcessManager::DisplayProcessInfo(void) const {
189+
// (1)
139190
std::wstring priorityClass{ L"(none)"};
140191
DWORD handleCount{ 0 };
141192
BOOL priorityBoost{ FALSE };
142193
bool is64bit{ this->peParser.is64bit() };
143194
SIZE_T minWorkSize{ 0 };
144195
SIZE_T maxWorkSize{ 0 };
145-
146-
147-
148196

149197
if (this->hProcess) {
150198
::GetProcessPriorityBoost(this->hProcess, &priorityBoost);
@@ -159,41 +207,97 @@ void ProcessManager::DisplayProcessInfo(void) const {
159207
printf("- - - - - - - - - - - - - - - - - - - -");
160208
}
161209

162-
210+
// (1). This is a wrapper function that calls DisplayProcessList with the filtering
211+
// mdoe enabled. It allows using this member function to obtain process id based
212+
// only on a process name.
163213
DWORD ProcessManager::GetProcessIdByName(const std::wstring& procName) {
164214
return this->DisplayProcessList(true, procName);
165215
}
166216

167-
217+
// (1). Nothing special here. We just call the documented TerminateFunction passing
218+
// the process handle that the class currently stores.
168219
void ProcessManager::TerminateProcess(DWORD ExitCode) {
169220
::TerminateProcess(this->hProcess, ExitCode);
170221
this->Cleanup();
171222
}
172223

173-
224+
// (1). Now, in this function and the next one (ResumeProcess) I do something that may seem
225+
// weird for those who are new to this type of programming. To hide the function prototypes from
226+
// the main program, I define VOID function pointers in the class declaration. Obviously, we need
227+
// to cast those two pointers (ptrSuspendProcess and ptrResumeProcess) to the apropriate prototype
228+
// so we can call the function from memory. Because the prototypes of those two functions are
229+
// in this .cpp file, the main.cpp cannot see them.
174230
void ProcessManager::SuspendProcess(void) {
175231
if (this->hProcess)
176232
static_cast<ptrNtSuspendProcess>(ProcessManager::ptrSuspendProcess)(this->hProcess);
177233
}
178234

179-
235+
// (1). Read SuspendProcess explanation
180236
void ProcessManager::ResumeProcess(void) {
181237
if (this->hProcess)
182238
static_cast<ptrNtResumeProcess>(ProcessManager::ptrResumeProcess)(this->hProcess);
183239
}
184240

241+
242+
// (1). Here we will have a large discussion.
243+
// Basically, consider that you have a CPU chip with 4 cores, 8 threads.
244+
// You probaly heard that before. Lets just rename "8 threads" to "8 logical processors".
245+
// Now, threads are the units of execution. They are the entities that actually RUN code.
246+
// It is a common misconception to beleive that processes "run". They dont, they are just
247+
// a container whose purpose is to MANAGE a group of threads.
248+
// Now, suppose that you have 8 logical processors. So you can run 8 threads simultaneasly.
249+
// But what if you have 20,40 or 2000 threads? (check your task manager) In that case we have to discuss something called
250+
// "thread states" and "priorities".
251+
// Begining with thread states, they just describe what that thread is doing at that particular moment.
252+
// Ready State -> Means that the thread WANTS to execute and is probably queued for a logical processor already.
253+
// Wait State -> Thread DOES NOT WANT TO RUN, it is waiting for some kind of event to occur (i.g user input).
254+
// Running State -> Is actually executing code.
255+
// So, based on that, we can correctly conclude that MOST of the threads are in the WAIT state.
256+
// Now, every thread has a priority which is defined by a combination of: thread relative priority (relative to the thread) + priority class (relative to the process)
257+
// By default, the task manager shows the Priority class ONLY and so doesn't tell us everything about the threads priority.
258+
// Points to note:
259+
// 1 - Threads Priority Range from 0 to 31 (considering kernel). In practice, for user mode programming: (1..31)
260+
// 2 - Threads Priority = Threads Relative Priority + Priority Class
261+
// -----------------------------------------------------------------------
262+
// PRIORITY CLASS | VALUE THREADS RELATIVE PRIORITY | VALUE
263+
//IDLE 4 [1-15] IDLE -15
264+
//BELOW NORMAL 6 [1-15] LOWEST -2
265+
// NORMAL 8 [1-15] BELOW NORMAL -1
266+
//ABOVE NORMAL 10 [1-15] NORMAL +0
267+
// HIGH 13 [1-15] ABOVE NORMAL +1
268+
// REAL TIME 24 [16-31] HIGHEST +2
269+
// -------------------------------- TIME CRITICAL +15
270+
//
271+
// 3 - Priority Class also defines the range of the final threads priority. For every priority class, with the exception of
272+
// the REAL TIME, the final threads priority is in the range [1,15]. The REAL TIME one defines the range [16,31].
273+
//
274+
// So, we basically sum the two numbers and if the value exceeds one of the extreme points (1 and (31 or 15)) then the value
275+
// considered is the closest extreme point. For example, if we have BELOW_NORMAL (6) priority class and IDLE relative priority (-15), we have a
276+
// FINAL thread priority of 1 and NOT -9.
277+
// [Round Robin Example]
278+
// [Thread Ideal Processor]
279+
// [Affinity and Processor Groups]
280+
//
281+
//
185282
void ProcessManager::SetPriorityClass(DWORD newPriority) {
186283
if (this->hProcess)
187284
::SetPriorityClass(this->hProcess, newPriority);
188285
}
189286

287+
// (1). Function that creates a process with a fake parent process id (you can see the process three with ProcessExplorer.exe)
288+
// This function is just a wrapper that allows executing with two string parameters.
190289
void ProcessManager::CreateSpoofedPProcess(const std::wstring& processPath, const std::wstring& parentProcessPath) {
191290
this->CreateSpoofedPProcess(processPath, this->GetProcessIdByName(parentProcessPath));
192291
}
193292

194-
293+
// (1). Initialize all the variables properly.
294+
// (2). Initialize LPPROC_THREAD_ATTRIBUTE_LIST and allocate memory
295+
// (3). Update the process and thread attribute list with PROC_THREAD_ATTRIBUTE_PARENT_PROCESS
296+
// passing the parent process handle as parameter. Finally, it creates the process with the
297+
// extended version of startup info.
195298
void ProcessManager::CreateSpoofedPProcess(const std::wstring& exePath, const DWORD pid) {
196299

300+
// (1)
197301
// These variables need to be nonconst due to CreateProcess documentation. So we pass them to the stack without const modifier.
198302
std::wstring procPathCpy(exePath);
199303
SIZE_T procThreadAttrListSize{ 0 };
@@ -212,7 +316,7 @@ void ProcessManager::CreateSpoofedPProcess(const std::wstring& exePath, const DW
212316
return;
213317
}
214318

215-
319+
// (2)
216320
::InitializeProcThreadAttributeList(nullptr, 1, 0, &procThreadAttrListSize);
217321

218322
procThreadAttr = (LPPROC_THREAD_ATTRIBUTE_LIST)(new byte[procThreadAttrListSize]);
@@ -222,6 +326,7 @@ void ProcessManager::CreateSpoofedPProcess(const std::wstring& exePath, const DW
222326
goto Cleanup;
223327
}
224328

329+
// (3)
225330
if (!::UpdateProcThreadAttribute(procThreadAttr, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &hParent, sizeof(HANDLE), nullptr, nullptr)) {
226331
printf("[-] Failed initializing proc_thread_attr values\n");
227332
goto Cleanup;

MinimalProcessManager/main.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,15 @@ void getInput(const std::wstring& prefix, T& p) {
3131
}
3232

3333

34-
34+
// The wmain function implements all the logic around gathering user input
35+
// to be used for the ProcessManager class.
3536
int wmain(const int argc, const wchar_t* argv[]) {
3637

38+
// We will need one instance of the class
3739
ProcessManager procMg;
3840

41+
// Besides all the named variables which their use should be clear
42+
// The aux0x variables are used to store temporary strings to be passed as parameters to functions
3943
unsigned short int option{ 0 };
4044
std::wstring menu = L"\t[ MENU ]\n\t(1) - Display Process List\n\t(2) - Set current process\n\t(3) - Display Process Information\n\t(4) - Kill Process\n\t(5) - Suspend Process\n\t(6) - Resume Process\n\t(7) - Set Process Priority\n\t(8) - Create process with spoofed parent id\n\t(9) - Exit\n";
4145
std::wstring lastOperationDone = L"(none)";
@@ -47,11 +51,16 @@ int wmain(const int argc, const wchar_t* argv[]) {
4751

4852
while (option != 9) {
4953

54+
// Display the menu and the last operation performed by the program
55+
//
5056
aux01 = menu + L"\n\t[info] Last operation done : " + lastOperationDone + L"\n\n[Input]: ";
5157
getInput(aux01, option);
5258

59+
// Clear the terminal screen
5360
system("cls");
5461

62+
// Process the option inserted by the user
63+
// To better understand everything here. I strongly suggest you to read the ProcessManager.cpp file
5564
switch (option){
5665
case 1:
5766
procMg.DisplayProcessList();

0 commit comments

Comments
 (0)