Memory Corruption Mechanics and Mitigation Analysis — DedSec Project
Memory Corruption Mechanics and Mitigation Analysis Purpose: Go beyond the introductory memory-safety module and understand how corruption changes program state, how modern mitigations…
Purpose: Go beyond the introductory memory-safety module and understand how corruption changes program state, how modern mitigations constrain impact, and how to diagnose the bug class without constructing weaponized exploits.
Learning objectives
- Reason about stack and heap object layout, bounds, lifetime, and control data.
- Distinguish spatial from temporal memory errors.
- Understand how ASLR, NX/DEP, canaries, CFI, CET, PAC, RELRO, and sandboxing change exploitability.
- Analyze a sanitizer report and connect it to the violated invariant.
- Understand the concepts behind code-reuse attacks without building a payload.
Spatial and temporal safety
Spatial safety means an access stays within the bounds of the object it is intended to access. Out-of-bounds reads/writes violate this property. Temporal safety means the object is still alive when it is accessed. Use-after-free and double-free failures violate lifetime assumptions.
This distinction is useful because mitigations differ. Bounds-checked abstractions help spatial safety; ownership and lifetime systems help temporal safety. Some allocators add quarantine or metadata checks, but they do not turn unsafe code into memory-safe code.
Stack objects
A function may have local variables, saved registers, spill slots, alignment padding, and call metadata in or around its stack frame. Exact layout is compiler-, architecture-, ABI-, and optimization-dependent. Security analysis should therefore derive layout from the build rather than from a diagram copied from another platform.
A stack out-of-bounds write can corrupt adjacent locals or control data. But the result depends on layout and protections. Modern stack protectors can detect some corruption before a return, while shadow stacks or control-flow protections can defend control data separately.
Heap objects
The heap is managed by an allocator that maps requests to blocks and maintains metadata. Different allocators organize size classes, arenas, caches, freelists, quarantine, and metadata differently. A heap bug should be analyzed at the object-lifetime level first: which allocation created the object, who owns it, when was it freed, and which reference outlived that event?
Avoid learning allocator internals only through exploitation recipes. The durable skill is reconstructing allocation and reference lifetimes.
Integer errors as memory precursors
Many memory bugs begin before a pointer is dereferenced. A size can overflow during multiplication, be truncated into a narrower integer, change sign, or be validated in one unit and used in another. For every length, track:
- source type and width;
- signed/unsigned status;
- arithmetic performed;
- conversion points;
- allocation size;
- copy/parse size;
- maximum legal semantic value.
The allocation and the later use must agree on the same validated quantity.
Type confusion
Type confusion occurs when memory holding one object type is interpreted as another incompatible type. In native code this can arise from unsafe casts or corrupted metadata; in managed runtimes it can arise from engine/runtime bugs. Security impact depends on what fields are subsequently treated as pointers, lengths, function references, or security state.
The defensive review question is: what establishes the type invariant, and can untrusted state violate it?
Control data versus ordinary data
Not all corruption needs to change instruction flow to be serious. Data-only corruption can change authorization flags, object identities, lengths, or policy decisions. Modern control-flow defenses therefore reduce one class of impact while leaving data integrity as a major concern.
When triaging, ask what semantic field was corrupted before asking whether control flow was hijacked.
ASLR in detail
ASLR randomizes selected virtual address locations. Its effectiveness depends on entropy, architecture, process lifetime, module configuration, memory disclosures, and whether code/data has fixed mappings. Position-independent executables and libraries allow more components to move.
An information disclosure can weaken ASLR because addresses reveal layout. This is why an apparently “read-only” memory bug can materially change the impact of a separate write bug.
NX / DEP and executable permissions
NX/DEP marks data regions non-executable when supported. This prevents directly executing injected bytes from ordinary writable data pages, but it does not stop corruption itself. Attack research historically shifted toward reusing existing code and manipulating data because of this separation.
The defensive lesson is layered controls: W^X, CFI, sandboxing, memory-safe code, and least privilege complement one another.
Stack canaries
A compiler can place a random/integrity value near sensitive stack state and verify it before function return. If a sequential overwrite crosses the canary, the process aborts. Coverage and implementation vary; a corruption that does not touch the canary may remain undetected.
Use compiler metadata or hardening inspection rather than assuming every build has a protector enabled.
RELRO and relocation hardening
On ELF systems, relocation metadata and dynamic-linking structures can be made read-only after startup. Full RELRO reduces the amount of loader-related state that remains writable at runtime. This is an example of a general principle: initialization data that should not change after setup should become immutable.
Control-Flow Integrity, CET, and shadow stacks
Control-Flow Integrity constrains indirect branches to expected targets based on compiler/runtime policy. Intel CET includes mechanisms such as shadow stacks and indirect branch tracking. Shadow stacks maintain protected return-address state separately from ordinary writable stack memory.
These controls make some control-flow corruption substantially harder, but they do not prevent all logic/data attacks. Security assessment should identify what class each control covers.
ARM pointer authentication and branch protection
ARMv8.3-A and later architectures can support Pointer Authentication (PAC), which adds authentication information to selected pointers using secret keys held in architectural state. Modern ARM64 platforms may also use branch target identification. Exact adoption differs by OS, device, and compiler.
For Android/mobile analysis, recognize PAC-related instructions as mitigation evidence rather than ordinary application logic.
Memory tagging
Memory tagging associates metadata with allocations/pointers so invalid spatial or temporal accesses can be detected probabilistically or deterministically depending on the technology. ARM Memory Tagging Extension (MTE) is especially relevant to modern Android devices. Tagging improves detection/containment but does not remove the need for correct ownership and bounds.
Safe code-reuse concepts
When data pages are non-executable, historic exploitation research demonstrated that existing instruction sequences can be chained rather than injecting new code. You should understand this as a reason NX alone is insufficient. This guide intentionally stops at the concept and defensive implications; it does not provide gadget-chain construction or payload instructions.
Exploitability triage matrix
For a memory bug in owned code, record:
| Question | Why it matters |
|---|---|
| Can untrusted input reach it? | Reachability |
| Read, write, execute, or lifetime error? | Primitive class |
| Which object/state is affected? | Semantic impact |
| Is the corruption attacker-controlled? | Reliability/impact |
| Are addresses disclosed? | ASLR context |
| Is the process sandboxed? | Blast radius |
| What privileges/credentials exist? | Security boundary |
| Which mitigations are enabled? | Constraints |
| Is reproduction deterministic? | Engineering confidence |
Do not reduce the analysis to one “exploitable/not exploitable” guess without evidence.
Sanitizer-guided local lab
Use a deliberately buggy training program from a course or create a toy out-of-bounds example that only runs locally. Compile with AddressSanitizer/UndefinedBehaviorSanitizer where supported. Your deliverable is not a payload; it is a root-cause report containing allocation/object bounds, the first invalid access, source line, fix, and regression test.
Then rebuild with hardening enabled and document what mitigations exist. Note that hardening should remain enabled even after the source bug is fixed.
Guided study workflow
Before you begin
Complete Modules 53, 62, 63, 64, and 65.
Practice task
Analyze one sanitizer-detected bug in toy code. Classify it as spatial/temporal, identify the violated invariant, and produce a source-level fix plus regression test. Inventory platform mitigations separately.
Evidence to keep
Keep source, sanitizer report, build flags, mitigation inventory, and regression test. Do not create or retain weaponized payloads.
Common mistakes to avoid
- confusing a mitigation with a source-code fix;
- assuming the same stack/heap layout across builds;
- ignoring integer transformations;
- assuming only control-flow corruption matters;
- declaring reliability without repeated evidence.
Mastery check
Explain how an out-of-bounds write differs from use-after-free, how NX and ASLR constrain different things, and why data-only corruption can still be critical.
Continue with
Modules 67, 68, 79, 82, and 84.