Academy

Offensive Systems Security experimental

Offensive Systems Security Seeing memory and control flow, then bending them, inside a sandbox that never touches your machine The machine you attack. Memory, registers and the instruction pointer: how a program's state lives in bytes, and how an attacker reads it.. Identify registers, memory and the instruction pointer in a running program.. Explain how a program's state is represented in bytes.. Operate the emulator's instrumentation to observe machine state directly.. Memory and registers, The instruction pointer, State as bytes, Reading state in the sandbox The machine you attack Every exploit in this course is, underneath the jargon, a manipulation of ordinary machine state: bytes in memory and a handful of processor registers. Before bending that state you need to see it clearly. This lesson builds the mental model you will use for the rest of FORGE, and every exercise runs inside a WebAssembly (WASM) CPU-emulator sandbox, so you can watch this state directly without any risk to your own machine. Memory and registers A running program has two kinds of storage. Memory is a large, flat array of bytes, each with a numeric address, holding the program's code, its variables, and its stack. Registers are a small number of very fast storage slots built into the processor itself, used to hold values the CPU is actively working with: an integer being added, a memory address being computed, the base of the current stack frame. Memory is large but comparatively slow to access; registers are tiny but instantaneous. A useful analogy is a desk (registers, a handful of items within arm's reach) next to a filing cabinet (memory, vast but requiring a trip to fetch or store anything). Nearly every instruction either moves a value between memory and a register, or computes on values already sitting in registers. Registers are conventionally named, and this course uses the names the emulator reports directly: general-purpose registers hold values, and a dedicated stack pointer register tracks the top of the current stack. A memory address is just a number, commonly written in hexadecimal (for example, 0x1000), and any register or memory location can hold one, which is what makes it possible for a value that started life as ordinary data to later be read as an address. The instruction pointer One register is special: the instruction pointer (also called the program counter). It holds the address of the next instruction the CPU will fetch and execute. After each instruction, the instruction pointer normally advances to the following one, but a jump, a call, or a return can set it to point somewhere else entirely. Because the instruction pointer decides which code executes next, controlling its value is the single most powerful thing an attacker can do to a program: it is the difference between corrupting some data quietly and redirecting the program's entire behaviour. On the architecture this course models, the instruction pointer is exposed by the emulator as a distinct, always-visible field, deliberately separate from the general-purpose registers, precisely because its value matters more than any other single piece of state. A jump changes it directly and unconditionally; a call changes it while also saving the old value for later; a return loads it from a value the CPU trusts implicitly. State as bytes There is no fundamental difference between 'code' and 'data' in memory: both are just bytes. Whether a given byte is interpreted as an instruction or as a number depends entirely on how it is used, specifically, whether the instruction pointer ever comes to reference it as something to execute. This is precisely why memory corruption is dangerous: an attacker who can influence the bytes sitting in memory can, in the right circumstances, influence what the CPU later treats as instructions or as addresses to jump to. Concretely: the single byte 0x90 is, on many architectures, a valid one-byte instruction that does nothing and simply falls through to the next one, and it is equally an ordinary numeric value a program might store as data. Nothing in the byte itself says which one it is; only the instruction pointer's trajectory decides. This is precisely the ambiguity later modules exploit. Reading state in the sandbox FORGE's emulator lets you pause a running program and inspect its registers, memory and instruction pointer at any point, then single-step forward and watch exactly how each instruction changes that state. This is instrumentation: observing a live program rather than only reading its source. Every lab in this course is graded by asserting on this same machine state, not by trusting a written answer, so learning to read it accurately here is the foundation for everything that follows. This grading approach has a direct consequence for how you should work through the labs: guessing an answer and writing it down is worthless here, because nothing checks your written explanation, only the emulator's actual state after your input runs. Treat every claim you make about what a piece of code does as something to verify by stepping through it, not something to reason out on paper alone. Related CCI capabilities Computer Architecture (Course): (https://www.cambridgecyberinternational.com/en/insights/academy/computer-architecture/). Reverse Engineering (Course): (https://www.cambridgecyberinternational.com/en/insights/academy/reverse-engineering/). Formal Methods (Course): (https://www.cambridgecyberinternational.com/en/insights/academy/formal-methods/). System Dynamics (Course): (https://www.cambridgecyberinternational.com/en/insights/academy/system-dynamics/). CCI Lab: Run it, build with it, read the thinking, reuse the data. (https://www.cambridgecyberinternational.com/en/insights/lab/) The stack and function calls. Stack frames, the saved return address, local variables, and how a call and return move the instruction pointer.. Describe a stack frame and its contents.. Operate the emulator to locate the saved return address of a call.. Explain how return transfers control back to the caller.. The stack, Stack frames, The saved return address, Call and return The stack and function calls With the machine model from Module 00 in hand, we can look at the single structure that almost every classic exploit targets: the call stack. Understanding its layout is not optional background, it is the reason a buffer overflow can turn into control over what the CPU executes next. The stack The stack is a region of memory used to manage function calls. It grows and shrinks as functions are entered and exited, and on most architectures FORGE models it grows downward, toward lower addresses, as new frames are pushed. Because it grows in a fixed direction, the position of anything placed on the stack relative to anything else placed on it is predictable, which is exactly the property an attacker needs to reason about it, and exactly the property this module teaches you to read directly in the emulator. Because the stack grows downward on the architecture FORGE models, pushing a new value moves the stack pointer to a lower address, and the most recently pushed value sits at the lowest address currently in use. This direction matters concretely in Module 03: when an overflow writes past the end of a buffer, it writes toward higher addresses, the direction toward older values still sitting further up the stack, including, eventually, the saved return address. Stack frames Each time a function is called, it gets its own stack frame: a region of the stack holding that function's local variables, some saved copies of registers the function will reuse, and, critically, the saved return address. Frames stack on top of each other as functions call other functions, and each frame is torn down when its function returns. Nothing here is exotic, it's simple bookkeeping, but the fact that local variables and the return address live in the same contiguous region is what makes memory corruption dangerous later. A frame is created the moment a function is called and destroyed the moment it returns; nothing about it persists between calls, which is why local variables lose their values once a function exits. In the emulator, you can watch a new frame appear the instant a call executes, and disappear the instant the matching return runs. The saved return address When function A calls function B, the CPU needs to remember where to resume in A once B finishes. That address, the instruction right after the call, is pushed onto the stack as part of B's frame: the saved return address. It sits in memory like any other value, which means it can, in principle, be read or overwritten just like a local variable can. This single fact is the hinge on which Modules 04 and 05 turn. Compare this to a local variable of the same size sitting in the same frame: at the byte level, there is no difference at all between the two. Both are simply bytes at a known offset. The only reason overwriting a local variable is usually harmless while overwriting the saved return address is catastrophic is what happens next: whether that memory is later read as data or loaded directly into the instruction pointer. Call and return A call instruction does two things: it pushes the current instruction pointer (the return address) onto the stack, and it sets the instruction pointer to the target function's first instruction. A return instruction reverses this: it pops that saved value off the stack and loads it back into the instruction pointer, resuming execution exactly where the call left off. This is a purely mechanical operation, the CPU trusts whatever value is sitting at that stack location. If that value has been changed before the return executes, the CPU will resume execution wherever the new value points, whether or not that was ever a legitimate return address. Locating and reading this value live, as the program runs in the sandbox, is this module's core skill. It is worth being explicit about what the CPU does not do here: it does not check whether the value it is loading into the instruction pointer is a legitimate return address, whether it points inside the calling function, or whether it has been tampered with since it was pushed. That single design choice, made for speed decades ago, is the root cause of every technique taught in Modules 03 through 05. Related CCI capabilities Computer Architecture (Course): (https://www.cambridgecyberinternational.com/en/insights/academy/computer-architecture/). Reverse Engineering (Course): (https://www.cambridgecyberinternational.com/en/insights/academy/reverse-engineering/). Formal Methods (Course): (https://www.cambridgecyberinternational.com/en/insights/academy/formal-methods/). System Dynamics (Course): (https://www.cambridgecyberinternational.com/en/insights/academy/system-dynamics/). CCI Lab: Run it, build with it, read the thinking, reuse the data. (https://www.cambridgecyberinternational.com/en/insights/lab/) Reading a program as it runs. Single-stepping, watching memory and registers change, and the difference between static and live state.. Operate the emulator to single-step a program one instruction at a time.. Predict how state changes across an instruction.. Contrast static reading with live observation.. Single-stepping, Watching state change, Predicting the next step, Static versus live Reading a program as it runs Modules 00 and 01 gave you the vocabulary: registers, memory, the instruction pointer, the stack. This module gives you the technique that turns that vocabulary into evidence: watching a program execute one instruction at a time and confirming, rather than assuming, what each instruction actually does. Single-stepping Single-stepping means telling the emulator to execute exactly one instruction, then stop, so you can inspect the full machine state before continuing. This is different from simply running a program to completion and looking at the result. Stepping lets you attribute each change in memory or registers to a specific instruction, which is essential when you later need to know precisely which instruction corrupted which byte. Single-stepping is slow compared to running a program at full speed, and that is the point: FORGE trades execution speed for observability, because the goal is understanding exactly what happens at each instruction. A single arithmetic operation that looks trivial in disassembly can still be worth stepping through carefully the first several times, until predicting its effect becomes automatic. Watching state change Between any two steps, you can compare the register values and the contents of memory before and after. Most instructions change very little: an add updates one register, a store writes a handful of bytes to one address. By watching these small, deliberate changes accumulate, you build a precise, causal picture of what the program is doing, rather than a vague impression from reading the source code alone. This is the same discipline you will use in Module 03 to see exactly which bytes an overflow overwrites. A useful discipline is to note the instruction's mnemonic before stepping, form an explicit expectation of which register or memory address it will touch, and only then step and compare. When the expectation is wrong, that mismatch is far more informative than a correct guess, because it usually reveals a detail about the instruction's actual semantics a quick read of the disassembly alone would not surface. Predicting the next step A useful habit is to predict the instruction pointer's next value before you step, then check your prediction against the emulator. For a normal, sequential instruction, the pointer simply advances to the next one in memory. For a call, it jumps to the callee's first instruction. For a return, it jumps to whatever value is currently sitting at the saved-return-address location. For a conditional branch, it depends on a flag or register set by an earlier comparison. Getting this prediction wrong, and then finding out why, is one of the fastest ways to correct a flawed mental model. Conditional branches deserve particular attention because they are the one case where the next instruction pointer value is not simply the next instruction in memory: it depends on a flag set by whatever comparison ran immediately before. Learning to read that flag state and predict which way a branch will go, before stepping, is a skill this module deliberately drills. Static versus live Reading a program's source code or its disassembly, without running it, is static analysis: it shows you the instructions but not the values they will operate on. Many important values, an input's length, a computed address, the current contents of the stack, exist only once the program actually runs. Live observation in the emulator is the only way to see them. Exploitation is fundamentally a live-state activity: you are not describing what a program could do in theory, you are producing a specific, observable machine state and proving it happened. That is also how every lab in this course is graded. This distinction has a direct practical consequence for how you should approach the labs: reading a target function's disassembly is a useful first step for forming a hypothesis, but it is never sufficient on its own, and every lab is graded on the live machine state your input actually produces, not on a correct-sounding written analysis. Related CCI capabilities Computer Architecture (Course): (https://www.cambridgecyberinternational.com/en/insights/academy/computer-architecture/). Reverse Engineering (Course): (https://www.cambridgecyberinternational.com/en/insights/academy/reverse-engineering/). Formal Methods (Course): (https://www.cambridgecyberinternational.com/en/insights/academy/formal-methods/). System Dynamics (Course): (https://www.cambridgecyberinternational.com/en/insights/academy/system-dynamics/). CCI Lab: Run it, build with it, read the thinking, reuse the data. (https://www.cambridgecyberinternational.com/en/insights/lab/) Buffer overflows: corrupting memory. Unchecked writes past a buffer's bounds and how adjacent memory is overwritten.. Explain how an unchecked write overflows a buffer.. Predict which adjacent bytes an overflow overwrites.. Corrupt a target byte range in the sandbox.. Buffers and bounds, Unchecked writes, Overwriting adjacent memory, Corrupting a target Buffer overflows: corrupting memory This is the module where the machine model becomes an exploit. A buffer overflow is one of the oldest and still one of the most consequential classes of vulnerability in software, first popularised for a mass audience by Aleph One's 1996 Phrack article 'Smashing the Stack for Fun and Profit', and still relevant enough that Szekeres et al.'s 2013 survey 'SoK: Eternal War in Memory' treats memory corruption as an ongoing arms race rather than a solved problem. Buffers and bounds A buffer is simply a fixed-size region of memory reserved to hold data, for example an array of 16 bytes meant to store a short string. It has bounds: the range of addresses it is supposed to occupy and nothing more. Correct code checks that whatever it writes into a buffer fits within those bounds before writing. The overflow vulnerability exists precisely where that check is missing or wrong. A fixed-size 16-byte buffer, for example, has exactly 16 valid addresses it is meant to occupy; the seventeenth byte, whatever the surrounding code does or does not do to prevent writing it, belongs to whatever the compiler placed immediately afterward in memory. The buffer itself has no awareness of its own size at runtime; that knowledge lives only in the source code. Unchecked writes When a program copies data into a buffer without verifying the data's length against the buffer's capacity, and the length of that data is influenced by attacker-controlled input, you have the ingredients for an overflow: a write whose size the attacker can push past the buffer's edge. The write instruction itself does nothing malicious on its own, it simply stores bytes at successive addresses starting from the buffer. It has no concept of 'stopping at the boundary' unless the surrounding code enforces one. A classic example, and the one Aleph One's original article used, is a function that copies a caller-supplied string into a fixed buffer using a copy routine that stops only at a terminating byte in the input, never checking the destination buffer's actual capacity. If the input is longer than the buffer and contains no early terminator, the copy keeps writing past the buffer's end regardless of what memory it overwrites. Overwriting adjacent memory Because memory is a flat, contiguous space, whatever sits immediately after the buffer in that region gets overwritten once the write runs past the buffer's end. On the stack, as you saw in Module 01, a local buffer typically sits inside a frame alongside other local variables, saved registers, and the saved return address. An overflow does not know or care what it is overwriting, it simply continues writing bytes. The consequence depends entirely on what those adjacent bytes happen to be. In the sandbox, you can make this concrete by writing a distinctive, easily-recognisable byte pattern and watching in the emulator's memory view exactly how far past the buffer's declared bounds that pattern actually lands. Doing this once, and seeing the pattern appear somewhere you did not expect, is usually more convincing than reading about the concept in the abstract. Corrupting a target To corrupt a specific target range, for example a particular variable or the saved return address, you need two pieces of information: the offset from the start of the buffer to that target, and the exact length and content the write needs to reach and set those bytes correctly. Too short an input falls short of the target; too long or wrongly shaped input may overwrite it with the wrong value or corrupt something else needed for the program to keep running. In the sandbox, you will craft an input, observe the memory before and after the write, and confirm you actually hit the intended range, a bounds violation you can see directly, rather than one you merely assert happened. This module's lab deliberately targets a byte range that is not the saved return address, precisely so you build the skill of computing an offset and hitting a target precisely before Module 04 raises the stakes by aiming at the one target in the frame whose corruption has the most serious consequence. Related CCI capabilities Computer Architecture (Course): (https://www.cambridgecyberinternational.com/en/insights/academy/computer-architecture/). Reverse Engineering (Course): (https://www.cambridgecyberinternational.com/en/insights/academy/reverse-engineering/). Formal Methods (Course): (https://www.cambridgecyberinternational.com/en/insights/academy/formal-methods/). System Dynamics (Course): (https://www.cambridgecyberinternational.com/en/insights/academy/system-dynamics/). CCI Lab: Run it, build with it, read the thinking, reuse the data. (https://www.cambridgecyberinternational.com/en/insights/lab/) Overwriting the saved return address. Reaching the saved return address from an overflow and controlling the value written there.. Compute the offset from a buffer to the saved return address.. Overwrite the saved return address with a chosen value.. Verify the overwrite by emulator-state assertion.. From buffer to return address, Computing the offset, Writing a chosen value, Verifying the overwrite Overwriting the saved return address Module 03 showed that an overflow can reach and corrupt bytes adjacent to a buffer. This module aims that corruption at one specific, high-value target: the saved return address sitting in the same stack frame. This is the technique Aleph One's classic Phrack article made famous, and it is the direct ancestor of every stack-based control-flow exploit that follows in this course. From buffer to return address Recall from Module 01 that a stack frame typically lays out local variables, saved registers, and the saved return address in a fixed, predictable arrangement. If a local buffer sits before the return address in that layout, and the direction the stack grows means writing past the buffer moves toward it, then an overflow of that buffer is not just corrupting 'something nearby', it has a clear, reachable path to the exact value the CPU will later load into the instruction pointer. Not every buffer overflow has this property: if a vulnerable buffer sits after the return address in a given frame's layout, or the stack grows in the opposite direction on a given architecture, an overflow might never reach the return address at all, however large it grows. Confirming that this target's layout actually places the buffer before the return address, using the emulator rather than assuming it, is the first real step of this module's exercise. Computing the offset To overwrite the return address deliberately rather than by accident, you need the precise offset: the number of bytes from the start of the buffer to the first byte of the saved return address. This is found by inspecting the frame's layout as the program runs, or by sending a distinctive, position-revealing pattern and observing in the emulator exactly where it lands. Get the offset wrong by even a few bytes and you will either fall short of the return address, or overwrite it only partially while corrupting something else the function needs before it returns. A common and effective technique for finding this offset is a De Bruijn-style pattern: a long input string in which every four-or-so-byte window is unique, so that whatever bytes end up at the saved-return-address location can be looked up directly against the pattern to reveal exactly how many bytes preceded them, without manual counting. Writing a chosen value Once the offset is known, the attacker pads the input up to that offset with arbitrary filler, then places the desired target value at the offset itself. Whatever value is written there is what the CPU will treat as the address to resume execution at once the function returns. Nothing about the write instruction distinguishes 'ordinary buffer data' from 'a return address', the CPU's return instruction will trust it unconditionally. In this module's lab, the chosen value you place at that offset does not yet need to be a meaningful code address; any distinctive, recognisable value is enough to prove you can land precisely on the saved-return-address location and nowhere else. Module 05 is where that placed value is upgraded from 'a value that happens to be there' to a real, chosen control-flow target. Verifying the overwrite A correct overwrite is not something to assert in prose, it is something to prove with machine state: after the crafted write, the emulator should show the saved-return-address location in memory holding exactly the chosen value, before the function has even returned. This is the proof-of-work standard used throughout FORGE: the lab passes only when this specific state is actually produced, not when a plausible description of it is submitted. It is worth noting what this lab does not yet claim: at this point you have corrupted the saved return address, but the function has not yet returned, and the instruction pointer has not yet been affected. The overwrite is real and machine-verified, but it remains latent data until Module 05's return instruction actually reads and acts on it. Related CCI capabilities Computer Architecture (Course): (https://www.cambridgecyberinternational.com/en/insights/academy/computer-architecture/). Reverse Engineering (Course): (https://www.cambridgecyberinternational.com/en/insights/academy/reverse-engineering/). Formal Methods (Course): (https://www.cambridgecyberinternational.com/en/insights/academy/formal-methods/). System Dynamics (Course): (https://www.cambridgecyberinternational.com/en/insights/academy/system-dynamics/). CCI Lab: Run it, build with it, read the thinking, reuse the data. (https://www.cambridgecyberinternational.com/en/insights/lab/) Redirecting the instruction pointer. Driving the program counter to a chosen target on return, the essence of a control-flow hijack.. Construct an input that drives the instruction pointer to a chosen target on return.. Confirm the hijack by emulator-state assertion.. Explain why control-flow integrity would stop it.. From overwrite to hijack, Choosing a target, Confirming the hijack, What would stop it Redirecting the instruction pointer Everything so far has been building toward this: turning a memory-safety bug into control over which code the CPU executes next. This is the module where an overwritten return address becomes an actual, observed control-flow hijack, and where Szekeres et al.'s survey work on control-flow-hijack attacks becomes concrete rather than abstract. From overwrite to hijack Module 04 ended with the saved return address holding an attacker-chosen value in memory, but nothing had happened yet: the overwrite only matters once it is used. A control-flow hijack occurs the moment the function actually executes its return instruction, loading that corrupted value into the instruction pointer. Up to that instant, the corruption is latent data sitting in memory. The return instruction is what converts it into a change in what the program does. This distinction is useful to hold onto precisely because it generalises: throughout security work, the presence of corrupted state and the actual exploitation of that state are two separate events, sometimes separated by a long delay, and a defender who only checks for the former without understanding the latter can miss exactly when and how a bug becomes dangerous. Choosing a target In this foundational lab, the target address you drive the instruction pointer to is another function that already exists inside the same sandboxed program, not arbitrary injected code and never anything outside the emulator. This mirrors the real technique of redirecting execution to existing code, while keeping the exercise a single, clean step: choose a legitimate address in the program, place it at the correct offset, and let the return instruction carry control there. Restricting this exercise to an address that already exists inside the sandboxed program, rather than injected shellcode, keeps the lab's proof-of-work assertion simple and unambiguous: the emulator only needs to confirm the instruction pointer equals one specific, known-good address, rather than needing to detect and validate arbitrary injected code. Confirming the hijack A hijack is confirmed the same way every claim in this course is confirmed: by machine-state assertion. After the function returns, the emulator checks that the instruction pointer now equals the chosen target address, no more, no less. This is deliberately a fact about the CPU's actual state, not a description of what should happen in theory, which is exactly why it cannot be faked by simply describing the right answer. The assertion the emulator performs here is deliberately narrow and mechanical: it reads the instruction pointer register's actual value after the return instruction has executed and compares it, byte for byte, against the target address you were asked to reach. There is no partial credit and no interpretation involved. What would stop it The entire attack depends on one assumption holding: that the value loaded into the instruction pointer on return is unchecked. Control-flow integrity is a defence built directly against this assumption, it restricts indirect transfers like returns to a pre-approved set of legitimate targets, and rejects anything else before it ever reaches the instruction pointer. Seeing exactly how the hijack works here is what makes that defence's purpose concrete rather than abstract, a thread Module 07 picks up directly. Control-flow integrity is only one member of a broader family of defences you will meet properly in Module 07, alongside stack canaries and address-space randomisation, and each targets a different point in the chain this module just walked through: some prevent the corruption in the first place, others detect it before the return executes, and others reject the corrupted destination even if the return does execute. Related CCI capabilities Computer Architecture (Course): (https://www.cambridgecyberinternational.com/en/insights/academy/computer-architecture/). Reverse Engineering (Course): (https://www.cambridgecyberinternational.com/en/insights/academy/reverse-engineering/). Formal Methods (Course): (https://www.cambridgecyberinternational.com/en/insights/academy/formal-methods/). System Dynamics (Course): (https://www.cambridgecyberinternational.com/en/insights/academy/system-dynamics/). CCI Lab: Run it, build with it, read the thinking, reuse the data. (https://www.cambridgecyberinternational.com/en/insights/lab/) Containment and the rules. Why the sandbox is safe, and the legal and ethical bounds of exploitation.. Explain why the emulator sandbox cannot harm a host.. Apply the legal and ethical bounds of exploitation when scoping a test.. Distinguish authorised from unauthorised testing.. Why the sandbox is safe, Authorisation and consent, The law (computer misuse), Responsible practice Containment and the rules You have just achieved a real control-flow hijack. Before going any further, this module fixes two things firmly in place: why doing that here was safe, and why doing it anywhere else without permission is not just discouraged but unlawful. Technique and rules are taught together deliberately, because the same skill is legitimate or criminal depending entirely on authorisation. Why the sandbox is safe Every exercise in FORGE runs inside a WebAssembly (WASM) CPU emulator that models only a virtual CPU and a virtual block of memory. It issues no host system calls, opens no files on your real machine, and makes no network connections; it cannot reach outside itself. Even a fully successful hijack, exactly like the one you produced in Module 05, stays entirely contained within the emulated instruction pointer and emulated memory. This is what makes it responsible to teach real exploitation technique to learners: the 'target' is a purpose-built teaching binary that exists only inside the sandbox, not a live system. This containment is enforced at the level of the emulator itself, not by any convention you are trusted to follow: the WebAssembly (WASM) sandbox has no system-call interface exposed to the emulated program at all, so there is no code path, however cleverly the emulated instruction pointer is redirected, that reaches outside the sandbox to touch a real file, socket, or any part of the host machine. Authorisation and consent Outside the sandbox, the single decisive factor separating legitimate security testing from a crime is authorisation: explicit, scoped permission from the system's owner to test that specific target. The tool, the technique, and even the intent behind an action do not change this. A stack overflow used in an authorised penetration test and the identical overflow used against a system you were never given permission to touch are the same technique, but only one of them is lawful. In professional practice, this authorisation is normally documented explicitly in a signed engagement letter or rules-of-engagement document before any testing begins, specifying exactly which systems, Internet Protocol (IP) ranges, or applications are in scope, which techniques are permitted, and the time window during which testing may occur; acting against a system merely because it seems vulnerable, without this documented permission, is not a grey area. The law (computer misuse) In the United States, unauthorised access to a computer system is chiefly governed by the Computer Fraud and Abuse Act, 18 U.S.C. Section 1030, which criminalises accessing a protected computer without authorisation or in excess of granted authorisation. Most jurisdictions have equivalent computer-misuse statutes. These laws apply regardless of whether the access was educational, curious, or well-intentioned: intent to cause harm is not a required element of the offence in many cases. Outside the United States, comparable statutes exist under different names, for example the UK's Computer Misuse Act 1990 and the EU's Directive 2013/40/EU on attacks against information systems, and all share the same essential structure: unauthorised access or interference with a computer system is an offence regardless of the actor's technical skill or stated intentions. Responsible practice Professional offensive security work is built on three habits: obtaining written authorisation with a clearly defined scope before testing anything, staying strictly within that agreed scope, and practising responsible disclosure, reporting any vulnerability found to the system's owner and giving them reasonable time to remediate before any public detail is shared. Practise the techniques from this course only against the sandboxed targets FORGE provides, never against systems you do not own or have not been explicitly authorised to test. Responsible disclosure timelines vary by organisation, but a common convention is to give the system owner a defined period, often 90 days, to remediate a reported vulnerability before any public detail is shared, and to coordinate the exact disclosure date with the owner wherever possible rather than unilaterally imposing one; the goal of disclosure is a fixed vulnerability, not a public embarrassment. Related CCI capabilities Computer Architecture (Course): (https://www.cambridgecyberinternational.com/en/insights/academy/computer-architecture/). Reverse Engineering (Course): (https://www.cambridgecyberinternational.com/en/insights/academy/reverse-engineering/). Formal Methods (Course): (https://www.cambridgecyberinternational.com/en/insights/academy/formal-methods/). System Dynamics (Course): (https://www.cambridgecyberinternational.com/en/insights/academy/system-dynamics/). CCI Lab: Run it, build with it, read the thinking, reuse the data. (https://www.cambridgecyberinternational.com/en/insights/lab/) Defences and the arms race. Stack canaries, non-executable memory (NX/DEP) and address-space layout randomisation (ASLR), observed stopping the prior exploit.. Explain how a stack canary detects a return-address overwrite.. Explain how NX/DEP and ASLR raise the cost of exploitation.. Configure a defence and observe it stopping the Module 05 exploit.. Stack canaries, Non-executable memory (NX/DEP), Address-space layout randomisation (ASLR), The arms race Defences and the arms race Having built the Module 05 hijack yourself, you are now well placed to understand why the industry built specific defences against it, and to watch each one actually stop that same exploit inside the sandbox. This module covers three of the most important mitigations in the history of memory-safety defence. Stack canaries A stack canary is a known guard value placed on the stack between local buffers and the saved return address. Before a function returns, it checks whether the canary is still intact. An overflow large enough to reach and overwrite the return address will, on its way there, also overwrite the canary, so the check fails and the program aborts before the corrupted return address is ever loaded into the instruction pointer. This defence was introduced by Cowan et al. in 1998 under the name StackGuard, and it directly targets the exact technique from Modules 04 and 05: in the lab, you will enable a canary and watch the Module 05 exploit now abort on return instead of succeeding. A canary's value is chosen to be difficult to guess or reproduce, often incorporating bytes that are awkward for common overflow techniques to write correctly, such as a null byte that many string-copy functions cannot pass through at all. In the emulator, you can watch this concretely: with a canary enabled, the same input that succeeded in Module 05 now corrupts the canary's known value first, and the pre-return check catches that corruption before the tampered return address is ever loaded. Non-executable memory (NX/DEP) No-eXecute (NX), more commonly known by its Microsoft name, Data Execution Prevention (DEP), marks memory regions like the stack as non-executable, so even if an attacker successfully injects code as data, the CPU refuses to fetch and run instructions from that region. This closes off a whole category of exploit, injecting shellcode directly onto the stack, without needing to detect the corruption at all; it simply removes the possibility of executing what got written there. This mitigation does not stop the overflow itself, and it does not stop the return address from being overwritten; it stops one specific consequence of a successful hijack, namely jumping directly into attacker-supplied bytes sitting on the stack and executing them as instructions. A hijack that redirects the instruction pointer to code that already exists and is already marked executable elsewhere is unaffected by this mitigation alone, which is precisely the gap code-reuse techniques exploit. Address-space layout randomisation (ASLR) ASLR randomises where a program's code, stack, and other regions are loaded in memory each time it runs. Many exploits, including hijacks that need to jump to a specific target address, depend on knowing or guessing that address in advance. ASLR does not stop corruption from happening, but it raises the cost of exploiting it by removing the fixed, predictable addresses an attacker would otherwise rely on. In the sandbox, you can observe ASLR's effect directly by running the same exploit input against two separate runs of the target with randomisation enabled and comparing the base addresses the emulator reports for the stack and code regions; where they were identical without randomisation, they differ unpredictably between runs once it is turned on, breaking any hardcoded target address the exploit relied on. The arms race None of these defences is a permanent, final answer, which is why Szekeres et al. describe this domain as an 'eternal war'. Each mitigation raises the cost or closes off one avenue, and attackers have historically responded with new techniques, for example, once NX/DEP made injecting new code harder, attackers turned to reusing existing executable code already in memory, a bridge to code-reuse techniques that belong to a higher offensive track beyond this Level 1 course. The clear defensive lesson is that no single mitigation suffices: memory-safe coding at the source, combined with layered mitigations like canaries, NX/DEP, and ASLR, is what actually raises the cost of each step in an exploit chain. This module deliberately stops short of teaching code-reuse techniques such as return-oriented programming, which chain together short, already-executable instruction sequences already present in the program to accomplish arbitrary computation without ever injecting new code; understanding why non-executable memory alone is insufficient here is the necessary foundation for that higher-track material. Related CCI capabilities Computer Architecture (Course): (https://www.cambridgecyberinternational.com/en/insights/academy/computer-architecture/). Reverse Engineering (Course): (https://www.cambridgecyberinternational.com/en/insights/academy/reverse-engineering/). Formal Methods (Course): (https://www.cambridgecyberinternational.com/en/insights/academy/formal-methods/). System Dynamics (Course): (https://www.cambridgecyberinternational.com/en/insights/academy/system-dynamics/). CCI Lab: Run it, build with it, read the thinking, reuse the data. (https://www.cambridgecyberinternational.com/en/insights/lab/) Capstone: a verified control-flow hijack. Chain the skills into one clean, contained hijack, observe a defence stop it, and defend the analysis, all within the sandbox and the rules.. Construct and achieve a stated control-flow property, verified by assertion.. Show a defence stopping the exploit and explain why.. Defend the analysis within the containment and legal bounds.. Planning the exploit, Achieving the property, Showing the defence, Defending the analysis Capstone: a verified control-flow hijack This capstone asks you to bring every earlier module together into one coherent piece of work: plan and execute a clean control-flow hijack, show a real defence stopping it, and explain the whole chain with evidence rather than assertion. Nothing here is new technique, it is the disciplined combination of everything already learned. Planning the exploit Good exploitation starts with analysis, not trial and error. Begin by understanding the target's stack layout: where the vulnerable buffer sits in its frame, and the offset from that buffer to the saved return address, exactly the reasoning built in Modules 01, 03 and 04. Decide on your target address before crafting any input, then design the input's length and content deliberately, filler up to the offset, the chosen address at the offset, rather than guessing. Treat this planning step as a written artefact in its own right, not just a mental exercise: before touching the emulator, write down the buffer's declared size, the computed offset to the return address, and the exact target address you intend to reach, so that if your first attempt does not produce the expected machine state, you have a concrete plan to check against rather than starting over from guesswork. Achieving the property The pass condition for this capstone is concrete and machine-checked: the emulator must assert that the instruction pointer actually reaches your chosen target address after the function returns, the same standard used throughout the course. A well-reasoned plan that was never actually run to completion in the sandbox does not satisfy this requirement; the proof-of-work principle behind every FORGE lab is that the state must be produced, not merely described. If your first attempt fails the assertion, the most common causes are an off-by-one error in the computed offset, filler bytes of the wrong length, or a target address copied down incorrectly; work through each systematically using the same single-stepping and state-inspection discipline from Module 02, rather than randomly adjusting the input and re-running until something happens to work. Showing the defence Having achieved the hijack, enable one of the mitigations from Module 07, a stack canary is the most direct fit, and rerun the same exploit unchanged. Observe, in the emulator, that the mitigation now stops it: the canary check fails and the corrupted return is never used, or the equivalent effect for whichever mitigation you demonstrate. If a mitigation is enabled and the exploit still somehow succeeds, the rigorous response is to diagnose why the mitigation failed to engage, for example a misconfiguration, rather than to assume you have bypassed it; bypass techniques belong to the higher offensive track beyond this course. Choosing which mitigation to demonstrate is itself part of the exercise: a stack canary gives the cleanest, most legible before-and-after contrast for this exploit chain, since the corrupted byte and the check that catches it are both directly visible in the same frame you already built your mental model of, but demonstrating an alternative mitigation instead is equally acceptable if you can show the mechanism clearly. Defending the analysis Finally, write up each step of the chain with evidence drawn directly from machine state: the offset you computed, the value you wrote, the instruction-pointer assertion that confirmed the hijack, and the assertion showing the defence stopping it. Be precise and honest about scope: this hijack held against the provided sandboxed teaching target under stated conditions, contained entirely within the emulator, with no host system calls and no host memory ever touched. It is a verified teaching exercise, not a claim about any real-world system, and it stays within the authorisation and containment principles set out in Module 06 throughout. A strong writeup reads like the emulator's own evidence trail translated into prose: each claim about what happened is immediately followed by the specific assertion or memory-state observation that proves it, so a reader could, in principle, reproduce your result from your writeup alone without having to trust your word for any individual step. Related CCI capabilities Computer Architecture (Course): (https://www.cambridgecyberinternational.com/en/insights/academy/computer-architecture/). Reverse Engineering (Course): (https://www.cambridgecyberinternational.com/en/insights/academy/reverse-engineering/). Formal Methods (Course): (https://www.cambridgecyberinternational.com/en/insights/academy/formal-methods/). System Dynamics (Course): (https://www.cambridgecyberinternational.com/en/insights/academy/system-dynamics/). CCI Lab: Run it, build with it, read the thinking, reuse the data. (https://www.cambridgecyberinternational.com/en/insights/lab/)