Section: What the Import Address Table does.
The detours in Lesson 8.3 changed instruction bytes at a verified site. Sometimes a program already calls a function through a table of imported addresses. In that case, one verified table entry can change the destination while leaving the call instruction in place.
Your program can call MessageBoxW without knowing where user32.dll will load. The P E, portable executable, import data says, “I need this named function.” The Windows loader finds its live address and writes that address into the Import Address Table, or IAT.
In brief: An IAT hook changes a module’s imported destination; it does not rewrite every call to that function throughout the process.
Later, a normal imported call follows the pointer stored in that table:
Code example. The surrounding prose explains this example; the full lesson contains the exact code.
If a program deliberately replaces its own slot, the same call site follows the replacement:
Code example. The surrounding prose explains this example; the full lesson contains the exact code.
This lab changes only the IAT of the course executable itself. It does not target another process, hide from security software, or bypass protections.
During the lab, the call still begins at the same instruction; only the pointer in the table chooses a different route:
When the patch owner is dropped, it puts the saved original pointer back into that same IAT slot.

Section: Keep the function signature exact.
The replacement must accept the same arguments and use the same Windows calling convention:
Code example. The code’s comments explain: 🔁 SAFETY: install() saved the original pointer from this exact IAT slot. 🛡️ SAFETY: the signature and Windows A B I, application binary interface, match MessageBoxW.
extern "system" tells the compiler to use the calling convention Windows expects. If the convention, parameter sizes, or return type are wrong, the stack or registers can be interpreted incorrectly.
Narration: The address may be right while the call is wrong: mismatched argument sizes can make the replacement interpret unrelated register or stack contents.
Section: Find the import directory.
For a loaded PE32 image:
get the current executable base with GetModuleHandleW(NULL);
verify MZ and P E\0\0;
require optional-header magic hexadecimal 1 0 B;
read the import-directory R V A, relative virtual address, from data-directory entry 1;
walk 20-byte import descriptors until the all-zero terminator;
find user32.dll;
walk its lookup names and IAT slots together;
find MessageBoxW by name.
Step 7 is the one that surprises people: each imported D L L, dynamic link library, has two parallel arrays, not one. The compiler writes them as identical lists of equal length, and the loader overwrites only one of them.
Code example. The surrounding prose explains this example; the full lesson contains the exact code.
Before the module is loaded, both arrays hold the same name references. During loading, Windows resolves each name and writes the resulting live address into the matching IAT slot, leaving the lookup table alone. That is exactly why you walk the two together: the lookup table still records which function this entry is, while the IAT records where that function currently lives. Searching the IAT by itself gives you addresses with no names attached; searching the lookup table by itself gives you names with nothing to patch.
Alternative: Read the import tables like paired columns: one identifies the requested name, while the other holds its resolved address.
(Some binaries ship without the separate lookup table. In a loaded image, the IAT has already been overwritten with live function addresses, so it cannot be treated as a list of name RVAs. This name-based lab refuses that case with a clear error. Handling it would require a different, independently verified way to identify the desired slot.)
The important matching loop is:
Code example. The code’s comments explain: 📏 Bound the thunk walk even if a malformed image omits its zero terminator. 🔍 The high bit marks an ordinal import; there is no name pointer to read. 🧭 Names identify the function; FirstThunk identifies the live pointer slot that the loader filled and the hook must replace. ✅ The installer now owns this validated four-byte PE32 slot.
The limits stop a malformed table from becoming an endless walk through memory.
Section: Change one pointer, then restore it.
VirtualProtect temporarily makes the four-byte slot writable. The code immediately restores the old page protection:
The helper below receives a validated PE32 import slot and the replacement 32-bit function pointer. It performs exactly those three operations and returns success only after the original page protection is restored:
Code example. The code’s comments explain: 🛡️ SAFETY: slot is the validated IAT cell in this executable. 📏 SAFETY: the page is writable and the slot is exactly four bytes on PE32. 🔁 Volatile keeps this externally visible pointer update as one explicit memory operation; it does not make an invalid slot safe. 🔒 SAFETY: old came from the successful protection change above.
Changing one pointer is not the whole lifecycle. The owning guard below remembers the original destination and puts it back when the guard is dropped, so the third message box in this lab uses the normal import again:
Code example. The code’s comments explain: 🧹 SAFETY: this owner restores the same slot exactly once. 🔒 Publish “no callable original” only after the slot restoration attempt, so the replacement cannot observe cleanup out of order.
This is RAII applied to reverse engineering: owning the modification also means owning the cleanup.
If restoring page protection fails after the pointer write, the slot may already contain the replacement even though write_slot returns an error. A complete installer must retain enough state to retry or roll back that partial installation; the returned Result alone does not undo it.
Section: Run the complete three-message proof.
Run this from windows-labs in the Windows V M, virtual machine. The three message boxes let you observe the original import, the redirected call, and the restored call in that order.
Terminal window
Code example. The surrounding prose explains this example; the full lesson contains the exact code.
You should see three message boxes:
the untouched call displays “The normal import runs first.”
the hooked call displays “Hook reached the replacement IAT entry.”
after IatPatch is dropped, the restored call displays “The original import is restored.”
Those three visible states prove interception and restoration. The full buildable source—including bounded P E parsing and every unsafe invariant—is iat_hook_lab.rs.
Section: How this connects to game labs.
An injected course D L L lives inside the game process, so the same idea can observe an imported graphics or file function used by that exact build. Before changing a game IAT:
prove the function is actually imported by the main executable;
use the exact function signature;
reject an unknown P E format or module version;
keep the hook tiny and non-blocking;
restore the original slot before unloading the D L L.
The Urban Terror OpenGL chapter uses an instruction detour because its verified draw path is not expressed as a convenient main-executable IAT slot. Choose a hook based on the real call path, not because one technique sounds fashionable.
References: Microsoft P E format, GetModuleHandleW, and importing functions using __declspec(dllimport).