Skip to content

Hotel-Zero/shroudware

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

================================================================================

                              S H R O U D W A R E

              A Dual-Header C99 Obfuscation Library for Windows

                                 mochabyte0x
                                 April 2026

================================================================================


ABSTRACT

    Shroudware is a compile-time obfuscation library implemented entirely in
    the C preprocessor. It targets the three primary artifacts that reverse
    engineers rely on during static analysis: readable strings in the .rdata
    section, recognizable integer constants in the disassembly, and named
    entries in the PE import table.

    The library is distributed as a dual header file (shroudware.h and structs.h) 
    with no external dependencies. It requires only a C99 compiler and produces
    standard Windows executables. All transformations occur at compile time
    through constant folding. There is no runtime unpacking phase, no
    initialization routine, and no external tooling.

    Each build produces unique ciphertexts. The encryption seed is derived
    from __TIME__ and __DATE__, making every compilation polymorphic.


================================================================================
1. MOTIVATION
================================================================================

    When a compiled binary is loaded into a disassembler (IDA Pro, Ghidra,
    Binary Ninja), the analyst's first actions are predictable:

        1. Open the strings window (Shift+F12 in IDA)
        2. Review the import table
        3. Search for known constants (0x40, 0x3000, etc.)

    These three data points provide immediate context. String references
    reveal file paths, registry keys, error messages, and API names. The
    import table shows every DLL and function the binary calls. Known
    constants like PAGE_EXECUTE_READWRITE (0x40) or MEM_COMMIT (0x1000)
    betray the program's intent without reading a single instruction.

    Most obfuscation tooling addresses this at the compiler level (LLVM
    passes like OLLVM, Hikari) or through post-build transformation
    (packers, crypters, VMProtect). These work, but they introduce
    toolchain dependencies, increase build complexity, and often require
    specific compiler versions or commercial licenses.

    Shroudware operates entirely within the C preprocessor. The compiler's
    own constant folding pass performs the encryption. No plugins, no
    post-build steps, no runtime unpacking stubs.


================================================================================
2. DESIGN
================================================================================

    2.1 Constraints

        - Dual header (shroudware.h + structs.h), zero external dependencies
        - C99 standard (compound literals, no C++ features)
        - All encryption at compile time via constant folding
        - Cross-compiler: GCC (MSYS2), Clang
        - Per-build polymorphism from timestamp-derived seed

    2.2 Architecture

        All internal constants are derived from a single root seed through
        cascading bit-mix operations:

                              OBF_SEED
                          (__TIME__/__DATE__)
                                 |
                    +------------+------------+
                    |            |            |
                 _OBF_C1     _OBF_C2     _OBF_C3
                (primary)  (secondary)  (tertiary)
                    |            |            |
            +-------+------+----+----+--------+-------+
            |              |         |                |
          OBFSTR       OBF_HASH  OBF_IMPORT      OBF_CONST
         (strings)    (hashing)  (PEB walk)     (integers)

        No well-known magic numbers appear in the binary. The constants
        change with every build. The | 1u operation ensures odd multipliers
        to avoid degenerate zero multiplication.


================================================================================
3. STRING ENCRYPTION (OBFSTR)
================================================================================

    Problem:
        String literals are stored verbatim in the .rdata section. Running
        strings.exe on the binary reveals every hardcoded path, API name,
        error message, and configuration value.

    Mechanism:
        OBFSTR("text") expands into a C99 compound literal containing 256
        XOR-encrypted bytes. Each byte position gets a unique subkey derived
        from: the call site's line number, the string's length, and the
        global seed. The compiler constant-folds the XOR at compile time.
        The plaintext never reaches the object file.

        At runtime, _obf_dec() reverses the XOR. This function is forced
        noinline -- without it, the compiler inlines the loop, recognizes
        the XOR-then-XOR identity, and optimizes the encryption away
        entirely. A memory barrier further prevents dead-store elimination.

    Usage:
        char* secret = OBFSTR("This is a secret message");
        printf("%s\n", secret);
        printf("%s\n", OBFSTR("also works inline"));

    Properties:
        - Max 255 characters per string
        - Position-dependent subkeys (same char encrypts differently
          at different positions)
        - Per-call-site keys (same string on different lines produces
          different ciphertext)
        - Stack-allocated (compound literal, not in .rdata)
        - Different ciphertext every build


================================================================================
4. COMPILE-TIME HASHING (OBF_HASH)
================================================================================

    Problem:
        Resolving functions by name against PE export tables requires the
        name string to exist somewhere. Plaintext names are trivially found.
        Encrypting with OBFSTR adds runtime overhead and a reversible
        decryption function.

    Mechanism:
        OBF_HASH("NtClose") expands into 16 nested FNV-1a macro steps.
        Each step conditionally XORs the next character and multiplies by
        the FNV-1a prime (0x01000193). The compiler evaluates the chain
        at compile time and emits a single uint32_t constant. The string
        literal is consumed by the preprocessor and never reaches the
        object file.

    Variants:
        OBF_HASH(s)     Case-sensitive, for export name matching
        OBF_HASH_CI(s)  Case-insensitive, for module name matching
        obf_hash_rt()   Runtime hash for dynamic strings
        obf_hash_rt_wci()  Runtime hash for wide strings (PEB names)

    Limits:
        15 characters at compile time. For longer names, OBF_IMPORT falls
        back to runtime hashing automatically. For standalone use:
        obf_hash_rt(OBFSTR("LongFunctionName"))


