-
Notifications
You must be signed in to change notification settings - Fork 19
/
Copy pathcryptostalker.go
137 lines (126 loc) · 3.57 KB
/
cryptostalker.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
package main
import (
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"time"
"github.com/rjeczalik/notify"
"github.com/unixist/go-ps"
"github.com/unixist/randumb"
)
type options struct {
path *string
count *int
sleep *int
stopAge *int
window *int
script *string
}
func stopProcsYoungerThan(secs int) {
age, _ := time.ParseDuration(fmt.Sprintf("%ds", secs))
for _, proc := range procsYoungerThan(age) {
if err := stopProc(proc); err != nil {
fmt.Printf("Failed to stop process: %d", proc)
}
}
}
func stopProc(pid int) error {
if os.Getpid() == pid {
return nil
}
p, err := os.FindProcess(pid)
if err != nil {
return err
}
if err := p.Signal(os.Kill); err != nil {
return err
}
return nil
}
func procsYoungerThan(age time.Duration) []int {
procs, _ := ps.Processes()
ret := []int{}
for _, i := range procs {
if time.Since(i.CreationTime()) < age {
ret = append(ret, i.Pid())
}
}
return ret
}
func isFileRandom(filename string) bool {
s, err := os.Stat(filename)
if err != nil {
// File no longer exists. Either it was a temporary file or it was removed.
return false
} else if !s.Mode().IsRegular() {
// File is a directory/socket/device, anything other than a regular file.
return false
}
// TODO: process the file in pieces, not as a whole. This will thrash memory
// if the file we're inspecting is too big. Suggestion: read data in PAGE_SIZE
// bytes, and call randumb.IsRandom() size/PAGE_SIZE number of times. If N
// pages are random, then return true.
// Processing a file in this way also has the side effect of protecting against
// ransomware evading detection by encoding non-random data inside the file along
// with the encrypted data--and then removing the non-random cruft data later.
data, err := ioutil.ReadFile(filename)
if err != nil {
// Don't output an error if it is permission related
if !os.IsPermission(err) {
log.Printf("Error reading file: %s: %v\n", filename, err)
}
return false
}
return randumb.IsRandom(data)
}
func Stalk(opts options) {
c := make(chan notify.EventInfo, 1)
// Make path recursive
rpath := filepath.Join(*opts.path, "...")
if err := notify.Watch(rpath, c, notify.Create); err != nil {
log.Fatal(err)
}
defer notify.Stop(c)
// Ingest events forever
for ei := range c {
path := ei.Path()
go func() {
if isFileRandom(path) {
log.Printf("Suspicious file: %s", path)
if *opts.stopAge != 0 {
stopProcsYoungerThan(*opts.stopAge)
}
if *opts.script != "" {
exec.Command(*opts.script,path).Start()
}
}
}()
if *opts.sleep != 0 {
time.Sleep(time.Duration(*opts.sleep) * time.Millisecond)
}
}
}
func flags() options {
opts := options{
count: flag.Int("count", 10, "The number of random files required to be seen within <window>"),
path: flag.String("path", "", "The path to watch"),
script: flag.String("script", "", "Script to call (first parameter is the path of suspicious file) when something happens"),
// Since the randomness check is expensive, it may make sense to sleep after
// each check on systems that create lots of files.
sleep: flag.Int("sleep", 10, "The time in milliseconds to sleep before processing each new file. Adjust higher if performance is an issue."),
stopAge: flag.Int("stopAge", 0, "Stop all processes created within the last N seconds. Default is off."),
window: flag.Int("window", 60, "The number of seconds within which <count> random files must be observed"),
}
flag.Parse()
if *opts.path == "" {
log.Fatal("Please provide a --path")
}
return opts
}
func main() {
Stalk(flags())
}