Chapter 9: Exploitation and Post-Exploitation#
“Exploitation is not an end; it is the beginning of understanding what the attacker could do next.”
Learning Objectives#
After completing this chapter, you will be able to:
Explain what exploitation means in a penetration-testing context.
Describe common vulnerability classes and their exploitation mechanisms.
Explain buffer overflows conceptually and how ASLR, DEP, and stack canaries mitigate them.
Describe how Metasploit is structured and how to use it responsibly.
Explain privilege escalation techniques for Linux and Windows environments.
Describe lateral movement techniques including pass-the-hash and pass-the-ticket.
Explain persistence mechanisms and their detection signatures.
Document post-exploitation activities with a timeline for the pentest report.
Key Terms#
Exploit: code or technique that triggers a vulnerability to achieve a desired effect.
Payload: the code executed after a successful exploit; commonly a reverse shell or Meterpreter.
Shellcode: machine code injected via a memory-corruption vulnerability.
Buffer overflow: writing beyond an allocated buffer to overwrite adjacent memory.
ASLR: Address Space Layout Randomisation; randomises memory addresses to defeat hardcoded jumps.
DEP/NX: Data Execution Prevention / No-Execute; marks memory pages non-executable.
Stack canary: a random value placed before the return address; checked before function return.
ROP: Return-Oriented Programming; chains existing code gadgets to bypass DEP.
Privilege escalation (privesc): gaining higher-privilege access than initially obtained.
Lateral movement: using initial access to compromise additional hosts on the network.
Pass-the-hash (PtH): authenticating with a captured NTLM hash without cracking it.
Pass-the-ticket (PtT): using a captured Kerberos ticket for authentication.
Persistence: mechanisms maintaining access after session termination or reboot.
Meterpreter: Metasploit’s in-memory payload providing an interactive post-exploitation shell.
9.1 What Exploitation Is (and Is Not)#
In a Penetration Test#
Exploitation in a pentest is the controlled demonstration that a vulnerability can be leveraged to achieve an attacker’s objective. The goal is evidence: a screenshot proving access was obtained, a hash or flag proving data was reached. Exploitation is not about causing damage; it stops at the minimum necessary to produce that evidence.
Ethical Boundaries#
A tester who gains a shell on a server does not run commands that could corrupt data, deny service
to users, or access data outside the authorized scope. The rule: minimum necessary access to prove
the point. Evidence of access to the /etc/passwd file proves Unix system compromise; it is not
necessary to also access the backup database unless that is specifically authorized.
9.2 Common Vulnerability Classes#
Injection Vulnerabilities#
Injection vulnerabilities arise when user-controlled data is interpreted as code or a command. SQL injection passes malicious SQL through an application input to query or modify a database. Command injection passes shell commands through an application that calls a system function. LDAP injection, XML injection, and template injection follow the same pattern. All injection attacks share a root cause: the application fails to separate data from instructions.
SQL Injection#
A login form that constructs SELECT * FROM users WHERE username='$u' AND password='$p' can be
bypassed by entering ' OR 1=1 -- as the username, producing a query that always returns true.
Union-based SQLi extracts data from other tables; blind SQLi infers data through true/false
responses or timing differences (time-based blind).
Memory Corruption#
Memory-corruption vulnerabilities occur when a program writes to memory it does not own. The classic example is a stack-based buffer overflow: a fixed-size buffer is filled with attacker- controlled data that overwrites the saved return address, redirecting execution to attacker-supplied shellcode.
Modern Mitigations#
Modern compilers and operating systems layer multiple mitigations:
Stack canary: a random 8-byte value placed between local variables and the saved return address. A canary check before function return detects stack corruption.
ASLR: randomises the base addresses of the stack, heap, and libraries at each execution, defeating attacks that hardcode addresses.
DEP/NX: marks the stack and heap as non-executable so injected shellcode cannot run.
PIE: Position-Independent Executable; randomises the executable’s own base address.
Return-Oriented Programming (ROP) was developed to bypass DEP: instead of injecting shellcode,
the attacker chains together short instruction sequences (gadgets) already present in the binary,
ending each gadget with a ret instruction. ROP does not require executable stack or heap.
Authentication and Session Vulnerabilities#
Broken authentication includes: default credentials (admin/admin), weak password policies, credential stuffing (re-using leaked username/password pairs), missing lockout after failed attempts, and insecure session tokens (predictable, short, or transmitted in the clear).
9.3 From Source to Machine Code: The Compilation Pipeline#
Memory-corruption exploitation requires understanding what a program is at runtime, so we begin with how source code becomes the bytes the CPU executes, the “programming survival skills” a low-level attacker needs. A C program passes through four stages, each of which can be inspected.
// hello.c -- the source
#include <stdio.h>
int main() { printf("Hello, world!\n"); return 0; }
Preprocessing (
gcc -E hello.c -o hello.i) expands#includeand macros into a single translation unit (hello.i).Compilation (
gcc -S hello.i -o hello.s) translates it to human-readable assembly.Assembly (
gcc -c hello.s -o hello.o) produces a binary object file (machine code with unresolved symbols).Linking (
gcc hello.o -o hello) resolves libraries into a runnable executable.
The assembly stage is where the runtime structure becomes visible. The x86-64 assembly for main sets up a
stack frame and calls into libc:
_main:
pushq %rbp ; save caller's base pointer
movq %rsp, %rbp ; establish this function's frame
leaq lC0(%rip), %rax ; address of "Hello, world!"
movq %rax, %rdi ; first argument
call _puts ; call library function
movl $0, %eax ; return 0
popq %rbp
ret ; pop return address into RIP and jump there
The two instructions that matter most for exploitation are call (which pushes the return address onto
the stack) and ret (which pops that address into the instruction pointer and jumps to it). Everything in
this chapter’s memory attacks comes down to controlling what ret finds on the stack.
9.4 Memory Corruption: The Stack, the Heap, and Buffer Overflows#
With the runtime model in hand, we can see how unsafe memory handling becomes code execution. A running process lays its memory out in regions: the text segment (the read-only machine code), the data/BSS segments (globals), the heap (dynamically allocated memory, growing up), and the stack (function frames with local variables, saved base pointer, and the all-important saved return address, growing down). A buffer overflow occurs when a program writes more data into a buffer than it can hold, spilling into adjacent memory, and depending on where the buffer lives, that adjacent memory may be another variable, the saved return address, or heap metadata.
The canonical example is a stack buffer overflow that subverts a decision. The program below reads a password
into a 15-byte buffer with gets(), a function so dangerous it was removed from the C standard because it has
no bounds checking at all.
// buf1_pass.c -- classic stack overflow / authentication bypass
int main(void) {
char buff[15];
int pass = 0;
printf("\n Enter the password : \n");
gets(buff); // NO bounds check -- writes past buff[15]
if (strcmp(buff, "thegeekstuff")) printf("\n Wrong Password \n");
else { printf("\n Correct Password \n"); pass = 1; }
if (pass) printf("\n Root privileges given to the user \n");
return 0;
}
Entering a password longer than fifteen characters overflows buff into the adjacent pass integer; a long
enough input sets pass to a non-zero value, so the program prints “Root privileges given to the user”
without the correct password. This is an authentication bypass produced purely by overwriting adjacent
stack memory, and it is a vivid argument for the principle of least privilege (Chapter 1): a program that need
not grant root should not, so that a memory bug cannot escalate to full control. Inspecting the program’s
assembly (buf1_pass.s) shows the same story at the machine level: subq $32, %rsp reserves the frame that
holds both buff and pass, and the overflow simply walks past the buffer into the rest of that frame, and,
with enough bytes, into the saved return address itself.
Stack Exhaustion and Heap Problems#
Not every memory fault is an exploitable overwrite; some merely crash, and distinguishing the cases matters for triage. Stack exhaustion happens when the stack grows beyond its limit. Declaring an enormous local array overflows the stack immediately,
// buf2_stack1.c -- allocating a 10^5 x 10^5 array on the stack -> stack overflow (crash)
int mat[100000][100000];
and unbounded recursion does the same by pushing frames forever (the buggy function below never reaches its
base case because it resets x to 6 each call):
// buf3_stack2.c -- infinite recursion exhausts the stack
void fun(int x) { if (x == 1) return; x = 6; fun(x); } // never terminates
The heap has its own failure modes. Allocating without freeing is a memory leak that slowly exhausts memory, while a single oversized request fails outright:
// buf4_heap1.c -- leak: malloc in a loop with no free()
for (int i = 0; i < 10000000; i++) { int *ptr = (int*)malloc(sizeof(int)); } // never freed
// buf5_heap2.c -- one huge allocation
int *ptr = (int*)malloc(sizeof(int) * 10000000);
These bugs typically cause denial of service rather than code execution, but related heap errors, use-after-free, double-free, and heap overflows that corrupt allocator metadata, are exploitable and are the dominant memory-safety bug class in modern browsers and kernels. The unifying lesson, and the reason memory-safe languages (Rust, Go, Java) and hardened allocators exist, is that C and C++ place the entire burden of bounds and lifetime checking on the programmer.
9.5 From Stack Smashing to Return-Oriented Programming#
The truly dangerous overflow overwrites not an adjacent variable but the saved return address. Because
ret loads that address into the instruction pointer, an attacker who controls it controls execution: this is
“smashing the stack” (Aleph One, 1996). Classically the attacker also injected shellcode (machine code that
spawns a shell) into the buffer and pointed the return address at it.
Defenses and counter-defenses then escalated in a well-documented arms race:
Non-executable memory (DEP / the NX bit) marks the stack and heap non-executable, so injected shellcode cannot run. Attackers responded with code reuse: instead of injecting code, jump to code already present.
Return-to-libc redirects
retinto an existing library function (such assystem("/bin/sh")).Return-Oriented Programming (ROP), formalized by Hovav Shacham (2007), generalizes this. The attacker finds short instruction sequences ending in
ret, called gadgets, scattered through existing executable code, and overwrites the stack with a chain of gadget addresses. Each gadget does a tiny operation (load a register, add, store) and its trailingretjumps to the next, so the chain performs arbitrary computation using only code that is already there, defeating NX without injecting anything.
The defenses that blunt ROP are layered:
Address Space Layout Randomization (ASLR) randomizes where code and stack live, so the attacker cannot predict gadget or function addresses.
Stack canaries (StackGuard, Cowan et al., 1998) place a random value before the return address and check it on return; an overflow that reaches the return address corrupts the canary and aborts the program.
Control-Flow Integrity (CFI) restricts indirect jumps and returns to legitimate targets, and hardware features such as shadow stacks (Intel CET) enforce that returns match calls.
Least privilege and sandboxing (Chapter 1, Chapter 11) limit what a successful exploit can reach.
flowchart TD
A[Overflow overwrites saved return address] --> B{Is the stack executable?}
B -- "Yes (no NX)" --> C[Inject and run shellcode]
B -- "No (NX/DEP)" --> D[Reuse existing code]
D --> E[return-to-libc]
D --> F[ROP gadget chain]
C --> G{Mitigations}
E --> G
F --> G
G --> H[ASLR randomizes addresses]
G --> I[Stack canary detects overwrite]
G --> J[CFI / shadow stack blocks bad returns]
G --> K[Least privilege limits impact]
Knowledge Check
In
buf1_pass.c, what exactly does the overflow overwrite to bypass the password check, and which secure function should replacegets()?Why did the NX bit not end memory-corruption exploitation, and what technique did attackers adopt instead?
Name two mitigations that specifically make ROP harder and say how each works.
Answers: (1) It overflows buff into the adjacent pass integer, setting it non-zero so the “root
privileges” branch runs; replace gets() with a bounded read such as fgets(buff, sizeof buff, stdin). (2) NX
stops injected code from executing, so attackers switched to code reuse, return-to-libc and ROP, which run
code already present. (3) ASLR randomizes addresses so gadget/function locations are unpredictable; stack
canaries place a random guard before the return address and abort if an overflow corrupts it; CFI/shadow stacks
restrict returns to legitimate targets.
9.6 Programming Survival Skills for Exploitation#
Before automating exploitation with frameworks, a practitioner needs a working mental model of how programs use the CPU and memory, the “programming survival skills” that separate someone who runs exploits from someone who understands them. Building on the compilation pipeline above, three ideas recur.
Registers and the calling convention. The CPU holds working values in registers. On x86-64, RIP is the
instruction pointer (the address of the next instruction to execute), RSP points to the top of the stack,
RBP anchors the current stack frame, and RAX typically holds return values; the first integer arguments are
passed in RDI, RSI, RDX, RCX, R8, R9. Exploitation is, at bottom, the art of getting attacker-chosen data
into RIP (or into the registers a ret/call will use), which is why the stack’s saved return address is the
prize.
The stack frame. Each function call pushes a frame: arguments, the saved return address (pushed by call),
the saved base pointer, and local variables. Because the stack grows downward while buffers are written upward,
a buffer overflow (above) walks toward the saved return address, the geometry that makes stack smashing
possible.
System calls and tooling. Ultimately a program asks the kernel for services (open a file, spawn a process)
via system calls. An exploit’s goal is usually to invoke execve("/bin/sh", ...) to get a shell. To see
all of this concretely, attackers use a debugger such as GDB (with extensions like GEF or pwndbg) to set
breakpoints, inspect registers and memory, and watch the stack as input is processed, and endianness
matters when writing addresses (x86 is little-endian, so the address 0xdeadbeef is written byte-reversed in
the payload). These skills are the prerequisite for everything that follows, and they are exactly the content
of the “Basic Linux Exploits” and “Programming Survival Skills” tradition this section draws on.
9.7 Shellcode and Shellcode Strategies#
When an overflow gives control of execution, the attacker needs something for the program to do; classically
that something is shellcode, a small piece of position-independent machine code, typically crafted to call
execve("/bin/sh") and hand the attacker an interactive shell. Writing shellcode imposes unusual constraints
that define the craft.
No null bytes. Because the vulnerable copy is often a C string function that stops at a null byte (
0x00), shellcode must usually be null-free, achieved by choosing instructions whose encodings avoid zero bytes (for examplexor eax, eaxto zero a register instead ofmov eax, 0).Position independence. The code cannot assume where it will land in memory, so it computes addresses relative to itself.
NOP sleds. Because the exact landing address is hard to predict, the attacker prepends a run of no-operation instructions (a NOP sled); jumping anywhere in the sled “slides” execution down into the shellcode, widening the target.
Encoders and bad characters. Some bytes are forbidden by the target’s input handling (for example newline,
0x0a). Attackers run shellcode through an encoder (such as Metasploit’s classicshikata_ga_nai) that rewrites it to avoid those “bad characters” and to evade naive signatures, prepending a small decoder stub that restores the original at runtime.
On modern systems, injecting and executing shellcode on the stack is blocked by the NX bit (above), so pure
shellcode injection is largely historical; it is defeated by NX and must be replaced by the code-reuse and ROP
techniques already described. Shellcode remains essential, however, as the final payload that an ROP chain
ultimately stages into executable memory (for example after calling mprotect to make a region executable).
9.8 The Exploit-Development Workflow#
Turning a crash into a working exploit follows a repeatable workflow, and understanding it demystifies what an exploit framework automates.
Fuzz and find a crash. Feed the target malformed or oversized input (manually or with a fuzzer) until it crashes, signaling that input reached something it should not have. Modern coverage-guided fuzzers such as AFL use genetic (evolutionary) algorithms: they treat inputs as a population, keep those that reach new code paths, and mutate and recombine them across generations to evolve toward deeper crashes, which is far more effective than random input. (Genetic algorithms, a general optimization technique inspired by natural selection, recur across security in fuzzing, malware detection, and parameter tuning.)
Control the instruction pointer. Reproduce the crash in a debugger and confirm that attacker bytes land in
RIP/EIP(the program tries to “return” to an attacker-controlled address). A cyclic pattern (such as Metasploit’spattern_create/pattern_offset) finds the exact offset at which the saved return address is overwritten.Find space and avoid bad characters. Determine where the payload can live and enumerate bad characters the input handling mangles, so the payload avoids them.
Redirect execution. Overwrite the return address with the address of something useful: classically a
JMP ESPgadget in a non-randomized module that bounces execution into the shellcode placed on the stack; on modern systems, the start of an ROP chain that defeats NX and, combined with an information leak, defeats ASLR.Stage the payload. Deliver shellcode (or a staged payload) and gain a shell or implant.
Each modern mitigation inserts a step: a stack canary forces the attacker to leak or avoid the canary; NX forces ROP; ASLR forces an information leak to discover addresses; CFI constrains which gadgets are reachable. Exploitation today is therefore rarely a single overflow but a chain of a memory bug plus an information leak plus a bypass, which is why memory-safe languages and these layered defenses (above) are so valuable.
In-Class Exercise: find the offset (authorized lab only)
On an intentionally vulnerable lab binary (for example from a CTF or a course VM), feed it a long input and
confirm in GDB that you can overwrite the saved return address. Use a cyclic pattern to compute the exact
offset to RIP, then craft an input that places a known value (such as 0x4242424242424242) into RIP.
Document the offset and the register state at the crash. Do this only on systems you are authorized to test.
Inside the Metasploit Framework#
The section heading above introduced Metasploit; here we go deeper, because it is the tool that ties this
chapter together and the one students will use most. The Metasploit Framework (open source, with the
commercial Pro edition) is a modular exploitation platform driven from the msfconsole command line. Its power
is in its module types:
exploit modules deliver a vulnerability trigger;
payload modules are what runs after a successful exploit;
auxiliary modules do scanning, fuzzing, and other non-exploit tasks;
post modules run on an already-compromised host (credential dumping, pivoting, enumeration);
encoder and nop modules transform payloads to avoid bad characters and signatures.
A typical session is search for a module, use it, show options and set the required parameters
(RHOSTS, LHOST, LPORT, PAYLOAD), then exploit. Payloads come in two crucial flavors. Staged
payloads (written windows/meterpreter/reverse_tcp) send a tiny first-stage stub that pulls down the rest over
the network, useful when buffer space is tight; stageless payloads (windows/meterpreter_reverse_tcp)
deliver everything at once. Orthogonally, a reverse payload makes the victim connect back to the attacker
(bypassing inbound firewall rules, the usual choice), while a bind payload opens a listener on the victim.
Meterpreter is Metasploit’s flagship payload: an in-memory, extensible agent that gives a rich
post-exploitation API (file system access, hashdump, screenshotting, keylogging, getsystem privilege
escalation, and pivoting through route) while leaving little on disk. The companion tool msfvenom
generates standalone payloads in many formats (msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=... -f exe -o payload.exe) and applies encoders, which is how payloads are embedded in phishing documents or trojaned
binaries (Chapter 4, Chapter 15). Used responsibly on authorized targets, Metasploit turns the manual
exploit-development workflow above into repeatable, auditable steps.
9.9 Passive and Static Analysis: Reverse Engineering for Exploitation#
Finding the vulnerability in the first place, and understanding a target binary or piece of malware, is the domain of program analysis, which divides into two complementary approaches. Static (passive) analysis examines a program without running it: reading its disassembly, strings, imports, and control flow. Dynamic analysis runs the program (often in a sandbox or debugger) and observes its behavior, which connects to the malware analysis of Chapter 15.
The static analyst’s toolkit is standard: file identifies a binary’s type and architecture; strings
extracts embedded text (URLs, error messages, hard-coded secrets); nm/objdump/readelf reveal symbols,
sections, and disassembly; and full disassemblers/decompilers, the NSA’s open-source Ghidra, the
commercial IDA Pro, and Binary Ninja or radare2, reconstruct higher-level structure and even
pseudo-C from machine code. Reverse engineering serves both offense and defense: an exploit developer reads a
patched function to find the bug it fixed (enabling “1-day” exploits and patch diffing), while a defender
reverses malware to extract indicators of compromise. The same skills underpin vulnerability research,
auditing source or binaries for new (0-day) flaws, which is where the memory-corruption knowledge of this
chapter is applied in anger. Static analysis is also exactly what the SAST tools of Chapter 10 automate over
source code, the visitor-pattern AST traversal mentioned there.
Privilege Escalation in Depth#
The existing discussion of privilege escalation deserves concrete technique, because gaining a low-privileged foothold is rarely the goal; attackers want root (Linux) or SYSTEM/Administrator (Windows). Escalation is either vertical (low to high privilege) or horizontal (to another same-level account), and it exploits misconfiguration as often as code bugs.
On Linux, common vectors include misconfigured SUID/SGID binaries (a program that runs as its owner,
often root, and can be coerced into running attacker commands, see GTFOBins), overly permissive sudo rules,
writable cron jobs or PATH entries, exposed credentials and SSH keys, and, as a last resort, kernel
exploits against an unpatched kernel. Enumeration scripts such as LinPEAS automate the search. On Windows,
vectors include unquoted service paths, weak service permissions, DLL hijacking (planting a malicious
DLL in a search-path location), token impersonation (Meterpreter’s getsystem, “potato” attacks abusing
SeImpersonatePrivilege), UAC bypasses, and stored credentials in the registry or LSASS memory (dumped
with Mimikatz); WinPEAS and PowerUp automate discovery. The unifying defense is the principle of least
privilege (Chapter 1): services run as low-privileged accounts, patches close kernel and service bugs, and
credential-guard features protect secrets in memory, so that a foothold cannot become total control.
Post-Exploitation, Lateral Movement, and Defense Evasion#
Once a host is compromised and privileges raised, the attacker pursues the objective, and the techniques here map directly onto the MITRE ATT&CK tactics defenders track. Lateral movement spreads to other systems using stolen credentials rather than new exploits: pass-the-hash (authenticating with an NTLM hash without the plaintext password), pass-the-ticket and Kerberoasting against Active Directory (Chapter 11), and remote-execution tools such as PsExec, WMI, and WinRM/RDP. Persistence ensures the attacker survives reboots and credential changes via scheduled tasks, services, registry run-keys, web shells, SSH keys, or cron jobs (the techniques cataloged here recur in Chapter 14’s incident response).
Defense evasion is the thread connecting all of it: disabling or blinding security tools, clearing logs,
living off the land with built-in binaries (LOLBins such as certutil and powershell) to avoid dropping
malware, and obfuscating or encoding payloads to evade antivirus (Chapter 15). Modern endpoint detection and
response (EDR) makes this harder, which is why attackers increasingly favor in-memory, fileless techniques. (In April 2026, ATT&CK v19 split the former Defense Evasion tactic into two: Stealth (TA0005), hiding malicious activity within legitimate behavior, and Defense Impairment (TA0112), actively disabling or degrading security controls, a distinction that maps cleanly onto the techniques above.) For
the ethical hacker, the point of practicing post-exploitation is not the access itself but demonstrating
impact, what an attacker could actually reach and do, which is the finding that drives remediation.
Knowledge Check
Why does the NX bit push attackers from shellcode injection toward return-oriented programming?
In Metasploit, what is the difference between a staged and a stageless payload, and between a reverse and a bind payload?
Name one Linux and one Windows local privilege-escalation vector and the least-privilege defense against it.
Answers: (1) NX makes injected stack/heap data non-executable, so attackers reuse existing executable code via ROP/ret2libc instead of running injected shellcode. (2) Staged sends a small stub that downloads the rest; stageless sends the whole payload at once. A reverse payload connects from victim back to attacker (bypassing inbound firewalls); a bind payload opens a listener on the victim. (3) Linux: a misconfigured SUID binary or sudo rule, fixed by removing unnecessary SUID bits and tightening sudoers; Windows: unquoted service paths or token impersonation, fixed by quoting paths, least-privilege service accounts, and removing SeImpersonatePrivilege where not needed.
Software Design Patterns and Security#
Design patterns are reusable solutions to recurring software-design problems, and they meet security in two ways: classic patterns can be implemented securely or insecurely, and a separate family of patterns exists specifically to encode security.
Among the classic Gang-of-Four patterns, several carry security implications. The Singleton centralizes shared state, handy for one audit-log or configuration object but a bottleneck and a tampering target if it is globally mutable. Factory and Builder patterns make good security choke points: routing all object creation through a factory lets you enforce validation and safe defaults in one place (a “secure factory”). The Proxy and Decorator patterns are the natural home for cross-cutting controls such as access checks, rate limiting, and logging wrapped around a sensitive object. Observer and event-driven designs must guard against untrusted subscribers and event injection. Insecure deserialization (a recurring web flaw, Chapter 10) often hides behind object-creation patterns that rebuild arbitrary types from input.
A second family, the security design patterns, encodes defensive best practice directly: the Intercepting Validator (validate and canonicalize all input at the boundary), the Authorization Enforcer or policy-decision-point pattern (centralize access decisions rather than scattering checks), the Secure Factory, the Single Access Point (one controlled entry such as a gateway or bastion, Chapter 11), and Defense in Depth itself as an architectural pattern (Chapter 1). These complement the secure-design principles of Saltzer and Schroeder (Chapter 1) and the threat-modeling step of Chapter 6: patterns provide vetted building blocks, but only a threat model tells you which controls each component actually needs. The anti-patterns are equally instructive and should be avoided: hardcoded secrets, security by obscurity, authorization checks scattered throughout the code, and trusting client-side validation alone.
A second example is the proxy and decorator pair. A protection proxy wraps a sensitive object and performs an authorization check before forwarding any call, which centralizes access control so it cannot be bypassed by forgetting a check at one call site; a decorator can add input validation or audit logging around an existing component without changing it. Used together with the factory and singleton patterns discussed above, they show how disciplined design makes security properties structural rather than incidental. The same patterns can be abused: an attacker who can substitute a malicious factory or proxy into a dependency-injection container can intercept every object it produces, which is why integrity of configuration and dependencies (Chapter 17) matters as much as the code itself.
9.10 Metasploit Framework#
Structure#
Metasploit organizes attack capabilities into modules:
Exploit modules: trigger a specific vulnerability.
Payload modules: execute after a successful exploit (reverse shell, Meterpreter, cmd).
Auxiliary modules: scanning, enumeration, brute-force, without exploitation.
Post modules: post-exploitation activities (gather credentials, escalate privileges).
Encoder modules: obfuscate payloads to evade signature detection.
Responsible Use#
Metasploit is a penetration-testing tool. Running it against systems without authorization is a criminal offense. In authorized engagements, the tester selects the narrowest exploit targeting the confirmed vulnerable version, uses the safest payload (avoid shellcode that crashes services), and documents every module and option used.
9.11 Privilege Escalation#
Linux Privilege Escalation#
Starting from a low-privilege shell, the tester seeks to reach root. Common vectors:
SUID/SGID Binaries#
Files with the SUID bit execute with the owner’s privileges (often root) regardless of who runs
them. find / -perm -4000 2>/dev/null finds all SUID files. GTFOBins documents SUID binaries
(vim, find, python) that can be leveraged to spawn a root shell.
Sudo Misconfiguration#
sudo -l lists commands a user can run as root. A misconfigured sudoers entry allowing sudo vim
or sudo python can trivially escalate to root via the editor’s shell escape or Python’s
os.system().
Kernel Exploits#
An unpatched kernel may be vulnerable to a privilege escalation exploit (DirtyCow, PwnKit). These are high-risk: kernel exploits can crash the system if they fail. Testers document the kernel version and CVE but often do not run kernel exploits in production environments without explicit authorization.
Windows Privilege Escalation#
Token Impersonation#
Windows uses access tokens to identify the security context of processes. A low-privilege user who obtains a high-privilege token (via a SeImpersonatePrivilege or SeAssignPrimaryTokenPrivilege vulnerability) can escalate. Potato exploits (RottenPotato, JuicyPotato) exploit this on service accounts.
Unquoted Service Paths#
Windows service executables with unquoted paths containing spaces allow path-traversal privilege
escalation: if C:\Program Files\Vendor\service.exe is configured without quotes, Windows will
try C:\Program.exe first. Placing a malicious executable at that path causes it to execute as
SYSTEM when the service starts.
9.12 Lateral Movement#
Pass-the-Hash#
Windows NTLM authentication accepts a hash in place of a password. An attacker who extracts NTLM hashes from the SAM database, LSASS memory, or network captures using Mimikatz can authenticate to other machines on the network as that user without cracking the hash. PtH is a fundamental reason organizations should mandate Credential Guard and disable NTLM where possible.
Pass-the-Ticket#
Kerberos authentication uses tickets. A forged or captured TGT or service ticket can be injected
into a session using Mimikatz’s kerberos::ptt. A Golden Ticket is a forged TGT signed with the
KRBTGT account’s hash; it grants access to any service in the domain and remains valid even after
a user’s password change (until the KRBTGT hash is rotated twice).
9.13 Persistence#
Common Persistence Mechanisms and Their Detection Signatures#
Mechanism |
OS |
Detection |
|---|---|---|
Registry Run keys |
Windows |
Monitor HKCU/HKLM Run key writes |
Scheduled tasks |
Windows |
Event ID 4698 (task created) |
Cron jobs |
Linux |
Monitor /etc/cron* and user crontabs |
Systemd service |
Linux |
New .service files in /etc/systemd |
SSH authorised_keys |
Linux |
Monitor ~/.ssh/authorized_keys writes |
WMI subscriptions |
Windows |
WMI activity logs; EDR alerts |
DLL hijacking |
Windows |
Monitor DLL loads from user-writable paths |
9.14 Privilege Escalation Paths: Windows, Linux, and Active Directory#
Section 9.11 introduced privilege escalation in general; in real engagements it usually follows well-worn paths worth naming. On Linux, common local escalations include misconfigured sudo rules, setuid binaries, writable cron jobs or service files, exposed credentials, and exploitable kernel versions, and tools such as LinPEAS automate the search for them. On Windows, frequent paths include unquoted service paths, services with weak file or registry permissions, the always-install-elevated policy, token impersonation, and credential theft from memory, which WinPEAS and similar tools enumerate.
In enterprise networks the highest-value paths run through Active Directory (AD), the directory service that authenticates users with Kerberos. Several abuses are standard knowledge:
Kerberoasting: any authenticated user can request service tickets for accounts that run network services, then crack them offline to recover those accounts’ passwords (the offline-cracking idea from Chapter 2 and Chapter 9 applies directly).
AS-REP roasting: accounts that do not require Kerberos pre-authentication leak a crackable hash to any requester.
Pass-the-hash and pass-the-ticket: a stolen password hash or Kerberos ticket can be reused to authenticate without ever knowing the plaintext password.
Golden and silver tickets: an attacker who obtains the domain krbtgt key or a service-account key can forge tickets that grant arbitrary access.
DCSync: an account with directory replication rights can ask a domain controller to hand over password hashes as if it were another controller.
Defenders map and cut these paths with tools such as BloodHound, which graphs the relationships in a directory to reveal the shortest route from a low-privilege foothold to domain administrator, and they reduce exposure with strong service-account passwords or group managed service accounts, tiered administration, least privilege, and monitoring for the telltale ticket requests. Frameworks such as Metasploit and dedicated post-exploitation toolkits package many of these techniques, which is exactly why detecting their characteristic behavior matters.
Chapter Summary#
This chapter explained how a discovered weakness becomes working code execution. It defined what exploitation is and is not, surveyed common vulnerability classes, and traced the compilation pipeline from source to machine code. It developed memory corruption in depth through the stack, the heap, and buffer overflows, advancing from stack smashing to return-oriented programming, and covered the programming survival skills, shellcode strategies, and exploit-development workflow that practitioners rely on. It then addressed reverse engineering for exploitation, the Metasploit Framework, and the post-access phases of privilege escalation, lateral movement, and persistence. The central message is that exploitation is a methodical engineering discipline and that the same understanding of memory and control flow underpins effective defenses such as ASLR, DEP, and stack canaries.
Why This Matters#
Understanding exploitation and post-exploitation from the attacker’s perspective is essential for defenders. Knowing that an unquoted service path allows privilege escalation drives the policy to scan for and remediate this configuration. Knowing that pass-the-hash works drives the decision to enable Credential Guard and disable NTLM. Defenders who understand how attacks work implement controls that address root causes rather than surface symptoms.
News in Focus: The Ransomware Post-Exploitation Playbook#
Documented ransomware campaigns consistently follow the same post-exploitation playbook: gain initial access (via phishing or exploitation of public-facing vulnerabilities), escalate privileges within hours, disable security tools, exfiltrate data, and then deploy the ransomware payload laterally across the network. The technical capabilities used (pass-the-hash, Golden Tickets, scheduled task persistence) are thoroughly documented in MITRE ATT&CK and are detectable with properly configured endpoint detection. The gap between the techniques being known and organizations detecting them reflects insufficient defensive implementation.
# Chapter 9 -- Safe simulation: bounds check, privilege check, MITRE ATT&CK mapper
# ── Safe buffer-bounds simulation (no actual shellcode) ───────────────────────
class SafeBuffer:
def __init__(self, size):
self.size = size
self._data = bytearray(size)
self.canary = 0xDEADBEEF
def write(self, data: bytes, offset: int = 0) -> str:
end = offset + len(data)
if end > self.size:
return (f"OVERFLOW DETECTED: attempted to write {len(data)} bytes "
f"at offset {offset} into a {self.size}-byte buffer "
f"(overflow by {end - self.size} bytes). "
f"Canary value would be overwritten.")
self._data[offset:end] = data
return f"Write OK: {len(data)} bytes at offset {offset}"
buf = SafeBuffer(64)
print("=== Buffer Bounds Checks ===")
for size, offset in [(20,0),(50,0),(10,55),(64,0),(65,0)]:
payload = b"A" * size
result = buf.write(payload, offset)
print(f" write({size} bytes @ offset {offset}): {result}")
# ── Privilege escalation checker ──────────────────────────────────────────────
print("\n=== Linux Privilege Escalation Checks (simulation) ===")
checks = [
("Sudo -l reveals vim or python", True, "GTFOBins shell escape: sudo vim -> :!/bin/bash"),
("SUID bit on /usr/bin/find", True, "find . -exec /bin/bash -p \\; escalates to owner UID"),
("Writable /etc/passwd", False, "Add root-equivalent entry"),
("Kernel 5.8 (CVE-2021-4034 PwnKit)", True, "Local root via polkit pkexec; patch immediately"),
("World-writable cron script", True, "Modify cron script to spawn reverse shell as root"),
]
escalation_paths = []
for check, vulnerable, technique in checks:
status = "VULNERABLE" if vulnerable else "OK "
print(f" [{status}] {check}")
if vulnerable:
escalation_paths.append(f" -> {technique}")
print("\n Escalation paths found:")
for p in escalation_paths:
print(p)
# ── MITRE ATT&CK technique mapper ─────────────────────────────────────────────
print("\n=== MITRE ATT&CK Technique Mapping ===")
attack_map = {
"T1059.001": ("Command and Scripting Interpreter: PowerShell", "Execution"),
"T1078": ("Valid Accounts", "Persistence, Defence Evasion"),
"T1550.002": ("Use Alternate Auth Material: Pass-the-Hash", "Lateral Movement"),
"T1558.001": ("Steal or Forge Kerberos Tickets: Golden Ticket","Credential Access"),
"T1053.005": ("Scheduled Task/Job: Scheduled Task", "Persistence, Privilege Escalation"),
"T1055": ("Process Injection", "Defence Evasion, Privilege Escalation"),
}
print(f" {'TTP ID':<12} {'Name':<48} {'Tactic'}")
print(" " + "-"*80)
for tid, (name, tactic) in attack_map.items():
print(f" {tid:<12} {name:<48} {tactic}")
=== Buffer Bounds Checks ===
write(20 bytes @ offset 0): Write OK: 20 bytes at offset 0
write(50 bytes @ offset 0): Write OK: 50 bytes at offset 0
write(10 bytes @ offset 55): OVERFLOW DETECTED: attempted to write 10 bytes at offset 55 into a 64-byte buffer (overflow by 1 bytes). Canary value would be overwritten.
write(64 bytes @ offset 0): Write OK: 64 bytes at offset 0
write(65 bytes @ offset 0): OVERFLOW DETECTED: attempted to write 65 bytes at offset 0 into a 64-byte buffer (overflow by 1 bytes). Canary value would be overwritten.
=== Linux Privilege Escalation Checks (simulation) ===
[VULNERABLE] Sudo -l reveals vim or python
[VULNERABLE] SUID bit on /usr/bin/find
[OK ] Writable /etc/passwd
[VULNERABLE] Kernel 5.8 (CVE-2021-4034 PwnKit)
[VULNERABLE] World-writable cron script
Escalation paths found:
-> GTFOBins shell escape: sudo vim -> :!/bin/bash
-> find . -exec /bin/bash -p \; escalates to owner UID
-> Local root via polkit pkexec; patch immediately
-> Modify cron script to spawn reverse shell as root
=== MITRE ATT&CK Technique Mapping ===
TTP ID Name Tactic
--------------------------------------------------------------------------------
T1059.001 Command and Scripting Interpreter: PowerShell Execution
T1078 Valid Accounts Persistence, Defence Evasion
T1550.002 Use Alternate Auth Material: Pass-the-Hash Lateral Movement
T1558.001 Steal or Forge Kerberos Tickets: Golden Ticket Credential Access
T1053.005 Scheduled Task/Job: Scheduled Task Persistence, Privilege Escalation
T1055 Process Injection Defence Evasion, Privilege Escalation
Review Questions (MCQ)#
Q1. A stack canary mitigates buffer overflows by: A. Preventing writing to the stack B. Detecting stack corruption before function return C. Randomising memory addresses D. Marking the stack non-executable
Q2. Return-Oriented Programming (ROP) bypasses which mitigation? A. Stack canaries B. ASLR C. DEP/NX D. PIE
Q3. In Metasploit, the module that executes after a successful exploit is called: A. Encoder B. Auxiliary C. Payload D. Post
Q4. Pass-the-Hash works because: A. NTLM accepts a hash in place of a password for authentication B. Windows stores passwords in plaintext C. Kerberos uses MD5 for ticket signing D. LSASS can be read without privileges
Q5. A SUID binary is dangerous in privilege escalation because: A. It is always world-writable B. It executes with the file owner’s (often root) privileges C. It is visible only to root D. It bypasses the firewall
Q6. Which command lists files that a user can run with sudo?
A. whoami B. id C. sudo -l D. find / -perm -4000
Q7. A Golden Ticket attack requires which account’s hash? A. Domain Administrator B. KRBTGT C. Local Administrator D. Guest
Q8. Unquoted service paths allow privilege escalation on Windows because: A. The service runs as a guest B. Windows will execute a malicious file placed earlier in the path C. The registry key is world-writable D. The service runs without a security token
Q9. Which Windows Event ID indicates a new scheduled task was created? A. 4624 B. 4776 C. 4698 D. 4688
Q10. The MITRE ATT&CK technique T1550.002 describes: A. Spear phishing B. Pass-the-Hash C. Golden Ticket D. PowerShell execution
Answers: Q1 B, Q2 C, Q3 C, Q4 A, Q5 B, Q6 C, Q7 B, Q8 B, Q9 C, Q10 B.
Lab Assignment#
Part A – Buffer overflow simulation: Using the SafeBuffer class above, add a canary-check method that returns True if the canary value is intact and False if overwritten. Simulate three writes: a safe write, a write that overflows by exactly 1 byte, and a write that overwrites the simulated return address. Print the canary status after each write.
Part B – Linux privesc audit: On a Linux VM you own, run find / -perm -4000 2>/dev/null and list all SUID binaries. Look up three of them on GTFOBins. For each, describe whether a privilege escalation path exists and what it is.
Part C – ATT&CK mapping: For a fictional incident where an attacker used phishing for initial access, then ran Mimikatz to extract hashes, authenticated via PtH to three workstations, and created a scheduled task for persistence, map each action to the MITRE ATT&CK technique ID and tactic. Present the full kill chain.
Part D – Persistence detection: List the five persistence mechanisms from the chapter table. For each, write the specific command or detection rule (Sigma, Sysmon, or EventID) that would alert a SOC analyst to its creation.
References#
Aleph One (1996). Smashing the Stack for Fun and Profit. Phrack 49.
Shacham, H. (2007). The Geometry of Innocent Flesh on the Bone: Return-into-libc without Function Calls (on the x86). ACM CCS 2007.
Cowan, C., et al. (1998). StackGuard: Automatic Adaptive Detection and Prevention of Buffer-Overflow Attacks. USENIX Security.