Join our Newsletter — 33% off our NHI Course

What happens when arbitrary read and write primitives are combined with writable function pointers or GOT entries?

An attacker can redirect execution by overwriting a function pointer or GOT entry with an address under their control. That lets a later, ordinary function call jump into attacker supplied payloads. In practical exploitation, the primitive is often enough to convert a crash into a stable shell or other code execution.

How arbitrary read-write primitives become code execution

Once an exploit can both read and write arbitrary memory, the attacker can stop treating the process as a black box and start steering it. The key step is usually not the read primitive by itself, but using the write primitive to alter a control-flow target that the program will later trust, such as a function pointer, vtable entry, return-oriented pointer, or a GOT slot in a dynamically linked binary.

That matters because these targets are not executed immediately. They are often dereferenced later during an ordinary program action, which makes the exploit look like normal application behaviour until the moment control flow is diverted. In many real-world cases, the attacker first leaks an address to defeat ASLR, then writes a chosen pointer value, and finally waits for the program to invoke that target.

When the overwritten target is reached, execution jumps to attacker-controlled code or to a gadget chain that pivots into further exploitation. The result can be anything from a clean process crash to reliable remote code execution, depending on what memory protections are in place and whether the attacker can also control registers, stack state, or surrounding arguments.

Why function pointers and GOT entries are high-value targets

Function pointers and GOT entries are attractive because they sit at the boundary between data and control. A function pointer in C or C++ may be stored in a struct, callback table, object, or global variable, while a GOT entry is part of the loader’s resolution machinery for imported functions. If the program later calls through that pointer, or if it invokes the imported function again, the poisoned address is used as though it were legitimate.

GOT overwrites are especially useful in partially protected binaries because they let an attacker redirect a future call without needing to corrupt the stack directly. A writable function pointer can be even more flexible, because the attacker may choose a callback path that is triggered by event handling, cleanup logic, or protocol parsing. The common theme is that control data is still writable after the attacker has reached memory corruption.

Mitigations such as full RELRO make GOT entries read-only after relocation, but that only shifts attention to other writable control-flow objects. If a program keeps callback tables, exception handlers, dispatch vectors, or object method pointers in writable memory, the underlying exploitation pattern remains the same: find a later indirect call and replace its destination with something the attacker can influence.

What the attack usually needs before the final jump

Successful use of these primitives usually depends on two things: a trustworthy write target and a usable destination. The write target must be something the program will dereference, and the destination must be a valid address for the attacker’s intended payload, whether that is shellcode in a permissive memory region, a code-reuse gadget chain, or a library function such as system-like behaviour in a lab setting.

In practice, the attacker often also needs an information leak to discover addresses in a hardened process. Modern protections such as ASLR, NX, stack canaries, CFI, and full RELRO do not eliminate the primitive, but they can change the exploit from simple pointer replacement into a multi-stage chain. That is why an arbitrary write is best understood as a capability amplifier: by itself it is powerful, but combined with a control target it becomes a direct path to execution.

Risk and Threat Considerations

Arbitrary read-write bugs are rarely interesting only because they corrupt memory, they are dangerous because they turn data corruption into control-flow corruption. Once an attacker can overwrite a future indirect branch target, the process may continue normally until a later call, which can delay detection and make the exploit appear stable rather than immediately destructive.

Failure mechanism: The exploit succeeds when a writable control-flow object, such as a function pointer or GOT entry, remains reachable after the memory corruption, and the program later dereferences that object as if it were trustworthy.

Impact: The attacker can redirect execution, bypass the intended code path, and often convert a crash-only bug into reliable code execution or a durable foothold inside the process.

Standards & Framework Alignment

This section maps relevant standards and security frameworks to the operational risks and controls described in this guidance.

MITRE ATT&CK addresses the attack and risk surface, while NIST SP 800-53 Rev 5, CIS Controls v8 and OWASP ASVS set the governance and control requirements practitioners need to meet.

Framework Control / Reference Relevance
MITRE ATT&CK T1055 — Process Injection Arbitrary write to control targets enables process-level execution redirection.
Recommendation — Map corrupted control-flow targets to ATT&CK techniques and hunt for execution redirection paths.
NIST SP 800-53 Rev 5 SI-16 — Memory Protection The issue is memory corruption leading to unsafe execution flow.
SC-34 — Non-modifiable Executable Programs Preventing writable executable paths reduces the impact of pointer hijacking.
Recommendation — Apply memory protection controls to reduce writable code-execution paths and corruptible control data. Enforce non-modifiable executable regions to limit exploitability after a pointer overwrite.
CIS Controls v8 CIS-4 — Secure Configuration of Enterprise Assets and Software Exploitability depends on hardened build and runtime memory settings.
Recommendation — Harden binaries and runtime settings to remove easy write-to-execute pathways.
OWASP ASVS V15 — Secure Coding and Architecture Secure coding guidance covers preventing memory corruption from becoming control-flow hijack.
Recommendation — Design code paths so writable data cannot become attacker-controlled execution targets.

Practitioner Guidance

What to verify: Treat any memory corruption bug as critical if it can both read and write and if the binary still contains writable indirect-call targets. Confirm whether the build uses full RELRO, whether control-flow objects live in writable segments, and whether the application has any callback or dispatch path that an attacker could realistically reach.

Common mistake: Teams sometimes focus on whether shellcode can be injected, but the more important question is whether the process has any later indirect control transfer that can be retargeted. If the attacker can already overwrite a pointer, code injection is no longer the only path to compromise.

Practitioner takeaway: The decisive issue is not the presence of a memory bug alone, but whether the bug can reach a writable control-flow target that the program will trust later. If it can, assume the primitive is exploitation-grade until proven otherwise.