Why C-Based Code Cannot Be Used as Shellcode

Using C to Build a calc.exe Executable

#include 
#include 

int main() {
    WinExec("calc.exe", SW_SHOW);
    return 0;
}

Then extract the .text section using objcopy:

x86_64-w64-mingw32-objcopy -O binary --only-section=.text exec_calc.exe exec_calc.bin

Disassemble with ndisasm

ndisasm -b 64 exec_calc.bin

From the disassembly, we can observe:

  • NOP Padding – Instructions such as nop word [cs:rax+rax+0x0] are inserted by the compiler for alignment or anti-disassembly purposes.

  • Strong Dependencies – Frequent usage of rel instructions (e.g., mov rax, [rel 0x34a0]) and API calls via the Import Address Table (IAT), such as jmp [rel 0x72ac] for WinExec.

  • Indirect Jumps – Instructions like FF25 (e.g., jmp [rel 0x72ac]) rely on fixed addresses in the import table, indicating tight coupling with the PE loader.

  • PE Format Reliance – The code uses system APIs through the IAT and expects the Windows PE loader to handle relocation, section initialization, and runtime setup.

  • Static API References – API calls like WinExec are accessed through statically defined entries in the import table.

  • Excessive Padding and Alignment – Compiler-inserted padding like 0F1F4000 nop dword [rax+0x0] and section alignment at addresses like 0x1800.

Using msfvenom to Generate Shellcode for calc.exe

payload:

$ msfvenom -p windows/x64/exec CMD=calc.exe -f raw -o calc.bin

Disassemble the Shellcode

$ ndisasm -b 64 calc.bin

Compare MSF shellcode and C

Msfvenom C
Generator Tool Metaploit’s msfvenom Extracted .text section from compiled PE
Code Type Pure position-independent code (PIC) Compiler-generated, PE-dependent
Dependency No external dependencies, self-contained Requires PE runtime, IAT, and relocation support
Size Compact (about 66 bytes) Large (about 6KB)
Feature 1. Direct system calls, no standard library dependencies
2. Dynamically resolve WinExec address (via PEB traversal)
3. No redundant data, completely location independent
1. Rely on PE Import Table (IAT) parsing API
2. Contains compiler-generated prologue/epilogue (such as stack frame adjustments)
3. Relocations and debugging information are present (even if the .text section is extracted)
Execute Environmental Can run from any memory address Requires full PE loader support
API call method Dynamic resolution (gs:[0x60]→PEB→kernel32.dll) Hard-coded address via IAT
Code alignment No padding bytes Contains NOPs and alignment for structure
Minimal, self-contained Contains additional runtime/library code

How to Improve C-Based Shellcode

To make your payload suitable for shellcode use, consider the following techniques:

  • PEB Traversal: Manually traverse the Process Environment Block (PEB) to locate loaded modules like kernel32.dll and resolve API addresses dynamically.

  • Dynamic API Resolution: Avoid using the Import Table; instead, resolve API addresses at runtime using hash-based or name-based lookup.

  • Avoid Compiler-Inserted Code: Use assembly or minimal C with inline assembly to bypass compiler-generated code (e.g., prologue/epilogue, SEH).

  • Position-Independent Code: Ensure your code can execute from any memory address without relying on relocations or fixed sections.