Before discussing shellcode, we need to understand what the CPU actually executes.
Processors do not understand C, C++, Python or any other high-level programming language directly. Instead, they execute machine instructions, often referred to as opcodes.
Assembly sits one layer above machine code. It provides a human-readable representation of the instructions that the processor executes.
For example, the Assembly instruction:
mov eax, 5
might be represented in machine code as:
B8 05 00 00 00
Similarly:
ret
is commonly represented as:
C3
From the CPU's perspective, it is these bytes that matter. Assembly is simply a human-readable representation of machine instructions.
Conceptually:
C Code
↓
Assembly
↓
Opcodes (Bytes)
↓
CPU Execution
When you hear someone refer to "shellcode," they are often referring to a sequence of these machine-code bytes that can be executed directly by the processor.
For example:
B8 05 00 00 00 C3
could represent:
mov eax, 5
ret
Every processor architecture has its own instruction set and corresponding opcodes. For example, x86, x64 and ARM all use different machine instructions.
If you are curious, Intel publicly documents the x86/x64 instruction set in its Software Developer Manuals. These references contain the complete list of instructions, their encodings and their behavior. While memorizing opcodes is unnecessary, it is useful to know that these references exist and can be consulted whenever deeper understanding is required.
In practice, operators rarely work with raw opcodes directly. Instead, tools such as disassemblers and debuggers translate machine code back into Assembly instructions that humans can read and reason about.
What is "Shellcode"?
A normal program relies heavily on its surrounding structure. When an executable starts, the operating system loader performs many tasks automatically:
- Loads the executable into memory
- Resolves imported functions
- Creates process structures
- Sets up memory permissions
- Transfers execution to the program
For example, a compiled binary may look like:
Executable
├── Code Section
├── Data Section
├── Imports
├── Metadata
└── Other Structures
Shellcode is often much simpler:
Executable Memory Region
└── Machine Instructions
Unlike a normal application, shellcode is typically designed to operate without the support of a traditional executable file or operating system loader. Instead, it is often injected into an already running process and must operate within the environment it finds itself in. Because of this, shellcode frequently needs to:
- Locate important operating system functions
- Resolve memory addresses
- Manage its own execution flow
- Operate without standard library support
At this point, you have already encountered many of the concepts that make shellcode possible: memory, pointers, functions, registers, branching and machine instructions. Shellcode simply combines these building blocks into a form that can execute directly from memory.