← Back to Security Research Portal (ccsimplyspolit.github.io)

Deterministic Snapshot Fuzzing & Relocation-Aware Auditing of Binary Protocol Parsers

Author: Serhii Shunko (@sshunko / @ccsimplyspolit) • ORCID: 0009-0002-4183-0361 • Category: Snapshot Fuzzing & Binary Auditing

0x00 // Why Traditional Process-Level Fuzzing Bottlenecks on Complex Engines

Many critical C/C++ deserializers (such as dedicated game-server tick processors, replay container parsers, and embedded telemetry routers) are deeply coupled to global engine singletons, thread-local allocators, and socket state machines. Spawning a full process per testcase yields < 5 execs/sec because 99% of CPU cycles are spent initializing subsystems unrelated to the target parser function.

Deterministic Snapshot Fuzzing (pioneered by frameworks such as 0vercl0k/wtf and hardware-assisted/page-table Copy-on-Write engines) solves this by capturing a complete architectural state snapshot (CPU registers + physical memory pages) immediately at the entry point of the target parser function (e.g., CNetChan::ProcessMessages or a container chunk decoder).

0x01 // Copy-on-Write (CoW) Dirty-Page Tracking & Reset Loop

Instead of restoring gigabytes of RAM after each fuzzing iteration, a snapshot harness marks all writable guest/process pages as read-only in the shadow page table and records page-faulting writes in a bounded dirty-page bitmap:

Snapshot Harness Lifecycle (10,000+ execs/sec per core):
+-----------------------------------------------------------------------+
| 1. Capture Baseline Snapshot at Parser Entry (RIP = ParsePacketEntry) |
|    - RCX = this (NetChannel / ParserContext*)                         |
|    - RDX = const uint8_t* input_buf                                   |
|    - R8  = size_t input_len                                           |
+-----------------------------------------------------------------------+
                                   |
                                   v
+-----------------------------------------------------------------------+
| 2. Per-Iteration In-Memory Mutation Injection                         |
|    - Write mutated testcase into pre-mapped page at RDX               |
|    - Set R8 = testcase_size                                           |
|    - Execute until Parser Return Address or Guard Page Fault          |
+-----------------------------------------------------------------------+
                                   |
                                   v
+-----------------------------------------------------------------------+
| 3. O(DirtyPages) Deterministic State Rollback                         |
|    - Restore only the 4-15 dirty 4 KiB pages touched during the call  |
|    - Restore GPRs/XMM state & advance coverage bitmap                 |
+-----------------------------------------------------------------------+

0x02 // Combining Static IDA Pro Provenance with Page-Heap Guarding

Without source-level AddressSanitizer (ASAN) instrumentation, small heap out-of-bounds reads or writes (CWE-125 / CWE-787) inside a custom slab allocator may not trigger an immediate hardware page fault. To achieve ASAN-equivalent sensitivity in binary-only snapshot harnesses:

// Hook custom allocator wrappers (malloc / realloc / CUtlMemory::Grow) inside the snapshot
// to place each allocation at the end of a 4 KiB page followed by an unmapped guard page:
void* GuardedPageAlloc(size_t requested_size) {
    size_t aligned_pages = (requested_size + 0xFFF) & ~0xFFFULL;
    uint8_t* base = AllocateVirtualPages(aligned_pages + 0x1000);
    MarkPageUnmapped(base + aligned_pages); // Immediate #PF on 1-byte OOB read/write
    return base + aligned_pages - requested_size;
}

By pairing static structure recovery in IDA Pro (identifying exact field offsets, uint32_t count * sizeof(Elem) multiplication sites, and signedness transitions) with guarded page allocations inside a deterministic snapshot loop, security auditors can systematically verify parser bounds invariants and author precise binary or source-level remediation patches.

0x03 // Overflow-Safe 64-Bit Offset Verification in Binary RPC Deserializers

When binary RPC handlers deserialize multi-dimensional descriptors alongside raw payload buffers, pointer arithmetic checks of the form base + offset + size <= end are vulnerable to 64-bit unsigned wraparound. Defensive deserializers must enforce subtraction-based bounds invariants:

// Enforce overflow-safe containment before indexing or copying tensor/segment buffers:
bool VerifySliceBounds(uintptr_t data_ptr, uintptr_t region_start, uintptr_t region_end,
                       size_t byte_offset, size_t byte_size) {
    if (data_ptr < region_start || data_ptr > region_end) return false;
    size_t avail = region_end - data_ptr;
    if (byte_offset > avail || byte_size > avail - byte_offset) return false;
    return true;
}