================================================================================
5. IMPORT HIDING (OBF_IMPORT)
================================================================================

    Problem:
        The PE import table is a manifest of every DLL and function the
        binary calls. An analyst reads it in seconds: VirtualAlloc means
        memory allocation, CreateRemoteThread means injection,
        InternetOpenA means network activity.

    Mechanism:
        OBF_IMPORT bypasses the import table through manual resolution:

        Step 1: Module resolution via PEB walk
            The Process Environment Block contains a linked list of loaded
            modules (PEB->Ldr->InLoadOrderModuleList). The library reads the
            TEB segment register directly (gs:0x60 on x64, fs:0x30 on x86)
            to reach the PEB without calling any API. Each module's
            BaseDllName is hashed at runtime and compared against the
            compile-time hash of the target module.

        Step 2: Function resolution via export table walk
            The library parses PE headers to locate the export directory.
            Each exported name is hashed at runtime and compared against
            the target hash. The function name is encrypted with a 64-byte
            OBFSTR variant and hashed at runtime, so there is no length
            restriction on function names.

        Step 3: Forwarded export handling
            Some exports point to forwarding strings rather than code
            (e.g., kernel32!HeapAlloc -> NTDLL.RtlAllocateHeap). The
            library detects this (RVA within export directory bounds),
            parses the forwarding string, resolves the target module by
            hash, and resolves the function recursively.

    Usage:
        typedef DWORD (WINAPI *GetTickCount_t)(void);
        GetTickCount_t pGTC = (GetTickCount_t)OBF_IMPORT("kernel32.dll",
                                                          "GetTickCount");
        DWORD tick = pGTC();

        // forwarded exports work transparently
        HeapAlloc_t pHA = (HeapAlloc_t)OBF_IMPORT("kernel32.dll",
                                                    "HeapAlloc");

        // long names (>15 chars) work automatically
        void* p = OBF_IMPORT("ntdll.dll", "NtQueryInformationProcess");

    Result:
        The import table is clean. No VirtualAlloc, no GetProcAddress,
        no suspicious entries. Only CRT imports (printf, etc.) remain.


================================================================================
6. VALUE OBFUSCATION (OBF_CONST)
================================================================================

    Problem:
        Integer constants are recognizable signatures. Searching for 0x40
        finds PAGE_EXECUTE_READWRITE. Searching for 0x3000 finds
        MEM_COMMIT | MEM_RESERVE. These reveal intent without reading
        surrounding code.

    Mechanism:
        OBF_CONST(0x40) XORs the value with a per-call-site key at compile
        time. At runtime, _obf_deconst() reverses the XOR in a noinline
        function. The key varies by line number and seed, so identical
        values at different call sites produce different ciphertexts.

    Usage:
        DWORD prot  = OBF_CONST(0x40);     // PAGE_EXECUTE_READWRITE
        DWORD flags = OBF_CONST(0x3000);   // MEM_COMMIT | MEM_RESERVE
        SIZE_T size = (SIZE_T)OBF_CONST(4096);

    Returns unsigned int. Cast for other types as needed.


================================================================================
7. SEED SYSTEM
================================================================================

    All encryption derives from a single root:

        #define OBF_SEED (_OBF_AUTO_SEED | 1u)

    _OBF_AUTO_SEED XOR-mixes each character of __TIME__ and __DATE__ with
    unique multipliers. The | 1u ensures odd values. From the seed, three
    constants cascade:

        _OBF_C1 = mix(SEED)         Primary mixing constant
        _OBF_C2 = mix(_OBF_C1)      Secondary
        _OBF_C3 = mix(_OBF_C2)      Tertiary

    To pin a seed for reproducible builds:

        #define OBF_SEED 0xDEADBEEFu
        #include "shroudware.h"


================================================================================
8. COMPILER SUPPORT
================================================================================

    Compiler        Version Tested     Notes
    -------         ---------------    --------------------------------
    Clang           21.1.0             GCC-style barriers, source
                                       location limits on heavy usage
    GCC (MSYS2)     x86_64-pc-msys     Inline asm PEB access, no
                                       _WIN32 (uses __MSYS__ guard)

    Clang note: Clang maintains a 32-bit source location counter. Heavy
    macro usage (many OBFSTR + OBF_HASH in one TU) can exhaust it. Split
    obfuscated code across multiple .c files if this occurs.


================================================================================
9. API REFERENCE
================================================================================

    Macro               Input                   Output
    -----               -----                   ------
    OBFSTR(s)           string, max 255 chars   char* (stack)
    OBF_HASH(s)         string, max 15 chars    unsigned int
    OBF_HASH_CI(s)      string, max 15 chars    unsigned int
    OBF_IMPORT(m, f)    two string literals      void*
    OBF_CONST(v)        integer constant         unsigned int

    Function              Purpose
    --------              -------
    obf_hash_rt(s)        FNV-1a of char* (case sensitive)
    obf_hash_rt_wci(s)    FNV-1a of wchar_t* (case insensitive)
    obf_get_module(h)     PEB walk, find module base by hash
    obf_get_proc(b, h)    Export table walk, resolve by hash


================================================================================
10. LIMITATIONS
================================================================================

    - OBFSTR: 255 char max (256 byte compound literal)
    - OBF_HASH: 15 char max at compile time
    - OBF_CONST: 32-bit unsigned only
    - OBFSTR lifetime: stack-scoped, do not return from a function
    - Requires -O1 or higher (constant folding needed)
    - Windows only (PEB/PE structures)
    - Clang source location budget on heavy usage
    - Not a packer. Removes static artifacts only. Does not protect
      against dynamic analysis, debugging, or memory dumping.


================================================================================
11. BUILDING
================================================================================

    gcc   -O2  -o demo.exe  demo.c
    clang -O2  -o demo.exe  demo.c

    Verify:
        strings demo.exe | findstr "secret"     (should find nothing)


================================================================================

                         github.com/Hotel-Zero

================================================================================

About

Shroudware is a compile-time obfuscation library implemented entirely in the C preprocessor.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages