-
-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathintercept.go
158 lines (128 loc) · 3.56 KB
/
intercept.go
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package main
import (
"fmt"
"os"
"runtime"
"syscall"
)
func watchProcess(pid int, stdout, stderr, stdin bool) error {
// ensure tracing all comes from same thread
runtime.LockOSThread()
if _, err := os.FindProcess(pid); err != nil {
return fmt.Errorf("could not find process with pid %d: %w", pid, err)
}
if err := syscall.PtraceAttach(pid); err == syscall.EPERM {
return fmt.Errorf("could not attach to process with pid %d: %w - check your permissions", pid, err)
} else if err != nil {
return err
}
status := syscall.WaitStatus(0)
if _, err := syscall.Wait4(pid, &status, 0, nil); err != nil {
return err
}
defer func() {
_ = syscall.PtraceDetach(pid)
_, _ = syscall.Wait4(pid, &status, 0, nil)
}()
// deliver SIGTRAP|0x80
if err := syscall.PtraceSetOptions(pid, syscall.PTRACE_O_TRACESYSGOOD); err != nil {
return err
}
for {
fd, data, err := interceptReadsAndWrites(pid)
if err != nil {
return err
}
if stdout && fd == uint64(syscall.Stdout) || stderr && fd == uint64(syscall.Stderr) || stdin && fd == uint64(syscall.Stdin) {
if fd == uint64(syscall.Stdin) {
fd = uint64(os.Stdin.Fd())
}
_, _ = fmt.Fprintf(os.NewFile(uintptr(fd), "pipe"), "%s", string(data))
}
}
}
func interceptReadsAndWrites(pid int) (fd uint64, data []byte, err error) {
// intercept syscall
err = syscall.PtraceSyscall(pid, 0)
if err != nil {
return 0, nil, fmt.Errorf("could not intercept syscall: %w", err)
}
// wait for a syscall
status := syscall.WaitStatus(0)
_, err = syscall.Wait4(pid, &status, 0, nil)
if err != nil {
return 0, nil, err
}
// if interrupted, stop tracing
if status.StopSignal().String() == "interrupt" {
_ = syscall.PtraceSyscall(pid, int(status.StopSignal()))
return 0, nil, fmt.Errorf("process interrupted")
}
var exited bool
waitForExit := func() error {
if exited {
return nil
}
exited = true
// continue the syscall we intercepted
err = syscall.PtraceSyscall(pid, 0)
if err != nil {
return fmt.Errorf("could not continue process: %w", err)
}
// and wait for it to finish
status = syscall.WaitStatus(0)
_, err = syscall.Wait4(pid, &status, 0, nil)
if err != nil {
return err
}
return nil
}
defer func() {
err = waitForExit()
if err == nil {
// process exited
if status.Exited() {
err = fmt.Errorf("process exited")
return
}
// if interrupted, stop tracing
if status.StopSignal().String() == "interrupt" {
_ = syscall.PtraceSyscall(pid, int(status.StopSignal()))
err = fmt.Errorf("process interrupted")
return
}
}
}()
// if we have a syscall, examine it...
if status.TrapCause()&int(syscall.SIGTRAP|0x80) > 0 {
// wait for syscall exit
if err := waitForExit(); err != nil {
return 0, nil, err
}
// read registers
regs := &syscall.PtraceRegs{}
if err := syscall.PtraceGetRegs(pid, regs); err != nil {
return 0, nil, err
}
// find the syscall number for the host architecture
syscallNo := grabSyscallNo(regs)
// if it's a read/write syscall, grab the args
switch syscallNo {
case syscall.SYS_READ, syscall.SYS_WRITE:
// grab the args to WRITE for the host architecture
// fd == file descriptor (generally 1 for stdout, 2 for stderr)
// ptr == pointer to the buffer
// lng == length of the buffer
fd, ptr, lng := grabArgsFromRegs(regs)
// if we want to see this output, read it from memory
if lng > 0 {
data := make([]byte, lng)
if _, err := syscall.PtracePeekData(pid, uintptr(ptr), data); err != nil {
return 0, nil, err
}
return fd, data, nil
}
}
}
return 0, nil, err
}